Service Provider Implementation Guide¶
This guide provides step-by-step instructions for implementing the UIM Protocol in a web service. It covers service registration, intent definition, policy management, and intent execution.
Overview¶
Web services use the UIM Protocol to expose their capabilities to AI agents through well-defined intents. This guide will walk you through the process of implementing the UIM Protocol in your web service, from service registration to intent execution.
Prerequisites¶
Before implementing the UIM Protocol in your web service, you should have:
- A basic understanding of the UIM Protocol concepts (intents, metadata, PATs)
- A web service with an API
- Knowledge of HTTP requests and JSON processing
- Cryptographic capabilities for signing and verifying signatures
Implementation Steps¶
1. Set Up Your Development Environment¶
First, set up your development environment with the necessary libraries for HTTP requests, JSON processing, and cryptography. Here's an example using Node.js:
// Required libraries
const express = require('express');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const fs = require('fs');
2. Generate Key Pair¶
Generate an RSA key pair for your web service. This will be used for signing PATs and verifying signatures:
function generateKeyPair() {
return crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
}
// Generate and save keys
const { privateKey, publicKey } = generateKeyPair();
fs.writeFileSync('private-key.pem', privateKey);
fs.writeFileSync('public-key.pem', publicKey);
3. Define Your Intents¶
Define the intents that your web service will expose to AI agents:
const intents = [
{
intent_uid: 'example.com:search-products:v1',
intent_name: 'SearchProducts',
description: 'Search for products based on criteria',
input_parameters: [
{ name: 'query', type: 'string', required: true, description: 'Search query' },
{ name: 'category', type: 'string', required: false, description: 'Product category' },
{ name: 'price_range', type: 'string', required: false, description: 'Price range (e.g., "10-100")' },
{ name: 'sort_by', type: 'string', required: false, description: 'Sort criteria' }
],
output_parameters: [
{ name: 'products', type: 'array', description: 'List of matching products' },
{ name: 'total_results', type: 'integer', description: 'Total number of matching products' }
],
endpoint: 'https://api.example.com/products/search',
tags: ['e-commerce', 'search', 'products']
},
{
intent_uid: 'example.com:get-product-details:v1',
intent_name: 'GetProductDetails',
description: 'Get detailed information about a specific product',
input_parameters: [
{ name: 'product_id', type: 'string', required: true, description: 'Product ID' }
],
output_parameters: [
{ name: 'product', type: 'object', description: 'Product details' }
],
endpoint: 'https://api.example.com/products/details',
tags: ['e-commerce', 'product', 'details']
}
];
4. Define Your Policy¶
Define the policy that AI agents must adhere to when using your web service:
const policy = {
'@context': 'http://www.w3.org/ns/odrl.jsonld',
'uid': 'http://example.com/policy/12345',
'type': 'Set',
'profile': 'http://example.com/profile/odrl-uim',
'permission': [
{
'target': 'http://example.com/api/intents',
'action': 'execute',
'constraint': [
{
'leftOperand': 'http://example.com/vocab/rateLimit',
'operator': 'lte',
'rightOperand': 1000,
'unit': 'http://example.com/vocab/hour'
}
],
'duty': [
{
'action': 'pay',
'target': 'http://example.com/vocab/intentPrice',
'amount': 0.01,
'unit': 'http://example.com/vocab/USD'
}
]
}
],
'prohibition': [
{
'target': 'http://example.com/api/intents',
'action': 'exceedRateLimit'
}
],
'obligation': [
{
'action': 'signPayload',
'assignee': 'http://example.com/ai-agent/1',
'target': 'http://example.com/vocab/payload',
'constraint': [
{
'leftOperand': 'http://example.com/vocab/publicKey',
'operator': 'use',
'rightOperand': 'MIIBIjANBgkqh...'
}
]
}
]
};
5. Create the agents.json
File¶
Create an agents.json
file that describes your web service and its intents:
const agentsJson = {
'service-info': {
'name': 'Example E-commerce Service',
'description': 'Provides e-commerce functionalities',
'service_url': 'https://api.example.com',
'service_logo_url': 'https://example.com/logo.png',
'service_terms_of_service_url': 'https://example.com/terms',
'service_privacy_policy_url': 'https://example.com/privacy'
},
'intents': intents,
'uim-public-key': publicKey,
'uim-policy-file': 'https://example.com/uim-policy.json',
'uim-api-discovery': 'https://api.example.com/uim/intents/search',
'uim-compliance': {
'standards': ['ISO27001', 'GDPR'],
'regional-compliance': {
'EU': 'GDPR',
'US-CA': 'CCPA'
},
'notes': 'Data is encrypted in transit and at rest.'
},
'uim-license': 'https://uimprotocol.com/licenses/uim-by-nc-v1.0'
};
// Save the agents.json file
fs.writeFileSync('public/agents.json', JSON.stringify(agentsJson, null, 2));
6. Configure DNS TXT Records¶
Configure DNS TXT records to help AI agents discover your web service:
uim-agents-file=https://example.com/agents.json
uim-api-discovery=https://api.example.com/uim/intents/search
uim-policy-file=https://example.com/uim-policy.json
uim-license=https://uimprotocol.com/licenses/uim-by-nc-v1.0
7. Implement the Discovery API¶
Implement the discovery API that allows AI agents to search for intents:
const app = express();
app.use(express.json());
// Intent search endpoint
app.get('/uim/intents/search', (req, res) => {
const { query, tags, intent_name, uid, namespace, description } = req.query;
// Filter intents based on query parameters
let filteredIntents = [...intents];
if (query) {
const queryLower = query.toLowerCase();
filteredIntents = filteredIntents.filter(intent =>
intent.intent_name.toLowerCase().includes(queryLower) ||
intent.description.toLowerCase().includes(queryLower)
);
}
if (tags) {
const tagList = tags.split(',');
filteredIntents = filteredIntents.filter(intent =>
tagList.some(tag => intent.tags.includes(tag))
);
}
if (intent_name) {
filteredIntents = filteredIntents.filter(intent =>
intent.intent_name.toLowerCase() === intent_name.toLowerCase()
);
}
if (uid) {
filteredIntents = filteredIntents.filter(intent =>
intent.intent_uid === uid
);
}
if (namespace) {
filteredIntents = filteredIntents.filter(intent =>
intent.intent_uid.split(':')[0] === namespace
);
}
if (description) {
const descLower = description.toLowerCase();
filteredIntents = filteredIntents.filter(intent =>
intent.description.toLowerCase().includes(descLower)
);
}
// Pagination
const page = parseInt(req.query.page) || 1;
const pageSize = parseInt(req.query.page_size) || 10;
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedIntents = filteredIntents.slice(startIndex, endIndex);
res.json({
intents: paginatedIntents,
pagination: {
total_results: filteredIntents.length,
total_pages: Math.ceil(filteredIntents.length / pageSize),
current_page: page,
page_size: pageSize
}
});
});
// Intent details endpoint
app.get('/uim/intents/:intent_uid', (req, res) => {
const intent = intents.find(i => i.intent_uid === req.params.intent_uid);
if (!intent) {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: `The intent '${req.params.intent_uid}' was not found.`
}
});
}
res.json(intent);
});
8. Implement the Policy API¶
Implement the policy API that allows AI agents to retrieve the policy:
9. Implement the PAT Issuance API¶
Implement the PAT issuance API that allows AI agents to request PATs:
// PAT issuance endpoint
app.post('/pat/issue', (req, res) => {
const { agent_id, signed_policy, agent_public_key } = req.body;
// Validate request
if (!agent_id || !signed_policy || !agent_public_key) {
return res.status(400).json({
error: {
code: 'INVALID_PARAMETER',
message: 'Missing required parameters.'
}
});
}
try {
// Verify the signature
const policyJson = JSON.stringify(policy);
const verifier = crypto.createVerify('SHA256');
verifier.update(policyJson);
const isValid = verifier.verify(
agent_public_key,
Buffer.from(signed_policy, 'hex')
);
if (!isValid) {
return res.status(400).json({
error: {
code: 'INVALID_SIGNATURE',
message: 'The signature is invalid.'
}
});
}
// Generate PAT
const pat = {
iss: 'example.com',
sub: agent_id,
exp: Math.floor(Date.now() / 1000) + 86400, // 24 hours
nbf: Math.floor(Date.now() / 1000),
jti: crypto.randomBytes(16).toString('hex'),
scope: intents.map(intent => `${intent.intent_uid}:execute`),
pol: 'https://example.com/uim-policy.json',
lmt: {
rate: 100,
period: 3600
}
};
// Sign PAT
const token = jwt.sign(pat, privateKey, { algorithm: 'RS256' });
res.json({
'uim-pat': token,
'expires_at': new Date(pat.exp * 1000).toISOString()
});
} catch (error) {
console.error('Error issuing PAT:', error);
res.status(500).json({
error: {
code: 'INTERNAL_SERVER_ERROR',
message: 'An unexpected error occurred on the server.'
}
});
}
});
10. Implement the Intent Execution API¶
Implement the intent execution API that allows AI agents to execute intents:
// Intent execution endpoint
app.post('/uim/execute', (req, res) => {
const { intent_uid, parameters } = req.body;
// Validate request
if (!intent_uid || !parameters) {
return res.status(400).json({
error: {
code: 'INVALID_PARAMETER',
message: 'Missing required parameters.'
}
});
}
// Verify PAT
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Missing or invalid Authorization header.'
}
});
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
// Check if the PAT allows executing this intent
if (!decoded.scope.includes(`${intent_uid}:execute`)) {
return res.status(403).json({
error: {
code: 'FORBIDDEN',
message: 'The PAT does not grant permission to execute this intent.'
}
});
}
// Check rate limit
// In a real implementation, you would use a rate limiting library or database
// Find the intent
const intent = intents.find(i => i.intent_uid === intent_uid);
if (!intent) {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: `The intent '${intent_uid}' was not found.`
}
});
}
// Validate parameters
for (const param of intent.input_parameters) {
if (param.required && !parameters[param.name]) {
return res.status(400).json({
error: {
code: 'INVALID_PARAMETER',
message: `Missing required parameter: ${param.name}`
}
});
}
}
// Execute the intent
// In a real implementation, you would call your actual business logic here
if (intent_uid === 'example.com:search-products:v1') {
// Simulate a product search
const products = [
{
id: '123',
name: 'Laptop',
price: 1000,
description: 'A powerful laptop'
}
];
res.json({
products,
total_results: products.length
});
} else if (intent_uid === 'example.com:get-product-details:v1') {
// Simulate getting product details
res.json({
product: {
id: parameters.product_id,
name: 'Laptop',
price: 1000,
description: 'A powerful laptop',
specifications: {
processor: 'Intel Core i7',
memory: '16GB',
storage: '512GB SSD'
}
}
});
} else {
res.status(400).json({
error: {
code: 'INTENT_NOT_SUPPORTED',
message: `The intent '${intent_uid}' is not supported.`
}
});
}
} catch (error) {
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Invalid PAT.'
}
});
} else if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'PAT has expired.'
}
});
}
console.error('Error executing intent:', error);
res.status(500).json({
error: {
code: 'INTERNAL_SERVER_ERROR',
message: 'An unexpected error occurred on the server.'
}
});
}
});
11. Start the Server¶
Start the server:
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Complete Example¶
Here's a complete example of a web service that implements the UIM Protocol:
```javascript const express = require('express'); const crypto = require('crypto'); const jwt = require('jsonwebtoken'); const fs = require('fs');
// Generate key pair function generateKeyPair() { return crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); }
// Load or generate keys let privateKey, publicKey; try { privateKey = fs.readFileSync('private-key.pem', 'utf8'); publicKey = fs.readFileSync('public-key.pem', 'utf8'); } catch (error) { console.log('Generating new key pair...'); const keys = generateKeyPair(); privateKey = keys.privateKey; publicKey = keys.publicKey; fs.writeFileSync('private-key.pem', privateKey); fs.writeFileSync('public-key.pem', publicKey); }
// Define intents const intents = [ { intent_uid: 'example.com:search-products:v1', intent_name: 'SearchProducts', description: 'Search for products based on criteria', input_parameters: [ { name: 'query', type: 'string', required: true, description: 'Search query' }, { name: 'category', type: 'string', required: false, description: 'Product category' }, { name: 'price_range', type: 'string', required: false, description: 'Price range (e.g., "10-100")' }, { name: 'sort_by', type: 'string', required: false, description: 'Sort criteria' } ], output_parameters: [ { name: 'products', type: 'array', description: 'List of matching products' }, { name: 'total_results', type: 'integer', description: 'Total number of matching products' } ], endpoint: 'https://api.example.com/products/search', tags: ['e-commerce', 'search', 'products'] }, { intent_uid: 'example.com:get-product-details:v1', intent_name: 'GetProductDetails', description: 'Get detailed information about a specific product', input_parameters: [ { name: 'product_id', type: 'string', required: true, description: 'Product ID' } ], output_parameters: [ { name: 'product', type: 'object', description: 'Product details' } ], endpoint: 'https://api.example.com/products/details', tags: ['e-commerce', 'product', 'details'] } ];
// Define policy const policy = { '@context': 'http://www.w3.org/ns/odrl.jsonld', 'uid': 'http://example.com/policy/12345', 'type': 'Set', 'profile': 'http://example.com/profile/odrl-uim', 'permission': [ { 'target': 'http://example.com/api/intents', 'action': 'execute', 'constraint': [ { 'leftOperand': 'http://example.com/vocab/rateLimit', 'operator': 'lte', 'rightOperand': 1000, 'unit': 'http://example.com/vocab/hour' } ], 'duty': [ { 'action': 'pay', 'target': 'http://example.com/vocab/intentPrice', 'amount': 0.01, 'unit': 'http://example.com/vocab/USD' } ] } ], 'prohibition': [ { 'target': 'http://example.com/api/intents', 'action': 'exceedRateLimit' } ], 'obligation': [ { 'action': 'signPayload', 'assignee': 'http://example.com/ai-agent/1', 'target': 'http://example.com/vocab/payload', 'constraint': [ { 'leftOperand': 'http://example.com/vocab/publicKey', 'operator': 'use', 'rightOperand': 'MIIBIjANBgkqh...' } ] } ] };
// Create agents.json const agentsJson = { 'service-info': { 'name': 'Example E-commerce Service', 'description': 'Provides e-commerce functionalities', 'service_url': 'https://api.example.com', 'service_logo_url': 'https://example.com/logo.png', 'service_terms_of_service_url': 'https://example.com/terms', 'service_privacy_policy_url': 'https://example.com/privacy' }, 'intents': intents, 'uim-public-key': publicKey, 'uim-policy-file': 'https://example.com/uim-policy.json', 'uim-api-discovery': 'https://api.example.com/uim/intents/search', 'uim-compliance': { 'standards': ['ISO27001', 'GDPR'], 'regional-compliance': { 'EU': 'GDPR', 'US-CA': 'CCPA' }, 'notes': 'Data is encrypted in transit and at rest.' } };
// Ensure public directory exists if (!fs.existsSync('public')) { fs.mkdirSync('public'); }
// Save agents.json fs.writeFileSync('public/agents.json', JSON.stringify(agentsJson, null, 2));
// Create Express app const app = express(); app.use(express.json()); app.use(express.static('public'));
// Intent search endpoint app.get('/uim/intents/search', (req, res) => { const { query, tags, intent_name, uid, namespace, description } = req.query;
// Filter intents based on query parameters let filteredIntents = [...intents];
if (query) { const queryLower = query.toLowerCase(); filteredIntents = filteredIntents.filter(intent => intent.intent_name.toLowerCase().includes(queryLower) || intent.description.toLowerCase().includes(queryLower) ); }
if (tags) { const tagList = tags.split(','); filteredIntents = filteredIntents.filter(intent => tagList.some(tag => intent.tags.includes(tag)) ); }
if (intent_name) { filteredIntents = filteredIntents.filter(intent => intent.intent_name.toLowerCase() === intent_name.toLowerCase() ); }
if (uid) { filteredIntents = filteredIntents.filter(intent => intent.intent_uid === uid ); }
if (namespace) { filteredIntents = filteredIntents.filter(intent => intent.intent_uid.split(':')[0] === namespace ); }
if (description) { const descLower = description.toLowerCase(); filteredIntents = filteredIntents.filter(intent => intent.description.toLowerCase().includes(descLower) ); }
// Pagination const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.page_size) || 10; const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const paginatedIntents = filteredIntents.slice(startIndex, endIndex);
res.json({ intents: paginatedIntents, pagination: { total_results: filteredIntents.length, total_pages: Math.ceil(filteredIntents.length / pageSize), current_page: page, page_size: pageSize } }); });
// Intent details endpoint app.get('/uim/intents/:intent_uid', (req, res) => { const intent = intents.find(i => i.intent_uid === req.params.intent_uid);
if (!intent) { return res.status(404).json({ error: { code: 'NOT_FOUND', message: The intent '${req.params.intent_uid}' was not found.
} }); }
res.json(intent); });
// Policy endpoint app.get('/uim-policy.json', (req, res) => { res.json(policy); });
// PAT issuance endpoint app.post('/pat/issue', (req, res) => { const { agent_id, signed_policy, agent_public_key } = req.body;
// Validate request if (!agent_id || !signed_policy || !agent_public_key) { return res.status(400).json({ error: { code: 'INVALID_PARAMETER', message: 'Missing required parameters.' } }); }
try { // Verify the signature const policyJson = JSON.stringify(policy); const verifier = crypto.createVerify('SHA256'); verifier.update(policyJson); const isValid = verifier.verify( agent_public_key, Buffer.from(signed_policy, 'hex') );
if (!isValid) {
return res.status(400).json({
error: {
code: 'INVALID_SIGNATURE',
message: 'The signature is invalid.'
}
});
}
// Generate PAT
const pat = {
iss: 'example.com',
sub: agent_id,
exp: Math.floor(Date.now() / 1000) + 86400, // 24 hours
nbf: Math.floor(Date.now() / 1000),
jti: crypto.randomBytes(16).toString('hex'),
scope: intents.map(intent => `${intent.intent_uid}:execute`),
pol: 'https://example.com/uim-policy.json',
lmt: {
rate: 100,
period: 3600
}
};
// Sign PAT
const token = jwt.sign(pat, privateKey, { algorithm: 'RS256' });
res.json({
'uim-pat': token,
'expires_at': new Date(pat.exp * 1000).toISOString()
});
} catch (error) { console.error('Error issuing PAT:', error); res.status(500).json({ error: { code: 'INTERNAL_SERVER_ERROR', message: 'An unexpected error occurred on the server.' } }); } });
// Intent execution endpoint app.post('/uim/execute', (req, res) => { const { intent_uid, parameters } = req.body;
// Validate request if (!intent_uid || !parameters) { return res.status(400).json({ error: { code: 'INVALID_PARAMETER', message: 'Missing required parameters.' } }); }
// Verify PAT const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Missing or invalid Authorization header.' } }); }
const token = authHeader.split(' ')[1]; try { const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
// Check if the PAT allows executing this intent
if (!decoded.scope.includes(`${intent_uid}:execute`)) {
return res.status(403).json({
error: {
code: 'FORBIDDEN',
message: 'The PAT does not grant permission to execute this intent.'
}
});
}
// Check rate limit
// In a real implementation, you would use a rate limiting library or database
// Find the intent
const intent = intents.find(i => i.intent_uid === intent_uid);
if (!intent) {
return res.status(404).json({
error: {
code: 'NOT_FOUND',
message: `The intent '${intent_uid}' was not found.`
}
});
}
// Validate parameters
for (const param of intent.input_parameters) {
if (param.required && !parameters[param.name]) {
return res.status(400).json({
error: {
code: 'INVALID_PARAMETER',
message: `Missing required parameter: ${param.name}`
}
});
}
}
// Execute the intent
// In a real implementation, you would call your actual business logic here
if (intent_uid === 'example.com:search-products:v1') {
// Simulate a product search
const products = [
{
id: '123',
name: 'Laptop',
price: 1000,
description: 'A powerful laptop'
}
];
res.json({
products,
total_results: products.length
});
} else if (intent_uid === 'example.com:get-product-details:v1') {
// Simulate getting product details
res.json({
product: {
id: parameters.product_id,
name: 'Laptop',
price: 1000,
description: 'A powerful laptop',
specifications: {
processor: 'Intel Core i7',
memory: '16GB',
storage: '512GB SSD'
}
}
});
} else {
res.status(400).json({
error: {
code: 'INTENT_NOT_