API Patterns and Usage
API Patterns and Usage
Common Operations
0. Checking Server Capabilities
Before making requests, discover what the server supports. GET /info requires no authentication:
const getServerInfo = async () => {
const response = await fetch('https://api.i3x.dev/v1/info', {
headers: { 'Accept': 'application/json' }
});
const data = await response.json();
return data.result; // { specVersion, serverVersion, serverName, capabilities }
};
// Check before calling optional features
const info = await getServerInfo();
if (info.capabilities.query.history) {
// Safe to call /objects/history
}
if (info.capabilities.subscribe.stream) {
// Safe to use SSE streaming
}
1. Discovering Available Objects
Query the API to discover what manufacturing objects are available:
const discoverObjects = async (token) => {
const response = await fetch('https://api.i3x.dev/v1/objects', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
const data = await response.json();
return data.result;
};
You can also explore available namespaces and object types:
// Get all namespaces
const getNamespaces = async (token) => {
const response = await fetch('https://api.i3x.dev/v1/namespaces', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
return data.result;
};
// Get all object types (optionally filtered by namespace)
const getObjectTypes = async (token, namespaceUri = null) => {
const url = namespaceUri
? `https://api.i3x.dev/v1/objecttypes?namespaceUri=${encodeURIComponent(namespaceUri)}`
: 'https://api.i3x.dev/v1/objecttypes';
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
return data.result;
};
2. Reading Object Values
Retrieve current values from manufacturing objects using POST. Note that the elementIds field is always an array:
const readObjectValues = async (token, elementIds, maxDepth = 1) => {
const response = await fetch('https://api.i3x.dev/v1/objects/value', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
elementIds: elementIds,
maxDepth: maxDepth // 0 = infinite recursion, 1 = no child recursion
})
});
const data = await response.json();
// data.results is an array of per-item results matching request order
// Each item: { success, elementId, result: { isComposition, value, quality, timestamp, components } }
return data;
};
// Retrieve historical data
const readObjectHistory = async (token, elementIds, options = {}) => {
const response = await fetch('https://api.i3x.dev/v1/objects/history', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
elementIds: elementIds,
startTime: options.startTime || new Date(Date.now() - 3600000).toISOString(),
endTime: options.endTime || new Date().toISOString(),
maxDepth: options.maxDepth || 1
})
});
// HTTP 206 means server hit its depth limit — results are partial
if (response.status === 206) {
console.warn('Partial results: server depth limit reached');
}
return await response.json();
};
3. Querying Multiple Objects
Retrieve multiple objects by their element IDs:
const getObjectsByIds = async (token, elementIds) => {
const response = await fetch('https://api.i3x.dev/v1/objects/list', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
elementIds: elementIds
})
});
const data = await response.json();
return data.results;
};
// Get related objects
const getRelatedObjects = async (token, elementIds, relationshipType = null) => {
const response = await fetch('https://api.i3x.dev/v1/objects/related', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
elementIds: elementIds,
relationshiptype: relationshipType,
includeMetadata: true
})
});
const data = await response.json();
return data.results;
};
4. Subscribing to Real-time Updates
For applications requiring real-time data, use Server-Sent Events (SSE) subscriptions. Note that in the Beta API, subscription management uses flat POST endpoints — not per-subscription URLs.
// Create a subscription and stream updates
const subscribeToObjects = async (token, elementIds, callback) => {
// Step 1: Create a subscription (optionally provide clientId and displayName)
const createResponse = await fetch('https://api.i3x.dev/v1/subscriptions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
clientId: 'my-app-001',
displayName: 'Dashboard Monitor'
})
});
const createData = await createResponse.json();
const subscriptionId = createData.result.subscriptionId;
// Step 2: Register objects to monitor (subscriptionId in body)
await fetch('https://api.i3x.dev/v1/subscriptions/register', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
subscriptionId: subscriptionId,
elementIds: elementIds,
maxDepth: 1
})
});
// Step 3: Open SSE stream for real-time updates (subscriptionId in body)
const streamResponse = await fetch('https://api.i3x.dev/v1/subscriptions/stream', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ subscriptionId: subscriptionId })
});
// Process the SSE stream
const reader = streamResponse.body.getReader();
const decoder = new TextDecoder();
const processStream = async () => {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// Parse SSE data lines
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
callback(data);
}
}
}
};
processStream();
return { subscriptionId };
};
// Alternative: Use sync endpoint for reliable polling instead of streaming
// Sync uses sequence numbers to guarantee no data loss
const syncSubscription = async (token, subscriptionId, lastSequence = 0) => {
const response = await fetch('https://api.i3x.dev/v1/subscriptions/sync', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
subscriptionId: subscriptionId,
acknowledgeSequence: lastSequence // Acknowledge previously received batch
})
});
const data = await response.json();
// data.result contains new updates with sequenceNumber
return data.result;
};
// Deleting a subscription
const deleteSubscription = async (token, subscriptionId) => {
await fetch('https://api.i3x.dev/v1/subscriptions/delete', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ subscriptionIds: [subscriptionId] })
});
};
5. Handling API Responses
All responses use a standard {success, result} or {success, error} envelope. Always check success before reading result:
const handleResponse = async (response) => {
// HTTP 206 means partial content (depth limit reached) — not an error
if (response.status === 206) {
console.warn('Partial results returned due to server depth limit');
}
const data = await response.json();
if (!data.success) {
throw new Error(`API Error ${data.error.code}: ${data.error.message}`);
}
return data.result;
};
// For bulk operations, check each item individually
const handleBulkResponse = (data) => {
const succeeded = data.results.filter(r => r.success);
const failed = data.results.filter(r => !r.success);
if (failed.length > 0) {
console.warn(`${failed.length} items failed:`, failed.map(r => r.error));
}
return succeeded.map(r => ({ elementId: r.elementId, ...r.result }));
};