codex-api

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.

Quickstart

Python

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)

Node

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

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."}]
  }'

Endpoints

MethodPathNotes
POST/v1/chat/completionsStreaming and non-streaming
POST/v1/completionsLegacy text completions
GET/v1/modelsAvailable models
GET/v1/models/{id}One model
GET/healthLiveness and capacity no auth
GET/llms.txtAgent-readable reference no auth
GET/openapi.jsonOpenAPI 3.1 spec no auth

Models

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 effortlow, 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.

Streaming

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="")

Supported parameters

ParameterNotes
messagesRequired. Roles system, developer, user, assistant. Content may be a string or an array of parts, including images.
modelOptionally suffixed with an effort.
stream, stream_optionsSee above.
reasoning_effortSee above.
response_formattext, json_object, or json_schema.

Refused parameters

These return 400 rather than being quietly ignored — ignoring them produces a client that misbehaves with no way to see why.

ParameterWhy
tools, functionscodex 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 messagesThe other half of the same story: there are no tool calls to respond to.
n > 1codex produces one completion per turn.

Accepted but ignored

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.

Differences from the OpenAI API

DifferenceDetail
Every request carries a large fixed prompt costcodex 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 nothingtemperature, 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 truncateThere is no output-length control. Ask for brevity in the prompt instead.
No tool / function callingSee the refusals above.
Unknown model names fall backA 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 queueOver the limit, a request waits briefly and then receives 429 with Retry-After. Back off and retry.

Errors

The standard OpenAI envelope:

{"error": {"message": "...", "type": "...", "param": null, "code": null}}
StatusTypeMeaning
400invalid_request_errorMalformed body, or a parameter this endpoint refuses (tools, n>1, a tool-role message).
401invalid_request_error / invalid_api_keyMissing or wrong bearer key.
404invalid_request_errorUnknown route, or an OpenAI endpoint this backend does not implement.
405invalid_request_errorWrong method for the route.
413invalid_request_errorBody over the size limit.
429rate_limit_errorAll concurrency slots busy and the queue wait elapsed. Honour Retry-After.
500server_errorThe turn failed, or the backend is unreachable.
502The 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.

Rate limits

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.