Developers
API reference
Status: WaitlistEverything the developer platform’s API surface does today, and exactly what it does not. This page documents only what exists — see the quickstart for a runnable example first.
Authentication
Every inference and model-list call carries Authorization: Bearer <key>, a key in the form cfrm_live_… or cfrm_test_… depending on the deployment. No Authorization header is 401 missing_api_key; an unrecognised, expired or revoked one is 401 invalid_api_key. A key is scoped at creation to one or both of the scopes below.
| Scope | Grants | Grantable in v1 |
|---|---|---|
models:read | GET /api/v1/inference/models, …/models/{model} | Grantable (default). |
inference:invoke | POST /api/v1/inference/chat/completions | Grantable (default). |
usage:read | reserved: a future API-key usage endpoint | Not grantable in v1 — usage, credits, statements and key management stay session-only, so a leaked key cannot read billing or mint keys. |
Chat completions
POST /api/v1/inference/chat/completions, called through client.chat.completions.create() on any OpenAI SDK. Streaming and non-streaming are the same endpoint.
| Parameter | Type and bounds | Notes |
|---|---|---|
model | string | Required. A model id from GET /api/v1/models or GET /api/v1/inference/models. |
messages | array, 1–1024 items | Required. See Messages below. |
max_completion_tokens | integer ≥ 1 | Must not exceed the model’s max output tokens, else 400 invalid_value (never clamped). Sent upstream as max_tokens. |
max_tokens | integer ≥ 1 | Same limit as max_completion_tokens. Setting both to different values is 400 invalid_value. |
temperature | number, 0–2 | Forwarded as sent. |
top_p | number, 0–1 | Forwarded as sent. |
presence_penalty, frequency_penalty | number, −2 to 2 | Forwarded as sent. |
seed | integer | Forwarded as sent. |
stop | string (1–256 chars), or array of 1–4 | Forwarded as sent. |
n | integer | Must equal 1, else 400 invalid_value. |
stream | boolean, default false | Set true for a Server-Sent Events response (see Streaming below). |
stream_options | { include_usage?: boolean } | Only with stream: true, else 400 invalid_value. The usage chunk is sent on every stream regardless of this flag. |
tools, tool_choice, parallel_tool_calls | OpenAI tool-calling shapes | Only for a model with tool support, else 400 unsupported_parameter. |
response_format | { type: "text" | "json_object" | "json_schema", … } | Same gate as tools. |
user, store, metadata, safety_identifier, prompt_cache_key | as the OpenAI SDK types | Accepted and dropped as non-semantic hints. store never stores anything. |
modalities | array | Only exactly ["text"] is accepted (and dropped); any other value is 400 unsupported_parameter. |
logprobs, top_logprobs | — | 400 unsupported_parameter (a large-response risk). |
any other key | — | 400 unsupported_parameter, naming the key. |
Messages
messages is 1–1024 items; an empty array is 400 invalid_value. Each item’s role decides which fields are accepted.
| Role | Accepted fields | Notes |
|---|---|---|
system | content: string or text parts, name? | name is dropped upstream. |
developer | same as system | Rewritten to role: "system": not every upstream chat template recognises developer. |
user | content: string or text parts, name? | Image, audio and file content parts are 400 unsupported_content. |
assistant | content?, tool_calls?, refusal?, name? | tool_calls needs a tool-capable model; refusal and name are dropped; audio and function_call are 400 unsupported_parameter. |
tool | content, tool_call_id | Needs a tool-capable model. |
Non-streaming response
Top-level fields are exactly id, object, created, model, choices and usage — no system_fingerprint, no service_tier, no upstream extras. usage reports the billed token counts, after any clamping.
{
"id": "chatcmpl-3fQm9ZkP0aWc7Lx2Rb8T1eK4d",
"object": "chat.completion",
"created": 1789632902,
"model": "example-model-8b",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "…", "refusal": null },
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 42, "completion_tokens": 128, "total_tokens": 170 }
}Streaming
Set stream: true for a Server-Sent Events response (content-type: text/event-stream). Each event is a chat.completion.chunk; the stream ends with data: [DONE]. Set stream_options.include_usage to get one final chunk with an empty choices array and the billed usage.
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1789632902,"model":"example-model-8b","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1789632902,"model":"example-model-8b","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1789632902,"model":"example-model-8b","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1789632902,"model":"example-model-8b","choices":[],"usage":{"prompt_tokens":42,"completion_tokens":128,"total_tokens":170}}
data: [DONE]Errors
Every error is { error: { message, type, param, code } }, the OpenAI shape. openai-node picks its error class by status only.
| Status | code | type, and any response header it carries |
|---|---|---|
400 | invalid_json, invalid_value, unsupported_parameter, unsupported_content | type: invalid_request_error |
401 | missing_api_key, invalid_api_key | type: invalid_request_error; WWW-Authenticate |
402 | insufficient_credits | type: insufficient_quota; x-should-retry: false |
403 | insufficient_scope, organisation_suspended | type: permission_error; x-should-retry: false on organisation_suspended |
404 | model_not_found, unsupported_endpoint | type: invalid_request_error |
405 | method_not_allowed | type: invalid_request_error; Allow header |
413 | request_too_large | type: invalid_request_error |
429 | rate_limit_exceeded, concurrency_limit_exceeded | type: rate_limit_error; Retry-After; x-ratelimit-* on rate_limit_exceeded |
500 | internal_error | type: server_error |
502 | upstream_error, upstream_auth_error | type: server_error |
503 | platform_waitlist, model_unavailable | type: server_error; x-should-retry: false |
503 | capacity_exhausted | type: server_error; Retry-After |
503 | metering_unavailable | type: server_error; no header — retry is safe |
504 | upstream_timeout | type: server_error |
Models and the catalogue
Inference models
GET /api/v1/inference/models (client.models.list()) and GET /api/v1/inference/models/<model_id> (client.models.retrieve()) list only models this deployment can actually call right now. An unknown or retired id is 404 model_not_found; a known but not-callable one is 503 model_unavailable.
{
"object": "list",
"data": [
{
"id": "example-model-8b",
"object": "model",
"created": 1789000000,
"owned_by": "example-provider",
"cfrm": {
"name": "Example Model 8B",
"context_window": 32768,
"max_output_tokens": 8192,
"supports_tools": false,
"modalities": ["text"],
"languages": ["en", "ne"],
"capacity": "interim",
"retires_at": null
}
}
]
}Model catalogue
GET /api/v1/models and GET /api/v1/models/<model_id> are public — no key needed — and list every model regardless of whether it can be called yet, with its price and its status (live, waitlist or planned). Filter with ?status=, ?category= or ?language=.
{
"data": [
{
"id": "example-model-8b",
"name": "Example Model 8B",
"provider": "Example Provider",
"description": "A general-purpose chat model.",
"category": "LLM",
"modalities": ["text"],
"languages": ["en", "ne"],
"parametersLabel": "8B",
"contextWindow": 32768,
"maxOutputTokens": 8192,
"supportsTools": false,
"license": null,
"licenseUrl": null,
"status": "waitlist",
"capacity": null,
"pricing": {
"currency": "USD",
"inputUsdPerMillion": "0.150000",
"outputUsdPerMillion": "0.600000",
"inputMicroUsdPerMillion": 150000,
"outputMicroUsdPerMillion": 600000,
"priceVersionId": "pv_example",
"effectiveFrom": "2026-09-01T00:00:00Z",
"indicative": true
}
}
],
"error": null,
"meta": { "requestId": "req_example" }
}Status and identity
Status
GET /api/v1/status is public and reports whether the API, inference, the model catalogue and the dashboard are each answering right now.
Identity
GET /api/v1/auth/whoami needs a valid key and no specific scope. It answers with the key’s own details and the organisation it belongs to — useful for confirming a key works before the first real call.
{
"data": {
"kind": "api_key",
"key": {
"id": "key_example",
"name": "Local dev",
"env": "test",
"displayPrefix": "cfrm_test_Ab12",
"last4": "wxyz",
"scopes": ["models:read", "inference:invoke"],
"rateLimitPerMinute": 60,
"expiresAt": null,
"createdAt": "2026-09-01T00:00:00Z"
},
"organisation": { "id": "org_example", "slug": "acme", "name": "Acme" }
},
"error": null,
"meta": { "requestId": "req_example" }
}Rate limits
Every counted response carries x-ratelimit-limit-requests, x-ratelimit-remaining-requests and x-ratelimit-reset-requests; a 429 also carries Retry-After. A successful completion carries x-cfrm-capacity (interim, sovereign or test), naming what actually served it.
| Scope | Default limit | Applies to |
|---|---|---|
Per key | the key’s own rate_limit_per_minute, default 60/min | Every API-key route. |
Per organisation | the organisation’s configured requests-per-minute, default 120/min | Inference (chat completions). |
Concurrency | the organisation’s configured limit, default 4 in flight | Inference (chat completions). |
Not supported in v1
The Responses API, Embeddings, legacy Completions, audio, images, batch, files, logprobs, n > 1, reasoning_effort, predicted outputs, web search, token-per-minute limits and headers, and per-key model allowlists and budgets.
Keys, usage, credits and statements
Managing keys, and reading usage, credits and billing statements, are session routes: cookie-authenticated, same-origin only, and not reachable with an API key by design — so a leaked key can never read billing or mint another key. Manage these from your dashboard.
Keys · Usage · Credits · Statements
Ready to call it?
Create a key from your dashboard, then follow the quickstart for a runnable first request.