API
Chat completions
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.
Example
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 os
from openai import OpenAI
client = 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.
Headers
| Header | ||
|---|---|---|
Authorization | Required | Bearer followed by your API key. See Authentication. |
Content-Type | Required | application/json |
X-Thread-Id | Optional | The thread to continue, for clients that cannot add fields to the body. A thread_id in the body takes precedence. |
X-Task-Id | Optional | The task this request is a step of, for the cost trace; the same as task_id in the body, which takes precedence. |
X-Step-Index | Optional | The request's position in that task, an integer; the same as step_index in the body. A non-integer value gets 400. |
Every successful response, streamed or not, carries X-Thread-Id.
Body
Standard fields
| Field | Type | |
|---|---|---|
model | string | 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: 400 model 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.
AlphaNeural fields
These are read by the API and are never forwarded to the model.
| Field | Type | |
|---|---|---|
thread_id | string | The conversation to continue, or to start under this id. Omitted: a new thread. See Threads and memory. |
task_id | string | Groups requests into one task for a cost trace. |
step_index | integer | This request's position within the task. |
escalation | object | 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: 400 invalid JSON body.
How messages are handled
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:
messagesis 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.
metadatais replaced when you sendtask_idorstep_index. The API setsmetadatato those two values so the usage can be traced, and anymetadatayou sent in the same request is dropped. Without them, yourmetadatais forwarded as sent.
Message content
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.
Response
The model's response body is returned with status 200, as the provider sent it, plus:
- the
X-Thread-Idheader; - with
escalationoralphaneural/auto, a top-levelescalationobject saying which models ran and what each cost, andX-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.
Limitations
- Tool calling is single-step. Tool definitions reach the model, and a reply containing
tool_callscomes back intact. But a thread keeps only each message'sroleandcontent: an assistant message'stool_callsand a tool message'stool_call_idare 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
nabove 1 you receive every choice, but onlychoices[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.