Skip to main content

Developers

API reference

Status: Waitlist

Everything 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.

Read the quickstart 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.

Create a key

API key scopes
ScopeGrantsGrantable in v1
models:readGET /api/v1/inference/models, …/models/{model}Grantable (default).
inference:invokePOST /api/v1/inference/chat/completionsGrantable (default).
usage:readreserved: a future API-key usage endpointNot 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.

Chat completion request parameters
ParameterType and boundsNotes
modelstringRequired. A model id from GET /api/v1/models or GET /api/v1/inference/models.
messagesarray, 1–1024 itemsRequired. See Messages below.
max_completion_tokensinteger ≥ 1Must not exceed the model’s max output tokens, else 400 invalid_value (never clamped). Sent upstream as max_tokens.
max_tokensinteger ≥ 1Same limit as max_completion_tokens. Setting both to different values is 400 invalid_value.
temperaturenumber, 0–2Forwarded as sent.
top_pnumber, 0–1Forwarded as sent.
presence_penalty, frequency_penaltynumber, −2 to 2Forwarded as sent.
seedintegerForwarded as sent.
stopstring (1–256 chars), or array of 1–4Forwarded as sent.
nintegerMust equal 1, else 400 invalid_value.
streamboolean, default falseSet 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_callsOpenAI tool-calling shapesOnly 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_keyas the OpenAI SDK typesAccepted and dropped as non-semantic hints. store never stores anything.
modalitiesarrayOnly 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.

Accepted message roles
RoleAccepted fieldsNotes
systemcontent: string or text parts, name?name is dropped upstream.
developersame as systemRewritten to role: "system": not every upstream chat template recognises developer.
usercontent: string or text parts, name?Image, audio and file content parts are 400 unsupported_content.
assistantcontent?, tool_calls?, refusal?, name?tool_calls needs a tool-capable model; refusal and name are dropped; audio and function_call are 400 unsupported_parameter.
toolcontent, tool_call_idNeeds 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.

Example (200 application/json)
{
  "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.

Example event sequence
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.

Chat completion errors
Statuscodetype, and any response header it carries
400invalid_json, invalid_value, unsupported_parameter, unsupported_contenttype: invalid_request_error
401missing_api_key, invalid_api_keytype: invalid_request_error; WWW-Authenticate
402insufficient_creditstype: insufficient_quota; x-should-retry: false
403insufficient_scope, organisation_suspendedtype: permission_error; x-should-retry: false on organisation_suspended
404model_not_found, unsupported_endpointtype: invalid_request_error
405method_not_allowedtype: invalid_request_error; Allow header
413request_too_largetype: invalid_request_error
429rate_limit_exceeded, concurrency_limit_exceededtype: rate_limit_error; Retry-After; x-ratelimit-* on rate_limit_exceeded
500internal_errortype: server_error
502upstream_error, upstream_auth_errortype: server_error
503platform_waitlist, model_unavailabletype: server_error; x-should-retry: false
503capacity_exhaustedtype: server_error; Retry-After
503metering_unavailabletype: server_error; no header — retry is safe
504upstream_timeouttype: 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.

Example (GET /api/v1/inference/models)
{
  "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=.

Example (GET /api/v1/models)
{
  "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.

See live status

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.

Example (GET /api/v1/auth/whoami)
{
  "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.

Rate-limit scopes
ScopeDefault limitApplies to
Per keythe key’s own rate_limit_per_minute, default 60/minEvery API-key route.
Per organisationthe organisation’s configured requests-per-minute, default 120/minInference (chat completions).
Concurrencythe organisation’s configured limit, default 4 in flightInference (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.