docs
// API Reference

Chat Completions

The full OpenAI surface area on a different base URL. Chat completions, embeddings, batch, files, responses, models, endpoints, all with the request and response shapes your SDK already speaks. Swap the URL and key; ship the same code.

Chat Completions

Creates a model response for the given chat conversation. This is the primary endpoint for interacting with language models.

Create Chat Completion

POST /:project_id/:endpoint_slug/v1/chat/completions

URL path: :project_id is your project's external ID (e.g., proj_ABC123) and :endpoint_slug is the slug of the endpoint to route through (e.g., my-endpoint). Your full base URL is https://api.erebine.ai/proj_ABC123/<endpoint-slug>/v1.

Request Body

Parameter Type Description
modelrequired string ID of the model to use. The field is informational; the actual model served is determined by the endpoint configuration, so any non-empty string is accepted (the canonical form is the endpoint's configured model name).
messagesrequired array A list of messages comprising the conversation so far. Supports roles: system, user, assistant, tool, developer.
max_tokensoptional integer Maximum number of tokens to generate. Sent to the model verbatim. When omitted, the backend uses the remaining context window as the limit. If this value does not fit the available context window, the request returns 400 context_length_exceeded unless clamping is opted in. See max_tokens and the Context Window below.
max_completion_tokensoptional integer Upper bound on the number of tokens to generate. Preferred over max_tokens. Same context-window behavior applies. When both are set, this takes precedence.
temperatureoptional number Sampling temperature (0.0-2.0). Higher values make output more random. Default: 0.7
top_poptional number Nucleus sampling parameter (0.0-1.0). Default: 0.9
streamoptional boolean If true, partial message deltas will be sent as Server-Sent Events. Default: false (omitted or null is treated as non-streaming).
stream_optionsoptional object Options for streaming mode. Set {"include_usage": true} to include token usage in the final stream chunk. When omitted or set to false, the final chunk will not contain a usage field. Token usage is always tracked internally for billing regardless of this setting.
stopoptional string | array Up to 4 sequences where the API will stop generating.
frequency_penaltyoptional number Penalty for repeated tokens (-2.0 to 2.0). Positive values decrease likelihood of repeating the same tokens. Default: 0
presence_penaltyoptional number Penalty for tokens already in the context (-2.0 to 2.0). Positive values increase likelihood of new topics. Default: 0
noptional integer Number of completions to generate. Default: 1. Only n=1 is supported in streaming mode. On the multi-model route every choice runs the whole plan, so cost scales with n multiplied by the plan's size; see Cost of n greater than 1.
seedoptional integer Seed for deterministic sampling. When set, repeated requests with the same seed and parameters should return the same result.
logprobsoptional boolean Return log probabilities of output tokens. Default: false
top_logprobsoptional integer Number of most likely tokens to return log probabilities for (0-20). Requires logprobs: true.
logit_biasoptional object Map of token IDs to bias values (-100 to 100). Modifies the likelihood of specified tokens appearing in the output.
toolsoptional array A list of tool definitions the model may call. See Tool Calling below.
tool_choiceoptional string | object Controls tool selection: "auto", "none", "required", or {"type": "function", "function": {"name": "fn_name"}}
parallel_tool_callsoptional boolean Enable parallel tool calls. When true, the model may generate multiple tool calls in a single response.
response_formatoptional object Response format: {"type": "text"}, {"type": "json_object"}, or {"type": "json_schema", "json_schema": {"name": "...", "schema": {...}, "strict": true}}
metadataoptional object Up to 16 key-value pairs for request metadata. Keys max 64 characters, values max 512 characters.
useroptional string A unique identifier for the end user. Used for abuse monitoring and usage tracking.
storeoptional boolean Store the completion for later retrieval. Default: false
reasoning_effortoptional string Reasoning effort level for reasoning models. Valid values: "low", "medium", "high". Controls how much reasoning the model applies before generating output. Invalid values return a 400 error. When set, the idle stream timeout uses the full request deadline to accommodate long reasoning phases. Note: By default the chat-completions stream emits no separate reasoning channel (the SSE delta carries content, refusal, and tool_calls only), matching the OpenAI shape. Send the X-Erebine-Augment-Reasoning-Normalization header to move reasoning out of the visible content: it then streams as reasoning_content on each delta (ahead of the first content delta) and is returned as message.reasoning_content on non-streaming responses. For the full reasoning-item lifecycle use the Responses API.
service_tieroptional string Requested service-tier hint. Routing is still bound to the endpoint's configured tier, but the value influences worker selection and billing: "flex" applies a -15 score adjustment (lower priority, no billing change), "priority" applies a +15 score adjustment and a 1.25x billing multiplier. "auto" and "default" are no-ops. The actual tier used is returned in the service_tier response field. See Service Tiers.
predictionoptional object Predicted output content for speculative decoding. When the model can verify the prediction, generation is faster because tokens are validated in parallel rather than generated sequentially. The object must have "type": "content" and a "content" field (string or array of content parts). Token counts for accepted and rejected predictions appear in completion_tokens_details. See Predicted Outputs below.
modalitiesoptional array Output modalities to generate: ["text"] (default) or ["text", "audio"] for a spoken reply. Audio output requires a speech endpoint in the project. Any other modality (for example image or video) returns a 400 error. See Audio in Chat Completions.
audiooptional object Voice and format for the spoken reply, { "voice": ..., "format": ... }. Honored only when modalities includes audio. The assistant message returns an audio object with the base64 audio and its transcript. See Audio Output.

Optional Request Headers

Header Type Description
X-SLO-TTFT-Msoptional number Target time-to-first-token in milliseconds. The router prefers workers likely to meet this target. Must be a positive number; invalid values are ignored.
X-SLO-TPOT-Msoptional number Target time-per-output-token in milliseconds. The router prefers workers likely to meet this target. Must be a positive number; invalid values are ignored.
X-Erebine-Augmentoptional on | off Opts the request into Erebine's agentic harness. Off by default: the request is passed through faithfully. Per-feature overrides are listed under Passthrough and Augmentation.
X-Conversation-Idoptional string Your identifier for the conversation this request belongs to. Read by the Semantic Router only: it is the affinity key that keeps a multi-turn conversation on one endpoint. Send the same value on every turn of a conversation and a new value for a new one. Ignored when you address an endpoint slug directly. See Pin a Conversation.

Every header Erebine reads or sends, request and response, is listed in the Header Reference.

Response Headers

Header Description
X-Request-ID Unique identifier for the request (matches the response body id field). Present on both streaming and non-streaming responses. Include this in support tickets for request tracing.
X-Erebine-Worker-ID Identifier of the worker that handled the request. Useful for correlating latency with routing decisions.
X-Erebine-Routed-Model The model that actually served the request. Sent on Semantic Router responses only, where the router -- not you -- picks the model. The response body echoes the model string you sent, so this header is how you learn what answered. See Which Model Served the Request.
X-Erebine-Routed-Endpoint The endpoint slug the Semantic Router dispatched to. Sent alongside X-Erebine-Routed-Model on the same responses.
X-Erebine-Budget-Warning A token budget you configured is close to its cap. The request was served in full. The value is <level>:<window> -- level is endpoint or workspace, window is hourly, daily, or hardCap (for example workspace:daily). Once the budget is spent, requests are rejected with 429 and error code endpoint_budget_exhausted or workspace_budget_exhausted instead.

Message Object

Parameter Type Description
rolerequired string The role of the message author: system, user, assistant, tool, or developer
contentrequired string | array The content of the message. Can be a string or an array of content parts. An array may include input_audio parts, which are transcribed before the model sees the turn. See Audio Input.
nameoptional string An optional name for the participant. Useful for distinguishing between multiple users or assistants in the same conversation.
tool_call_idoptional string Required when role is tool. The ID of the tool call this message responds to.
tool_callsoptional array Tool calls generated by the model (present in assistant messages).

Multimodal Input

A message's content array carries typed parts. Text rides text parts, image input rides image_url parts, and audio input rides input_audio parts. All three are the OpenAI content-part shapes, so an unmodified OpenAI client sends them as-is. A part is served only when the resolved model advertises the matching input modality (vision for images, audio for input_audio); audio input is operator-gated and on by default. See Audio Input for the input_audio shape.

video_url is a non-standard vLLM extension, not part of the OpenAI specification, and is off by default. It is served only when the operator enables video input (EREBINE_VIDEO_INPUT) and the resolved model advertises video_input; otherwise a video_url part returns 400. The faithful /v1 surface never advertises video as an OpenAI-standard input.

Example Request

Python
from openai import OpenAI client = OpenAI( base_url="https://api.erebine.ai/proj_ABC123/my-endpoint/v1", api_key="ere_myproject_your_api_key" ) response = client.chat.completions.create( model="deepseek-r1-distill-llama-70b", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"} ], max_tokens=100, temperature=0.7 ) print(response.choices[0].message.content)
Node.js
import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.erebine.ai/proj_ABC123/my-endpoint/v1", apiKey: "ere_myproject_your_api_key" }); const response = await client.chat.completions.create({ model: "deepseek-r1-distill-llama-70b", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "What is the capital of France?" } ], max_tokens: 100, temperature: 0.7 }); console.log(response.choices[0].message.content);
curl
curl -X POST https://api.erebine.ai/proj_ABC123/my-endpoint/v1/chat/completions \ -H "Authorization: Bearer ere_myproject_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1-distill-llama-70b", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"} ], "max_tokens": 100, "temperature": 0.7 }'

Response

{ "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1706123456, "model": "deepseek-r1-distill-llama-70b", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "The capital of France is Paris.", "refusal": null, "annotations": [] }, "finish_reason": "stop", "logprobs": null } ], "usage": { "prompt_tokens": 25, "completion_tokens": 8, "total_tokens": 33, "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": null }, "completion_tokens_details": { "reasoning_tokens": null, "audio_tokens": null, "accepted_prediction_tokens": null, "rejected_prediction_tokens": null } }, "service_tier": "gpu_nvidia_shared", "system_fingerprint": null }

Log Probabilities Response

When logprobs: true is set in the request, each choice includes a logprobs object with per-token log probabilities. The top_logprobs parameter controls how many alternative tokens are returned (0-20).

Field Type Description
logprobs.content array | null Array of token log probability objects for each content token. Null when the model produces no content tokens (e.g., a pure refusal or tool call).
logprobs.content[].token string The token string.
logprobs.content[].logprob float Log probability of this token. 0.0 means 100% confidence; more negative values indicate lower confidence.
logprobs.content[].bytes array | null UTF-8 byte representation of the token.
logprobs.content[].top_logprobs array Top alternative tokens at this position, each with token, logprob, and bytes fields. Array length matches the top_logprobs request parameter.
logprobs.refusal array | null Array of token log probability objects for refusal tokens. Present when the model refuses to comply with a request. Each entry has the same structure as logprobs.content[] entries (token, logprob, bytes, top_logprobs). Null when the model does not refuse.

Log Probabilities Request and Response Example

# Request with logprobs enabled curl -X POST https://api.erebine.ai/proj_ABC123/my-endpoint/v1/chat/completions \ -H "Authorization: Bearer ere_myproject_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1-distill-llama-70b", "messages": [{"role": "user", "content": "Is Paris the capital of France? Answer yes or no."}], "logprobs": true, "top_logprobs": 3, "max_tokens": 5 }' # Response (truncated) { "id": "chatcmpl-abc456", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Yes" }, "finish_reason": "stop", "logprobs": { "content": [ { "token": "Yes", "logprob": -0.00012, "bytes": [89, 101, 115], "top_logprobs": [ {"token": "Yes", "logprob": -0.00012, "bytes": [89, 101, 115]}, {"token": "yes", "logprob": -9.08, "bytes": [121, 101, 115]}, {"token": "YES", "logprob": -12.31, "bytes": [89, 69, 83]} ] } ], "refusal": null } } ], "usage": {"prompt_tokens": 18, "completion_tokens": 1, "total_tokens": 19} }

Usage Object

Field Type Description
prompt_tokens integer Number of tokens in the input prompt.
completion_tokens integer Number of tokens in the generated output.
total_tokens integer Sum of prompt_tokens and completion_tokens.
prompt_tokens_details.cached_tokens integer Number of prompt tokens served from the prefix cache (KV cache reuse). A higher value indicates more cache hits, reducing time-to-first-token. 0 if no tokens were cached.
prompt_tokens_details.audio_tokens integer | null Audio input tokens. Null for text-only models.
completion_tokens_details.reasoning_tokens integer | null Tokens used for internal reasoning, reported by the engine and never fabricated by the router. null for non-reasoning models and whenever no engine reasoning parser is engaged. These tokens are also counted in completion_tokens (the parent total always includes reasoning).
completion_tokens_details.audio_tokens integer | null Audio output tokens. Null for text-only models.
completion_tokens_details.accepted_prediction_tokens integer | null Predicted tokens that appeared in the output.
completion_tokens_details.rejected_prediction_tokens integer | null Predicted tokens that did not appear in the output.

Response Message Fields

The message object in each response choice contains the assistant's output. In addition to role and content, the following fields may be present:

Field Type Description
role string Always "assistant" in completion responses.
content string | null The generated text content. Null when the model produces only tool calls or a refusal.
refusal string | null A refusal message when the model declines to respond (content policy, safety filters). Null when the model does not refuse. When present, content is typically null. In streaming mode, refusal text is delivered incrementally via delta.refusal.
tool_calls array | null Tool calls generated by the model. Present when finish_reason is "tool_calls". See Tool Calling.
annotations array Message annotations such as URL citations. Defaults to an empty array. Reserved for future use with web search and citation features.

Additional Response Fields

Field Type Description
service_tier string | null The tier slug the endpoint is configured with, echoed verbatim -- for example "gpu_nvidia_shared" or "free". It is the resolved tier, not the service_tier hint you sent, so it never reads "flex", "default", or "priority". See Service Tiers for the slug set. Always present in both streaming and non-streaming responses.
system_fingerprint string | null Identifies the backend system configuration (model weights, quantization, GPU type) used for the request, alongside the seed parameter for reproducibility debugging. The router does not yet carry a fingerprint back from the worker: non-streaming responses return null and streaming chunks omit the field. Do not key on it.

Passthrough and Augmentation

Erebine is an OpenAI drop-in, and it behaves like one by default. A request that carries no Erebine headers is served exactly as it was sent:

  • Your messages array reaches the model unchanged. Nothing is prepended, inserted, rewritten, or dropped.
  • max_tokens, tools, tool_choice, and response_format are honored verbatim.
  • No tool call is executed that the model did not emit.
  • One client request is one model turn. You are billed for that turn and no other.
  • Where the request cannot be served as specified, you get an error, not a substitute answer. See Error Semantics.

Erebine's agentic harness -- parallel-tool-call nudging, corrective retries, research grounding and fallback search, context compaction, in-path token compression -- is opt-in. Ask for it and you get it, per request, in whole or by the feature.

Under the harness, a corrective retry is an additional billed model turn: one client request can drive several dispatches. Your max_tokens still bounds the total -- it caps the cumulative output across the whole request, not each turn -- so once the run's generated output reaches max_tokens the loop stops and the response closes with finish_reason: "length". At the faithful floor (the default for API traffic) none of this applies: one request is one turn, billed once.

Turn the Harness On

HTTP Header
X-Erebine-Augment: on

One header, every feature. Send off (the default for API traffic) and none of them run.

Augmentation Headers

Twelve per-feature X-Erebine-Augment-* overrides turn the harness on or off one feature at a time -- the max_tokens floor, context compaction, the output-token clamp, capability tool-drop, corrective retries, the fan-out nudge, fabricated tool calls, the chat tool allowlist, tool-choice demotion, reasoning-channel normalization, tool top-K, and media-URL resolution -- and X-Erepress is the thirteenth, for in-path token compression (Erepress). One header, X-Erebine-Augment-Tool-Top-K, is off by default everywhere -- including first-party origins and under X-Erebine-Augment -- and is the only switch that turns it on, because narrowing your declared tools is an augmentation, not transport translation. One header runs the other way: X-Erebine-Augment-Media-URL-Resolution is on everywhere, including at the faithful floor, because fetching a remote image_url server-side is what the OpenAI vision request shape already means; : off is the way to opt out. Every remaining header follows the bundle default: off at the faithful floor, on for first-party origins, and toggleable either way per request. Each header, what it does when it is on, and what you get when it is off: Header Reference.

One reachability note: the fan-out nudge (X-Erebine-Augment-Fanout-Nudge) fires only when a turn advertises two or more server-owned tools (the built-in research tools, or workspace / Responses server tools). A request whose tools are all your own client-side functions -- the usual chat-completions case -- never triggers it, so the header is a safe no-op there. Erebine always honors the header; it never rejects it for being unreachable.

Values

Every one of these headers takes on, 1, true, or yes to enable, and off, 0, false, or no to disable. Values are case-insensitive. A value that is none of these is treated as if the header were absent, so a typo never flips a feature. This is the same vocabulary every boolean header on the API speaks.

Precedence

  1. A per-feature header wins.
  2. Otherwise X-Erebine-Augment decides.
  3. Otherwise the default applies: off for API traffic, on for Erebine's own chat and MCP surfaces.

So X-Erebine-Augment: on plus X-Erebine-Augment-Compaction: off runs the whole harness except compaction, and a context overflow still returns a 400.

Error Semantics

Faithful mode has no repair path, so a request that cannot be served as specified fails with a spec-shaped error instead of quietly becoming a different request. These are guarantees, not regressions: the answer you get is always an answer to the question you asked.

Condition Response
The conversation plus the requested output does not fit the model's context window. 400, invalid_request_error, code context_length_exceeded. Erebine does not drop your oldest turns and answer from a truncated conversation.
A server-side conversation holds more than 2000 items, the most that can be replayed in one request. 400, invalid_request_error, code context_length_exceeded, param of input. Send truncation: "auto" to use the most recent 2000 items, or branch the conversation.
The request declares tools, or carries tool-call history, and the endpoint's model has no tool-call support. 400, invalid_request_error, code unsupported_parameter, param of tools or messages. Erebine does not answer as though you sent no tools.
The worker exceeds the endpoint's request deadline. 408, timeout. Erebine does not discard history and retry.
JSON
{ "error": { "message": "This request exceeds the model's context window: max_tokens of 32000 leaves no room alongside the estimated input (context window 131072 tokens, about 18943 output tokens available). Reduce max_tokens or shorten the input.", "type": "invalid_request_error", "code": "context_length_exceeded", "param": "max_tokens" } }

Both conditions are recoverable client-side: shorten the input, lower max_tokens, route to a tool-capable model -- or opt into the matching augmentation and let Erebine handle it.

Predicted Outputs

The prediction parameter enables speculative decoding: you supply a predicted output and the model verifies it in parallel rather than generating each token sequentially. When the prediction matches, generation is significantly faster. When it does not match, the model falls back to normal generation.

Prediction Object

Field Type Description
typerequired string Must be "content".
contentrequired string | array The predicted output text. Can be a plain string or an array of content parts (each with type and text fields). Arrays are normalized to a single concatenated string internally.

Example Request

JSON
{ "model": "deepseek-r1-distill-llama-70b", "messages": [ {"role": "user", "content": "Replace 'hello' with 'goodbye' in: hello world, hello there"} ], "prediction": { "type": "content", "content": "goodbye world, goodbye there" } }

Response Token Details

When a prediction is provided, the response usage.completion_tokens_details includes prediction-specific token counts:

JSON
{ "usage": { "prompt_tokens": 32, "completion_tokens": 6, "total_tokens": 38, "completion_tokens_details": { "reasoning_tokens": null, "accepted_prediction_tokens": 4, "rejected_prediction_tokens": 2 } } }
  • accepted_prediction_tokens, Tokens from your prediction that the model verified and used. Higher values indicate a better prediction.
  • rejected_prediction_tokens, Tokens from your prediction that the model discarded and regenerated. These still count toward billing.

Best Practices

  • Use predictions for code editing, document reformatting, and template-based generation where you can anticipate the output structure.
  • Accurate predictions reduce latency via parallel verification. Inaccurate predictions may be slower than no prediction at all.
  • Monitor accepted_prediction_tokens vs rejected_prediction_tokens to evaluate prediction quality.

Tool Calling

Erebine supports OpenAI-compatible function calling. Define tools in your request and the model may generate tool calls in its response.

Tool Definition

JSON
{ "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City name" } }, "required": ["location"] }, "strict": false } } ], "tool_choice": "auto" }

