POST /v1/chat/completions: the OpenAI request and response, plus the fields that add threads, routing and cost traces.
POSThttps://backend.alfnrl.io/v1/chat/completions
The OpenAI Chat Completions API, with the conversation kept on the server. Send the request you would send to OpenAI and you get the same response back; add a thread id and the conversation is kept for you.
curl https://backend.alfnrl.io/v1/chat/completions \ -H "Authorization: Bearer $ALPHANEURAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openrouter/openai/gpt-4o-mini", "messages": [ {"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What is a context window?"} ], "temperature": 0.2 }'
import osfrom openai import OpenAIclient = OpenAI( base_url="https://backend.alfnrl.io/v1", api_key=os.environ["ALPHANEURAL_API_KEY"],)completion = client.chat.completions.create( model="openrouter/openai/gpt-4o-mini", messages=[ {"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What is a context window?"}, ], temperature=0.2,)print(completion.choices[0].message.content)
import OpenAI from "openai";const client = new OpenAI({ baseURL: "https://backend.alfnrl.io/v1", apiKey: process.env.ALPHANEURAL_API_KEY,});const completion = await client.chat.completions.create({ model: "openrouter/openai/gpt-4o-mini", messages: [ { role: "system", content: "Answer in one sentence." }, { role: "user", content: "What is a context window?" }, ], temperature: 0.2,});console.log(completion.choices[0].message.content);
The body that comes back is the model's OpenAI-format completion, unchanged. The response also has an X-Thread-Id header naming the thread the exchange was saved to.
Required. A model id from the catalogue, such as openrouter/openai/gpt-4o-mini; the name of one of your deployments; or alphaneural/auto to let the server choose (see Routing). Without it: 400model is required.
messages
array
The messages you are adding. With a thread, that is only the new turn; see how messages are handled.
stream
boolean
Stream the reply as server-sent events. See Streaming.
Every other field you send is forwarded to the gateway unchanged: temperature, top_p, max_tokens, tools, tool_choice, response_format, stop, seed and provider-specific fields alike, with the two exceptions below. The gateway then drops any parameter the chosen provider does not support rather than refusing the request, so a seed or logit_bias sent to a model that cannot take it returns 200 with the parameter ignored, not an error. GET /v1/models reports each model's supported_parameters where known.
A ladder of models and a JSON schema to check the answer against. See Routing. Cannot be combined with stream, except with alphaneural/auto.
max_spend
number
A ceiling in US dollars, margin included, on what the request may be billed across every model tried. Used with escalation or alphaneural/auto. With your own ladder, 0 or omitted means no ceiling; with alphaneural/auto, omitted means the server's default and 0 is refused.
A field of the wrong type, step_index sent as a string for example, makes the whole body invalid: 400invalid JSON body.
Each message in messages is appended to the thread first. Then the thread, not your messages array, is what goes to the model: rendered for the model you named and fitted to its context window. Three things follow from that:
messages is replaced. The model receives the thread, which by then includes the messages you just sent.
Send only the new turn. Resending earlier messages appends them again. See Threads and memory.
metadata is replaced when you send task_id or step_index. The API sets metadata to those two values so the usage can be traced, and any metadata you sent in the same request is dropped. Without them, your metadata is forwarded as sent.
role is system, user, assistant or tool. content is either a string or an array of parts, where each part is {"type": "text", "text": "..."} or {"type": "image_url", "image_url": {"url": "..."}}. Other part types are not supported on this endpoint.
image_url.url is stored exactly as sent. An https URL is a short reference; a data: URL carries the whole image in base64, and is stored in full, returned in full by Read a thread, and re-sent to every image-capable model on every later turn of the thread, while the context budget counts it as a flat 800 tokens. On a thread, send images by URL.
The model's response body is returned with status 200, as the provider sent it, plus:
the X-Thread-Id header;
with escalation or alphaneural/auto, a top-level escalation object saying which models ran and what each cost, and X-Escalation-* headers repeating it. See Routing.
If the model call itself fails, the provider's error comes back with its own status and body, unchanged. See Errors.
Tool calling is single-step. Tool definitions reach the model, and a reply containing tool_calls comes back intact. But a thread keeps only each message's role and content: an assistant message's tool_calls and a tool message's tool_call_id are not stored, so the follow-up request that returns tool results is not one a provider accepts. A reply made only of tool calls adds nothing to the thread. Run multi-step tool loops against the raw gateway.
Only the first choice is saved. With n above 1 you receive every choice, but only choices[0] is added to the thread.
Every call is saved. Without a thread id, each request starts and stores a new thread. If you do not want a conversation kept as a thread, call the raw gateway, which saves no thread; it does keep its own usage logs for metering, so that is not the same as nothing being stored. There is no API to delete a thread yet.