An OpenAI-compatible chat completions API. Use it exactly as you would use
api.openai.com/v1 — same request and response shapes, same client libraries.
Base URL https://codex.sprigloop.ai/v1
Auth Authorization: Bearer <your key>
Default model gpt-5.6-sol
Pointing an agent here? Give it https://codex.sprigloop.ai/llms.txt —
the same reference as this page, written for machines. An OpenAPI 3.1 spec is at
/openapi.json. Both are readable without a key.
from openai import OpenAI
client = OpenAI(
api_key="YOUR_CODEX_API_KEY",
base_url="https://codex.sprigloop.ai/v1",
)
resp = client.chat.completions.create(
model="gpt-5.6-sol",
messages=[{"role": "user", "content": "Explain a hash join in two sentences."}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.CODEX_API_KEY,
baseURL: "https://codex.sprigloop.ai/v1",
});
const resp = await client.chat.completions.create({
model: "gpt-5.6-sol",
messages: [{ role: "user", content: "Explain a hash join in two sentences." }],
});
console.log(resp.choices[0].message.content);
curl https://codex.sprigloop.ai/v1/chat/completions \
-H "Authorization: Bearer $CODEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6-sol",
"messages": [{"role": "user", "content": "Explain a hash join in two sentences."}]
}'
| Method | Path | Notes |
|---|---|---|
| POST | /v1/chat/completions | Streaming and non-streaming |
| POST | /v1/completions | Legacy text completions |
| GET | /v1/models | Available models |
| GET | /v1/models/{id} | One model |
| GET | /health | Liveness and capacity no auth |
| GET | /llms.txt | Agent-readable reference no auth |
| GET | /openapi.json | OpenAPI 3.1 spec no auth |
gpt-5.6-sol gpt-5.6-terra gpt-5.6-luna gpt-5.5 gpt-5.4 gpt-5.4-mini
Any model name is accepted. A name this backend does not have is served by
gpt-5.6-sol rather than refused, so a client configured for another provider
still works — the response's model field always reports what actually ran.
Reasoning effort — low, medium, high,
xhigh, max (model-dependent). Send reasoning_effort, or put it
in the model name when your client cannot send that parameter:
{"model": "gpt-5.6-sol:high", "messages": [...]}
Precedence: reasoning_effort → model suffix → server default.
Set stream: true for server-sent events — one chat.completion.chunk per
frame, terminated by data: [DONE]. Add
stream_options: {"include_usage": true} for a final usage frame (its
choices array is empty, per the OpenAI spec).
stream = client.chat.completions.create(
model="gpt-5.6-sol",
messages=[{"role": "user", "content": "Count to five."}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
| Parameter | Notes |
|---|---|
messages | Required. Roles system, developer, user, assistant. Content may be a string or an array of parts, including images. |
model | Optionally suffixed with an effort. |
stream, stream_options | See above. |
reasoning_effort | See above. |
response_format | text, json_object, or json_schema. |
These return 400 rather than being quietly ignored — ignoring them produces a client
that misbehaves with no way to see why.
| Parameter | Why |
|---|---|
tools, functions | codex runs its own tools inside its sandbox and never hands a tool call back to the caller, so an agent framework pointed here would loop forever waiting for one. A 400 says so immediately. |
tool / function role messages | The other half of the same story: there are no tool calls to respond to. |
n > 1 | codex produces one completion per turn. |
temperature top_p presence_penalty frequency_penalty logit_bias max_tokens max_completion_tokens stop seed logprobs top_logprobs user service_tier
Accepted so default client configurations work unchanged. They have no effect — in
particular, do not rely on temperature or seed for determinism, or on
max_tokens to truncate output.
| Difference | Detail |
|---|---|
| Every request carries a large fixed prompt cost | codex sends its own base instructions and tool definitions on each turn, so expect roughly 9,000–10,000 prompt tokens before your own text — even for a one-word question. Much of it comes back cached (see prompt_tokens_details.cached_tokens). This endpoint suits ordinary conversational and reasoning work; it is a poor fit for high-volume, low-latency, cost-per-token workloads. |
| Sampling parameters do nothing | temperature, top_p, seed and the penalties are accepted for compatibility and ignored. Determinism knobs in particular are NOT honoured — do not rely on seed. |
| max_tokens does not truncate | There is no output-length control. Ask for brevity in the prompt instead. |
| No tool / function calling | See the refusals above. |
| Unknown model names fall back | A model this backend does not have is served by the default model rather than refused, so a client hard-wired to another provider's model name still works. The response's `model` field always reports what actually ran — check it if you care. |
| finish_reason is always "stop" | There is no length-based truncation to report. |
| Concurrency is limited and requests may queue | Over the limit, a request waits briefly and then receives 429 with Retry-After. Back off and retry. |
The standard OpenAI envelope:
{"error": {"message": "...", "type": "...", "param": null, "code": null}}
| Status | Type | Meaning |
|---|---|---|
400 | invalid_request_error | Malformed body, or a parameter this endpoint refuses (tools, n>1, a tool-role message). |
401 | invalid_request_error / invalid_api_key | Missing or wrong bearer key. |
404 | invalid_request_error | Unknown route, or an OpenAI endpoint this backend does not implement. |
405 | invalid_request_error | Wrong method for the route. |
413 | invalid_request_error | Body over the size limit. |
429 | rate_limit_error | All concurrency slots busy and the queue wait elapsed. Honour Retry-After. |
500 | server_error | The turn failed, or the backend is unreachable. |
502 | — | The backend is not currently connected. Retry shortly. |
If a stream has already begun, a failure arrives as a frame carrying an error object
followed by data: [DONE] — the status is already 200 by then.
Every response carries an x-request-id header. Quote it when reporting a problem.
At most 4 turns run at once. Beyond that a request
queues briefly, then receives 429 with a Retry-After header — honour it.
The backend runs on a personal subscription with rolling usage windows, so sustained parallel
load will exhaust them. Prefer sequential requests, keep concurrency modest, and retry
429 and 5xx with exponential backoff.