Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Endpoints and cost shapes

The 17 endpoint kinds TokenTriage recognises, which descriptors declare each, the five units they bill in, and the four kinds no descriptor names.

ReferenceLast updated

On this page

A provider is not uniformly supported. It serves some endpoints and not others, and the ones it serves bill in different units: chat bills per token, image generation bills per image, text-to-speech bills per character. This page is the lookup for both questions.

An endpoint kind is a dialect-neutral name for a surface. It is what lets /v1/chat/completions and /v1/messages be the same kind of thing, and it is the field the router, the cache and the cost engine all key off. The vocabulary registers 17 of them, and four are declared by no descriptor at all. The generator prints that zero rather than omitting the row, so a registered-but-undeclared kind is a stated fact instead of a gap in a table.

Two different facts sit behind those 17, and reading one as the other is the most common way to misread this page. Recognition is whether a path dispatches to a kind at all, and it comes from a wire driver’s own table of paths. Declaration is whether a provider descriptor lists the kind in its metadata, which is what config validation, virtual-key scoping and governance matching read. A kind can be recognised and served while no descriptor declares it, and one is.

The 17 registered kinds

TokenTriage ships 95 built-in provider descriptors, gated cell by cell against mocks, with ten of the 95 carrying no gated cell at all; the provider reference sets out the standard and names the ten. The right-hand column is how many of those 95 declare each kind, taken from the generated matrix on 2026-09-06. Declaring a kind and being gated on it are different facts, which is why the batch row below reads 0 while the endpoint is served.

Kind Path the openai driver registers Descriptors declaring it
chat /v1/chat/completions (and /v1/messages on the Anthropic wire) 80
responses /v1/responses 1
embeddings /v1/embeddings 17
images /v1/images/generations 7
audio_speech /v1/audio/speech 3
audio_transcription /v1/audio/transcriptions 4
rerank /v1/rerank 3
models GET /v1/models 61
batch POST /v1/batches 0
completions /v1/completions 3
moderations /v1/moderations 1
images_edits /v1/images/edits 3
audio_translations /v1/audio/translations 1
files /v1/files 2
fine_tuning /v1/fine_tuning/jobs 0
vector_stores /v1/vector_stores 0
realtime /v1/realtime 0

Only the openai driver registers all 17 paths; files is additionally registered by assemblyai and soniox. The four zeros in the right-hand column count declaring descriptors and nothing else, which is why a zero there is not a statement about whether the path is served.

The batch row, which reads zero and is served anyway

batch is served on the path OpenAI documents, POST /v1/batches, by the openai wire driver, with reconciliation from the batch’s output file and a settlement companion row. No built-in descriptor declares it, so the declaration column still reads zero: recognition here comes from the driver’s own table of paths, not from provider metadata. The undocumented /v1/batch that earlier builds bound no longer resolves at all, and a test pins both halves of that change.

A batch submission does not price on the spot. It carries the per-token cost shape, so the submit row lands UNPRICED(settlement_pending) and a settlement companion row resolves it later by summing the output file’s per-line usage. Providers that take a job and answer later is the mechanism, and Reading a settled cost is the arithmetic.

Two different fours

Four kinds have no declaring descriptor, and four kinds are recognition-only surfaces. They are not the same four, and they overlap in three members.

The four with no declaring descriptor The four recognition-only surfaces
batch, fine_tuning, vector_stores, realtime files, fine_tuning, vector_stores, realtime

They differ on batch against files. batch is served by the driver and declared by nobody; files is a recognition-only surface that two descriptors do declare.

Recognition-only is not silence. For files, fine_tuning, vector_stores and realtime the driver registers the path with no request parser, no usage extractor and no stream scanner, so both directions relay byte for byte. What the request gains is the ledger dimension and the controls that key off it: the row carries http.endpoint, the dialect resolves and picks its default upstream, a virtual key can be scoped to or away from the path by name, a governance limit can match it, a cache rule naming it is rejected as not cacheable instead of as an unknown kind, a descriptor may declare it, and rows carrying it are savings-ineligible rather than falling back to the chat-like default.

What recognition does not give you is the stateful half. File content is not proxied, fine-tune lifecycle state is not modelled, and vector-store consistency is not maintained. Matching is on the collection path only, so /v1/files/{id}/content, /v1/fine_tuning/jobs/{id}/cancel and /v1/vector_stores/{id}/search stay unrecognised ledgered passthrough. For a scoped virtual key without allow_passthrough an unrecognised path is a scope rejection rather than a free pass, which is the conservative direction and worth deciding on before you scope a key at /v1/files.

/v1/realtime is a path and not a transport. It is a bidirectional WebSocket surface, and this gateway’s observation model is a tap on a request-and-response pair, which does not cover that transport class. Recognition adds two facts to such a request: the row is stamped realtime, and the OpenAI dialect resolves. There is no upgrade handler, no frame codec and no session state, and no claim that a realtime session can be served.

Who declares what, beyond chat

Chat and models are near-universal, so the interesting rows are the other fifteen.

Kind Descriptors
embeddings azure_ai, azure_openai, cohere, databricks, libertai, mistral, nebius, novita, openai, openai_compatible, perplexity, scaleway, together_ai, vercel_ai_gateway, voyage
images aiml, black_forest_labs, fal_ai, openai, recraft, runwayml, stability
audio_transcription assemblyai, deepgram, openai, soniox
audio_speech elevenlabs, openai, runwayml
rerank jina_ai, novita, voyage
completions azure_text, openai, text_completion_openai
images_edits black_forest_labs, openai, stability
files assemblyai, soniox
responses, moderations, audio_translations openai
batch, fine_tuning, vector_stores, realtime none

