# API reference

The hosted API base URL is **https://www.synderesis.eu/v1**. Send JSON with `Content-Type: application/json` for POST requests and authenticate with one customer API-key header.

## Endpoint map

| Method and path | Purpose | Authentication |
| --- | --- | --- |
| GET /health | Runtime/database readiness; outside the /v1 base | None |
| GET /v1/models | Public product aliases and available metadata | API key |
| POST /v1/answers | A question with optional official-source retrieval | API key |
| POST /v1/chat/completions | Transcript or answer-adapter completion | API key |
| GET /v1/sources/search | Search passages using query parameters | API key |
| POST /v1/sources/search | Search passages with optional source filters | API key |
| GET /v1/usage | Account allocation, usage and credit summary | API key |
| GET /v1/me | Profile and key attribution | API key |

The base URL already includes `/v1`; append `/answers`, not `/v1/answers`, when a client accepts a base URL. See [Authentication](authentication.md) for key lifecycle and header rules.

A shared inference-only key (`sk_inf_` prefix) can call only `POST /v1/chat/completions`, `POST /v1/answers` and `GET /v1/models`. Every other route answers HTTP 403 `inference_scope_denied`. Such a key has a stored lifetime token cap and an expiry; see [Limits and billing](limits-and-billing.md#shared-inference-only-keys).

## Models and serving

`GET /v1/models` returns an object with `object: "list"` and a `data` array. Each item has `id`, `object: "model"`, `owned_by: "synderesis"` and `provider: "synderesis"`.

The reviewed implementation lists:

| Identifier | Integration guidance |
| --- | --- |
| spark | Spark answer service. An explicit request uses it directly and never calls the router. |
| pro | Pro product alias. Use this exact identifier. |
| labora | Listed product alias; passing it to chat does not expose the Labora orchestration API. |
| scriptor | Listed compatibility/product alias. |
| qwen | Listed compatibility/product alias. |
| grok | Listed compatibility/product alias. |
| minimax | Listed compatibility/product alias. |
| glm | Listed compatibility/product alias. |
| synderesis-spark | Explicit transcript/tool chat interface; preferred for chat SDK examples. |
| synderesis-pro | Documented public id for Pro; the same choice as `pro`. |
| synderesis-auto | Automatic selection between Spark and Pro. Omitting `model` is the same choice. |

The `synderesis-spark` entry also advertises `context_length: 65536`, `max_completion_tokens: 32768` and `supports_tool_calls: true`. This is advertised model metadata; request-byte, transcript, output and account limits still apply. The handler does not provide a preflight tokenizer endpoint or enforce this advertised context number as a universal input-token guarantee.

### Automatic selection

On `POST /v1/answers` and `POST /v1/chat/completions`, omitting `model` is the same as `"model": "synderesis-auto"` (request alias `auto`). The alias `auto` is accepted but is not its own catalog row. When routing is enabled, one router call may select Spark or Pro and an effort; the router's tokens are metered with the answer and included in `usage`. Only a selected Spark or Pro decision is followed. A disabled router, a timeout, an invalid or low-confidence decision, or a Labora choice serves Spark at low effort and never Pro.

Automatic responses carry `"model": "synderesis-auto"` and a `routing` object: `mode` is `spark` or `pro`, `status` is `selected` or `fallback`, and `effort` is the effort used. Explicit `synderesis-spark`, `spark`, `pro` and `synderesis-pro` keep their own paths and never call the router. On `POST /v1/chat/completions`, automatic selection rejects `tools` and tool results with HTTP 400 `unsupported_parameter`.

Aliases are stable product names, not proof of distinct underlying checkpoints or suppliers. The implementation supports a deployment-controlled retrained-checkpoint route for eligible answer services and other configured serving paths. On this API only an explicit Spark or Pro request to `POST /v1/answers` is eligible for it; Chat Completions, automatic selection and shared inference-only keys do not select that route. If checkpoint provenance, fallback policy or a specific model is a requirement, confirm the serving arrangement with Synderesis before production. Public aliases alone do not establish it.

## POST /v1/answers

Ask one question. Official-source retrieval is on by default. The request is a JSON object of at most **65536 bytes**.

| Field | Recommended type | Behavior |
| --- | --- | --- |
| question | string; required | Trimmed, non-empty, at most 16,000 characters. |
| prompt | string | Legacy alias used only when question is absent. Prefer question. |
| model | string | Omitted, `synderesis-auto` or `auto` selects [automatic selection](#automatic-selection). Otherwise use a listed alias; `synderesis-pro` is the same choice as `pro`. |
| grounding | boolean | Defaults to enabled; false disables official-source retrieval. |
| retrieve_sources | boolean | Earlier name for grounding. Ignored when grounding is present. |
| instructions | string | Optional style/task instructions. Trimmed and truncated to 16,000 characters; Catholic response boundaries remain applied. |
| max_tokens | integer | Defaults to 1024. Converted to an integer and clamped to 1–4096. Send an integer in that range. |
| stream, tools | — | Ignored. `POST /v1/answers` ignores `stream` and `tools`; use the transcript chat interface for SSE or function calls. |

The implementation retains numeric coercion for finite floats and numeric strings in max_tokens. Non-numeric, null, container and non-finite values are rejected. Do not depend on coercion in a new integration.

Use actual JSON booleans for grounding. Internally, false, 0 and certain false-like strings disable it; omission and null leave retrieval enabled. If both names are sent, grounding takes precedence even when null. Other unrecognised fields do not gain functionality by being accepted.

Retrieval uses up to the first 8,000 characters of the question and requests up to eight initial passages. The source context is bounded to 16,000 characters; passages can be selected or dropped before generation. This does not cap the complete prompt at 16,000 characters.

### Answer response

| Field | Meaning |
| --- | --- |
| object | catholic_ai.answer |
| model | Public product alias: `synderesis-auto` under automatic selection, `pro` for `pro` or `synderesis-pro`, `spark` for `spark` or `synderesis-spark`. It can differ from the requested alias. |
| customer_id | Account identifier for the authenticated caller. |
| answer | Returned answer text. |
| structured_answer | null in this native handler. Do not expect parsed answer sections here. |
| structured_answer_validation | unvalidated_native_gateway |
| grounding_status | retrieved_sources_only; a fixed status in this handler, including requests with grounding disabled. |
| source_refs | Array of source_id and location pairs supplied as context. |
| retrieved_sources | Source records supplied to generation. This is not a sentence-level verification result. |
| usage | prompt_tokens, completion_tokens, total_tokens, plus usage_event_id after ordinary settlement. |
| retrieval_selection | Contains the citation-selection status for an ordinary generated answer. |
| routing | Present only under automatic selection: `mode` (`spark` or `pro`), `status` (`selected` or `fallback`) and `effort`. |

Retrieved records include source_id, location, title, url, publisher, source_type, summary and text, plus applicable source metadata for source authority and provenance. Text is limited to 2,400 characters per returned record. Additional metadata may appear.

Usage can include passage-selection work and, under automatic selection, the router call, as well as answer generation. It is not necessarily just the token count of the user-visible question and answer. Provider usage is used when valid; bounded estimates are possible.

The exact connectivity question `Reply with exactly one word: ok` has a special authenticated response path. Its output is not a model-quality, retrieval or billing test. It returns no usage_event_id or retrieval_selection. A shared inference-only key does not get this shortcut; its probe is metered as an ordinary request. Use an ordinary question to test a real generation.

## POST /v1/chat/completions

The request is a JSON object of at most **921600 bytes for chat completions**. The model alias and tool-related fields select one of two handling paths.

### Chat dispatch and history

| Request | Handling |
| --- | --- |
| model is synderesis-spark | Transcript/tool path, with or without tools. |
| model is spark, pro or synderesis-pro, and tools is present (including an empty array) or the transcript contains a tool role or tool_calls | Transcript/tool path. |
| model omitted, auto or synderesis-auto, with tools or tool history | 400 unsupported_parameter. |
| Any other model with tools or tool history, for example labora | 400 unsupported_model. |
| model omitted, auto or synderesis-auto, without tools or tool history | Takes the answer path with [automatic selection](#automatic-selection). |
| spark, pro, synderesis-pro or another accepted alias, without tools or tool history | Takes the answer path: uses the last user message and system messages. |

For a Pro transcript without function definitions, send `"model":"pro","tools":[]` (or `synderesis-pro`). To preserve conversational history, select the transcript path deliberately. The answer adapter does not send earlier user/assistant conversation turns to generation.

### Transcript/tool request fields

| Field | Type | Behavior |
| --- | --- | --- |
| model | string | synderesis-spark, or spark, pro or synderesis-pro with tools present. |
| messages | array; required | 1–255 messages, with roles system, developer, user, assistant or tool. |
| messages[].content | string, text-part array or null | Text parts use type=text and a string text field. Only text parts are accepted. Null is valid for an assistant tool-call message. |
| tools | array | Optional function definitions; an empty array selects the transcript path for supported aliases. |
| tool_choice | string or named function object | auto by default; none, required, or a named supplied function are accepted. |
| parallel_tool_calls | boolean | Defaults to true. |
| max_completion_tokens | integer | Takes precedence over max_tokens. Defaults to 8192; valid range 1–32768. Explicit null uses the default. |
| max_tokens | integer | Used when max_completion_tokens is absent; same range/default. |
| stream | boolean | Defaults to false. True returns buffered SSE after generation. |
| stream_options | object | include_usage=true adds a final usage chunk when streaming. |
| n | number | Only 1 is supported. |
| response_format | — | Must be omitted or null on this path. Structured-output modes are not supported here. |

Additional sampling, seed, user, metadata and persistence fields are not forwarded by this handler. Acceptance does not mean they affect model behavior. See the [SDK compatibility matrix](sdk-and-examples.md#compatibility-matrix).

The complete transcript is sent again for every call. All pending tool calls must have corresponding tool results before the next ordinary message. Validate the complete sequence and [follow the tool loop](streaming-and-tools.md#function-calling).

### Transcript/tool response

A non-streaming response contains:

```json
{
  "id": "chatcmpl-example",
  "created": 1793966400,
  "object": "chat.completion",
  "model": "synderesis-spark",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "Illustrative answer text."},
    "finish_reason": "stop"
  }],
  "usage": {"prompt_tokens": 120, "completion_tokens": 30, "total_tokens": 150}
}
```

Values are illustrative. The response `model` is `synderesis-spark` for the Spark aliases and `pro` for `pro` or `synderesis-pro`. The assistant message can instead contain tool_calls and nullable content. A tool call has id, type=function, and function.name and function.arguments; arguments is a JSON string, not a decoded object. Completion reasons can include stop, length and tool_calls. Handle unknown future values without discarding the response.

A provider-dependent assistant reasoning_content string may be retained for transcript continuation. Treat it as an optional transport field; do not depend on its availability or display/log it by default.

### Answer-adapter request differences

The adapter takes the last user message as the question and concatenates system-message content as instructions. Earlier conversation turns and developer-role instructions are not forwarded as a full transcript. Under automatic selection, up to two earlier user turns (the first 500 characters of each) are given to the router only, to classify a follow-up; they do not reach answer generation and are not stored.

max_tokens defaults to 4096 and is clamped to 1–4096, following the answer endpoint's coercion. max_completion_tokens does not control this adapter. `stream: true` returns **400 unsupported_parameter**. The adapter can use grounding/retrieve_sources like answers.

Its response is smaller: id=chatcmpl-native, object, model, choices and usage, plus `routing` under automatic selection. `model` is `synderesis-auto` under automatic selection and otherwise the public alias, for example `spark` or `pro`. It does not include created. Its choices contain an assistant string and finish_reason=stop; do not use that fixed stop value as proof that generation was not truncated. Citation/source fields from the answer response are not included in the adapted chat response.

## Source search

Use **GET /v1/sources/search?query=...&limit=6** or **POST /v1/sources/search** with a JSON object. Authentication is required.

| Field | Where | Behavior |
| --- | --- | --- |
| query | GET query or POST body | Non-empty search string, at most 8,000 characters. |
| q | GET query or POST body | Legacy fallback when query is absent. Prefer query. |
| limit | GET query or POST body | Integer 1–20; defaults to 6. |
| source_ids | POST body | Up to 100 IDs, each at most 256 characters. An ID string is also accepted; prefer an array. |
| source_id | POST body | Singular compatibility name, with precedence over source_ids. |

A GET source_ids query parameter is not a documented filter; use POST for filtering. POST uses the ordinary 65,536-byte body limit.

The response has object=source_search, query and data. Each data entry has source_id, location, title, url, publisher, source_type, chunk_kind, paragraph_index, summary and text. summary is bounded to 1,200 characters and text to 4,000. These are retrieved passages; respect the rights of their original publishers.

A search reserves quota and records an account usage event with zero generation tokens on success. Zero generation tokens does not mean it bypasses request quotas. Search failures currently return service_unavailable for several backend/quota failures; do not assume every quota cause is exposed as 429 on this endpoint.

## GET /v1/usage

This endpoint reports **account-wide** values, including activity attributed to other keys and browser use. It does not return an itemised per-request billing ledger.

| Field | Meaning |
| --- | --- |
| object | synderesis.usage |
| customer_id | Account identifier. |
| api_key_prefix | The presented key's prefix; omitted for session-only access. |
| used_tokens | Allocation usage across periods, including pending reserved tokens. |
| initial_allocation | The account authority's one-time starting token allocation. |
| remaining_tokens | max(0, initial_allocation - used_tokens). |
| allocation_exhausted | Indicates exhaustion when the account is not released by paid/complimentary entitlement. |
| paid | Whether a current paid entitlement is recognised. |
| complimentary | Whether a complimentary grant is recognised. |
| credit_used_eur | Informational used credit amount in euros. |
| credit_remaining_eur | Informational credit balance in euros. |

Token fields are not monthly refill counters. A paid account can have remaining_tokens=0 while its free-allocation restriction has been lifted. Credit fields are informational and can fall back to zero if the billing summary cannot be read; use Account/support for reconciliation.

## GET /v1/me

An authenticated customer key can read its own profile. The response contains object=synderesis.me, customer_id, email, plan, plan_label, api_key_prefix, name, organization, chat_access, service_available and service_live.

Service flags and plan labels are application metadata; they do not confer account-management privileges or replace the actual endpoint's authorization decision. Treat profile values as personal data.

## Common HTTP behavior

Native API responses carry X-Request-ID and no-store security headers. Use error.request_id and the response header when reporting an issue. A supplied X-Request-ID is accepted only as 1–128 ASCII letters, digits, dots, underscores, colons or hyphens; invalid values are replaced.

Unknown paths return **404 not_found**. Unsupported methods return **405 method_not_allowed**, with an Allow header where applicable. OPTIONS is included because the customer API does not enable CORS.

There is no general server-published /openapi.json or Swagger UI in this runtime. This reference and the corresponding repository implementation define the reviewed surface.
