Dedicated endpoints
Capacity reserved for your organization on one model.
A dedicated endpoint is provider capacity reserved for your organization on one model, with its own limits, prices and failover rules. RouterPlus sets one up with you under a contract. There is no self-serve setup.
You call the endpoint with the model id RouterPlus gives you, in your organization's namespace: <your-namespace>/<name>, for example acme/gemma-31b.
Calling an endpoint
Use the endpoint's model id in place of a catalog model id. Both surfaces (/v1/chat/completions and /v1/messages), streaming, and every request field the model accepts work as usual.
curl https://dev2-api.routerplus.com/v1/chat/completions \
-H "Authorization: Bearer $TM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "acme/gemma-31b", "messages": [{"role": "user", "content": "Hello"}]}'Only your organization's keys can call the endpoint, and it can be limited to named workspaces. Any other key gets the same 404 model_unavailable as for an unknown model.
On an open endpoint, the response's model is the model that ran, for example "model": "google/gemma-4-31b-it". On a closed endpoint it is the endpoint id (see Closed endpoints). Two headers say how the request was served:
| Header | Value |
|---|---|
x-tm-dedicated-endpoint | The endpoint's id, for example acme/gemma-31b. |
x-tm-route-role | primary (your dedicated capacity) or fallback (the shared pool). |
- In
/v1/generation, each attempt'sroute_context.dedicatedrecords the endpoint id, the revision of its configuration in force, and the role. GET /v1/modelslists your organization's active endpoints withowned_byrouterplus, on the base URL that serves the endpoint.- The Endpoints page lists your endpoints before the models you deployed from Optimize. For each endpoint it shows the id, model, base URL, status, provider and limits, and the price per million tokens: input, cached input and output, and the cache write where the rate card has one above $0.
What serves your request
- 01Your dedicated capacity first.
- 02Then the shared pool, for the same model only. The request moves on when the dedicated deployment fails before your answer starts, when its circuit is open, or when its capacity is full. It goes to public deployments of the same model that meet your endpoint's constraints, never to a different model.
- 03The commit boundary holds. Once the first token reaches you, the request stays with that provider. See Routing & failover.
Your endpoint's configuration sets the constraints. They apply to every fallback:
| Constraint | A fallback host qualifies only if |
|---|---|
| Precision | It serves one of the listed precisions, for example bf16. |
| Zero data retention | It keeps no prompts. |
| Data collection denied | It neither stores nor trains on requests. |
| Maximum price | Its price per million tokens is at or below the limit. This is the price of the provider that serves it (its live price when that provider sets its own). |
| Regions | It runs in one of the listed countries. |
| Hosts | It is one of the named hosts. |
| Require parameters | It accepts every parameter the request sends. |
- A host that has not stated a value for a constrained property does not qualify.
- A fallback that reaches several hosts through one route sends the constraints with the request. Only hosts that meet them serve it.
- A region constraint excludes such a route, because the route makes no promise about where a host runs.
An endpoint set to primary_only never falls back. When the dedicated deployment cannot serve, you get the error.
Request-level provider fields
For one request you can narrow the routes. You cannot widen them.
"provider": {"allow_fallbacks": false}keeps the request on your dedicated capacity. It works on open and closed endpoints.- On an open endpoint,
provider.onlyandprovider.ignorefilter by provider name, as on any request. - A closed endpoint selects its providers and hosts through the contract configuration.
provider.only,provider.ignore,provider.order,provider.upstreamandprovider.require_parametersreturn400 invalid_request, empty selectors included. Omit those fields on closed endpoints. provider.sortandprovider.max_pricereturn400 invalid_requeston every dedicated endpoint, open or closed. The message names the field. The endpoint's own constraints, including its maximum price, decide its routes.
Closed endpoints
An endpoint is open unless your contract says otherwise. An open endpoint names the deployment and host of the shared pool that served you, like any request. Your dedicated capacity shows as routerplus.
A closed endpoint never names a deployment or a host. Every surface names RouterPlus as the provider, whichever route served the request. Only the route role differs between routes:
| Surface | On a closed endpoint |
|---|---|
x-tm-provider | routerplus |
x-tm-served-by, x-tm-upstream-model, x-tm-upstream-status, x-tm-dropped-params, x-tm-affinity-wait-ms | Absent. |
x-tm-dedicated-endpoint, x-tm-route-role | As on an open endpoint. |
x-tm-remaining-rpm, x-tm-remaining-tpm | Counted against the endpoint's own limits only. |
| Chat completion body | A fixed set of fields, below. model is the endpoint id. |
| Stream chunks | The same fields in each chunk. A provider's SSE comment lines are dropped. A provider's error in the stream ends it with the gateway's own error event, and nothing follows that event. |
| Error messages | They name routerplus, never a deployment. A provider's own error body is never relayed. |
| Error statuses | A provider's 401, 402, 403 or 404 is a retryable 503 upstream_error. A retry-after is the gateway's own, never a provider's. |
| Limits | A refusal because the capacity behind one route is full has x-tm-limit-scope: endpoint and x-tm-limit-id: endpoint:<endpoint id>. |
provider.only, provider.ignore, provider.order, provider.upstream | Refused with 400 invalid_request. |
/v1/generation | deployment is routerplus and model the endpoint id. route_context keeps only dedicated (the endpoint, its revision and the role). admission_context is null, dropped_params is empty, and admission events carry no pool scope id. |
/v1/usage | deployment is routerplus and model the endpoint id. |
| Console: Overview, Usage, Logs and a request's details | RouterPlus as the provider, no host, the endpoint id as the model, and "contract rate" as the price. |
| The Playground's receipt | Served by RouterPlus. |
Records stay masked once an endpoint has been closed. Requests it served before it closed, and after it opens again, read as above.
The fields of a closed chat completion:
| Level | Fields |
|---|---|
| Top level | id (chatcmpl- and the request id without dashes), object, created (when the gateway received your request), model, choices, usage |
| A choice | index, message (not streamed) or delta (streamed), finish_reason (stop, length, tool_calls, content_filter or function_call; null until a stream ends), logprobs (only when you ask for them) |
message or delta | role, content, tool_calls (id, type, function.name, function.arguments, and index in a stream), function_call, refusal, reasoning_content |
usage | prompt_tokens, completion_tokens, total_tokens, prompt_tokens_details.cached_tokens and completion_tokens_details.reasoning_tokens (both always present, 0 when none), cost |
- Every other field is dropped, including any field a provider adds later.
- A field without a value is left out. A message keeps
content: null beside tool calls. - A model's thinking arrives as
reasoning_contenton every route. - A tool call's id is the gateway's own:
call_and 24 hex characters. It is the same on every chunk of the call, and it is thetool_useid on/v1/messages. - In a stream, the first delta of a choice has the role. The first delta of a tool call has its id, type and name. The finish and the usage each come in a chunk of their own.
Prices
| Attempt | Price |
|---|---|
| On your dedicated capacity | Your contract rate card, per million tokens. |
| On a fallback | The price of the provider that serves it, as for any other request (its live price when that provider sets its own). Your rate card instead, when your contract sets one price on every route. |
- A closed endpoint bills your rate card on every route. A second price would tell you which route served.
- RouterPlus tries a fallback only when its price is known and at or below the endpoint's maximum price, if one is set. This holds whichever price you then pay.
- In
/v1/generation, an attempt billed at your rate card hasinference_price_sourcecustomer_rateand aprice_snapshot_idof the formdedicated:<endpoint id>:r<revision>, which names the rate card that produced the charge. usage.coston the response is the full debit across all attempts, as on every request.
Limits
For the endpoint's traffic, its own limits replace the default organization, workspace, principal and key limits:
| Limit | Counted for |
|---|---|
| Requests per minute | The endpoint |
| Requests per second (optional): a count per second, or a paced rate | The endpoint |
| Tokens per minute | The endpoint |
| Concurrent requests | The endpoint |
A limit your organization set in Billing > Rate limits (/console/limits) still applies on top. You can lower what one key or workspace may use. You cannot raise the endpoint's limits.
A refusal is the usual 429 rate_limit (see Rate limits & spend caps), with x-tm-limit-scope: endpoint:
| Case | x-tm-limit-kind | x-tm-limit-id |
|---|---|---|
| Over the endpoint's RPM, RPS, TPM or concurrency | rpm, rps, tpm or concurrency | endpoint:<endpoint id> |
| Above a paced endpoint's hard rate | rps | endpoint:<endpoint id> |
| A paced endpoint's wait would be longer than its bound | rps_queue | endpoint:<endpoint id> |
| The dedicated capacity is full and the endpoint does not spill over to the shared pool | the measure that is full | endpoint:<endpoint id> |
| A closed endpoint: the capacity behind every route it could take is full | the measure that is full | endpoint:<endpoint id> |
| Fallback traffic is over the endpoint's fallback cap | rpm, tpm or concurrency | endpoint-fallback:<endpoint id> |
GET /v1/limits?model=<your-namespace>/<name> returns your key's effective limits on the endpoint and a dedicated object: the endpoint, its revision, the model, the output default, the fallback caps and whether it spills over.
When the shared counter store is unreachable:
- In outage mode
closed, requests are refused. - In the default
bounded_openmode, requests are admitted under the endpoint's own limits, divided among the gateways that serve it, for up to 10 minutes. See Limits and capacity.
The paced limit
When traffic arrives at random, a limit that counts each calendar second refuses requests in many seconds, even when the average rate is below the limit. A paced limit queues a short burst instead. It has three settings: the paced rate, a hard rate above it, and the longest wait (the bound).
- Requests leave admission no faster than the paced rate. A request that must wait waits in the gateway.
x-tm-queue-mson the response gives the wait in milliseconds. - A request whose wait would pass the bound gets 429 with
x-tm-limit-kind: rps_queue. Itsretry-aftersays when its wait would fit. - The hard rate refuses a request at once, with
x-tm-limit-kind: rps. It allows a burst as long as the bound (at least one second). It never refuses a request the queue would hold. With a bound of a second or more, a full queue refuses first. - The wait comes before the request's own deadline starts, so it does not use the endpoint's total time.
- A waiting request holds no concurrency slot. Its place in the pace stays used if you disconnect.
For example, with a paced rate of 6 requests a second, a hard rate of 6.5 and a 2 s bound:
| Average rate, arriving at random | Refused (429) | Typical wait |
|---|---|---|
| 4 a second | Almost none | About 0.1 s |
| 5 a second | About 0.3 % | About 0.3 s |
| 6 a second | About 4 % | About 0.9 s |
| Steadily above 6 a second | The excess, once the queue is full | Up to 2 s |
A queue cannot hold a steady excess. Over time, the endpoint passes its paced rate.
Timeouts and defaults
| Setting | On other requests | On an endpoint |
|---|---|---|
| Attempts per request | Up to 8 | 1 to 8. Default 3. |
| Total time per request | 450 s | 1 to 600 s |
| Wait for a streamed attempt's response headers before the next route | 20 s | 1 to 120 s |
| Time one attempt may take on a request that is not streamed, before the next route | 450 s | 1 to 600 s, below the total |
max_tokens written into a request that sets none | The model's own maximum, fitted to its context window | Set per endpoint |
A shorter header wait (streamed) or attempt limit (not streamed) makes failover faster when the dedicated deployment stalls. A stalled request still takes that limit plus the fallback's own time. The attempt limit applies only while another route remains. The last route can run for the rest of the total time.
Other limits
POST /v1/routedoes not explain dedicated endpoints yet.- Every request to a deployment shares its health circuit. Two failures in a row on the dedicated deployment send all its traffic to the fallbacks for 30 seconds. Per-endpoint circuit settings are not available yet.
- RouterPlus changes an endpoint's configuration on request. Each change is a new revision. It reaches every gateway within seconds.
For agents: /docs/dedicated-endpoints.md and /llms.txt