Tool Call Response

When the model decides to call a tool, the response includes tool_calls instead of text content:

JSON
{ "choices": [{ "message": { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"location\":\"Paris\"}" } } ] }, "finish_reason": "tool_calls" }] }

Tool Result Message

Send the tool result back using role tool with the matching tool_call_id:

JSON
{ "messages": [ {"role": "user", "content": "What is the weather in Paris?"}, {"role": "assistant", "content": null, "tool_calls": [ {"id": "call_abc123", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\":\"Paris\"}"}} ]}, {"role": "tool", "tool_call_id": "call_abc123", "content": "{\"temperature\": 18, \"condition\": \"sunny\"}"} ] }

Semantic Router

The semantic router picks the generative endpoint that best matches each request, so a single base URL serves your whole workspace. Point an OpenAI-compatible client at it and send chat completions as usual; the router reads the request and dispatches it to the endpoint that fits.

This section covers the route itself. How a Turn Is Routed covers what decides the endpoint, including operator rules and the workspace routing mode; Endpoint Eligibility covers which endpoints are candidates in the first place; Multi-Model Turns covers the decomposing variant of this route; and the Semantic Router Management API covers configuring all of it.

Create Chat Completion (routed)

POST /:project_id/_semantic/router/v1/chat/completions

URL path: :project_id is your project's external ID (e.g., proj_ABC123). Unlike the per-endpoint path, there is no endpoint slug; the router selects the endpoint for you. The request and response bodies are the standard Chat Completions shapes.

