Difinity API Reference
Authenticate an application, call the Flow runtime at api.difinity.ai, and read the evidence a governed run leaves behind.
Before you start
Three things must exist before your application can make a governed call.
An organisation administrator creates an application in Hub. An application is a registered caller with its own identity and its own rate limit; it is not an agent and it is not a person.
The administrator issues an API token for that application. The token's secret is shown once, at creation, and never again.
The administrator assigns at least one use case to the application. A use case is the bounded governed context your calls run in: its policies, its guardrails, and (for the provider-compatible endpoints and POST /api/v1/chat) whose model provider account pays for the call.
You never create the application or issue the token yourself through this API. Those are Hub tasks, covered in "What an administrator sets up in Hub" below.
Hosts
| Host | What runs there |
|---|---|
| api.difinity.ai | Flow, the runtime. Everything in this reference except the token exchange. |
| platform.difinity.ai | The Platform API, the system of record. The token exchange, and everything an administrator manages through Hub. |
Every governed action an agent takes leaves through the tool gateway, which holds the credential, decides, and executes. The tool gateway has no public host and you never call it directly; every example in this reference calls only api.difinity.ai and platform.difinity.ai.
Getting started
Five steps from setup to evidence review.
- An administrator creates an application in Hub and issues it an API token. This happens once per application.
- An administrator assigns the application at least one use case.
- Your application exchanges the API token for a short-lived identity token, once per session, by calling platform.difinity.ai.
- Your application calls api.difinity.ai with that identity token and the use case it wants to run in.
- An administrator reads the run in Hub's AI Trail afterwards. Your application does not read its own trail through this API.
Steps 1 and 2 are covered in "What an administrator sets up in Hub". Steps 3 and 4 are covered in "Authentication" and the endpoint sections that follow. Step 5 is covered in "Reading the evidence".
Authentication
Exchange your application's API token for a short-lived identity token. This is the one call in this reference made to platform.difinity.ai rather than api.difinity.ai, and the one call that carries no bearer token, because obtaining one is what it does.
POST https://platform.difinity.ai/api/v1/auth/exchange
Content-Type: application/json
{
"api_token": "<your application's API token>",
"subject": { "id": "end-user-42", "name": "Jane" }
}
subject is optional and names the end user your application is acting for. It is not verified: it is your application's own claim, carried into the identity token you receive back and shown in the trail as an attested subject, never as a signed-in person. Omit it for calls with no end user behind them, such as a scheduled job.
Response:
{
"access_token": "<identity token>",
"token_type": "Bearer",
"expires_in": 900,
"app_id": "app_...",
"tenant_id": "org_...",
"available_use_cases": ["uc_support_bot", "uc_billing_assistant"]
}
The identity token is valid for 15 minutes. Request a new one when it expires; there is no refresh call, because the exchange call is the refresh call. When you sent subject, it travels inside this token rather than as a separate field in this response; the run trail reads it back from there.
available_use_cases is a snapshot taken at the moment of exchange. If an administrator changes which use cases your application may reach, that change takes effect the next time you exchange, not immediately on the token you are already holding.
Send the identity token as a bearer token on every call to api.difinity.ai:
Authorization: Bearer <identity token>
Every call also names the use case it runs in, as a query parameter or a header. Use whichever fits your client:
POST https://api.difinity.ai/api/v1/chat?use_case_id=uc_support_bot
or
POST https://api.difinity.ai/api/v1/chat
X-Use-Case-ID: uc_support_bot
A missing use case, or a use case your application was not assigned, is refused. See "Errors".
Rate limits
The default rate limit is 60 requests per 60 seconds per application. An administrator can configure a different limit for a specific application in Hub.
Every response that passes authentication and the use-case check carries these headers:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | The limit for the current window. |
| X-RateLimit-Remaining | Requests left in the current window. |
| X-RateLimit-Reset | When the current window resets. |
| Retry-After | Present only on a 429. Seconds to wait before retrying. |
A request over the limit is refused with HTTP 429. See "Errors" for the shape.
POST /api/v1/chat
A single governed chat turn. Runs the guardrail pipeline, calls the model, and returns the answer.
Request fields:
| Field | Type | Notes |
|---|---|---|
| model | string, required | The model to use. |
| provider | string or null | One of openai, anthropic, google, deepseek, grok. Leave it null and Difinity.ai selects a model for you based on what the turn needs. |
| message | object, required | The turn's message. Has role (one of system, user, assistant, tool) and content. |
| history_messages | array, optional | Earlier turns, oldest first. Defaults to an empty list. |
| parameters | object, required | temperature, max_tokens, top_p, top_k, presence_penalty, frequency_penalty, stop, seed. All optional inside the object. |
| tools | array or null | Function-calling tool definitions, in the same shape as OpenAI's. |
| outputFormat | object, optional | type (one of raw, json, xml, markdown, html) and an optional schema. The one field on this endpoint that is camelCase rather than snake_case. |
| stream | boolean | Defaults to false. Set it to true, or send an Accept: text/event-stream header, to stream the answer. See "Streaming". |
| conversation_id | string, optional | Threads several turns into one run in the trail. Omit it and each call is its own single-turn run. |
POST https://api.difinity.ai/api/v1/chat?use_case_id=uc_support_bot
Authorization: Bearer <identity token>
Content-Type: application/json
{
"model": "gpt-5.6-terra",
"provider": null,
"message": { "role": "user", "content": "What is our policy on damaged-goods refunds?" },
"parameters": { "temperature": 0.7, "max_tokens": 800 },
"stream": false
}
Response fields:
| Field | Type | Notes |
|---|---|---|
| id | string | An id for this turn. |
| content | string | The answer. |
| usage | object or null | prompt_tokens, completion_tokens, total_tokens, latency_ms. |
| model | string | The model that actually answered. |
| finish_reason | string or null | One of stop, length, content_filter, tool_calls, error, or END_TURN as observed; that last value has no lowercase form. |
| tool_calls | array | Empty unless the model called a tool. |
| status | object or null | A guardrail-stage status, where present: stage, label, state, and an optional detail. |
| transparency | object or null | Disclosure strings for the person using the answer, where the transparency pipeline set any. |
{
"id": "resp_8f2ac1",
"content": "Our policy allows a replacement or refund within 30 days of receipt for goods that arrived damaged.",
"usage": { "prompt_tokens": 142, "completion_tokens": 41, "total_tokens": 183, "latency_ms": 910 },
"model": "gpt-5.6-terra",
"finish_reason": "stop",
"tool_calls": []
}
Streaming
Set stream to true, or send Accept: text/event-stream, and the response streams as server-sent events. Each event is a complete JSON object in the same shape as the non-streaming response above, sent as an unnamed data frame. There is no closing sentinel; the stream simply ends when the connection closes.
If you drop the connection, the work behind it stops. Flow cancels the model or tool call in progress at its next suspension point, rather than continuing to spend the turn after nobody is listening.
POST /api/v1/agent/runs
Starts an agent run. Not streamed: a run can pause partway through to wait for a person's approval, and answering in one response keeps that question separate from the run loop itself.
Request fields:
| Field | Type | Notes |
|---|---|---|
| agent_id | string, required | Which agent. |
| version_id | string, required | Which published version of it. |
| message | string, required | What you are asking the agent to do. |
| model | string, required | The model the run uses. |
| provider | string, optional | One of openai, anthropic, google, deepseek, grok. |
| purpose | string, optional | A name from the closed set the agent's policy defines. |
| conversation_id | string, optional | Threads several runs into one conversation in the trail. |
There is no on_behalf_of field, and this is deliberate. Who the run acts for comes from your identity token, never from a value in the request body, because a field like that would let a caller name anybody in the organisation. The token itself is verified; the subject claim inside it, if you sent one at exchange, is not (see Authentication).
POST https://api.difinity.ai/api/v1/agent/runs?use_case_id=uc_billing_assistant
Authorization: Bearer <identity token>
Content-Type: application/json
{
"agent_id": "agt_refund_assistant",
"version_id": "ver_14",
"message": "Refund order 90142, it arrived damaged.",
"model": "gpt-5.6-terra",
"purpose": "customer_refund"
}
Response fields:
| Field | Type | Notes |
|---|---|---|
| conversation_id | string | |
| outcome | string | One of answered, needs_approval, reached_limit, failed. |
| steps | integer | How many steps the run took. |
| content | string or null | The run's answer, once it has one. |
| because | string or null | Why the run stopped or is asking. |
| approval | object or null | Present only when outcome is needs_approval. See "Approvals and resume". |
{
"conversation_id": "conv_7a19",
"outcome": "needs_approval",
"steps": 2,
"content": null,
"because": "refunding more than the order total needs a person to confirm",
"approval": {
"approval_id": "apr_5c02",
"action_digest": "sha256:9f3a...",
"connector": "stripe",
"tool": "refund_payment",
"arguments": { "order_id": "90142", "amount_cents": 4200 },
"expires_at": "2026-09-02T18:00:00Z"
}
}
An agent's reasoning tier, Quick or Deep, is set on the approved version, not on the request, and it decides the run's step budget and time limit. This reference does not publish those numbers. Ask the person who configured the agent about its limits.
Approvals and resume
Some agent actions need a person to confirm them before they run. Only the person the agent is acting for may answer; there is no approver group, no shared queue, and no delegation. A run nobody answers eventually expires.
A standing "always allow" answer is possible for some escalations, and it is keyed to the specific policy clause that raised the question, not to how similar a future action looks. A refusal is never standing: saying no once does not become a rule for next time.
A person answers a pending approval through the Platform API's approvals endpoint, using their own sign-in, never your application's token. That answer becomes a signed packet. Your application picks the run back up by sending that packet, exactly as issued, to the resume endpoint:
POST https://api.difinity.ai/api/v1/agent/runs/resume?use_case_id=uc_billing_assistant
Authorization: Bearer <identity token>
Content-Type: application/json
{
"approval_id": "apr_5c02",
"approval": "<the signed packet, exactly as issued>"
}
The response has the same fields as the response shown under "POST /api/v1/agent/runs".
An unknown approval id, an already-resumed one, and an expired one all come back as the same 404. This is deliberate: telling them apart would let a caller probe which approval ids exist.
A run started through this application surface does not currently record which person it is acting for. The approval endpoint only allows the run's own recorded person to approve it, so with no person recorded, nobody can pass that ownership check. Treat needs_approval on this surface as informational until Flow records a person for application-surface runs, and do not build a customer-facing approval flow on it yet.
POST /api/v1/audit and POST /api/v1/verify
Two guardrail-only endpoints. Each runs the configured guardrail tasks (personal-information detection, harmful and toxic content, disallowed topics, blacklisted keywords, and the rest of the checks listed below) against a message, and returns a redacted copy ready for you to send onward. Neither generates an answer and neither performs model routing. A check that needs a model's judgement calls Difinity's own evaluation service, on Difinity-hosted models, never the provider or model your request named.
The only difference is the record it leaves. POST /api/v1/audit adds a row to the run trail, the same evidence a chat turn or agent run produces. POST /api/v1/verify runs the identical checks and makes no such record, for pre-flight checks and development.
Both take the same request:
| Field | Type | Notes |
|---|---|---|
| message | object, required | Same shape as POST /api/v1/chat's message. |
| history_messages | array, optional | Defaults to an empty list. |
Both return the same shape:
| Field | Type | Notes |
|---|---|---|
| overall_status | string | passed or failed. |
| checks | object | One entry per configured check (harmful_content, toxic_content, manipulative_behaviour, insensitive_language, social_scoring, fact_checking, fake_news, phishing, blacklisted_keywords, topic_policy, disallowed_topics, custom_ethical_rules, human_escalation), each with status (one of passed, failed, not_applied), and optionally reason, probability and threshold. |
| pii_redaction | object or null | detected, entities_found, redaction_count, mode. Null if PII redaction did not run. |
| redacted_content | object or null | message and history_messages, with any detected PII replaced. Null only if PII redaction did not run at all, or ran in detect-only mode; present otherwise, even when nothing was found. |
Each entry in pii_redaction.entities_found carries both the placeholder and the original value that was detected, in plain text, so the caller can see exactly what was found. Handle this response with the same care you would handle the personal information itself.
POST https://api.difinity.ai/api/v1/audit?use_case_id=uc_support_bot
Authorization: Bearer <identity token>
Content-Type: application/json
{
"message": { "role": "user", "content": "Please send the invoice to jane@example.com." }
}
{
"overall_status": "failed",
"checks": {
"harmful_content": { "status": "passed" },
"blacklisted_keywords": { "status": "passed" }
},
"pii_redaction": {
"detected": true,
"entities_found": [
{ "type": "EMAIL", "original_text": "jane@example.com", "redacted_as": "[EMAIL_1]" }
],
"redaction_count": 1,
"mode": "redact"
},
"redacted_content": {
"message": "Please send the invoice to [EMAIL_1].",
"history_messages": []
}
}
The checks object above is shortened for space; a real response includes every configured check.
Provider-compatible endpoints
This surface has six paths across five providers, each following one provider's own API shape for the fields Difinity documents. The base URL changes to api.difinity.ai. The Authorization header carries your Difinity identity token, not the provider's own key. Every call also still needs use_case_id (or X-Use-Case-ID), the same as the rest of this application surface. The request and response follow the provider's shape for the fields listed below; fields outside that list are ignored or rejected as noted for each endpoint.
| Path | Mimics |
|---|---|
| POST /openai/v1/chat/completions | OpenAI's Chat Completions API |
| POST /anthropic/v1/messages | Anthropic's Messages API |
| POST /deepseek/chat/completions | DeepSeek's OpenAI-compatible chat API |
| POST /gemini/v1/models/{model}:generateContent | Google's Gemini API |
| POST /gemini/v1/models/{model}:streamGenerateContent | Google's Gemini API, streaming |
| POST /grok/v1/chat/completions | xAI's OpenAI-compatible chat API |
Model names follow whichever provider you are pointed at; Flow reads the model your existing integration already sends and maps it to its own catalogue.
OpenAI-compatible
The request accepts these fields: model, messages (each with role and content), temperature, max_tokens, top_p, frequency_penalty, presence_penalty, stop, stream, stream_options, seed, response_format, tools, tool_choice and user. Fields outside this list are ignored. The response carries id, object, created, model, choices (each with an OpenAI-shaped message and finish_reason), and usage.
POST https://api.difinity.ai/openai/v1/chat/completions?use_case_id=uc_support_bot
Authorization: Bearer <identity token>
Content-Type: application/json
{
"model": "gpt-5.6-terra",
"messages": [{ "role": "user", "content": "Summarise this ticket in one sentence." }],
"temperature": 0.7
}
Streaming sends chat.completion.chunk frames in OpenAI's own delta shape, and closes with a literal [DONE] frame, exactly as the real OpenAI streaming API does.
Anthropic-compatible
The request accepts model, max_tokens, messages, system, temperature, top_p, top_k, stop_sequences, stream, metadata, tools and tool_choice. The response carries id, type, role, a content array of typed blocks, model, stop_reason and usage in input_tokens/output_tokens form. Fields outside the request list are ignored or rejected; check the request against a real call before relying on an edge case.
POST https://api.difinity.ai/anthropic/v1/messages?use_case_id=uc_support_bot
Authorization: Bearer <identity token>
Content-Type: application/json
{
"model": "claude-sonnet-5",
"max_tokens": 800,
"messages": [{ "role": "user", "content": "Summarise this ticket in one sentence." }]
}
Streaming sends a sequence of typed frames: message_start, content_block_start, content_block_delta, content_block_stop, message_delta and message_stop. Do not assume this exactly reproduces Anthropic's own streaming sequence; for example, a message_start and content_block_start pair can be sent more than once in one response. Confirm the exact sequence against a real streaming call before building strict event-sequence handling.
DeepSeek-compatible, Gemini-compatible, Grok-compatible
DeepSeek and Grok mimic the OpenAI Chat Completions shape, because both providers' own APIs are themselves OpenAI-compatible. Gemini mimics Google's own generateContent and streamGenerateContent request and response shape, including its contents array and generationConfig object. Field-level behaviour for these three follows the provider's own API shape; verify against your provider's documentation before relying on an edge case.
Errors
Four different error shapes exist across this API, depending on which layer and which host refuses the request. None of them is a normalised, single format; document and handle all four.
The authentication envelope
Returned by the layer that checks your bearer token, your use case, and your rate limit, before any endpoint logic runs.
{
"error": "missing_use_case",
"error_description": "Use case ID is required"
}
| HTTP status | error | When |
|---|---|---|
| 401 | missing_authorization | No Authorization header. |
| 401 | invalid_token | The token does not verify. |
| 403 | missing_use_case | No use_case_id or X-Use-Case-ID was sent. |
| 403 | unauthorized_use_case | The use case is not one your application was assigned. |
| 401 | authentication_error | An unexpected failure while authenticating, distinct from an invalid token. |
| 429 | rate_limit_exceeded | Over the rate limit. Carries a Retry-After header. |
The token exchange envelope
The Platform API returns this envelope for the token exchange, the one call in this reference made to platform.difinity.ai. It uses the RFC 9457 Problem Details format (application/problem+json) and differs from the three Flow envelopes below.
{
"type": "https://difinity.ai/problems/validation-failed",
"title": "Bad Request",
"status": 400,
"detail": "the request could not be accepted; see errors",
"instance": "/api/v1/auth/exchange",
"correlation_id": "...",
"errors": [ { "field": "api_token", "detail": "is required" } ]
}
The errors field is present only on a validation failure; other failures on this call carry type, title, status, detail, instance and correlation_id with no errors array.
The handler envelope
Returned by endpoint logic once authentication has passed: a bad request body, a policy failure, a provider error, or an unexpected fault.
{
"error_code": "DIF-ERR31",
"message": "the message did not pass a configured guardrail check"
}
| HTTP status | error_code | When |
|---|---|---|
| 400 | DIFF-ERR01 | The request body is not valid JSON of the expected shape. |
| 400 | INVALID_ARGUMENT | A bad argument value. |
| 422 | DIF-ERR11 / DIF-ERR12 | The requested provider is not allowed, or not configured, for this use case. |
| 422 | DIF-ERR21 / DIF-ERR22 | The requested model is not allowed, or not configured, for this use case. |
| 422 | DIF-ERR31 | A guardrail or policy check failed. Carries an additional failure_details array. |
| 401 | UNAUTHORIZED | An upstream session Flow depends on has expired or was never valid. |
| 500 | PROCESSING_ERROR | An internal failure after the model answered. |
| 500 | INTERNAL_ERROR | An unhandled fault. |
| (passed through) | PROVIDER_ERROR or the provider's own code | The model provider itself returned an error. |
Field names in this shape are snake_case: error_code, message, and, where present, model and finish_reason.
Running out of credits returns HTTP 402 with error_code OUT_OF_CREDITS. This applies to the chat and agent workspace at chat.difinity.ai; this application surface does not return it.
The streaming envelope
The streaming envelope has two shapes, depending on when the failure happens.
A failure before your stream opens, such as a bad request body or a policy failure caught before any answer starts, arrives as a named error event, in the flat handler shape:
event: error
data: {"error_code": "DIF-ERR31", "message": "the message did not pass a configured guardrail check"}
A failure partway through an already-open stream arrives as one more unnamed data frame, with the error nested under an error key:
{
"error": {
"error_code": "DIF-ERR31",
"message": "the message did not pass a configured guardrail check"
}
}
A provider error nested either way also carries model and finish_reason alongside error_code and message.
An unhandled fault mid-stream carries error_code DIF-ERR00, the streaming equivalent of the handler envelope's INTERNAL_ERROR. An upstream session expiring mid-stream nests the same way, with error_code UNAUTHORIZED:
{
"error": {
"error_code": "UNAUTHORIZED",
"message": "..."
}
}
Reading the evidence
Every governed run leaves a record: the guardrail checks that ran, the model call, any tool calls and approvals, and the final answer. Hub calls this the AI Trail.
An organisation administrator reads it in Hub. This API does not expose a route for your application to read its own trail; that is a deliberate separation between the party that made a call and the party that reviews the evidence of it.
What an administrator sets up in Hub
Everything below is configured by a signed-in person in Hub, using their own sign-in. None of it is something your application calls with its API token; naming it here is so you know what exists and who owns it.
- Applications and API tokens: creating an application, issuing and revoking its tokens.
- Use-case assignment: which governed contexts an application may call.
- Approvals: a person answers an agent's pending approval from the chat and agent workspace or from Hub.
- MCP servers: registering the organisation's own tool servers, by name, endpoint, and a reference to a stored credential. The credential itself is never returned once set.
- Connectors: turning a gallery connector on or off, or wiring a registered MCP server in as one, and choosing which of its tools an agent may call.
- Provider keys: where an organisation's own provider keys are enabled for it, its provider keys are write-only. An administrator can set, replace or remove a key, and can see that one is set, but nothing in the platform can read the key's value back.
- AI Trail: reading the evidence described above.
Not in this API
These families are real but not part of this application surface, and this reference does not document them. Who can reach each one differs:
- /api/v1/conversations/** on Flow. Reachable by a signed-in person; this is the product surface for chat.difinity.ai, the chat and agent workspace. An application's token is refused on it.
- /api/v1/compliance/** on Flow. Reachable by a signed-in person through Hub's compliance screens; not an integration point.
- Roles, groups, permissions and the activity and access logs, and /api/v1/admin/** on the Platform API. Reachable by a signed-in organisation administrator, or, for /admin, by Difinity staff with the right directory role; not reachable with your application's token.
- /api/v1/internal/** on the Platform API. Service-authenticated only, between Flow, the tool gateway and Platform. Not reachable by any person's sign-in or by an application's token at all.
Compliance evidence
Difinity's guardrail and evidence controls can contribute operational evidence to a wider EU AI Act, ISO/IEC 42001, risk and audit programme. Difinity does not determine that an organisation or an AI system is compliant, and does not provide ISO/IEC 42001 certification.
See the platform documentation for the architecture and controls this API sits on top of.