Service Integration Actions
Connecting external APIs through service integration handlers, data services, secrets, and healing.
Service integration actions connect your matrix to external APIs. They're the most common handler type for bridging the semantic world with your systems of record. This guide covers the handler configuration, how data services and secrets work together, the healing mechanism, reconciliation, and edge cases you'll encounter when integrating real-world APIs.
For the data product model that underlies service connections, see Data Products and Governance. For the request and response template mechanics, see Request Mapping and Response Mapping.
The ServiceIntegration Handler
A ServiceIntegration handler connects an action to an external API by wiring together four things: the data service (where to call), the request template (how to construct the request), the response template (how to map the response), and optionally healing and reconciliation behavior.
tasks:ListTasksIntegration
a rars-act:ServiceIntegration ;
rars-act:service tasks:TaskAPI ;
rars-act:requestTemplate tasks:ListTasksRequest ;
rars-act:responseTemplate tasks:ListTasksResponseMap ;
rars-act:healingEnabled true .The action itself is handler-agnostic. It defines the I/O contract. The handler defines the implementation:
tasks:ListTasks
a rars-act:Action ;
rars-act:isClassifiedAs rars-act:Query ;
rdfs:label "list tasks" ;
dct:description "Lists all tasks from the task management system." ;
rars-act:resultScheme tasks:Task ;
rars-act:handler tasks:ListTasksIntegration ;
rars-act:cacheTTL 60 .When RARS invokes tasks:ListTasks, it materializes the request template into an HTTP request, sends it to the data service endpoint, processes the response through the RML template, and binds the resulting resources to the caller's result variables.
Data Services
The data service declares the base URL and protocol for your API. Multiple actions can share a single service:
tasks:TaskAPI
a rars-svc:RemoteDataService ;
rdfs:label "Task API" ;
rars-svc:endpointUrl "https://api.example.com/v1"^^xsd:anyURI ;
rars-svc:protocol rars-http:HTTP .Each action's request template appends its specific path to this base URL. A ListTasks action might use /tasks, while GetTask uses /tasks/\{id\}. The service definition stays the same.
When to Use Separate Services
Use separate services when actions hit genuinely different systems. A matrix that integrates both a task API and a notification service needs two services, likely under two different data products:
tasks:TaskAPI
a rars-svc:RemoteDataService ;
rars-svc:endpointUrl "https://api.example.com/v1"^^xsd:anyURI ;
rars-svc:protocol rars-http:HTTP .
tasks:NotificationService
a rars-svc:RemoteDataService ;
rars-svc:endpointUrl "https://hooks.slack.com/services"^^xsd:anyURI ;
rars-svc:protocol rars-http:HTTP .Don't create separate services for different endpoints on the same API. One service per API base URL.
Secret Management
Secrets are declared in secrets.ttl and resolved at runtime. The spec declares that a secret exists and who can access it. The actual value is set through the Poliglot console at deployment time.
tasks:TaskAPIKey
a rars-scrt:ManagedSecret ;
rars-scrt:description "API key for the task management service." .
tasks:TaskAPIKeyPolicy
a rars-iam:ResourcePolicy ;
rars-iam:effect rars-iam:Allow ;
rars-iam:action rars-scrt:ResolveSecret ;
rars-iam:resource tasks:TaskAPIKey ;
rars-iam:role tasks:TaskAgentRole .The IAM policy is critical. Without it, the agent can't resolve the secret at runtime and the action fails with an authorization error. Every secret needs a resource policy granting rars-scrt:ResolveSecret to the appropriate agent role.
Secret Resolution at Runtime
Secrets are never exposed in the RDF graph. When a request template calls rars-scrt:resolve_secret, it produces a marker string. The marker stays in the graph as an opaque token. Only when the actual HTTP request is constructed does RARS replace the marker with the real secret value. This means the AI never sees API keys, tokens, or credentials during reasoning.
Managed vs Temporary Secrets
Managed secrets (rars-scrt:ManagedSecret) persist across deployments. Set them once in the console, and they're available until rotated. Use these for API keys, service account credentials, and other long-lived tokens.
Temporary secrets (rars-scrt:TemporarySecret) are short-lived and typically provided at context creation time. Use these for user-specific tokens (OAuth access tokens, session tokens) that expire and can't be shared across users.
Healing
When rars-act:healingEnabled true is set on a handler, failed invocations trigger a diagnostic agent before the error propagates. The healer receives the full failure context:
- The exception chain
- The HTTP request that was constructed
- The response body (if one was received)
- The RML mapping definition
- The action's payload, subject, and result schemas
The healer agent diagnoses the root cause and attempts recovery. If it succeeds, the action completes normally from the caller's perspective. If it fails, the original error propagates.
What Healing Handles Well
- Transient errors: 503s, timeouts, rate limits. The healer retries with backoff.
- Response format changes: the API added a wrapper object or renamed a field. The healer inspects the actual response and adapts.
- Auth failures: a token expired or a permission changed. The healer identifies the specific auth issue.
What Healing Doesn't Handle (yet)
- Logic errors: your request template sends the wrong data. Healing can diagnose this but can't fix your spec.
- Missing resources: the API returns 404 because the resource doesn't exist. This is a data problem, not an integration problem.
- Permanent API changes: if the API fundamentally changed its contract, healing will fail repeatedly. Update your templates.
When to Disable Healing
Disable healing (rars-act:healingEnabled false) when:
- The action is in a tight loop where healing latency is unacceptable
- You're still in development and want to see raw errors without the healer intervening
For most external API integrations, leave healing enabled by default.
Reconciliation
When an action's output supersedes previous data (e.g., a sync action that refreshes all tasks from the source), use rars-act:onReconcile to retract stale statements:
tasks:SyncTasksIntegration
a rars-act:ServiceIntegration ;
rars-act:service tasks:TaskAPI ;
rars-act:requestTemplate tasks:SyncTasksRequest ;
rars-act:responseTemplate tasks:TaskResponseMap ;
rars-act:healingEnabled true ;
rars-act:onReconcile tasks:SyncReconciliation .
tasks:SyncReconciliation
a rars-os:UpdateScript ;
rars-os:update """
PREFIX tasks: <https://example.org/spec/tasks#>
PREFIX rars-act: <https://poliglot.io/rars/spec/actions#>
PREFIX rars-os: <https://poliglot.io/rars/spec/os#>
DELETE { ?s ?p ?o }
WHERE {
?s a tasks:Task ;
?p ?o .
FILTER(?p IN (tasks:title, tasks:status, tasks:assignee))
FILTER NOT EXISTS {
GRAPH ?changeset { ?s ?p ?o }
}
}
""" .The reconciliation script runs after the handler's output is staged. It receives ?changeset, a named graph containing the new output. The DELETE removes any existing task properties that aren't present in the new data. The result is a single atomic observation with both rars-os:attests (new statements) and rars-os:retracts (removed statements).
When You Need Reconciliation
- Sync actions: refreshing data from a source of truth where deleted records should disappear from the graph
- Refresh actions: updating a cached dataset where stale properties should be replaced
- Import actions: importing data where duplicates should be deduplicated
When You Don't
- Create actions: new data, nothing to reconcile
- Single-resource queries: fetching one resource by ID doesn't supersede other data
- Append-only patterns: adding new records without affecting existing ones
Query vs Mutation Behavior
The action's classification (rars-act:Query vs rars-act:Mutation) changes how the platform handles the service integration:
Queries are cached. Results are stored for rars-act:cacheTTL seconds and served on subsequent calls with the same inputs. The default TTL is 300 seconds. Set it based on data volatility:
# Task lists change often
tasks:ListTasks
rars-act:cacheTTL 60 .
# Search results are stable for hours
crawl:Search
rars-act:cacheTTL 21600 .
# Site maps change daily
crawl:Map
rars-act:cacheTTL 86400 .Mutations are never cached, regardless of TTL settings. They also can't be used in cross-action joins during SPARQL execution.
Result Graphs
By default, the handler's response template produces resources that bind directly to the caller's result variables. For actions where you want to store the mapped response in a named graph (e.g., for batch processing or staged validation), use rars-act:resultGraph:
tasks:SyncTasksIntegration
a rars-act:ServiceIntegration ;
rars-act:resultGraph [
rars-os:datatype xsd:anyURI ;
rars-os:fromValue """
PREFIX rars-os: <https://poliglot.io/rars/spec/os#>
SELECT ?value WHERE {
BIND(URI(CONCAT("urn:graph:tasks:sync:", STRUUID())) AS ?value)
}
"""
] .The result graph URI is bound to the first result variable instead of the individual mapped resources. The caller can then query the named graph to access the batch results.
Common Patterns
CRUD Operations on One API
A typical REST integration has five actions sharing one service and one auth header:
| Action | Method | Path | Classification | Subject |
|---|---|---|---|---|
| ListTasks | GET | /tasks | Query | None |
| GetTask | GET | /tasks/{id} | Query | Task (required) |
| CreateTask | POST | /tasks | Mutation | None |
| UpdateTask | PUT | /tasks/{id} | Mutation | Task (required) |
| DeleteTask | DELETE | /tasks/{id} | Mutation | Task (required) |
All share tasks:TaskAPI as the service and tasks:BearerAuthHeader as the auth header function. Each has its own request template (different method and path) and response template (list vs single-object iterator). The POMs are shared across all response templates that return task data.
Paginated APIs
For APIs that return paginated results, the response template maps each page. Pagination logic (following next-page tokens) is handled by the handler at runtime, not in the mapping. The response template just maps the items on each page:
tasks:TaskPageResponseMap
a rml:TriplesMap ;
rml:logicalSource [
rml:referenceFormulation rml:JSONPath ;
rml:iterator "$.items[*]"
] ;
rml:subjectMap [
rml:template "https://example.org/tasks/\{id\}" ;
rml:class tasks:Task
] ;
rml:predicateObjectMap tasks:pom_externalId ;
rml:predicateObjectMap tasks:pom_title ;
rml:predicateObjectMap tasks:pom_status .Multiple Response Formats
Some APIs return different response structures for list vs detail endpoints. Define separate response templates but share POMs:
# List endpoint: {"data": [{"id": ..., "title": ...}], "total": 42}
tasks:ListResponseMap
a rml:TriplesMap ;
rml:logicalSource [
rml:referenceFormulation rml:JSONPath ;
rml:iterator "$.data[*]"
] ;
rml:subjectMap [ rml:template "https://example.org/tasks/\{id\}" ; rml:class tasks:Task ] ;
rml:predicateObjectMap tasks:pom_externalId ;
rml:predicateObjectMap tasks:pom_title .
# Detail endpoint: {"id": ..., "title": ..., "description": ..., "comments": [...]}
tasks:DetailResponseMap
a rml:TriplesMap ;
rml:logicalSource [
rml:referenceFormulation rml:JSONPath ;
rml:iterator "$"
] ;
rml:subjectMap [ rml:template "https://example.org/tasks/\{id\}" ; rml:class tasks:Task ] ;
rml:predicateObjectMap tasks:pom_externalId ;
rml:predicateObjectMap tasks:pom_title ;
rml:predicateObjectMap tasks:pom_description ;
rml:predicateObjectMap tasks:pom_assignee .The list response map only maps the fields available in the summary. The detail response map includes additional fields. Both produce consistent task URIs because they share the same subject template.
See Also
- Actions: action fundamentals, I/O contracts, dispatch, overloading
- Request Mapping: dynamic paths, headers, and JSON bodies
- Response Mapping: RML patterns for complex API responses
- Data Products and Governance: structuring data products and services
- Security: IAM policies and secret management
-
rars-act:ServiceIntegrationReferencerars-act:ServiceIntegrationReference