Base URL

HTTP
https://api.erebine.ai/proj_ABC123/_semantic/router/v1/chat/completions

To pin a request to a specific workspace, add the workspace ID (a ws_-prefixed value, e.g. ws_9f2c8a) as a path prefix:

HTTP
https://api.erebine.ai/proj_ABC123/ws_9f2c8a/_semantic/router/v1/chat/completions

Automatic model selection

The router chooses the endpoint, so you do not pin a model. The model field in the request body is advisory and any non-empty string is accepted; the model that actually served the request is reported on the X-Erebine-Routed-Model response header.

How it works. Every generative endpoint in a workspace carries a short profile of what it is best at, drawn from its model and a routing description you can edit in workspace settings. For each request, the router compares the intent of the prompt against those profiles and dispatches it to the closest fit. When no endpoint clears the workspace's match threshold, the request goes to your configured fallback, or a deterministic default, so a call never stalls waiting for a perfect match. The routing decision itself is not billed: only the completion that serves the request is metered, exactly as a direct call would be.

Point one base URL at a workspace and let it place traffic across your endpoints. Tune the behavior per workspace from the Semantic Router settings: turn routing on or off, set the match threshold, pin a fallback, or take an endpoint out of the routing path. Reach for it whenever a workspace runs more than one generative endpoint and you want each request routed on merit instead of naming an endpoint slug yourself.

