Inference¶
Five paths spend model quota: three chat dialects that share one routing decision, and two typed endpoints. All five require a bearer and an accepted transport (transport and identity).
The three inference dialects¶
POST /v1/chat/completions, POST /v1/messages and
POST /v1/responses are one workflow. Identity, allowlist, alias resolution,
selector semantics, billing ownership and the error contract are identical; only the
request and answer shapes differ.
Each requires a model naming an allowed alias, a canonical
provider/model route, or a selector. The alias names exactly one route, that
route is attempted once, and its answer is the answer to the request; when it refuses, the
refusal is what the caller receives and no other route is attempted. An alias that cannot
serve is refused before any provider is contacted, with the 503
alias_unserviceable body in
the refusal envelope.
Shared validation¶
- A request with no
modelis400 missing field `model`, and a bare vendor name is400 model must be a canonical provider/model route or a supported selector(both captured). max_tokensreaches the provider only when the request states one, and a stated zero is refused withmax_tokens must be at least one when it is set. An omitted value is omitted on wires that do not require it. On the Anthropic Messages wire, Brama sends the model's stated output limit: on a subscription route, the limit the provider's own listing states in the published discovery inventory, and only when the listing states none (or discovery has not completed or failed) the one in the model table this build carries — the provider's statement wins because a provider changes a limit after a build is cut. On a direct route the table is the source. No numeric substitute is invented: a request no source sizes is refused before dispatch with400, naming the provider/model, what the listing said and that the table holds no limit, and asking formax_tokens, instead of the provider'smax_tokens: Field required. A table that does not parse, or a row missing a field, refuses sizing with503naming the row. An explicitly suppliedmax_tokensdoes not require this limit lookup.- Subscription output limits are indexed by provider and model when discovery is published. A request reads one immutable publication, including its discovery outcome; it neither scans the full model inventory nor joins provider discovery. Inventory copying and replacement cleanup happen outside the publication lock. Repeated entries keep the first advertised limit, including an absent limit.
temperaturereaches the provider only when the request states one; an omitted temperature is omitted on the wire too, so the provider applies its own default. A gateway that invents a temperature for every request without one gets Claude's newest models refusing it —`temperature` is deprecated for this model— for callers that never asked. A stated value is refused unless it is finite and no greater than two, withtemperature must be finite and between zero and 2.- An empty conversation is refused with
messages must not be empty.
The real subscription qualification is tests/subscriptions/journeys/unlisted_output_limit.sh,
with BRAMA naming the binary, AGENT the paying agent and
MODEL a provider/model absent from the embedded catalog. Before any provider
request, it requires a readable catalog, the provider's array and a successful boolean
absence result. Missing tooling, unreadable or malformed metadata cannot count as an
unlisted model. The journey then checks the cost-acknowledgement refusal and the actual
provider answer, retaining commands, exit statuses and complete output under
target/real-tests/subscriptions. An admission failure is not a provider test.
POST /v1/chat/completions¶
Accepts exactly model, messages, max_tokens,
temperature, tools, tool_choice,
billingTarget (providerId/accountId/subscriptionId)
and stream. An unknown field is refused with the deserializer's own sentence
(captured):
400 invalid JSON: unknown field `frequency_penalty`, expected one of `model`,
`messages`, `max_tokens`, `temperature`, `tools`, `tool_choice`,
`billingTarget`, `stream` at line 1 column 93
Success (captured):
{"choices":[{"finish_reason":"stop","index":0,
"message":{"content":"...","role":"assistant"}}],
"id":"chatcmpl-...","model":"<route>","object":"chat.completion",
"usage":{"completion_tokens":7,"prompt_tokens":9,"total_tokens":16}}
The model in the answer is the route that served the request, which is the
one route the named alias resolves to. usage is the provider's stated meter;
an answer whose provider stated no usage has no usage member, never zeros,
and a stated count that is not a whole number is refused as a provider failure. The same
holds for the buffered /v1/messages and /v1/responses answers below.
With "stream": true the answer is a text/event-stream of
chat.completion.chunk frames: a role delta first, then content deltas, a
finish_reason frame, a usage frame, closed by data: [DONE].
finish_reason is the provider's stop in this vocabulary
(stop, length, tool_calls,
content_filter); a reason it has no word for is sent as the provider stated
it, and null when the provider stated none. The usage frame is sent only when
the provider stated both counts.
POST /v1/messages¶
The Anthropic Messages dialect over the same routing decision. It accepts the Anthropic
request shape: model, max_tokens, messages, system,
tools and streaming. Anthropic fields the provider-neutral request cannot hold — stop
sequences, cache-control hints, non-function tool types — are accepted and have no effect
on the attempt; no approximation of them is sent in their place. Success (captured):
{"content":[{"text":"...","type":"text"}],"id":"msg_...",
"model":"<route>","role":"assistant","stop_reason":"end_turn",
"stop_sequence":null,"type":"message",
"usage":{"input_tokens":9,"output_tokens":7}}
Streaming emits message_start, content_block_*,
message_delta and message_stop events, with no
[DONE] sentinel. message_start carries a meter of nothing
counted yet; message_delta carries the provider's stop as
end_turn, stop_sequence, max_tokens,
tool_use or refusal (a content filter), any other reason as the
provider stated it, and its usage only when the provider stated one.
POST /v1/responses¶
The OpenAI Responses dialect, with input in place of
messages. Reasoning options and stored-response identifiers are accepted and
have no effect on the attempt. Success (captured):
{"created_at":...,"id":"resp_...","model":"<route>","object":"response",
"output":[{"content":[{"annotations":[],"text":"...","type":"output_text"}],
"id":"msg_...","role":"assistant","status":"completed","type":"message"}],
"status":"completed",
"usage":{"input_tokens":9,"output_tokens":7,"total_tokens":16}}
Streaming emits response.* events closed by
response.completed, or by response.incomplete with
incomplete_details.reason when the provider stopped short (an output limit,
a content filter, or the reason it stated); the terminal response carries
usage only when the provider stated one.
Streaming commit rule¶
Everything that can change about a request — which credential redeems it, which account is billed — is settled before the first byte reaches the caller. Once a byte has been written, the generation is committed: a stream that ends without its terminal event was cut after commit, and Brama has already stopped. It does not resume a committed generation anywhere, and the caller sees the truncated stream and decides what to do about it.
Streaming a model reachable only through the shared catalogue is refused as
400 invalid_request before any provider is contacted.
POST /v1/embeddings¶
A typed, alias-only endpoint on the wisent-backend/embeddings route. It
accepts exactly model, input, encoding_format
(float or base64), dimensions and
user; its shape refusals are invalid embedding input and
invalid embedding encoding format. Success is the OpenAI embeddings shape:
object: "list", data[].embedding, usage.
On a deployment whose registry declares no embeddings alias, the captured answer is
500 {"code":"internal_error","message":"embedding alias missing"}. Declaring the
alias is a registry write
(PUT /v1/admin/routes);
an existing registry document is brought to the current shape by
brama routes migrate.
POST /v1/moderations¶
A typed, alias-only endpoint on wisent-backend/moderation. It requires
model and input, and answers invalid moderation input
on a shape error. Success is the OpenAI moderations shape.
POST /v1/decisions¶
The endpoint that answers instead of generating. It accepts exactly
model, state and questions, and exactly two model names:
decision-model, on a route this deployment pays for, and
best-decision-model, whose route may be best — in which case the
caller's own signed identity selects the subscription that pays, exactly as
it does for a chat completion. Everything the shape means is in
decision.
{
"model": "decision-model",
"state": "Help! My payouts have been failing for 3 days.",
"questions": {
"is_urgent": {"type": "noul", "instructions": "Does this convey urgency?"},
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {"billing": "Payments, invoicing, refunds", "technical": "Bugs, outages, integrations"}
}
}
}
Success is one answer per question, beside the route and engine that
answered and the tokens it cost:
{"model", "route", "engine", "answers", "usage"}. A noul answers a
probability and a confidence; a choice answers one of the labels the
question declared, its distribution and a confidence; a score answers the
probability-weighted level index, its distribution, a confidence and the
legend it was graded against.
The refusals are its own. A malformed question is 400 invalid_request
naming the question — question `mood` declares unsupported type `vibe`: a
question is a noul, a choice or a score — and nothing is sent to a
provider. A model name that is not one of the two is
400 model must be `decision-model` or `best-decision-model`. An alias with
no serviceable route is 503 alias_unserviceable carrying
details.state and details.route. An answer outside the schema the
caller declared is 502 decision_contract_violated, whose message names the
route that answered and why, carrying details.alias, details.route
(what the alias resolves to), details.answered_by (the route that answered:
for best-decision-model, the subscription model it selected),
details.reason and details.reply (the reply as the model wrote it):
the provider answered, and what it answered cannot be used. The usage
latency_ms of an answer is the measured value, unrounded.
A decision alias is refused on the chat endpoints by name, and a canonical
route belonging to a decision-only provider is refused there too —
route `typesafe/jev-latest` serves typed decisions only; call POST
/v1/decisions.
The three media endpoints¶
POST /v1/images/generations takes {"model", "prompt"} with the
optional n, size, quality, background,
output_format, response_format and user, and answers the
provider's image document with model rewritten to the route the caller
named. Input images ride in image as data: URLs, with an optional
mask beside them. POST /v1/images/edits is the OpenAI edit
contract for the same request: a multipart/form-data form whose text fields
are those options and whose files are the input images (image, or
image[] for several) and an optional mask. A provider that
declares an edit endpoint (OpenAI) is sent the edit there; Gemini and Seedream read
the images in their generation request. A form with no input image, a file that is
not an image, or a body that is not a form is 400 naming what was wrong,
and JSON sent to the edit path is pointed at /v1/images/generations.
POST /v1/videos takes {"model", "prompt"} with
size and seconds, and answers a job — an id and a
status — which GET /v1/videos/{id}?model=… reads back from the
provider that issued it. POST /v1/audio/speech takes
{"model", "input", "voice"} with response_format and
speed, and answers the encoded audio itself, with the provider's own
Content-Type: it is the one endpoint here whose body is not JSON.
Each accepts the deployment's media alias — image-model,
video-model, voice-model — or a canonical route whose provider
serves that shape and which the catalogue lists as that kind. A chat model is
refused before any credential is redeemed
(`openai/gpt-4o-mini` is a text model: name `image-model` or a
provider/model route the catalogue lists as image), an unrouted media
alias is 503 naming the command that declares it, and a status read
with no model is 400 a video status read names the model that started the
job: GET /v1/videos/{id}?model=…. Media is paid by the deployment's own
capability: no subscription carries an image, video or voice quota, so
best cannot stand behind a media alias
(media).
Which aliases the presenting bearer may name, and which of them can serve right now, is
GET /v1/aliases.
Which concrete models the caller can execute is
GET /v1/models.