AI gateway
Use the AI accounts connected to your own AirCode Ø instance from your personal applications. Supply your instance’s Base URL, a separate application token, and an exact model ID. The instance invokes its connected provider CLI and returns the answer through a Chat Completions subset for text and an Images subset for pictures.
Your application's backend
→ your instance URL + application token
→ a connected native provider account
→ validated answer + available usage counters
The website hosts documentation only. API requests go directly to your instance; aircodezero.com does not receive your instance address, token or prompts. Use this service only for your own applications and accounts. Provider terms, subscription access and quotas still apply.
Set up the instance
- Connect a compatible native provider account in AI Usage. The gateway lists the account models supported by the installed instance; API-key and managed-credit connections are excluded.
- Open Settings → AI gateway on desktop/web, or More → AI gateway on native mobile. Under Create access, turn on Allow private connections.
- Copy the displayed Base URL. It ends in
/api/ai-gateway/v1. - In Create a token, choose An application, enter your application’s name, select Create a token, and save the token before dismissing its one-time reveal. The application choice grants text and image API access; it does not allow remote instance sessions.
- Configure these server-side environment variables in your application:
| Variable | Value |
|---|---|
AIRCODE_AI_BASE_URL |
The full Base URL copied from the instance, without an extra /v1 |
AIRCODE_AI_TOKEN |
The application token you just created |
AIRCODE_AI_MODEL |
An exact id from Compatible models or GET /models |
Keep tokens in your backend’s secret configuration, not in browser code, a mobile app bundle, a repository, a URL or a shared conversation. The examples read environment variables instead of embedding credentials. Never use the instance password or an owner login token in the API-key field.
The instance must remain running and reachable from your application’s backend.
localhost means the computer running that backend. Use the instance’s HTTPS
address for remote access, or your trusted local/private-network connection.
The gateway does not create a public origin. A provider not listed by /models
cannot be selected by inventing an ID.
Authentication and model discovery
Send Authorization: Bearer YOUR_APPLICATION_TOKEN on each call. Send JSON with
Content-Type: application/json. No query parameters or cookie authentication
are supported. Cross-origin browser calls are not supported; use your backend.
GET {BASE_URL}/models returns an object with object: "list" and a data
array. Each model provides an id, display label, engine, engineLabel,
provider, connectionLabel, efforts and capabilities: chat.completions
for text models, images.generations and images.edits for the image model.
Pass the returned id unchanged;
it may already contain percent encoding. The list can change when accounts,
provider access or installed models change.
For an OpenAI-compatible application’s configuration, select Chat Completions, use the full Base URL, put the application token in its API key field, and copy one of the returned model IDs. Images use the Images subset described below. An application that requires Responses, tools or unsupported sampling parameters is not compatible with the current gateway.
Generate a response
POST {BASE_URL}/chat/completions accepts the following fields. Unknown fields
are refused rather than silently ignored.
| Field | Supported value |
|---|---|
model |
Required exact model ID returned by /models |
messages |
Required array of 1–100 messages; at least one non-empty user message |
messages[].role |
system, developer, user or assistant |
messages[].content |
A string, or an array of { "type": "text", "text": "..." } blocks |
reasoning_effort |
Optional value from the selected model’s efforts; omit it for the default |
stream |
Optional boolean; defaults to false |
stream_options |
Only { "include_usage": true } or false for that flag, with stream: true |
n |
Omit it or use 1 |
store |
Omit it or use false |
Messages contain only role and content. Text blocks are joined with newlines.
Null characters are refused. The normalized message array must fit within
64 KiB of UTF-8 JSON; the full HTTP body is limited to 80 KiB. The validated
answer is limited to 49,152 JavaScript string code units. These are gateway
limits, not promises about a provider’s context window or output capacity.
Supply the complete relevant conversation on every call. To continue a conversation, append the previous assistant answer and the next user message. The gateway does not create or retain a conversation, session or completion history. A token grants no access to instance projects, files, terminal, sessions or owner settings.
A normal response has object: "chat.completion"; read the text from
choices[0].message.content. It also includes id, created, the requested
model, and finish_reason: "stop". Native counters appear in usage when
complete enough to report. The aircode_usage extension preserves accounting
details and incomplete estimates. Missing counters or prices are unknown,
never zero. API-equivalent estimates are not extra subscription charges.
Streaming
Set stream: true to receive Content-Type: text/event-stream. The current
implementation uses buffered streaming: it sends an initial waiting comment
and keepalive comments while the provider runs, then sends the complete answer
after validation. It does not stream words or tokens as they are generated.
A successful stream contains:
- SSE comments beginning with
:while waiting; ignore them. - A
data:JSONchat.completion.chunkwith the assistant role and complete text inchoices[0].delta.content. - A chunk with an empty delta and
finish_reason: "stop". - An optional chunk with
choices: [],usage(possiblynull) andaircode_usagewhenstream_options.include_usageis true. data: [DONE].
After a stream starts, failures arrive as an SSE data: event containing
error; do not treat HTTP 200 alone as success. A failed stream closes without
the success marker. SSE frames may be split across transport chunks; use an
SDK or an SSE parser rather than parsing each network chunk as JSON.
Closing a connection cancels its active request. Revoking a token, disabling the gateway or stopping the instance also cancels matching work. There is no automatic replay or resume. Long-running requests need an appropriate client and reverse-proxy timeout; keepalives cannot override a network’s absolute request-duration limit.
Generate an image
The image model is listed while the instance’s native Codex account can use its
own image tool, which requires a ChatGPT plan that includes image generation.
Its id is codex/image-generation. Codex decides which image model it uses;
the gateway does not choose one.
POST {BASE_URL}/images/generationstakes JSON withmodel,prompt(up to 32,000 characters) and optionallysize,n: 1andresponse_format: "b64_json".POST {BASE_URL}/images/editstakesmultipart/form-datawith the same fields and 1 to 4 reference images inimageorimage[]. Each must be a PNG or JPEG of at most 4 MiB. Other file fields, such asmask, are refused.
size is auto or WIDTHxHEIGHT between 64 and 4096 pixels. It is passed to
the model as guidance; the response reports the actual size. Any other field is
refused rather than ignored.
curl -sS "$AIRCODE_AI_BASE_URL/images/edits" \
-H "Authorization: Bearer $AIRCODE_AI_TOKEN" \
-F model=codex/image-generation \
-F "prompt=Place this product in a premium studio shot on a stone pedestal." \
-F size=1024x1536 \
-F image=@product.png
A response carries created, one data[0].b64_json, output_format (png,
jpeg or webp), the actual size and aircode_usage. The accounting covers
the agent that drove the image tool; the provider does not report the image
tool’s own cost, so costComplete is always false. A generation usually takes
one to three minutes, shares the limits below with text calls and is cancelled
when the client disconnects. When the model answers without an image, the call
fails with 422 AI_GATEWAY_NO_IMAGE and the model’s sentence, if it gave one.
Connect a saved AirCode Ø app automatically
An app saved in Apps that runs its own server can receive this access without copying a token. On the app’s page, open Settings → Access → AI accounts and turn it on. AirCode Ø then:
- turns on the private AI gateway if it was off;
- restarts the app and, at every start, gives its server a fresh token of its own, revoking the token of the previous start;
- sets
AIRCODE_AI_BASE_URL(the gateway on the address the instance listens on),AIRCODE_AI_TOKEN, and, when available,AIRCODE_AI_MODEL(a text model) andAIRCODE_AI_IMAGE_MODEL(the image model).
A published version of the app gets its own token in the same way. Turning AI accounts off revokes the app’s tokens immediately. Call the gateway from the app’s server and keep the token there: anyone who can open the app can use your accounts through it, and their usage counts toward your plans.
Quotas, token lifecycle and errors
The gateway allows two active calls instance-wide, one active call per token, and 30 admitted generation requests per token per minute. Calls are not queued. These local limits do not replace provider quotas. Tokens expire after 90 days; create a replacement, update your application, then revoke the previous token. Disabling the gateway retains tokens but suspends their use. Restoring instance state disables the gateway and clears token authority; create fresh tokens.
Errors use error.message, error.type, error.code and error.param. Read the
code as well as the HTTP status. Do not automatically retry a generation whose
outcome is unknown: it may already have consumed provider quota. The examples
disable SDK retries.
| Status/code | What to do |
|---|---|
401, AI_GATEWAY_AUTH_REQUIRED |
Check the token, expiry, revocation and target instance |
403, AI_GATEWAY_DISABLED |
Enable the gateway on the target instance |
403, AI_GATEWAY_ORIGIN_FORBIDDEN |
Call from your backend, not a foreign browser origin |
AI_GATEWAY_ACCESS_REFUSED |
Check the instance’s access/subscription requirements |
400, unsupported parameter or effort |
Remove unsupported fields; use the selected model’s advertised efforts |
404/409, AI_GATEWAY_MODEL_UNAVAILABLE |
Refresh /models and explicitly select an available ID |
413, AI_GATEWAY_INPUT_TOO_LARGE |
Reduce the supplied history, prompt or reference images; an oversized HTTP body is also refused |
415 on /images/edits |
Send multipart/form-data |
422, AI_GATEWAY_NO_IMAGE |
The model answered without an image; read its sentence and change the request |
429, AI_GATEWAY_BUSY |
Wait for the active call to finish before trying again |
429, AI_GATEWAY_RATE_LIMIT |
Stop immediate retries and wait for the local minute window |
SAFE_STRUCTURED_AI_QUOTA_EXHAUSTED |
Wait for provider quota recovery; error.resets_at may be null |
409, cancellation |
Treat the call as cancelled; do not resume automatically |
502/503, provider/unavailable error |
Check the account connection and instance health before an explicit retry |
An unknown provider reset time stays unknown. Never derive one from a model name or an unrelated provider’s quota schedule.
Using another instance from the application
An external application can call another own instance with that instance’s Base URL and a token created there. Each token belongs to its issuing instance. For an agent on B working on A’s project, choose Another instance when creating the token on B and follow Remote sessions. The default keeps A’s files and commands on A. Save that connection in Settings, then choose it under New session → Run on and keep Project location → This instance. Files and commands stay on A; B supplies the agent and account. Choosing This instance and a project while creating the token on B, then Project location → Remote instance at launch, uses that authorized project on B. Both modes send live events. Ordinary tokens cover text and images. Neither token signs the client in as the remote owner; Responses, Anthropic Messages and Chat Completions tool calling remain outside the text API’s supported subset.
For AI-assisted integration, see Documentation for AI, the raw Markdown guide, the OpenAPI schema, and the integration skill.
Code examples
Set the three environment variables above before running an example. These snippets are also available in Settings and in the raw Markdown guide.
cURL
Bash, curl and jq. List models first, then use the exact model ID you selected.
set -euo pipefail
# The token is read from standard input, not a command-line argument.
curl --fail-with-body --silent --show-error --location --max-redirs 0 \
"$AIRCODE_AI_BASE_URL/models" --header @- <<EOF
Authorization: Bearer $AIRCODE_AI_TOKEN
EOF
body=$(jq -n --arg model "$AIRCODE_AI_MODEL" \
'{model: $model, messages: [{role: "user", content: "Hello!"}]}')
curl --fail-with-body --silent --show-error --location --max-redirs 0 \
"$AIRCODE_AI_BASE_URL/chat/completions" \
--header 'Content-Type: application/json' \
--header @- --data "$body" <<EOF
Authorization: Bearer $AIRCODE_AI_TOKEN
EOFPython
Install the OpenAI Python SDK 3 with python -m pip install "openai>=3,<4".
import os
from openai import OpenAI, DefaultHttpx2Client
client = OpenAI(
base_url=os.environ["AIRCODE_AI_BASE_URL"],
api_key=os.environ["AIRCODE_AI_TOKEN"],
max_retries=0,
http_client=DefaultHttpx2Client(follow_redirects=False),
)
for model in client.models.list():
print(model.id)
response = client.chat.completions.create(
model=os.environ["AIRCODE_AI_MODEL"],
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)JavaScript streaming
Node.js and the OpenAI SDK: npm install openai. This is buffered SSE: the complete validated answer arrives at the end.
import OpenAI from "openai";
function required(name) {
if (!process.env[name]) throw new Error("Set " + name + " first.");
return process.env[name];
}
const client = new OpenAI({
baseURL: required("AIRCODE_AI_BASE_URL"),
apiKey: required("AIRCODE_AI_TOKEN"),
maxRetries: 0,
fetchOptions: { redirect: "error" },
});
const stream = await client.chat.completions.create({
model: required("AIRCODE_AI_MODEL"),
messages: [{ role: "user", content: "Hello!" }],
stream: true,
stream_options: { include_usage: true },
});
try {
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
if (chunk.usage) console.error("Tokens:", chunk.usage.total_tokens);
}
process.stdout.write("\n");
} finally {
stream.controller.abort();
}SDK configuration references:OpenAI Python SDKand OpenAI JavaScript SDK. This gateway supports the subset documented above.