Conversation routing. The router reads the whole thread, not just your latest message, so a short follow-up still routes by what the conversation is about. A conversation sticks to the endpoint it landed on while that endpoint stays the best fit, and only moves when the topic clearly shifts. Stickiness is keyed on the conversation; see Pin a Conversation for how the key is decided and how to control it.

Which Model Served the Request

The router re-picks an endpoint on every request, so the serving model can change from one turn of a conversation to the next. The response body is not where you find that out: the model field echoes the string you sent, because rewriting your request body is not something the router does. The served model is reported out of band, on two response headers:

Header Value
X-Erebine-Routed-Model The name of the model that served the request.
X-Erebine-Routed-Endpoint The slug of the endpoint it was dispatched to.

Both are set on every request this base URL dispatches, including the ones that fall through to your configured fallback, and on streaming responses they are committed before the first SSE byte -- so a streaming client reads them on the same turn, not after it. If you log one thing about a routed request, log these.

Pin a Conversation

Send a stable X-Conversation-Id for the life of a conversation. That value is the affinity key: it is what holds a multi-turn thread on one endpoint instead of re-deciding it per turn. This matters most for tool calling, where a swap mid-thread hands your tool-call history to a model with a different tool-call dialect.

HTTP Header
X-Conversation-Id: 9f2c8a1e-thread-1

Any string works. It is opaque to the router: use your own thread ID, a UUID, whatever you already key the conversation on. Use a new value when the conversation is new.

Without the header the router derives a key from the conversation's own turns instead -- the first user message carrying text, plus the opening of the first turn after it that is neither system nor developer (the assistant reply, or a tool result) -- so an OpenAI-compatible client that replays its history verbatim on each turn gets stickiness for free. System and developer turns are never folded in: they mutate between turns of the same conversation, and hashing them would mint a new key every turn. That derivation is only as stable as those two turns. A client that trims its context head, or edits the first user message or the first reply, changes those bytes and mints a new key: the conversation loses its affinity and can be routed to a different model without anything in the response saying so. If your client does any of that, send the header.

Example Request

