> ## Documentation Index
> Fetch the complete documentation index at: https://aletyx.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration and APIs

> API reference documentation, authentication patterns, integration examples, and error handling guidance for integrating Decision Control into your enterprise architecture.

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](/docs/decision-control-tower/overview) 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.

<Tabs>
  <Tab title="Browser (PKCE)">
    ```javascript theme={null}
    // 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
    ```
  </Tab>

  <Tab title="Service Account">
    ```bash theme={null}
    # 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:

    ```json theme={null}
    {
      "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
      "expires_in": 300,
      "refresh_expires_in": 0,
      "token_type": "Bearer",
      "not-before-policy": 0,
      "scope": "profile email"
    }
    ```
  </Tab>
</Tabs>

### Using Access Tokens

Include the access token in the Authorization header for all API requests:

```bash theme={null}
curl https://decision-control-dev.example.com/api/management/units \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI..."
```

<Warning>
  **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](#token-refresh) for details.
</Warning>

### Token Refresh

Refresh tokens before expiration to maintain continuous authentication:

```bash theme={null}
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

```bash theme={null}
GET /api/management/units
```

**Response**:

```json theme={null}
[
  {
    "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"
  }
]
```

<Info>
  **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`.
</Info>

**Example**:

```bash theme={null}
curl -X GET https://decision-control-dev.example.com/api/management/units \
  -H "Authorization: Bearer $TOKEN" \
  | jq .
```

#### Create New Unit

```bash theme={null}
POST /api/management/units
Content-Type: application/json

{
  "name": "customer-onboarding",
  "description": "Customer onboarding decision models",
  "status": "ENABLED"
}
```

**Response**: `201 Created`

```json theme={null}
{
  "id": 3,
  "name": "customer-onboarding",
  "dateCreated": [2025, 1, 25, 9, 45, 12, 123456000],
  "description": "Customer onboarding decision models",
  "status": "ENABLED"
}
```

**Example**:

```bash theme={null}
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

```bash theme={null}
GET /api/management/units/{unitId}
```

**Response**:

```json theme={null}
{
  "id": 1,
  "name": "financial-services",
  "dateCreated": [2025, 1, 15, 10, 30, 0, 0],
  "description": "Financial services decision models",
  "status": "ENABLED"
}
```

#### Update Unit

```bash theme={null}
PUT /api/management/units/{unitId}
Content-Type: application/json

{
  "name": "financial-services",
  "description": "Updated description for financial services",
  "status": "ENABLED"
}
```

#### Delete Unit

```bash theme={null}
DELETE /api/management/units/{unitId}
```

**Response**: `204 No Content`

<Warning>
  **Cascade Delete**

  Deleting a unit removes all associated versions and models. This operation cannot be undone. Consider setting `status: "DISABLED"` instead for soft deletion.
</Warning>

### 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

```bash theme={null}
GET /api/management/units/{unitId}/versions
```

**Response**:

```json theme={null}
[
  {
    "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**:

```bash theme={null}
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

```bash theme={null}
POST /api/management/units/{unitId}/versions
Content-Type: application/json

{
  "version": "1.2.0",
  "changeLog": "Updated credit scoring thresholds",
  "status": "DRAFT"
}
```

**Response**: `201 Created`

```json theme={null}
{
  "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.

```bash theme={null}
POST /api/management/units/{unitId}/versions/{versionId}/publish
Content-Type: application/json

{
  "publishedBy": "sarah@demo.local"
}
```

**Response**: `200 OK`

```json theme={null}
{
  "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**:

```bash theme={null}
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

```bash theme={null}
GET /api/management/units/{unitId}/versions/{versionId}/models
```

**Response**:

```json theme={null}
[
  {
    "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**:

```bash theme={null}
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.

```bash theme={null}
GET /api/management/units/{unitId}/versions/{versionId}/models/{modelId}/content
```

**Response**: `200 OK`

```xml theme={null}
<?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.

```bash theme={null}
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**:

```bash theme={null}
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

```bash theme={null}
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`

```json theme={null}
{
  "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**:

```bash theme={null}
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.

```bash theme={null}
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`

```json theme={null}
{
  "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

```bash theme={null}
GET /actuator/health
```

**Response**:

```json theme={null}
{
  "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**:

```bash theme={null}
curl https://decision-control-dev.example.com/actuator/health \
  | jq '.status'
```

### Metrics

```bash theme={null}
GET /actuator/metrics
```

Returns list of available metrics:

```json theme={null}
{
  "names": [
    "dmn.execution.duration.seconds",
    "http.server.requests",
    "jvm.memory.used",
    "system.cpu.usage"
  ]
}
```

Get specific metric:

```bash theme={null}
GET /actuator/metrics/dmn.execution.duration.seconds
```

**Response**:

```json theme={null}
{
  "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.:

```json theme={null}
{ "error": "Unit name 'financial-services' already exists" }
```

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:

```javascript theme={null}
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

```javascript theme={null}
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

```python theme={null}
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

```java theme={null}
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](/docs/decision-control/usage-scenarios): Common integration patterns and workflows
* [Architecture](/docs/decision-control/architecture): Deep dive into system design
* [FAQ and Troubleshooting](/docs/decision-control/faq-troubleshooting): Common issues and solutions
