your way into the api.
from your first request to streaming and tools. formats, actual limits and differences between channels.
https://api.2api.me/v1parameter supportdepends on the offerretrying a requestIdempotency-Key2api connects independent channels through one API key. A model is what you request; a channel is a supplier; an offer (listing) is one model on one channel, with its own prices, limits and capabilities.
Open your workspace, sign in and create a key in API keys. Save the full secret: it is shown once. Generation requires an available balance. If registration is closed or you need help with access, use contact.
Keep the key in an environment variable on your server. Do not put it in a public frontend, mobile build, repository or screenshot. The API sends messages to the selected channel; take this into account when sending data.
Base URL: https://api.2api.me/v1. https://2api.me/v1 also works. Fetch the model list first, then use the exact ID of a text model. The examples below use illustrative model names, listing IDs, responses and counters. They are templates, not available offers or real call results.
These commands use bash/zsh with curl and uuidgen installed. Enter the full key at the hidden prompt and press Enter; it will be available in this terminal environment and its child commands. For an application, use its private environment settings.
printf '2api key: '
read -r -s TWOAPI_API_KEY
export TWOAPI_API_KEY
printf '\n'# TWOAPI_API_KEY contains the full 2api secret.
curl -sS https://api.2api.me/v1/models \
-H "Authorization: Bearer $TWOAPI_API_KEY"Save this example as request.json, replacing provider/model with your selected ID. A minimal request is easier to check: add temperature, tools and other settings when you need them.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Explain what an API is in one sentence."
}
],
"max_completion_tokens": 256
}# Create once for this operation; keep the value if the result is unknown.
REQUEST_KEY="$(uuidgen)"
curl -sS -D response-headers.txt \
https://api.2api.me/v1/chat/completions \
-H "Authorization: Bearer $TWOAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $REQUEST_KEY" \
--data-binary @request.jsonThe text of a regular response is in choices[0].message.content. For a tool call, read tool_calls; text may be absent. A limit of 256 is suitable for a short example, but reasoning models may consume it on reasoning before producing visible text.
The tables below describe the format understood by 2api. They do not promise that every offer supports every parameter. The same model can have different restrictions on different channels; models within one channel can differ too.
2api first validates the request shape. It then selects an offer of that model matching its stored profile: parameters, ranges, combinations, streaming, tools and output limit. An unknown top-level field gives 400 invalid_request; a valid parameter with no matching offer gives 404 no_compatible_listing.
Unsupported parameters are normally not removed and values are not clamped. Explicit zero and false values are preserved. With direct, only the specified offer is checked. With smart, selection considers compatible offers of the same model.
Profiles and technical probes do not prove support for every schema or combination, or the actual effect of temperature/seed. A provider can change after testing and reject parameters. That rejection returns a normalized error. Smart switches to another compatible channel of the same model only on HTTP 5xx before the response starts.
/v1/models returns model IDs without a shared capabilities matrix. Each offer profile is available from GET https://2api.me/api/workspace/catalog: capabilities, parameter_limitations, context_limit and the offer id. The market detail panel exposes the same parameters. These are stored declarations and routing rules, not proof of support for every combination. Check the scenario you need with a small request; successful generation is billed.
| layer | meaning |
|---|---|
| 2api format | The gateway recognizes the field and its type/value. |
| offer profile | Declared capabilities and limited technical checks allow route selection. |
| provider response | The actual request can still be rejected, time out or return invalid output. |
| method | path | purpose |
|---|---|---|
| GET | /v1/models | Published model list |
| POST | /v1/chat/completions | Text chat, function tools, SSE |
| POST | /v1/messages | Text and tools in Anthropic Messages format, SSE |
| POST | /v1/responses | Text and tools in Responses format, SSE |
| POST | /v1/embeddings | Text embeddings |
| GET | /v1/requests/{id} | Status and accounting for your request |
Endpoints require Authorization: Bearer <2api key>. /v1/messages also accepts x-api-key: <2api key>, but not both methods together. A supplier key or browser cookie cannot replace a 2api key. POST requests require Content-Type: application/json. Compressed bodies are not accepted. Do not add /v1 twice.
Use Chat Completions, Messages or Responses with a configurable base URL. Your request endpoint determines JSON, streaming and error formats. The model name and supplier response format do not change it. Compatibility covers text requests and function tools, not every feature of a third-party app.
/v1/completions, native Gemini, Assistants, images, audio, video, files, batches, fine-tuning and realtime are not mounted. Unknown routes return 501 not_implemented and may use a different error envelope.
POST /v1/messages: system, text messages, tool_use/tool_result, tools with input_schema, tool_choice, stop_sequences, temperature, top_p and required max_tokens. Omit anthropic-version or set it to 2023-06-01. Read content in the response. Streams end with message_stop, without [DONE].
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Hello"
}
],
"max_tokens": 256
}POST /v1/responses: input as text or text/function-call history, instructions, tools, tool_choice, parallel_tool_calls, max_output_tokens, reasoning.effort and text.format for JSON. Send history explicitly. store and background can only be false, truncation only disabled. Read output in the response. Streams end with response.completed or response.incomplete.
{
"model": "provider/model",
"input": "Hello",
"max_output_tokens": 256,
"store": false
}previous_response_id, conversation, files, server tools, cache_control, thinking budget/adaptive, verbosity and beta features are unsupported. Thinking/refusal/reasoning history items are not accepted yet. Unknown fields return an error before dispatch.
Messages returns answer text and tool calls. Unsigned reasoning and summaries are not displayed, but their tokens remain part of output and are charged once. Native thinking is returned only with a nonempty provider signature. If reasoning exhausts the output limit, content may be empty with stop_reason=max_tokens. Chat and Responses retain reasoning in their own formats.
Streaming, routing, limits and billing rules are shared. Final confirmation follows settlement. Messages input_tokens excludes cache read/write; Responses input_tokens includes cache. reasoning_tokens is part of output, not an extra charge. The following parameter tables and streaming examples describe canonical Chat Completions.
GET /v1/models returns {"object":"list","data":[...]}. Each item has id, object: "model" and owned_by: "2api". The latter identifies the 2api catalog, not the model author or owner. The list can be empty and changes when offers are published or withdrawn.
{
"object": "list",
"data": [{"id": "provider/model", "object": "model", "owned_by": "2api"}]
}Use an exact id from the list. A short name is accepted only when unambiguous; otherwise the result is 400 ambiguous_model. Responses use the canonical model name. A matching model string is not proof of authenticity.
Open a model in the market and compare offers. The chat or embeddings kind, context and prices belong to an individual offer. A listing_id identifies that offer, not the channel, API key or model. Context is declared by the seller; technical approval does not prove operation at the full maximum context.
For programmatic inspection, the public web catalog is https://2api.me/api/workspace/catalog, without an API key. This is a separate website endpoint, not /v1/models: it returns success, data.offers and data.truncated. Use offers[].id as routing.listing_id; model, kind, context_limit, input, output, cache_read and cache_write describe the offer. Each page contains up to 500 offers. Pass data.next_cursor as ?cursor=... until it is empty; truncated: true indicates another page. capabilities and parameter_limitations belong to that specific offer.
Public prices are decimal strings in USD per million tokens and already include the platform markup. null means the category is unavailable, not free usage. In the web catalog, the embeddings rate is in input. Catalog data is a snapshot: an offer being listed does not guarantee the next call will succeed.
curl -sS https://2api.me/api/workspace/catalogSend JSON to POST /v1/chat/completions. model and messages are required. The ranges below are outer 2api bounds; an offer or platform settings can impose tighter limits.
| field | format / bounds | behavior |
|---|---|---|
| model | String from /v1/models | Exact model; short names must be unambiguous. |
| messages | 1..1024 messages | Conversation history; see the next section. |
| max_completion_tokens / max_tokens | Integer 1..131072; choose one field | Output bound. 2api maps the field name to the offer format. The value is not clamped and must be below its context limit. |
| stream | boolean | Defaults to false. True requires streaming support on the offer. |
| stream_options | {"include_usage": true} | Only with stream: true; include_usage is boolean and defaults to false. |
| temperature | 0..2 | Requires support and a matching range; combinations with top_p or reasoning can be restricted. |
| top_p | 0..1 | Range and combinations depend on the offer. |
| frequency_penalty / presence_penalty | -2..2 | Each setting requires its own support. |
| seed | 64-bit integer | Sent to a compatible offer; reproducibility is not guaranteed. |
| stop | Nonempty string or 1..4 strings | Requires stop support. |
| response_format | text / json_object / json_schema | An object with type; JSON modes require their respective support. |
| tools | 1..128 function definitions | Functions only, with unique names. |
| tool_choice | none / auto / required / function | Requires tools; a separate offer capability. |
| parallel_tool_calls | boolean | Requires tools; a separate capability, even for false. |
| reasoning_effort | none / minimal / low / medium / high / xhigh / max | Only the levels declared for the offer are accepted. |
| n | 1 | Other values are unsupported. |
| routing | Object | Offer selection and explicit permission to ignore parameters. |
If no output limit is supplied, the service uses default_output_tokens (initial value 1024). The platform maximum is configurable too. Set the limit you need explicitly: the default may not suit a model. Do not use null instead of omitted scalar parameters.
Fields such as max_input_tokens, user, metadata, store, logprobs, logit_bias, service_tier, verbosity, prediction, modalities and legacy functions/function_call are not accepted. Arbitrary provider-specific fields do not pass through automatically. ignore_parameters cannot make an unknown field valid.
Allowed roles: system, developer, user, assistant, tool. developer requires separate offer support and is not rewritten to system. Other allowed message fields are content, name, tool_calls and tool_call_id.
content is a string or a nonempty array of {"type":"text","text":"..."} parts. Text parts are joined in order without inserted separators. Images, audio, files and other part types are unsupported. Assistant messages with tool_calls can omit content or use null; other messages require content.
Each request includes the full history it needs. The server does not resume a conversation using a previous response ID. Add previous user/assistant messages yourself; do not send the entire response object as a message. Service fields and optional output fields such as reasoning_content are not part of the input contract.
If supplied, name must be a nonempty string. tool_call_id is allowed only on tool messages. Tool results go in content as strings, including when the string contains JSON.
Routing precedence: an explicit request route → the saved rule for this API key and model → smart / price / allow_unrated=true. Supplying only ignore_parameters inside routing does not override a saved route. Save a key rule from the market using “apply to API key”; sorting the table alone does not change it.
| strategy | meaning |
|---|---|
| smart | Compatible offers, then health and measurement filters. priority: price, latency or uptime. Explicit smart defaults allow_unrated to false. |
| direct | Only the specified listing_id. A mature rating is not required; technical approval, publication, availability and compatibility are required. Omit priority. |
| cheapest | Alias for smart / price / allow_unrated=true. Do not add priority, listing_id or allow_unrated; use smart to configure them. |
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Hello"
}
],
"max_completion_tokens": 256,
"routing": {
"strategy": "smart",
"priority": "price",
"allow_unrated": false
}
}price compares input + output at a 1:1 ratio, or the embeddings rate. This is not an exact estimate of your request cost. latency compares p95 time to the first meaningful stream fragment and is available only for chat. uptime uses an observed reliability score. These are not response-time or availability guarantees.
Smart prefers suitable offers with mature, fresh measurements. If none are available, allow_unrated: true permits choosing by price among offers without enough history. It does not permit stale measurements, a known success rate below 99%, or a route temporarily excluded after failures. Insufficient suitable measurements produce 503 no_measured_route.
If a channel returns HTTP 500-599 before the response starts, smart automatically tries another available compatible channel of the same model. Each channel is called at most once within the original two-minute deadline. Parameters are adapted again from the original request, retaining smart preferences saved on the key. X-2api-Attempts reports the attempt count; X-2api-Listing-ID identifies the last selected offer. Direct routing, 4xx errors, network timeouts and interrupted responses do not trigger failover.
The failed attempt reservation is released before switching channels. The next attempt gets its own small reservation and price snapshot. Only a valid response with consistent usage is charged. If suitable channels are exhausted, the request fails without a charge. The original request_id status shows the latest attempt and an attempts array with separate accounting. Keep this ID for support.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Hello"
}
],
"max_completion_tokens": 256,
"routing": {
"strategy": "direct",
"listing_id": "11111111-1111-4111-8111-111111111111"
}
}In the direct example, replace the UUID with a real offer ID for the selected model from the catalog. Model and offer must match. Direct sends the request only to that offer and returns its error without automatic failover.
Use routing.ignore_parameters only if a result without those settings is acceptable. It permits removing an unsupported parameter during channel selection; it does not always delete it. Compatible parameters stay in the request.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Hello"
}
],
"max_completion_tokens": 256,
"temperature": 0.9,
"routing": {
"strategy": "smart",
"priority": "price",
"allow_unrated": true,
"ignore_parameters": [
"temperature"
]
}
}In this example, temperature 0.9 stays on a compatible offer and may be removed on an incompatible one. Check X-2api-Ignored-Parameters: it lists the fields actually removed, separated by commas. They also appear in the request status as ignored_parameters.
Allowed names: temperature, top_p, seed, frequency_penalty, presence_penalty, stop, response_format, tools, tool_choice, parallel_tool_calls, reasoning_effort, dimensions. Model, messages, input, stream, output limits and tool-call history cannot be ignored.
When tools are removed, any supplied tool_choice and parallel_tool_calls require separate removal permission. If temperature/top_p cannot coexist and both may be ignored, top_p is removed first. Removing response_format drops the format requirement; removing dimensions may change vector size. Do not allow this if your code depends on them.
{
"id": "2api-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"object": "chat.completion",
"created": 1788868800,
"model": "provider/model",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "An API lets programs communicate."},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 20, "completion_tokens": 8, "total_tokens": 28}
}Read message and finish_reason, not just HTTP 200. stop means completion, length means the limit was reached, tool_calls requests function execution, and content_filter indicates filtering. Content may be absent for tool calls; refusal and reasoning_content are optional and depend on the channel response.
An admitted inference request has an ID shaped as 2api-<64 hex>, matching the chat ID and X-Request-ID / X-Oneapi-Request-Id headers. Tool-call IDs use 2api-<32 hex>. Errors before admission also get a 2api-... diagnostic ID, but it may not have a record in /v1/requests.
Channel request/response/tool IDs are replaced with 2api IDs; arbitrary upstream headers and unknown service fields are not copied. Model text and argument contents are not rewritten. listing_id, api_key_id and price_version_id identify separate 2api objects and may use UUID format.
Choose an offer supporting streaming. Save the example as stream.json and use curl with -N to disable output buffering. Enable include_usage if you need final token counts in the stream.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Write a short story about the sea."
}
],
"max_completion_tokens": 512,
"stream": true,
"stream_options": {
"include_usage": true
}
}REQUEST_KEY="$(uuidgen)"
curl -sS -N -D stream-headers.txt \
https://api.2api.me/v1/chat/completions \
-H "Authorization: Bearer $TWOAPI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $REQUEST_KEY" \
--data-binary @stream.jsondata: {"id":"2api-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","object":"chat.completion.chunk","created":1788868800,"model":"provider/model","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]}
data: {"id":"2api-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","object":"chat.completion.chunk","created":1788868800,"model":"provider/model","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"2api-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","object":"chat.completion.chunk","created":1788868800,"model":"provider/model","choices":[],"usage":{"prompt_tokens":10,"completion_tokens":1,"total_tokens":11}}
data: [DONE]
Read SSE events up to the blank-line delimiter. A network chunk is not an event: it can contain partial JSON or multiple events. Append delta.content in order; not every delta has content. For tool_calls, group by index, retain the ID and concatenate function.arguments fragments. A usage event can have empty choices.
Successful completion is marked by [DONE]. Final usage, if requested, and [DONE] are sent after successful settlement. A finish_reason alone does not complete the whole operation. If a started stream fails, it may emit data: {"error":...,"request_id":...} while HTTP stays 200. A connection failure can prevent even that error from arriving.
On error or EOF without [DONE], treat the result as incomplete and check request status. After streaming starts, disconnecting does not cancel generation: 2api reads the same provider response within the original 2-minute deadline and charges its valid final usage, including generation after you disconnect. Cancellation before streaming starts stops the request. A broken upstream response or incomplete usage releases the reservation without a charge.
2api sends function definitions to the model; it does not run your code or perform external actions. The offer must support tools; tool_choice and parallel_tool_calls are separate capabilities. auto lets the model answer in text without calling a function.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "What is the weather in Almaty?"
}
],
"max_completion_tokens": 512,
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": [
"city"
]
}
}
}
],
"tool_choice": "auto"
}If the response contains message.tool_calls, check the function name, parse the arguments string as JSON and validate it against your own schema. Run the allowed function in your app. Then make a new request with the original history, an assistant message containing the returned calls, and one tool-result message per call.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "What is the weather in Almaty?"
},
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "2api-0123456789abcdef0123456789abcdef",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Almaty\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "2api-0123456789abcdef0123456789abcdef",
"content": "{\"temperature_c\":18,\"conditions\":\"cloudy\"}"
}
],
"max_completion_tokens": 256
}The weather and ID in the example are illustrative. Reuse the returned 2api-... unchanged in assistant.tool_calls[].id and the corresponding tool.tool_call_id. This is a separate billable generation with a new Idempotency-Key. Tool history still requires support even without definitions in the new request.
Send tools again to allow further function selection. tool_choice accepts none, auto, required or {"type":"function","function":{"name":"get_weather"}}; the named function must be in tools. A definition’s strict flag is passed as a function setting, but does not replace your validation or prove that the channel supports every schema.
response_format is an object. {"type":"text"} selects regular text. json_object and json_schema require separate offer capabilities; support for one does not imply support for the other.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Return a JSON object with a short greeting in the greeting field."
}
],
"max_completion_tokens": 256,
"response_format": {
"type": "json_object"
}
}{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "Return a short greeting."
}
],
"max_completion_tokens": 256,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "greeting",
"strict": true,
"schema": {
"type": "object",
"properties": {
"greeting": {
"type": "string"
}
},
"required": [
"greeting"
],
"additionalProperties": false
}
}
}
}json_schema requires a nonempty name and schema object; strict is optional. The result still arrives as a string in message.content and needs parsing. 2api checks request shape and response protocol, but does not validate generated JSON against your schema. Check JSON, schema, refusal and finish_reason in your application.
{
"model": "provider/model",
"messages": [
{
"role": "user",
"content": "What is 17 multiplied by 23?"
}
],
"max_completion_tokens": 2048,
"reasoning_effort": "low"
}The example needs an offer accepting reasoning_effort: "low". The API’s overall level list does not mean every model accepts none or max. Some offers permit temperature/top_p only with an explicit reasoning_effort: "none"; others forbid temperature and top_p together. 2api does not insert none automatically or rewrite the developer role.
completion_tokens includes reasoning tokens when the channel reports them in a supported format. They are already included in output and are not added again. reasoning_content may be absent: reasoning support does not promise disclosure of hidden reasoning. Size the output limit accordingly; increasing it does not override the 2-minute execution limit.
Send JSON to POST /v1/embeddings, choosing an embedding model. A text model may have no embeddings offer. This endpoint does not support streaming or chat parameters.
{
"model": "provider/embedding-model",
"input": [
"A document about the sea",
"A document about mountains"
],
"encoding_format": "float"
}| field | format |
|---|---|
| model | Required string: embedding model ID. |
| input | Required nonempty string or 1..128 nonempty strings. Token-ID arrays are unsupported. |
| encoding_format | float (default) or base64. 2api encodes base64 from little-endian float32 values. |
| dimensions | Integer 1..65536, only if the offer supports that dimension. When omitted, the channel default applies. |
| routing | direct or smart with price/uptime; latency is invalid for embeddings. cheapest is also accepted as an alias. |
{
"object": "list",
"model": "provider/embedding-model",
"data": [{"object":"embedding","index":0,"embedding":[0.125,-0.25,0.5]}],
"usage": {"prompt_tokens":5,"total_tokens":5}
}The response example illustrates one input; a batch returns a vector per input string. Match vectors by index, not just array order. The embeddings body has no extra ID: save X-Request-ID. The model and dimensions used for documents and queries must match your vector store configuration.
Before calling a channel, 2api reserves a small amount, capped at $0.05 and your available balance. It depends on the submitted request and output limit, not the model’s full context. This is a temporary hold, not the final charge.
The estimate uses serialized JSON bytes as a conservative estimate of visible input at the highest available input/cache rate, plus the output limit for chat. This is not an exact token count or a guaranteed maximum cost. Final billing uses validated usage only and may exceed the reservation. An admitted request may reduce available balance to -$2. New paid requests at zero or below return 402 insufficient_funds. Settlement that would exceed -$2 is rejected atomically, the reservation is released and settlement_failed is returned.
After a complete valid response, actual usage is charged at the price version selected on admission; the remainder is released. A price change during execution does not affect that request. The exact USD amount is client_usd in request status, a decimal string. Do not add markup again to public prices.
| counter | meaning |
|---|---|
| prompt_tokens | All input, including reported cache reads/writes. |
| completion_tokens | All output, including reported reasoning tokens. |
| total_tokens | prompt_tokens + completion_tokens; prompt_tokens for embeddings. |
| prompt_tokens_details.cached_tokens | Cache reads, a subset of input. |
| prompt_tokens_details.cache_write_tokens | Cache writes, a separate subset of input. |
| completion_tokens_details.reasoning_tokens | A subset of output; not charged again separately. |
plain_input = prompt_tokens - cached_tokens - cache_write_tokens
cost = (
plain_input * input_price
+ cached_tokens * cache_read_price
+ cache_write_tokens * cache_write_price
+ completion_tokens * output_price
) / 1_000_000Cache reads are accounted for by default at a separate rate. Chat offers require this price to publish; explicit 0 means free. A cache hit is not guaranteed, and this API has no separate cache switch. Absent cache counters are treated as zero. Invalid or incomplete usage causes an error and reservation release, never an invented charge. The checks establish counter consistency, not independently verified tokenization for every supplier.
A recorded timeout, unknown outcome or broken upstream response automatically releases the reservation. A client disconnect after SSE starts does not cancel reading and charging the full usage within the original request deadline. After a process or database failure, background recovery releases unfinished reservations older than 3 minutes once the database is available. Check the operation status.
Create an Idempotency-Key before the first POST and keep it with the exact request body bytes. Format: 8..128 ASCII letters, digits, ., _, -; a UUID works. Scope is the entire account, across API keys and all inference endpoints.
| situation | result |
|---|---|
| Key + same bytes of an admitted request | 409 request_already_exists. No second call. Read status using request_id. |
| Admitted key, but changed body or endpoint | 409 idempotency_conflict. Even JSON formatting changes count. |
| Request was not admitted; no reservation | No record has claimed the key. A retry can become the first admitted request. |
| No key supplied | The server generates it and returns it in a header. If the response is lost before headers, the client may lack the key needed for a safe retry. |
Model responses are not stored for replay. A 409 points to status, not the lost text. A new key after an unknown outcome risks a second billable call. First retry with the original key and unchanged body, or check the known request_id.
Client auto-retries must preserve the key and body too. After 429, honor Retry-After when present; after concurrency_limit, wait for running calls to finish. Do not retry indefinitely or assume every 5xx is an unbilled rejection.
# REQUEST_ID is the X-Request-ID of the original admitted request.
curl -sS "https://api.2api.me/v1/requests/$REQUEST_ID" \
-H "Authorization: Bearer $TWOAPI_API_KEY"Status is available only to the request owner using an active key for the account. The response is a record object without a data wrapper. This GET’s request header identifies the status lookup itself; the body ID identifies the original generation. The response contains neither prompts nor generated text.
| status | money and action |
|---|---|
| running | Admitted and not finalized; funds reserved. Check again later. |
| succeeded | Settled successfully; client_usd charged, reservation remainder released. |
| released | Reservation released; no charge for this operation. |
| review | Historical request held before automatic releases were introduced. Contact support with the ID. New failures do not receive this status. |
| field | meaning |
|---|---|
| id, model, kind | Operation ID, canonical model, chat or embeddings. |
| api_key_id, listing_id, price_version_id | Key, offer and selected price-version IDs; not the secret key. |
| status, code | Financial state and reason code; code may be empty. |
| reserved_usd, client_usd | Original reservation and actual charge, decimal strings. reserved_usd remains after completion and is not the currently held amount. |
| attempts, parent_request_id, latest_attempt_id | After failover, the original ID status contains the latest attempt and an attempts array with all reservations and outcomes. Top-level amounts describe only the latest attempt. parent_request_id links a child record to the original; latest_attempt_id identifies the latest one. These fields are absent without failover. |
| usage | Null before accounting, or normalized input, output, cache_read, cache_write, reasoning; input excludes cache here. This differs from chat usage. |
| ignored_parameters | Actually removed settings, an array or null. |
| routing_strategy, routing_priority, streaming | Recorded route mode and streaming flag. |
| output_limit, context_budget | Selected output limit and offer context × batch. Bounds, not actual consumption. |
| duration_ms, ttft_ms | Processing duration; TTFT may be null and is not measured for buffered responses/embeddings. |
| provider_outcome, provider_http_status, provider_observed_at | Call observation separate from accounting. HTTP status 0 means no recorded HTTP response; observation time can be null. |
| created_at, completed_at | UTC timestamps in RFC3339; completed_at may be null. |
404 request_not_found means no record was found for this account: for example, the request was not admitted, the ID is wrong or it belongs to someone else. It does not confirm a charge or refund for another account’s operation.
{
"error": {
"message": "no published technically approved listing supports this request",
"type": "no_compatible_listing",
"code": "no_compatible_listing"
},
"request_id": "2api-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}Chat Completions, Responses and embeddings return error.code. Messages returns a standard error.type, such as authentication_error or rate_limit_error. message explains the cause and request_id helps diagnostics. Proxies and unknown endpoints may return different JSON or plain text: inspect HTTP status and Content-Type first. In a started SSE stream, watch for an error event.
| HTTP / code | action |
|---|---|
| 400 invalid_request | Check JSON, types, ranges, unknown fields and conflicting options. |
| 400 ambiguous_model | Use the full model ID from /v1/models. |
| 401 invalid_api_key | Check Bearer formatting, the full key, revocation and account availability. |
| 402 insufficient_funds | A new paid request requires positive available balance. Add funds or wait for current reservations to be released. |
| 404 no_compatible_listing | No published offer matches the model/kind/parameters. Check direct ID, stream, tools, output limit and required settings. |
| 404 request_not_found | Check ID and account; no record may exist before admission. |
| 409 request_already_exists / idempotency_conflict | Do not blindly create another call; see retries. |
| 413 invalid_request | Body could not be read or exceeds 1 MiB. |
| 415 invalid_request | Use application/json without Content-Encoding. |
| 429 concurrency_limit / rate_limit_exceeded | Reduce concurrency or request rate; honor Retry-After when present. |
| 502 upstream_rejected | The channel explicitly rejected the request; the reservation is released when finalization succeeds. This is not necessarily an issue with your 2api key. |
| 502 upstream_not_reached / upstream_unavailable / unsafe_endpoint / credential_unavailable | The channel was not reached or prepared; check final status and report the ID if it recurs. |
| 502 upstream_outcome_unknown / invalid_upstream_response | Outcome or usage is unreliable. Finalization releases the reservation without a charge. Check status. |
| 503 no_measured_route | No suitable measurements for smart. Choose an offer directly or explicitly allow unrated routes if acceptable. |
| 503 settlement_failed | Settlement was rejected atomically, the reservation was released and nothing was charged. This can happen when the debt limit would be exceeded or settlement could not be saved. Check request details by ID. |
| 503 settlement_pending | Settlement is pending; check status before another generation. In a started SSE stream this is an error event; before streaming it may also use 502. |
| 503 service_paused / inference_unavailable | Service is paused or could not admit the request; keep the retry key and check status if the ID is known. |
| 503 authentication_unavailable / catalog_unavailable / settings_unavailable / request_status_unavailable | Temporary failure of the named service; do not randomly change credentials, retry the read later. |
| 501 not_implemented | The endpoint is not mounted; use a documented endpoint. |
upstream_rejected may include the original HTTP status and safe code/type/param values. Raw provider messages and IDs are not exposed, so a detailed cause may be unavailable. For support, retain request_id, time, model, listing_id, HTTP status and error code. Do not include API keys or private prompt text.
| limit | value |
|---|---|
| Inference request body | 1 MiB JSON |
| Buffered provider response | 16 MiB |
| One SSE event | 1 MiB |
| Execution after reading/parsing the request | 2 minutes, including selection and the channel call. This is not a guaranteed response time. |
| Concurrent inference | Per account, shared by all keys. Initially 4; configurable by the service. |
| GET /v1/models | No API-key RPM limit, including the model catalog. |
| Context | Per offer, at most 2 million. Declared size is not proof of maximum-request support. |
Additional shared network limits can trigger earlier. Chat and embeddings parameter limits above also apply. The API provides no separate guarantee of uninterrupted access from every country or freedom from network failures.
| response header | purpose |
|---|---|
| X-Request-ID / X-Oneapi-Request-Id | 2api diagnostic ID; the second is a compatibility header carrying the same value. |
| Idempotency-Key | The POST key used, when the handler reached key acceptance. |
| X-2api-Listing-ID | Selected offer after admission. |
| X-2api-Attempts | Number of selected attempts, including the current one; 1 without failover. |
| X-2api-Routing-Strategy / X-2api-Routing-Priority | Applied route; direct has no priority. |
| X-2api-Unrated-Route | true when smart selected an offer without enough history. |
| X-2api-Ignored-Parameters | Fields actually removed; absent when none were removed. |
| Retry-After | Seconds to wait when provided by the limiter. |
| Cache-Control: no-store | /v1 responses are not intended to be cached by intermediaries. |
This example uses built-in fetch and crypto.randomUUID in modern Node.js. Set TWOAPI_API_KEY and TWOAPI_MODEL in the environment. It makes one generation and prints the ID; it has no automatic retries.
import { randomUUID } from 'node:crypto';
const apiKey = process.env.TWOAPI_API_KEY;
const model = process.env.TWOAPI_MODEL;
if (!apiKey || !model) throw new Error('Set TWOAPI_API_KEY and TWOAPI_MODEL');
const idempotencyKey = randomUUID();
const body = JSON.stringify({
model,
messages: [{ role: 'user', content: 'Explain what an API is.' }],
max_completion_tokens: 256,
});
// Persist idempotencyKey + body in your app before sending.
// Reuse both unchanged if the result is unknown.
const response = await fetch('https://api.2api.me/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + apiKey,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
},
body,
});
const requestId = response.headers.get('x-request-id');
console.log({ requestId, status: response.status });
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = JSON.parse(text);
console.log(result.choices[0].message);
console.log(result.choices[0].finish_reason, result.usage);On a network exception, do not create a new key for an automatic retry. The saved body and idempotencyKey let you determine whether the request was admitted. Handle errors, cancellation, tool_calls and incomplete completion explicitly in your application.
If a third-party app fails, check the base URL for a duplicated /v1, selected API format, full key, exact model name and available balance. Start with a minimal example for your format. Add required settings one at a time. The app may be sending unsupported fields automatically.
After a disconnect, save the ID and check /v1/requests/{id}. For review or recurring errors, contact support. Data handling and disputed charges are covered in privacy and refunds. This guide describes API behavior and does not promise unverified properties of an external channel.