Python
from openai import OpenAI client = OpenAI( base_url="https://api.erebine.ai/proj_ABC123/_semantic/router/v1", api_key="ere_myproject_your_api_key" ) response = client.chat.completions.create( model="auto", messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"} ], extra_headers={"X-Workspace-Id": "ws_9f2c8a"} ) print(response.choices[0].message.content)
Node.js
import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.erebine.ai/proj_ABC123/_semantic/router/v1", apiKey: "ere_myproject_your_api_key" }); const response = await client.chat.completions.create({ model: "auto", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "What is the capital of France?" } ] }, { headers: { "X-Workspace-Id": "ws_9f2c8a" } }); console.log(response.choices[0].message.content);
curl
curl -X POST https://api.erebine.ai/proj_ABC123/_semantic/router/v1/chat/completions \ -H "Authorization: Bearer ere_myproject_your_api_key" \ -H "Content-Type: application/json" \ -H "X-Workspace-Id: ws_9f2c8a" \ -d '{ "model": "auto", "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is the capital of France?"} ] }'

How a Turn Is Routed

Four things can decide where a generative turn runs, and they are consulted in a fixed order. The first one that answers wins.

  1. An operator routing rule. Rules are evaluated before anything else, including the workspace's routing mode and the project's semantic-router switch. A matching rule dispatches to the endpoint it names.
  2. A pinned endpoint. A pin on the chat or on the workspace wins over every routing mode. This is the guarantee: a pin always wins.
  3. The project's semantic-router switch. With semantic_router_enabled explicitly false, the workspace routes concretely no matter what mode it recorded. Operator policy outranks the workspace's choice.
  4. The workspace's routing mode, and failing that, the default its cohort was created with.

Workspace Routing Mode

A workspace records how it wants generative turns routed, on the field inference_routing_mode. It is readable and writable on the workspaces API and through erectl workspaces.

Value Meaning
"semantic" Route each generative turn through the semantic router.
"concrete" Use deterministic endpoint selection.
null No choice recorded. The workspace follows the default it was created with.

On a PATCH, omitting the field leaves the recorded choice alone, a string sets it, and an explicit null clears it. Any other value is rejected with 400. Setting "semantic", or clearing back to null, also drops the workspace's pinned inference endpoint -- a pin outranks the mode, so leaving one in place would record a choice that could never take effect.

Workspaces created from this release on default to semantic routing. Existing workspaces are never flipped. The default is stamped at creation, so nothing already running changes. A workspace created today inside an older project gets the new default; the stamp follows creation time, not the parent. An explicitly chosen mode is inherited by child workspaces.

What semantic routing needs

Semantic routing engages when all of the following hold. Note the second one: an embedding endpoint is not required unconditionally, only when there is more than one candidate to choose between.

  • The workspace has at least one active generative endpoint.
  • If it has two or more, the project also has an embedding endpoint. With exactly one generative endpoint the router short-circuits straight to it and needs no embedding endpoint at all.
  • The project's semantic_router_enabled setting is not explicitly false. It fails open: a missing or unreadable value reads as enabled.

An operator routing rule bypasses all three, because it names an endpoint outright and nothing needs classifying.

Operator Routing Rules

A routing rule short-circuits routing: when one fires, no classification runs and the request goes straight to the endpoint the rule names. A rule can match on the query text (query_contains), on the estimated input size (min_input_tokens), on the model string the caller sent (exact_request_model), or on any combination of the three. Rules are evaluated on the semantic routes only -- _semantic/router/v1/chat/completions, _semantic/router/v1/responses, and the multi-model path. Addressing an endpoint slug directly bypasses them, as it bypasses routing generally.

Evaluation order. Rules run in descending priority; equal priorities break by stored order, earliest first. Disabled rules are skipped before any condition is tested. The first rule whose every condition matches wins and evaluation stops -- later rules, including higher-specificity ones, are never consulted.

Match conditions

A rule matches only when all of its conditions hold.

Field Type Description
query_containsrequired array of strings Every entry must appear as a substring of the composed query text. Matching is case-insensitive. The array may be empty only when another condition narrows the rule.
min_input_tokensoptional integer The rule fires at or above this estimated input-token count. All three surfaces now estimate on the same scale, so a floor behaves identically wherever it is evaluated.
exact_request_modeloptional string Exact, case-sensitive match against the model field of the incoming request. See the note below.

Action

Field Type Description
endpoint_idrequired string (UUID) The endpoint to dispatch to. An endpoint id -- never a model id, never a slug.
fallback_to_classificationoptional boolean What to do when the named endpoint is not currently routable. true (default) falls through to normal routing; false fails the request with 404 rule_endpoint_not_found. Either way, evaluation does not continue to the next rule.

Rules also carry name, an integer priority (default 0), and a boolean enabled (default true). Author them from the Semantic Router settings page, from erectl semantic-router rules, or over the management API.

Region Locality

An endpoint with serving capacity in the router's own region earns a small ranking preference, controlled by semantic_router_proximity_bonus (default 0.02, range 0 to 1 inclusive).

It is a tie-breaker, not an override, and the three properties that make it safe are worth knowing:

  • Rank-only. The match threshold is applied to the unmodified score, so the bonus can reorder candidates that already qualify but can never lift one that does not.
  • Symmetric. An incumbent endpoint is ranked with its own bonus exactly as a challenger is, so a conversation will not drift to a different model just because one side is local.
  • Off by setting it to zero. Zero is a real value meaning "no locality preference", not an unset one.

Region names are compared exactly, including case. Locality applies on the chat-completions and Responses semantic routes; the multi-model path does not use it.

Endpoint Eligibility

Before ranking, the router removes endpoints that cannot serve the request as written. Four filters run, in this order: modality (images, video, audio input), context window (estimated input plus your max_tokens), tool calling (a request declaring tools), and structured output (a request naming response_format).

Modality

A turn carrying images, video, or audio input requires an endpoint whose model advertises the matching capability. A request whose modality no endpoint in the project can serve is rejected rather than answered by a model that will ignore the attachment.

Context window

An endpoint is eligible when its context window fits the request's estimated input plus the output budget the caller asked for. The output budget is your max_tokens (or max_completion_tokens) when you send one, and a conservative reserve of 2048 tokens when you do not. The input side is deliberately over-estimated so a near-miss errs toward the larger endpoint.

Two consequences worth planning around:

  • Models with a context window under 4096 tokens are routable. The requirement is computed from the actual request, not from a fixed floor, so a small-context endpoint is eligible for a small request.
  • An explicitly large max_tokens narrows the candidate set. Sending max_tokens: 32000 makes every endpoint that cannot reserve 32000 output tokens ineligible, even for a one-line prompt. Clients that hardcode a large default will see a smaller pool than they expect.

When the workspace had candidates and this filter removed all of them, the request returns 400 with code: "context_length_exceeded". When the workspace had no routable endpoints to begin with, it returns 404 with code: "no_routable_endpoints" -- a different problem with a different fix.

Tool calling

A request that declares tools, or carries tool-call history, is narrowed to endpoints whose model supports tool calling.

Structured output

A request naming response_format with json_object or json_schema is narrowed to endpoints known to honour a JSON schema. This matters more than most capability checks: a model that ignores a schema does not error, it returns plausible prose, and the failure surfaces in your parser rather than in the response.

Multi-Model Turns

POST /:project_id/_semantic/router/v1/chat/completions/multimodel

This route decomposes a turn into sub-tasks, dispatches each to the endpoint best suited to it, and merges the results. Request and response bodies are the standard Chat Completions shapes. The workspace prefix and the X-Workspace-Id header work exactly as they do on the single-model semantic route.

Turn it off for a project by setting semantic_router_multi_model_enabled to false; the route then degrades to ordinary single-endpoint dispatch rather than erroring.

Cost of n greater than 1

Every choice runs the whole plan. If you send n > 1 to this route, re-check your cost model before you upgrade.

For a plan of S sub-tasks, a turn requesting n choices costs n x S upstream dispatches, plus one synthesis dispatch per choice when the plan has three or more sub-tasks. Previously only the first choice ran the plan; the remaining choices ran a plain single-model completion that answered a weaker question while still being billed through this route.

Turn Dispatches now Dispatches before
2 sub-tasks, n = 1 2 2
2 sub-tasks, n = 3 6 4
3 sub-tasks, n = 3 12 (9 steps + 3 syntheses) 6

Treat those as floors. A sub-task whose model emits tool calls costs more than one dispatch, and an unusually large combined result can add the synthesis pass to a plan of fewer than three sub-tasks. n is not capped on this route. A turn that sets n > 1 and asks for research is rejected with 400, code: "unsupported_value", param: "n".

Plans of three or more steps

A plan of three or more sub-tasks runs a real synthesis pass over the step results, and the reported usage covers both the steps and the synthesis. Smaller plans return the terminal step's own answer. Internal merge scaffolding never appears in an assistant message on either path.

Errors

A sub-task that needs a modality no endpoint in the project can serve fails the whole turn rather than quietly handing the work to a text-only model:

Status Code Cause
400 no_vision_capable_endpoint An image sub-task with no vision-capable endpoint.
400 no_video_input_capable_endpoint A video sub-task with no video-capable endpoint.
400 no_audio_input_capable_endpoint An audio-input sub-task with no transcription-capable endpoint.
JSON
{ "error": { "message": "No endpoint with the required vision capability is available for this project", "type": "invalid_request_error", "code": "no_vision_capable_endpoint" } }

A turn that fails outright surfaces the upstream error instead of returning 200 with an empty body. On a buffered request that is an ordinary error response carrying the upstream code -- a dispatch failure with no equivalent client-facing code reports 500 internal_error, while a recognized upstream condition keeps its own mapping (for example context_length_exceeded stays 400). On a streaming request the status line is already committed, so the same error envelope arrives as a data: frame followed by [DONE]; with n > 1 that frame is written once, after the last choice closes.

Semantic Router Management API

Read and write a project's semantic-router configuration, manage its routing rules, and read routing telemetry. This is the surface erectl semantic-router drives; use the CLI unless you are building automation.

Authorization

Every route takes Authorization: Bearer <key> with a key carrying the management scope. A key without it returns 403 with code: "scope_insufficient"; a key bound to a different project returns 403 with code: "project_mismatch". :project_id accepts either the project UUID or its external ID.

There is no owner-versus-operator distinction on these routes. Any holder of a management-scoped key can write the project's routing configuration and rules, where the dashboard reserves the equivalent write to a project owner. Two consequences follow, and both are reasons to scope keys narrowly: see Route Decisions for the read side, which is broader still.

Routes

Method Path Result
GET /:project_id/v1/management/semantic-router/config 200, the effective configuration
PATCH /:project_id/v1/management/semantic-router/config 200, the resulting settings object
GET /:project_id/v1/management/semantic-router/rules 200, {"data": [...]}
POST /:project_id/v1/management/semantic-router/rules 201, the created rule
PATCH /:project_id/v1/management/semantic-router/rules/:rule_id 200, the updated rule
DELETE /:project_id/v1/management/semantic-router/rules/:rule_id 204, no body
GET /:project_id/v1/management/semantic-router/decisions 200, {"data": [...]}
GET /:project_id/v1/management/semantic-router/health 200, routing health and usage

Request bodies are capped at 16 KiB. These routes are subject to the Management API rate limit.

Semantic Router Settings

PATCH .../config takes a flat JSON object of settings to change. Keys you omit are left alone; an explicit null clears a key back to the deployment default. A key outside this table is rejected with 400 unknown_semantic_router_setting, so a management key cannot reach unrelated project preferences through this endpoint.

Key Type Description
semantic_router_enabled boolean Project-wide switch. Fails open: only an explicit false disables semantic routing.
semantic_router_multi_model_enabled boolean Whether multi-model dispatch runs. Fails open. Default true.
semantic_router_similarity_floor number, 0 to 1 The match threshold an endpoint must clear to be chosen.
semantic_router_switch_margin number, 0 to 1 How far a challenger must beat the incumbent to move a conversation mid-thread.
semantic_router_proximity_bonus number, 0 to 1 Region-locality ranking bonus. Default 0.02; 0 disables it.
semantic_router_fallback_endpoint_id string (UUID) The endpoint that serves a turn when nothing clears the threshold.
semantic_router_excluded_endpoint_ids array of UUIDs Endpoints kept out of routing. Replaces the stored list whole.
semantic_router_dynamic_rules array of rule objects The whole rule array. Prefer the per-rule routes, which mutate one rule and carry the rest through.

A number outside 0 to 1 is rejected, not clamped: the router discards a value it cannot use and falls back to the deployment default, so an out-of-range setting would store cleanly and then change nothing. Likewise a boolean key must be a JSON boolean -- the string "false" is rejected rather than read as enabled.

GET .../config returns those settings under preferences, alongside the routable and referenced-but-unavailable endpoint lists, whether an embedding endpoint is present, the resolved availability, the deployment defaults, a 7-day usage roll-up, and the same routing_health block the health route returns.

Managing Rules Over the API

Rule request bodies are flat; rule responses are nested. A POST or PATCH takes the match and action fields at the top level -- name, query_contains, min_input_tokens, exact_request_model, endpoint_id, fallback_to_classification, priority, enabled -- and the response returns them grouped under match and action. See Operator Routing Rules for what each field means.

JSON
// POST .../semantic-router/rules -- request { "name": "Long context", "query_contains": [], "min_input_tokens": 32000, "endpoint_id": "11111111-2222-3333-4444-555555555555", "priority": 100 } // 201 -- response { "id": "8f0a...", "name": "Long context", "match": {"query_contains": [], "min_input_tokens": 32000}, "action": {"endpoint_id": "11111111-...", "fallback_to_classification": true}, "priority": 100, "enabled": true }

id is minted by the server and is not accepted on create. On a PATCH, an omitted field means "leave it alone" -- there is no way to clear min_input_tokens or exact_request_model, so a rule that must lose a condition has to be deleted and recreated. query_contains replaces the stored array whole.

Validation

Rules are validated before the write, not rejected after it. A project may hold at most 20 rules; a rule may carry at most 10 query_contains entries of at most 200 characters each. endpoint_id must name a functional generative endpoint in the project -- a suspended endpoint is still a legal target. A rule with an empty query_contains and no min_input_tokens or exact_request_model is rejected: an all-of over an empty list is vacuously true, so it would capture every request in the project.

The rules are stored as one array and revalidated whole on every write, so a pre-existing rule whose target endpoint has since been deleted will block an edit to an unrelated rule. The error names the offender in its param field, indexed by position in GET .../rules. Deleting the blocking rule always succeeds, so this cannot wedge a project.

JSON
{ "error": { "message": "Invalid dynamic routing rule at semantic_router_dynamic_rules[2].action.endpoint_id: Must reference a functional generative endpoint in this project", "type": "invalid_request_error", "code": "invalid_semantic_router_config", "param": "semantic_router_dynamic_rules[2].action.endpoint_id" } }

GET .../rules returns rules in stored order, which is not evaluation order. Read priority to predict which rule fires; stored order matters only as the index in the error above.

Routing Health

GET .../health returns counts and totals over a fixed 24-hour window and reads no prompt content, which makes it the right thing to poll and the right thing to paste into a ticket.

Field Description
window_hours Hours covered. Always 24; derived from the query bound so the two cannot drift.
decision_count Every routing decision in the window.
classified_count Decisions the classifier actually decided: matched_count + sticky_count + fallback_count.
matched_count Fresh matches.
sticky_count Conversation affinity kept the incumbent endpoint.
rule_count Operator-rule dispatches. Excluded from classified_count, because the classifier never ran.
fallback_count Nothing cleared the threshold and the fallback endpoint served the turn.
fallback_pct fallback_count as a percentage of classified_count, never of decision_count.

fallback_pct is deliberately taken over classified decisions only. Single-endpoint and multi-model turns are decided before any classification happens, and folding them in would drive the ratio toward zero while saying nothing about how classification is going. The residue -- decision_count minus classified_count minus rule_count -- is exactly those turns.

Health is telemetry, so a failure to compute it returns zeros rather than failing the request.

Route Decisions

GET /:project_id/v1/management/semantic-router/decisions

Answers "why did this request go to that endpoint". Rows cover a fixed 24-hour window, newest first. limit defaults to 50 and is clamped to 1-200; a non-numeric value is rejected with 400 invalid_limit rather than quietly defaulted. There is no time or workspace parameter.

This route returns prompt text, for every workspace in the project. Read this before you issue a management-scoped key.

Each row carries query_preview, an excerpt of the user prompt behind the decision, truncated to 500 characters when it is recorded, encrypted at rest, and decrypted for this response. The dashboard shows the same view narrowed to the workspaces the signed-in operator is allowed to read, so an operator never sees another member's private workspace there. An API key carries no user identity, so there is no principal to evaluate that rule against and no equivalent filter is applied.

This widening is deliberate -- it is what gives automation parity with the dashboard -- but it means a management key is as sensitive as the prompts in the project. Scope keys accordingly, and reach for GET .../health when you only need to know whether routing is working.

Response

Field Description
decision_request_id Correlation id for the routing decision. It is named this rather than request_id because it does not join to the request id on usage records.
timestamp When the decision was made.
query_preview Prompt excerpt, up to 500 characters. Empty when the stored excerpt cannot be decrypted -- one unreadable row does not fail the page.
endpoint_slug, endpoint_name The endpoint that served the turn.
similarity_score The score behind the choice. Absent on single-endpoint and multi-model turns, where no score governed the decision.
matched_keywords Populated when a routing rule fired; empty otherwise.
was_sticky, was_fallback Whether conversation affinity, or the fallback endpoint, decided the turn.
rule_id The routing rule that short-circuited the decision, when one did.
route_reason The affinity outcome. This is the field that answers "why did my model change mid-conversation".
evicted_incumbent_id The endpoint a conversation moved away from, when it moved.
fallback_reason Why the fallback was used. Alert on this, not on the fallback rate -- the rate unions causes with opposite meanings.
filtered_out How many candidates each eligibility filter removed.
route_time_ms How long the routing decision took.

Decision rows are retained for a short operational window and are removed with a project's content when the project is purged.

Audio and Realtime

Speech-to-text, text-to-speech, and streaming transcription, all on the OpenAI shapes. Full request and response detail, form fields, and error codes are on the Audio page; the routes are:

Create Transcription

POST /:project_id/:endpoint_slug/v1/audio/transcriptions

Transcribes an uploaded audio file in its own language. Multipart upload, served by a transcription endpoint. See Audio.

Create Translation

POST /:project_id/:endpoint_slug/v1/audio/translations

Transcribes an uploaded audio file and translates the transcript to English. See Translations.

Create Speech

POST /:project_id/:endpoint_slug/v1/audio/speech

Synthesizes audio from text. JSON body, served by a speech endpoint; returns audio bytes or a Server-Sent Events audio stream. See Speech.

Realtime Transcription

WS /:project_id/:endpoint_slug/v1/realtime

Streams speech to text over a WebSocket, the transcription subset of the OpenAI Realtime API. Streaming ASR only, not speech-to-speech. Requires a realtime endpoint. See Realtime Transcription.

Rate Limits

Every API response includes rate limit headers. Rate limits are enforced per service tier using a sliding window algorithm.

Rate Limit Response Headers

Both standard (draft IETF) and X- prefixed headers are returned for broad client compatibility:

Header Description
RateLimit-Limit X-RateLimit-Limit
Maximum requests allowed per window. This is the enforced ceiling -- the tier's base rate plus its burst capacity, not the base rate alone. For the free tier that is 96 (64 base + 32 burst), so Remaining counts down from 96 to 0 and a 429 is returned exactly when it reaches 0. See "Rate Limits by Tier" below for each tier's effective ceiling.
RateLimit-Remaining X-RateLimit-Remaining
Remaining requests in the current window, counted against the enforced ceiling (base + burst) reported by RateLimit-Limit.
RateLimit-Reset X-RateLimit-Reset
Seconds until the current window resets.
X-RateLimit-Warning Set to approaching_limit when remaining requests are at or below 20% of the limit, including 0. Not present otherwise.

Rate Limits by Tier

Tier Requests/Min Burst Capacity
free 64 +32 (50%, min 3)
cpu 128 +64 (50%, min 10)
gpu 256 +128 (50%, min 10)
self_hosted Unlimited N/A

Burst capacity allows short-term traffic spikes above the base limit without immediately blocking requests.

The Requests/Min column is the tier's base rate. The RateLimit-Limit / X-RateLimit-Limit header advertises the enforced ceiling = base + burst, which is the point at which a 429 is actually returned. The effective ceilings are therefore free 96 (64 + 32), cpu 192 (128 + 64), and gpu 384 (256 + 128). A client that reads "free = 64/min" and receives X-RateLimit-Limit: 96 is seeing the base rate and the enforced ceiling respectively -- there is no discrepancy. Self-hosted is unmetered and omits the numeric rate-limit headers entirely.

429 Rate Limit Exceeded

When the rate limit is exceeded, the API returns a 429 Too Many Requests response with retry guidance:

JSON
{ "error": { "message": "Rate limit exceeded. Please retry after 15 seconds using exponential backoff.", "type": "rate_limit_error", "code": "rate_limit_exceeded", "retry_after": 15 } }

The Retry-After header is also set to the same value. The retry_after value is the number of seconds until a slot frees for your request; clients should back off at least that long and add exponential backoff with jitter across successive 429s.

Error envelope type and code values shown here illustrate intent; the canonical list of error types and their HTTP status mapping is documented on the Errors page. Treat that page as the source of truth if a value differs.

Management API Rate Limits

Management API endpoints (batch, conversations, files, uploads, webhooks, and SLOs) are subject to a separate per-project rate limit, distinct from the inference rate limit -- management traffic does not consume your inference budget, and inference traffic does not consume the management limit. Most management (control-plane) operations share a 60 rpm per-project window. Two high-volume data-plane routes carry their own dedicated, higher per-project window so bulk workloads are not throttled by the control-plane limit:

  • File upload and content download (POST /v1/files, GET /v1/files/{id}/content): default 600 rpm, configurable via EREBINE_FILES_API_RATE_LIMIT_RPM.
  • Conversation item append (POST /v1/conversations/{id}/items): default 600 rpm, configurable via EREBINE_CONVERSATIONS_API_RATE_LIMIT_RPM.
Setting Value
Default limit 60 requests per minute per project
Configuration Fixed limit; not configurable per project
Window Sliding 60-second window

Response headers on management API endpoints:

Header Description
RateLimit-Limit / X-RateLimit-Limit Maximum requests allowed per window -- the enforced ceiling. The management limiter applies no burst, so this equals the base management limit (60 for control-plane routes, 600 for the data-plane routes noted above), unlike the inference limiter whose ceiling is base + burst.
RateLimit-Remaining / X-RateLimit-Remaining Remaining requests in the current window.
RateLimit-Reset / X-RateLimit-Reset Seconds until the window resets.
X-RateLimit-Warning Set to approaching_limit when remaining requests are at or below 20% of the limit (including 0). Not present otherwise.
Retry-After Seconds to wait before retrying (only on 429 responses).

Both the IETF draft form (RateLimit-*) and the vendor form (X-RateLimit-*) are returned, matching the inference surface. When the limit is exceeded, the API returns HTTP 429 Too Many Requests. Inference endpoints (chat completions, embeddings, responses) have separate per-endpoint rate limits as described above and are not affected by the management API rate limit.

Request Timeouts

Request timeouts are determined by your endpoint's service tier. These are not client-configurable.

Request Deadline Timeout

The maximum time a request can wait for a response before being cancelled:

Tier Timeout
free30 seconds
cpu300 seconds
gpu300 seconds
self_hosted1800 seconds

Idle Stream Timeout

For streaming requests, the maximum time between chunks before the stream is terminated:

Tier Idle Timeout
free120 seconds
cpu600 seconds
gpu600 seconds
self_hosted3600 seconds

For requests with reasoning_effort set, the idle timeout uses the full request deadline timeout to accommodate long reasoning phases with no output.

Timeout Error Responses

For non-streaming requests, timeout returns HTTP 408. For streaming requests, an SSE error event is sent:

SSE
event: error data: {"error": {"type": "timeout_error", "message": "Request timed out after 30s. Your free tier has a 30-second timeout limit."}}

max_tokens and the Context Window

max_tokens (or max_completion_tokens) must be a positive integer; 0 or a negative value returns 400 with param: "max_tokens". There is no fixed upper limit -- the real ceiling is the endpoint's deployed context window minus your input. Your value is sent to the model verbatim: Erebine does not lower it behind your back.

When the requested output does not fit alongside the input inside the model's context window, the request returns 400 context_length_exceeded with the context length and the output budget actually available. Lower max_tokens, shorten the input, or route to an endpoint with a larger context window.

Opt In to Clamping

Clients that hardcode a large default (e.g. max_tokens: 32000) can ask Erebine to fit the value to the remaining context instead of failing:

HTTP Header
X-Erebine-Augment-Max-Output-Tokens-Clamp: on

With the clamp on, the router estimates the input token count from message length and lowers max_tokens to what is left, at minimum 1 token. max_completion_tokens takes precedence over max_tokens when both are set, and the clamp replaces whichever is in effect. The estimate is deliberately conservative, so a clamped ceiling can land below the true available budget. See Passthrough and Augmentation.

Models

List and describe the models available through your Erebine endpoint.

List Models

GET /proj_ABC123/v1/models

Lists the currently available models and their metadata.

curl
curl https://api.erebine.ai/proj_ABC123/v1/models \ -H "Authorization: Bearer ere_myproject_your_api_key"

Response

{ "object": "list", "data": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "object": "model", "created": 1706000000, "owned_by": "my-project" }, { "id": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "object": "model", "created": 1706000000, "owned_by": "my-project" } ] }

