Integration and APIs¶
Decision Control exposes RESTful APIs for model management, decision execution, and system monitoring. This document provides API reference documentation, authentication patterns, integration examples, and error handling guidance for integrating Decision Control into your enterprise architecture.
API Architecture¶
Decision Control provides two API surfaces:
Decision Control Management API: Model lifecycle operations (create, version, publish, execute) exposed by each Decision Control environment instance.
Actuator API: Health checks, metrics, and operational endpoints for monitoring and observability.
All APIs require OAuth2 Bearer token authentication via Keycloak and return JSON responses with standard HTTP status codes.
Governance workflows — approvals, four-eyes enforcement, and audit trails — are managed through Aletyx Decision Control Tower (Keystone Edition). See the Aletyx Decision Control Tower documentation for governance features.
Authentication¶
Obtaining an Access Token¶
Decision Control uses OAuth2 Authorization Code flow with PKCE for browser-based clients and Client Credentials flow for service-to-service integration.
// Step 1: Generate PKCE challenge
const verifier = generateRandomString(64);
const challenge = await sha256(verifier);
// Step 2: Redirect to Keycloak
const authUrl = 'https://keycloak.your-domain.com/realms/aletyx/protocol/openid-connect/auth';
const params = new URLSearchParams({
client_id: 'decision-control-ui',
redirect_uri: 'https://your-app.com/callback',
response_type: 'code',
scope: 'openid profile email',
state: generateRandomString(24),
code_challenge: challenge,
code_challenge_method: 'S256'
});
window.location.href = `${authUrl}?${params}`;
// Step 3: Exchange code for tokens (in callback handler)
const tokenResponse = await fetch(
'https://keycloak.your-domain.com/realms/aletyx/protocol/openid-connect/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'decision-control-ui',
code: authCode,
code_verifier: verifier,
redirect_uri: 'https://your-app.com/callback'
})
}
);
const tokens = await tokenResponse.json();
// tokens.access_token, tokens.refresh_token, tokens.id_token
# Request access token using client credentials
curl -X POST https://keycloak.your-domain.com/realms/aletyx/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=decision-control-service" \
-d "client_secret=your-client-secret"
Response:
Using Access Tokens¶
Include the access token in the Authorization header for all API requests:
curl https://decision-control-dev.example.com/api/management/units \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI..."
Token Expiration
Access tokens expire after 5 minutes by default. Implement automatic token refresh using the refresh token to avoid authentication failures. See Token Refresh for details.
Token Refresh¶
Refresh tokens before expiration to maintain continuous authentication:
curl -X POST https://keycloak.your-domain.com/realms/aletyx/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=decision-control-ui" \
-d "refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI..."
Decision Control Management API¶
The Management API provides operations for creating, versioning, publishing, and executing DMN models. Each environment (dev, test, prod) exposes its own Management API instance.
Base URLs¶
| Environment | Base URL |
|---|---|
| Development | https://decision-control-dev.example.com |
| Test (UAT) | https://decision-control-test.example.com |
| Production | https://decision-control-prod.example.com |
Units API¶
Units are top-level organizational containers for decision models. They represent logical groupings like business domains, applications, or functional areas.
List All Units¶
Response:
[
{
"id": 1,
"name": "financial-services",
"dateCreated": [2025, 1, 15, 10, 30, 0, 0],
"description": "Financial services decision models",
"status": "ENABLED"
},
{
"id": 2,
"name": "compliance",
"dateCreated": [2025, 1, 20, 14, 15, 30, 500000000],
"description": "Regulatory compliance models",
"status": "ENABLED"
}
]
Date Format
The dateCreated field uses Java LocalDateTime array format: [year, month, day, hour, minute, second, nanosecond]. Convert to ISO 8601 for display: 2025-01-15T10:30:00.000Z.
Example:
curl -X GET https://decision-control-dev.example.com/api/management/units \
-H "Authorization: Bearer $TOKEN" \
| jq .
Create New Unit¶
POST /api/management/units
Content-Type: application/json
{
"name": "customer-onboarding",
"description": "Customer onboarding decision models",
"status": "ENABLED"
}
Response: 201 Created
{
"id": 3,
"name": "customer-onboarding",
"dateCreated": [2025, 1, 25, 9, 45, 12, 123456000],
"description": "Customer onboarding decision models",
"status": "ENABLED"
}
Example:
curl -X POST https://decision-control-dev.example.com/api/management/units \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "risk-assessment",
"description": "Risk assessment and scoring models",
"status": "ENABLED"
}'
Get Unit by ID¶
Response:
{
"id": 1,
"name": "financial-services",
"dateCreated": [2025, 1, 15, 10, 30, 0, 0],
"description": "Financial services decision models",
"status": "ENABLED"
}
Update Unit¶
PUT /api/management/units/{unitId}
Content-Type: application/json
{
"name": "financial-services",
"description": "Updated description for financial services",
"status": "ENABLED"
}
Delete Unit¶
Response: 204 No Content
Cascade Delete
Deleting a unit removes all associated versions and models. This operation cannot be undone. Consider setting status: "DISABLED" instead for soft deletion.
Versions API¶
Versions represent numbered releases of decision models within a unit. They enable versioning, rollback capabilities, and environment promotion tracking.
List Versions for Unit¶
Response:
[
{
"id": 1,
"unitId": 1,
"version": "1.0.0",
"dateCreated": [2025, 1, 15, 11, 0, 0, 0],
"publishedAt": [2025, 1, 15, 11, 30, 0, 0],
"publishedBy": "sarah@demo.local",
"status": "PUBLISHED",
"changeLog": "Initial release"
},
{
"id": 2,
"unitId": 1,
"version": "1.1.0",
"dateCreated": [2025, 1, 20, 9, 15, 0, 0],
"publishedAt": null,
"publishedBy": null,
"status": "DRAFT",
"changeLog": "Added fraud detection rules"
}
]
Example:
curl -X GET https://decision-control-dev.example.com/api/management/units/1/versions \
-H "Authorization: Bearer $TOKEN" \
| jq '.[] | select(.status == "PUBLISHED")'
Create New Version¶
POST /api/management/units/{unitId}/versions
Content-Type: application/json
{
"version": "1.2.0",
"changeLog": "Updated credit scoring thresholds",
"status": "DRAFT"
}
Response: 201 Created
{
"id": 3,
"unitId": 1,
"version": "1.2.0",
"dateCreated": [2025, 1, 25, 10, 0, 0, 0],
"publishedAt": null,
"publishedBy": null,
"status": "DRAFT",
"changeLog": "Updated credit scoring thresholds"
}
Publish Version¶
Marks a version as published and ready for execution or promotion.
POST /api/management/units/{unitId}/versions/{versionId}/publish
Content-Type: application/json
{
"publishedBy": "sarah@demo.local"
}
Response: 200 OK
{
"id": 3,
"unitId": 1,
"version": "1.2.0",
"dateCreated": [2025, 1, 25, 10, 0, 0, 0],
"publishedAt": [2025, 1, 25, 14, 30, 0, 0],
"publishedBy": "sarah@demo.local",
"status": "PUBLISHED",
"changeLog": "Updated credit scoring thresholds"
}
Example:
curl -X POST https://decision-control-dev.example.com/api/management/units/1/versions/3/publish \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"publishedBy": "sarah@demo.local"}'
Models API¶
Models are individual DMN files within a version. Each model contains decision logic, decision tables, and business knowledge models.
List Models for Version¶
Response:
[
{
"id": 1,
"versionId": 1,
"name": "CreditScoring",
"fileName": "CreditScoring.dmn",
"dateCreated": [2025, 1, 15, 11, 0, 0, 0],
"lastModified": [2025, 1, 15, 11, 0, 0, 0],
"modifiedBy": "sarah@demo.local",
"description": "Credit risk scoring model",
"namespace": "https://aletyx.com/dmn/financial-services/credit-scoring",
"decisionCount": 5,
"inputCount": 8
}
]
Example:
curl -X GET https://decision-control-dev.example.com/api/management/units/1/versions/1/models \
-H "Authorization: Bearer $TOKEN" \
| jq '.[0] | {name, decisionCount, inputCount}'
Get Model Content¶
Retrieves the complete DMN XML for a model.
Response: 200 OK
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"
xmlns:dmndi="https://www.omg.org/spec/DMN/20191111/DMNDI/"
xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/"
id="CreditScoring"
name="Credit Scoring Model"
namespace="https://aletyx.com/dmn/financial-services/credit-scoring">
<inputData id="InputData_Age" name="Applicant Age">
<variable name="Applicant Age" typeRef="number"/>
</inputData>
<decision id="Decision_RiskScore" name="Risk Score">
<variable name="Risk Score" typeRef="number"/>
<informationRequirement>
<requiredInput href="#InputData_Age"/>
</informationRequirement>
<decisionTable id="DecisionTable_RiskScore">
<!-- Decision table logic -->
</decisionTable>
</decision>
</definitions>
Upload Model¶
Creates or updates a model within a version.
POST /api/management/units/{unitId}/versions/{versionId}/models
Content-Type: multipart/form-data
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="CreditScoring.dmn"
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<definitions ...>
...
</definitions>
------WebKitFormBoundary--
Response: 201 Created
Example:
curl -X POST https://decision-control-dev.example.com/api/management/units/1/versions/2/models \
-H "Authorization: Bearer $TOKEN" \
-F "file=@CreditScoring.dmn"
Decision Execution API¶
Execute published DMN models with input data and receive decision results.
Execute Decision¶
POST /api/runtime/units/{unitName}/versions/{version}/execute
Content-Type: application/json
{
"modelName": "CreditScoring",
"decisionName": "Risk Score",
"context": {
"Applicant Age": 35,
"Annual Income": 75000,
"Credit History Length": 10,
"Existing Debt": 15000,
"Loan Amount": 50000
}
}
Response: 200 OK
{
"executionId": "exec-123e4567-e89b-12d3-a456-426614174000",
"timestamp": "2025-01-25T14:30:00.000Z",
"modelName": "CreditScoring",
"decisionName": "Risk Score",
"result": {
"Risk Score": 720,
"Risk Category": "LOW",
"Approval Recommended": true
},
"executionTimeMs": 42,
"status": "SUCCESS"
}
Example:
curl -X POST https://decision-control-prod.example.com/api/runtime/units/financial-services/versions/1.2.0/execute \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"modelName": "CreditScoring",
"decisionName": "Risk Score",
"context": {
"Applicant Age": 28,
"Annual Income": 55000,
"Credit History Length": 5,
"Existing Debt": 8000,
"Loan Amount": 25000
}
}' | jq .result
Batch Execution¶
Execute decisions for multiple input contexts in a single request.
POST /api/runtime/units/{unitName}/versions/{version}/batch-execute
Content-Type: application/json
{
"modelName": "CreditScoring",
"decisionName": "Risk Score",
"contexts": [
{
"Applicant Age": 35,
"Annual Income": 75000,
"Credit History Length": 10,
"Existing Debt": 15000,
"Loan Amount": 50000
},
{
"Applicant Age": 28,
"Annual Income": 55000,
"Credit History Length": 5,
"Existing Debt": 8000,
"Loan Amount": 25000
}
]
}
Response: 200 OK
{
"batchId": "batch-789e0123-e45b-67c8-d901-234567890abc",
"results": [
{
"index": 0,
"result": {"Risk Score": 720, "Risk Category": "LOW"},
"status": "SUCCESS"
},
{
"index": 1,
"result": {"Risk Score": 680, "Risk Category": "MEDIUM"},
"status": "SUCCESS"
}
],
"totalExecutionTimeMs": 78,
"successCount": 2,
"failureCount": 0
}
Health and Monitoring API¶
Health Checks¶
Response:
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "PostgreSQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 107374182400,
"free": 53687091200,
"threshold": 10485760
}
},
"ping": {
"status": "UP"
}
}
}
Example:
Metrics¶
Returns list of available metrics:
{
"names": [
"dmn.execution.duration.seconds",
"http.server.requests",
"jvm.memory.used",
"system.cpu.usage"
]
}
Get specific metric:
Response:
{
"name": "dmn.execution.duration.seconds",
"description": "Time taken to execute DMN decisions",
"baseUnit": "seconds",
"measurements": [
{"statistic": "COUNT", "value": 1247},
{"statistic": "TOTAL_TIME", "value": 52.341},
{"statistic": "MAX", "value": 0.142}
],
"availableTags": [
{"tag": "model", "values": ["CreditScoring", "FraudDetection"]},
{"tag": "status", "values": ["SUCCESS", "FAILURE"]}
]
}
Error Handling¶
HTTP Status Codes¶
Decision Control uses standard HTTP status codes:
| Status Code | Meaning | Common Causes |
|---|---|---|
200 OK |
Request successful | — |
201 Created |
Resource created successfully | POST operations |
204 No Content |
Resource deleted successfully | DELETE operations |
400 Bad Request |
Invalid request data | Missing required fields, invalid JSON, malformed DMN payload |
401 Unauthorized |
Authentication required | Missing or invalid access token |
403 Forbidden |
Operation not permitted | Free-tier model limit exceeded, AI Assistant not configured |
404 Not Found |
Resource not found | Invalid unit / version / model ID |
409 Conflict |
Resource conflict | Duplicate unit name; deletion blocked by existing versions |
500 Internal Server Error |
Unexpected server error | Database connectivity, unhandled runtime failure |
Error Response Format¶
Decision Control returns a minimal JSON body with the failure message in the error field, e.g.:
The HTTP status code is the primary signal; clients should branch on it and surface the error text to users or logs. Decision Control does not emit machine-readable error codes, timestamps, or request IDs in the response body.
Retry Strategy¶
For transient errors (5xx status codes), implement exponential backoff:
async function executeWithRetry(fn, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status < 500 || attempt === maxRetries - 1) {
throw error; // Don't retry client errors or final attempt
}
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// Usage
const result = await executeWithRetry(() =>
fetch('https://decision-control-dev.../api/management/units', {
headers: { 'Authorization': `Bearer ${token}` }
})
);
Integration Examples¶
Node.js Integration¶
const fetch = require('node-fetch');
class DecisionControlClient {
constructor(baseUrl, keycloakConfig) {
this.baseUrl = baseUrl;
this.keycloakConfig = keycloakConfig;
this.accessToken = null;
}
async authenticate() {
const response = await fetch(
`${this.keycloakConfig.authServerUrl}/realms/${this.keycloakConfig.realm}/protocol/openid-connect/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: this.keycloakConfig.clientId,
client_secret: this.keycloakConfig.clientSecret
})
}
);
const data = await response.json();
this.accessToken = data.access_token;
}
async listUnits() {
if (!this.accessToken) await this.authenticate();
const response = await fetch(`${this.baseUrl}/api/management/units`, {
headers: { 'Authorization': `Bearer ${this.accessToken}` }
});
return response.json();
}
async executeDecision(unitName, version, modelName, decisionName, context) {
if (!this.accessToken) await this.authenticate();
const response = await fetch(
`${this.baseUrl}/api/runtime/units/${unitName}/versions/${version}/execute`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ modelName, decisionName, context })
}
);
return response.json();
}
}
// Usage
const client = new DecisionControlClient(
'https://decision-control-prod.example.com',
{
authServerUrl: 'https://keycloak.your-domain.com',
realm: 'aletyx',
clientId: 'decision-control-service',
clientSecret: 'your-client-secret'
}
);
const result = await client.executeDecision(
'financial-services',
'1.2.0',
'CreditScoring',
'Risk Score',
{ 'Applicant Age': 35, 'Annual Income': 75000 }
);
console.log('Risk Score:', result.result['Risk Score']);
Python Integration¶
import requests
from typing import Dict, Any
class DecisionControlClient:
def __init__(self, base_url: str, keycloak_config: Dict[str, str]):
self.base_url = base_url
self.keycloak_config = keycloak_config
self.access_token = None
def authenticate(self):
token_url = f"{self.keycloak_config['auth_server_url']}/realms/{self.keycloak_config['realm']}/protocol/openid-connect/token"
response = requests.post(token_url, data={
'grant_type': 'client_credentials',
'client_id': self.keycloak_config['client_id'],
'client_secret': self.keycloak_config['client_secret']
})
response.raise_for_status()
self.access_token = response.json()['access_token']
def _headers(self) -> Dict[str, str]:
if not self.access_token:
self.authenticate()
return {'Authorization': f'Bearer {self.access_token}'}
def list_units(self) -> list:
response = requests.get(
f"{self.base_url}/api/management/units",
headers=self._headers()
)
response.raise_for_status()
return response.json()
def execute_decision(self, unit_name: str, version: str, model_name: str,
decision_name: str, context: Dict[str, Any]) -> Dict:
response = requests.post(
f"{self.base_url}/api/runtime/units/{unit_name}/versions/{version}/execute",
headers={**self._headers(), 'Content-Type': 'application/json'},
json={
'modelName': model_name,
'decisionName': decision_name,
'context': context
}
)
response.raise_for_status()
return response.json()
# Usage
client = DecisionControlClient(
'https://decision-control-prod.example.com',
{
'auth_server_url': 'https://keycloak.your-domain.com',
'realm': 'aletyx',
'client_id': 'decision-control-service',
'client_secret': 'your-client-secret'
}
)
result = client.execute_decision(
'financial-services', '1.2.0', 'CreditScoring', 'Risk Score',
{'Applicant Age': 35, 'Annual Income': 75000}
)
print(f"Risk Score: {result['result']['Risk Score']}")
Java Integration¶
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.http.*;
import java.net.URI;
import java.util.Map;
public class DecisionControlClient {
private final String baseUrl;
private final KeycloakConfig keycloakConfig;
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
private String accessToken;
public DecisionControlClient(String baseUrl, KeycloakConfig keycloakConfig) {
this.baseUrl = baseUrl;
this.keycloakConfig = keycloakConfig;
this.httpClient = HttpClient.newHttpClient();
this.objectMapper = new ObjectMapper();
}
public void authenticate() throws Exception {
String tokenUrl = String.format(
"%s/realms/%s/protocol/openid-connect/token",
keycloakConfig.getAuthServerUrl(),
keycloakConfig.getRealm()
);
String body = String.format(
"grant_type=client_credentials&client_id=%s&client_secret=%s",
keycloakConfig.getClientId(),
keycloakConfig.getClientSecret()
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString()
);
Map<String, Object> tokenResponse = objectMapper.readValue(
response.body(), Map.class
);
this.accessToken = (String) tokenResponse.get("access_token");
}
public Map<String, Object> executeDecision(
String unitName, String version, String modelName,
String decisionName, Map<String, Object> context
) throws Exception {
if (accessToken == null) authenticate();
String url = String.format(
"%s/api/runtime/units/%s/versions/%s/execute",
baseUrl, unitName, version
);
Map<String, Object> requestBody = Map.of(
"modelName", modelName,
"decisionName", decisionName,
"context", context
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
objectMapper.writeValueAsString(requestBody)
))
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString()
);
return objectMapper.readValue(response.body(), Map.class);
}
}
// Usage
DecisionControlClient client = new DecisionControlClient(
"https://decision-control-prod.example.com",
new KeycloakConfig(
"https://keycloak.your-domain.com",
"aletyx",
"decision-control-service",
"your-client-secret"
)
);
Map<String, Object> result = client.executeDecision(
"financial-services", "1.2.0", "CreditScoring", "Risk Score",
Map.of("Applicant Age", 35, "Annual Income", 75000)
);
System.out.println("Risk Score: " + result.get("result"));
Next Steps¶
- Usage Scenarios: Common integration patterns and workflows
- Architecture: Deep dive into system design
- FAQ and Troubleshooting: Common issues and solutions