What this is
The AI-CORE API Gateway is the single public entry point to AI-CORE job services (metadata extraction, PDF anonymization, …). You authenticate with an API key, declare who is calling via identity headers, and submit jobs over plain HTTPS — either synchronously (for bounded tasks) or asynchronously with a callback webhook.
Interactive API reference: Swagger UI (try-it-out, Authorize button) · ReDoc (readable reference) · machine-readable OpenAPI schema.
Authentication
Every call to /api/v1/… requires three headers:
| Header | Required | Meaning |
|---|---|---|
X-Api-Key | yes | The API key issued by the gateway admin. 401 if missing, unknown, disabled or expired. Identifies your consumer system (baked into the key at issue time). |
X-Tenant | yes | Tenant on whose behalf the call is made. 400 if missing. Job results are only visible to this tenant. |
X-User | yes | End user on whose behalf the call is made. 400 if missing. |
Every identified call is recorded in the key's usage history (system, tenant, user, route, status, duration) — including denied attempts. Keys are issued per system with per-service permissions via the admin console (Google sign-in, invitation only) — ask the gateway administrator for one.
Quickstart
Synchronous call — metadata extraction
No callback_url: the response body is the service's native result (may take up to ~3 minutes).
curl -sS https://<gateway-host>/api/v1/metadata_extraction \
--max-time 220 \
-H "X-Api-Key: $API_KEY" \
-H "X-Tenant: mytenant" -H "X-User: some.user" \
-H "Content-Type: application/json" \
-d '{
"doc_reference": "https://example.org/documents/decreto.pdf",
"document_kind": "auto",
"max_pages": 10
}'
Asynchronous call — PDF anonymization
callback_url set: you get 202 {"job_id"} at once; the result is POSTed to your webhook. (Omit it to poll instead — see fire-and-poll.)
curl -sS https://<gateway-host>/api/v1/pdf_anonymization \
-H "X-Api-Key: $API_KEY" \
-H "X-Tenant: mytenant" -H "X-User: some.user" \
-H "Content-Type: application/json" \
-d '{
"doc_reference": "https://example.org/documents/referto.pdf",
"callback_url": "https://mysystem.example.org/webhooks/aicore"
}'
PDFs travel either by reference (doc_reference: a URL) or inline (doc_content: base64) — exactly one of the two must be set.
sync Sync-emulation flow
Available on bounded tasks only (currently metadata_extraction).
Omit callback_url and the gateway parks your request while the job runs:
- 200 with the service's native response — the normal case, within the 180 s park budget.
- Business errors keep their original HTTP status (
ResponseErrorbody; job timeout → 504). - If the budget expires (cold start, queue, long job) the call degrades to
202
{"job_id"}with aLocationheader → poll for the result.
async Async + callback flow
Set callback_url (optional on every route) and the submit returns
202 {"job_id"} immediately. When the job reaches a terminal
state, the platform POSTs this envelope to your webhook:
POST <callback_url>
Content-Type: application/json
{
"job_id": "1f0c1e0a-7b6e-4b9e-9b1a-2a7c8d9e0f11",
"service": "pdf-anonymization",
"task": "pdf_anonymization",
"status": "ok", // "ok" | "error"
"response": { ... }, // the service's native response when status=ok
"error": null, // error message when status=error
"error_code": null // business HTTP status when status=error (e.g. 504 timeout)
}
- A terminal callback is always delivered — business errors, timeouts and poison payloads all produce an error envelope, never silence.
- Undeliverable callbacks are retried with backoff, then dead-lettered for operator replay — answer with a 2xx quickly (ack first, process after).
- Your webhook must be reachable from the platform over HTTPS.
No webhook? Fire-and-poll
Omitting callback_url works on every route. On long tasks
(pdf_anonymization) the submit still answers 202
{"job_id"} immediately, with a Location header pointing at the
poll endpoint. Mind the windows: the job stays pollable as
pending while it runs, and the terminal result stays available for
600 s after completion — poll promptly, or prefer a callback for
unattended integrations.
Polling
For jobs submitted without a callback (fire-and-poll, or a sync call that degraded to 202):
curl -sS https://<gateway-host>/api/v1/jobs/$JOB_ID/result \
-H "X-Api-Key: $API_KEY" \
-H "X-Tenant: mytenant" -H "X-User: some.user"
| Status | Meaning |
|---|---|
| 200 | Terminal result — same envelope as the callback (status is ok or error). |
| 202 | Still pending — poll again (suggested interval: 2–5 s; for long jobs like pdf_anonymization, 5–15 s). |
| 404 | Unknown job_id, or the result expired — it stays available 600 s after completion. Jobs submitted by another tenant also read as 404. |
| 403 | Your key is not scoped for the polled job's task. |
Idempotency & retries
- Polls are always safe to retry —
GET, no side effects. - Do not blind-retry a submit that already returned 202. Each
POSTmints a newjob_id, so retrying a successful submit runs the job twice. Retry a submit only on a network error or 5xx where nojob_idreached you. - Transport-level retries of a single submit are deduplicated inside the platform — a job is never
dispatched twice for one accepted
job_id.
Error model
Two body shapes, depending on which layer rejects the call:
| Shape | Produced by | Statuses |
|---|---|---|
{"detail": "…"} |
Authentication / validation layer | 401 bad key · 403 system mismatch or missing scope · 400 missing identity headers · 422 request body validation |
{"code": int, "message": "…"} |
Job layer (ResponseError) |
404 unknown/expired job · 502 job failed · 503 job plane unavailable · 504 job timeout |
Sync business errors propagate the service's own HTTP status (error_code parity with the callback envelope).
Permissions (scopes)
Keys carry a list of allowed services: ["*"] (all) or an explicit subset
(e.g. ["metadata_extraction"]). Calling a route outside your key's scopes returns
403 "API key not authorized for service '…'".
The jobs poll route checks the scope of the polled job's task.
A scope grants a capability, not a deployment, so the agents service is reached
through two of them: agents converses with an agent someone else built,
agents_builder creates, configures and publishes them. A key that may ask
an agent a question has no business rewriting its prompt, and this split is the only
gate between the two — see Agents.
Services
| Endpoint | Mode | Notes |
|---|---|---|
POST /api/v1/metadata_extraction |
sync or async | Extracts structured metadata (title, dates, issuing body, act number, …) from the leading pages of a PDF. Bounded task (~2 min typical) → sync-emulation allowed. max_pages 1–30, taxonomy hint via document_kind, optional output language. |
POST /api/v1/pdf_anonymization |
async — callback or poll | Redacts personal data from a PDF (returns the anonymized PDF as base64 + stats). Jobs can run ~15 min, so the submit always answers 202: result via callback_url, or poll the Location header without one (result window 600 s). Options: custom category prompt, checksum-validated Italian fiscal-code/VAT detection, embedded-file handling, metadata scrubbing. |
GET /api/v1/jobs/{job_id}/result |
— | Poll a parked job (see Polling). |
Full request/response schemas with field-level docs and examples: Swagger UI · ReDoc.
Agents
Standing HTTP routes, not jobs: no job_id, no callback_url, no
poll window. An answer arrives as Server-Sent Events, and every event's
data: payload is base64-encoded (the framing and the event:
names are not, so a client still dispatches on the event name).
| Endpoint | Scope | Notes |
|---|---|---|
GET /api/v1/agents |
agents |
The agents this caller may converse with. An agent appears only once it is published AND exposed to your system and tenant; one that exists but was not exposed to you reads as 404, never 403. |
POST /api/v1/agents/{id_agent}/conversationsPOST …/conversations/{id_conv}/messages |
agents |
Open a conversation, then send a message. A consumer conversation always runs the published version. The message answers 202 with an id_query; one turn at a time per conversation, so a second message while one is running answers 409. |
GET …/conversations/{id_conv}/stream?id_query=… |
SSE agents |
Stream that turn. Event kinds: token, tool_call, tool_result, subagent_start, subagent_end, error, nop (keepalive), done. Unknown fields in data are additive — ignore what you do not know. |
GET /api/v1/builder/agents · PUT …/draft · POST …/validate · POST …/publish |
agents_builder |
Build an agent. Drafts are stored leniently, so a half-written manifest round-trips; validate and publish are where it must hold up, and a refusal is a 422 listing what stops it. |
PUT /api/v1/builder/agents/{id_agent}/audiences |
agents_builder |
Whole-set replacement of who may reach the agent, as (id_system, id_tenant) rows. A key may only grant within its own system, so the rows it writes are its own; an empty list makes the agent private again. |
POST /api/v1/builder/agents/{id_agent}/conversations · GET /api/v1/builder/conversations/{id_conv}/stream |
SSE agents_builder |
The playground: run an agent before publishing it (target: draft), same byte contract as the consumer stream. |
X-Tenant and X-User are required here as everywhere, and the
tenant is half of what authorizes the call. The system is never a header: it comes off
the key. Full schemas: Swagger UI · ReDoc.