Retrieve Model

GET /proj_ABC123/v1/models/{model}

Retrieves a model instance, providing information about the model.

Reranking and Scoring

Reranking endpoints (/v1/rerank, /v1/score) are documented on the Reranking page. The /v1/score endpoint scores a query against one or more candidate documents using the endpoint's configured reranker model.

Endpoints

List inference endpoints configured for your project.

List Endpoints

GET /proj_ABC123/v1/endpoints

Returns all non-deleted endpoints for your project, including those in provisioning, suspended, or error states.

curl
curl https://api.erebine.ai/proj_ABC123/v1/endpoints \ -H "Authorization: Bearer ere_myproject_your_api_key"
Python
import requests headers = {"Authorization": "Bearer ere_myproject_your_api_key"} response = requests.get( "https://api.erebine.ai/proj_ABC123/v1/endpoints", headers=headers ) for endpoint in response.json()["data"]: print(f"{endpoint['name']} ({endpoint['status']})")
Node.js
const response = await fetch( "https://api.erebine.ai/proj_ABC123/v1/endpoints", { headers: { "Authorization": "Bearer ere_myproject_your_api_key" } } ); const data = await response.json(); for (const endpoint of data.data) { console.log(`${endpoint.name} (${endpoint.status})`); }

Response

{ "object": "list", "data": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "object": "endpoint", "slug": "my-endpoint", "name": "My Endpoint", "model_id": "00000000-1111-0000-1111-000000000000", "model_name": "llama-3.1-8b-instruct", "tier_id": "free", "status": "active", "custom_domain": null, "max_requests_per_minute": 60, "max_tokens_per_minute": 100000, "provisioning_state": null, "provisioned_worker_id": null, "created": 1706123456 } ] }