Gateway, Tools, and MCP API
API surface for the OpenAI-compatible /v1 gateway, the Anthropic Messages API, direct tool calls, and the /mcp endpoint.
These endpoints are how code outside the SOIT console calls a workspace's models and tools. Every call is a governed run. For concepts and configuration, see OpenAI-Compatible Gateway and MCP Server. This page describes v1.3.0, with the changes in v1.4.0, v1.5.0, and v1.6.0 marked.
Prefixes
/v1 OpenAI-compatible gateway (since v1.2.0), and the Anthropic Messages API at /v1/messages (since v1.5.0)
/api/v1/tools direct tool calls (since v1.3.0)
/mcp MCP server (since v1.3.0)New in v1.4.0: a process started with SOIT_ROLE=gateway serves only these prefixes, the /.well-known discovery metadata of /mcp, and /health and /metrics; every other path answers 404 there. See Running the gateway on its own.
Authentication
- Send a SOIT API key as
Authorization: Bearer sk_…. SOIT's API also accepts the key inX-API-Key, which is what the Anthropic SDKs send to/v1/messages. - The key's scopes (
read,write,admin) cap what it may do below its owner's role; calling models and tools needswrite.POST /v1/messages/count_tokens, which calls no model, needsread. - A signed-in session works too; over
/mcpit names the workspace inX-Workspace-Id. - A key issued to a service principal authenticates as the principal.
/v1 endpoints
| Method | Path | Notes |
|---|---|---|
| POST | /v1/chat/completions | Whole or streamed (stream, stream_options.include_usage). Tools and tool_choice, images in user messages, response_format (json_object, json_schema), temperature, top_p, max_tokens / max_completion_tokens, stop, seed. n must be 1. |
| POST | /v1/messages | New in v1.5.0: the Anthropic Messages API, whole or streamed in Anthropic's named events. model, max_tokens, messages, system, temperature, top_p, stop_sequences, stream, tools, tool_choice. See Anthropic Messages. |
| POST | /v1/messages/count_tokens | New in v1.5.0: {"input_tokens": n} for a prompt. SOIT's estimate, not the provider's tokenizer; calls no model, opens no run, and is not billed. |
| GET | /v1/models | Active models and virtual models the key may call, by the ref a call names. From v1.5.0 each entry also carries Anthropic's type, display_name, and created_at, and the list has_more, first_id, and last_id. |
| POST | /v1/embeddings | encoding_format float or base64 (the SDKs' default). |
| POST | /v1/images/generations | n, size (64 to 4096 pixels a side, or auto), response_format. A size out of range is refused before the call is billed. New in v1.4.0: background, output_format (png, jpeg, webp), and quality. |
| POST | /v1/images/edits | Multipart image and optional mask. As in OpenAI's API, the mask's transparent pixels mark the area to edit. Takes the same n, size, response_format, background, output_format, and, new in v1.4.0, quality as generations; whether each reaches the provider depends on the model's route. |
- The
modelfield takes a model ref (model:{provider}:{model}) or a virtual model (vmodel:{slug}). - Fields SOIT does not know are ignored rather than refused. From v1.4.0, OpenAI image parameters SOIT knows but does not send (
style,moderation,output_compression,partial_images,stream, andinput_fidelityon edits) are accepted at their defaults and refused with400at any other value. See Image options. - On models reached through OpenAI's Responses API (the GPT-5.5 family on an
openaiprovider),stopis refused because that API cannot honour it, andseedis not sent. - Not served: the Responses API (
/v1/responses), Assistants, files, audio, batch, and fine-tuning, and Anthropic's Message Batches and Files APIs. - On
/v1/messages,top_k,metadata,service_tier,thinking, andcache_controlare accepted and not sent, anddisable_parallel_tool_useintool_choicelikewise; theanthropic-versionandanthropic-betaheaders are accepted and not interpreted. Adocumentor other content block SOIT cannot carry, an image inside atool_result, and tools Anthropic runs itself are refused with400naming them.
Response headers and ids
x-soit-run-idnames the run the call became. A chat completion's id ischatcmpl-{run id}, and an Anthropic message's id ismsg_{run id}.- The API lists
X-SOIT-Run-IdinAccess-Control-Expose-Headers, so a browser on another origin can read it.
Errors
Refusals under /v1 use OpenAI's error body without SOIT's envelope, so SDKs raise their usual exception classes:
{"error": {"message", "type", "param", "code"}}code is SOIT's error code in lower case.
| Status | type | Typical cause |
|---|---|---|
| 400 | invalid_request_error | A malformed request, n above 1, an image size out of range, an image parameter SOIT does not send (v1.4.0) |
| 401 | authentication_error | A missing, revoked, or expired key |
| 402 | insufficient_quota | A hard-stop budget is spent (budget_exhausted), or credit is exhausted |
| 403 | permission_error | The key's scope, model list, or address list does not allow the call, or (v1.6.0) the workspace refuses calls with no price (pricing_not_configured) |
| 404 | invalid_request_error | The model is not configured in the workspace |
| 409 | invalid_request_error | The model or its provider is disabled, or the provider has no credential or governed base URL |
| 422 | invalid_request_error | The model lacks a capability the call needs, such as tools or embeddings, (v1.4.0) its route cannot carry an image option asked for, or the provider's host does not resolve |
pricing_not_configured is answered before the provider is called, and a virtual model moves on to a priced target first; see Calls with no price.
Anthropic errors
New in v1.5.0. Under /v1/messages, refusals use Anthropic's body instead, so the Anthropic SDKs raise their usual exception classes:
{"type": "error", "error": {"type", "message"}}| Status | type |
|---|---|
| 400 | invalid_request_error |
| 401 | authentication_error |
| 402 | billing_error |
| 403 | permission_error |
| 404 | not_found_error |
| 413 | request_too_large |
| 429 | rate_limit_error, with Retry-After |
| 5xx | api_error |
The causes are those of the table above. A request that fails validation names the fields, as Anthropic's API does (max_tokens: Field required). A failure after a stream has started arrives as an error event, since the status line is already sent.
| 429 | rate_limit_error | A rate limit or quota; Retry-After says when to try again |
| 5xx | api_error | The provider failed or timed out, and no virtual model target was left to try |
Streams
A streamed response starts when the model's first chunk arrives, so a refusal before the stream answers with its own status code. A failure part way through ends the stream with an OpenAI error event. A stream the client abandons fails its run with CLIENT_DISCONNECTED. From v1.4.0 every stream that reached the provider is charged, with an estimate flagged usage_estimated when the provider's usage never came.
A streamed /v1/messages call (v1.5.0) uses Anthropic's named events: message_start, ping, content_block_start, content_block_delta, content_block_stop, message_delta, and message_stop. message_start carries SOIT's estimate of the prompt's tokens, and message_delta the provider's count once it is known.
Rate limits
A call over a rate limit or a daily quota answers 429 with a Retry-After header and the code RATE_LIMIT_EXCEEDED (lower case under /v1). Per-key limits — calls per minute, calls per 24 hours, tokens per UTC day — apply on top of the key owner's own limits. From v1.4.0 the call limits count a key's tool calls through /api/v1/tools and /mcp as well as its model calls, and a refusal names the limit in details.quota (per_minute, daily_requests). A call from an address or to a model the key does not allow answers 403. See Per-key limits.
Tools
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/tools | The tools the caller may invoke: built-ins, installed plugin tools, and the tools of enabled MCP servers, each with the schema of its arguments and whether it needs approval. |
| GET | /api/v1/tools/{ref} | One tool. |
| POST | /api/v1/tools/{ref}/invoke | Body {"arguments": {...}}; optional Idempotency-Key header. |
These endpoints answer in SOIT's envelope, {"success": true, "data": {...}}, not OpenAI's. data carries run_id, status (succeeded, failed, waiting_approval, or rejected), result or error, idempotency_key, and replayed. The run is also named in x-soit-run-id.
| Status | Meaning |
|---|---|
200 | The tool ran; a tool that ran and failed is 200 with status: failed |
202 | The tool requires approval: status: waiting_approval with an approval_id |
400 | The arguments do not match the tool's schema |
403 | A tool or address the key may not use |
404 | An unknown tool |
409 | An Idempotency-Key reused with another tool or other arguments |
- Idempotency. The same
Idempotency-Keyreturns the recorded outcome (replayed: true) without calling the tool again. Keys belong to the caller, so two API keys never share one. Without a key, SOIT assigns one and returns it in theIdempotency-Keyresponse header. - Approval. Once a reviewer decides in Govern › Approvals, send the same call with the same key: an approved call runs and returns its result, a rejected one returns
status: rejectedand never runs. Only the arguments that were put up for approval can run.
MCP
| Method | Path | Notes |
|---|---|---|
| POST | /mcp | One JSON-RPC message per request, answered with application/json. tools/list, tools/call, server/discover (2026-07-28), initialize and ping (earlier revisions). |
| GET / DELETE | /mcp | 405: no server-to-client stream and no sessions. |
| GET | /.well-known/oauth-protected-resource/mcp | OAuth 2.1 protected resource metadata (RFC 9728) for the endpoint. |
| GET | /.well-known/oauth-protected-resource | The same metadata. |
/mcp and /.well-known/* answer their protocols' own shapes, not SOIT's envelope. Protocol versions, headers, and error codes are covered in MCP Server.
Related endpoints
| Method | Path | Purpose |
|---|---|---|
| POST / GET | /api/v1/api-keys | Create keys (scopes, limits, allowed_tools, principal_id) and list them |
| PATCH | /api/v1/api-keys/{key_id} | Set a key's limits |
| GET / POST | /api/v1/service-principals | List and create service principals |
| GET / POST | /api/v1/modelhub/virtual-models | List and create virtual models |
| GET / POST | /api/v1/billing/budgets | List and create budgets |
| GET | /api/v1/runs?source=gateway | Runs that came through the gateway; also filterable by api_key_id |
| GET | /api/v1/runs/costs/entries, /api/v1/runs/costs/reconciliation | Cost entries and their reconciliation, filterable by key, principal, source, and the provider's upstream_id and upstream_request_id (v1.6.0) |
| PATCH | /api/v1/workspaces/{workspace_id} | unpriced_call_policy: what calls with no price do (v1.6.0) |
See Identity API, ModelHub API, Runs, Tasks, and Observe API, and Budgets and Cost for the full families.