Thirteen descriptors declare no chat at all and exist for one or two surfaces each: assemblyai, azure_text, black_forest_labs, deepgram, elevenlabs, fal_ai, jina_ai, recraft, runwayml, soniox, stability, text_completion_openai and voyage. Pointing a routing tier at one of those is a configuration mistake, and one check catches it: with routing.allow_cross_dialect: true, validation rejects a tier whose upstream serves no chat endpoint. Without that flag the check does not run, so nothing warns you.

completions is worth a caveat rather than a footnote. Two dated retirements ride its three descriptors: OpenAI shuts down gpt-3.5-turbo-instruct, babbage-002 and davinci-002 on 2026-09-28, and Azure’s published retirement schedule lists no instruct row at all. Two further catalog namespaces were left out of this kind rather than counted into it, because text-completion-codestral and text-completion-inception are fill-in-the-middle wires on /v1/fim/completions and not /v1/completions providers.

What each kind is allowed to do

Serving an endpoint is not the same as routing, caching or streaming it. Each kind carries four capability facts, and they explain behaviour that otherwise looks like a bug.

Kind Routable Shadow-eligible Streamable Cacheable
chat yes yes yes yes
responses yes yes yes yes
embeddings yes no no no
images no no no no
audio_speech no no no no
audio_transcription no no no no
rerank no no no no
models no no no no
batch no no no no
completions no no yes no
moderations no no no no
images_edits no no no no
audio_translations no no no no
files no no no no
fine_tuning no no no no
vector_stores no no no no
realtime no no no no

Read the first column first. Only chat, responses and embeddings are routable, and only chat and responses are shadow-eligible, which is why savings evidence is a chat-traffic story: a counterfactual needs a comparable alternative call, and an image or audio request does not have one.

completions is the one surprise in the third column. It streams because the legacy text-completion stream shape is one the usage scanner already parses, which is why wiring the kind meant a scanner and not a new codec. Streamable here means the kind has a server-sent-events usage scanner, which is also why realtime reads no: a WebSocket does not have one.

Two of these columns are enforced where you can see them. A cache rule matching a non-cacheable kind is a configuration error named at its path rather than a silent no-op:

cache.rules[0].match.endpoint: endpoint kind "images" is not cacheable (chat|responses only in v1)

And the specialist audio drivers implement a model rewrite as a no-op, because neither audio kind is routable, so nothing ever asks them to rewrite one. Where their model travels differs per driver and is worth reading before you assume: Deepgram carries it in the URL query as ?model=nova-2, while ElevenLabs carries it in the body as model_id and puts a voice id in the path. Elsewhere the table is the product’s own classification of what each kind is for rather than a gate on the request path, so treat a non-routable kind as “not designed to be routed” rather than “rejected if you try”.

The five cost shapes

A cost shape selects the arithmetic, not the price. Every endpoint kind maps to one.

Shape Kinds The bill is
tokens chat, responses, embeddings, rerank, batch, completions tokens in each bucket × that bucket’s rate
per_image images, images_edits images returned × the size-and-quality-qualified rate
per_second audio_transcription, audio_translations seconds of audio × the rate
per_character audio_speech characters of input text × the rate
none models, moderations, files, fine_tuning, vector_stores, realtime nothing; the kind is cost-exempt

Cost-exempt is not priced-at-zero, and the two look identical in a column. A cost-exempt kind returns a zero with no pricing key and no recorded pricing miss, because the surface bills nothing per proxied call. The question that separates the two is whether a dollar arrives later: for batch it does, which is why batch carries the per-token shape and settles rather than reading exempt.

Three of these carry a trap worth stating.

Per-image rates are keyed by size and quality, not by model alone. The catalog stores them as <quality>/<size>/<model>, and where a model has only those qualified forms an unqualified request has nothing to hit. openai/dall-e-3 goes further: it carries the image mode and no per-image rate at all, so it resolves unit_missing rather than a rate that happens to be lying around:

PREFIX     MODEL      ENDPOINT  RESULT    REASON
openai     dall-e-3   images    UNPRICED  unit_missing

Audio bills from different places at each end. Text-to-speech counts characters in the request you sent, which is known before the provider answers. Transcription counts seconds from the provider’s own response metadata, which means a response that omits the duration produces no figure rather than a guessed one.

audio_translations carries the per-second shape and prices nothing. No entry in the vendored catalog declares support for /v1/audio/translations, so a translation resolves UNPRICED(endpoint_unpriced). The transcription rate for the same model sits next door and is not borrowed for it, which is the difference between a missing rate and an invented one.

Where a kind is decided

The endpoint kind is resolved from the inbound path and the dialect, before routing and before pricing, and lands on the row as http.endpoint. Everything downstream reads it there:

"http": { "path": "/v1/chat/completions", "dialect": "openai",
          "endpoint": "chat", "provider": "groq", "status": 200 }

If a call you expected to be priced comes back with no cost at all, http.endpoint is the first field to check. A path that matched no registered kind is forwarded and ledgered, which is the behaviour you want for an unusual call and looks identical to a pricing failure until you look.

What happens when a path matches no kind

A descriptor that does not declare a kind is not an error and does not block the call, and neither is a path no driver registers. The request takes the passthrough path: forwarded, relayed byte for byte, and written to the ledger, without endpoint-specific parsing or a cost figure. So “not supported” here means “not priced and not routed”, rather than “rejected”.

/v1/assistants is the clearest case, and the only one refused on a vendor’s own notice. OpenAI’s deprecations page lists the Assistants API with a shutdown date of 2026-08-26, two days after this page was written, and names the Responses API and Conversations API as replacements. Building an adapter for a surface its vendor is removing would spend code, tests and documentation on something dead on arrival, so no kind exists for it and none is planned. The call still relays and it is still logged; it is not recognised, not priced and not claimed. If you are on that API, /v1/responses has been a first-class kind here since well before it.