OpenAI-compatible API
Connect external text clients to your allowed Assistant models with a models-only personal API key.
Use an external text client, such as a dictation app that cleans up transcripts, with the Assistant models you may already use. Only the client's messages reach the provider. This API has no Cloud tools, chats, files, memory, personalization, projects or global instructions. For Assistant with tools, use the Cloud CLI.
Connect a client
An administrator must enable OpenAI-compatible API in AI settings
(ai.openai_api_enabled, off by default). You also need
access to a chat model.
In your profile under Developer → API keys, create a key with the access AI models only, or run:
cld account api-keys create "Transcript cleanup" --access modelsStore the token when it is shown; Cloud returns it only once. Assistant settings → API access shows the base URL and your model IDs. Configure the client with:
| Field | Value |
|---|---|
| Base URL | https://<your-cloud>/api/ai/openai/v1 |
| API key | Your models-only personal key |
| Model | A profile ID from GET /models |
Send the key as Authorization: Bearer <key>. Full-access personal keys,
sessions and OAuth tokens are refused here. Listing models returns only allowed
chat profiles, with id, object: "model", created: 0 and
owned_by: "cloud". Transcription profiles, prices, provider details and data
boundaries are omitted.
Replace <your-cloud> and MODEL_PROFILE_ID. With the key already loaded
into CLOUD_MODELS_KEY from your secret store:
curl 'https://<your-cloud>/api/ai/openai/v1/chat/completions' \
-H @- \
-H "Content-Type: application/json" \
-d '{"model":"MODEL_PROFILE_ID","messages":[{"role":"user","content":"Clean up this transcript: um, the meeting starts at ten."}]}' <<EOF
Authorization: Bearer $CLOUD_MODELS_KEY
EOFRequest fields
POST /chat/completions accepts JSON. It generates one text answer.
| Supported field | Behavior |
|---|---|
model |
Required allowed model profile ID |
messages |
Required non-empty array; text only |
temperature |
0–2; omitted uses the profile/provider default |
top_p |
0–1; forwarded in the provider's parameter format |
max_tokens, max_completion_tokens |
Positive integers; max_completion_tokens wins |
stream |
Boolean, default false |
stream_options.include_usage |
Include reported usage at the end of a stream |
Message content is a string or an array of {"type":"text","text":"..."}
parts, joined without a separator. system and developer messages become
the request system prompt, in order, separated by a blank line. user and
assistant messages retain their roles. Cloud adds no instructions.
The model profile's thinking level still applies; thinking output is omitted.
Unknown fields are ignored. These include stop, seed, presence_penalty,
frequency_penalty, logprobs, user, thinking, enable_thinking,
chat_template_kwargs, top_k, reasoning_effort, metadata, store,
tool_choice and parallel_tool_calls. They are not forwarded to the provider.
The following requests return 400:
| Rejected input | Error code |
|---|---|
Non-empty tools or functions |
unsupported_parameter |
n other than 1 |
unsupported_parameter |
response_format.type other than text |
unsupported_parameter |
Any audio field, or audio in modalities |
unsupported_parameter |
tool or function message roles; assistant tool_calls or function_call |
unsupported_content |
| Image, audio, file or other non-text content parts | unsupported_content |
Empty tool/function arrays, n: 1, response_format: {"type":"text"} and
null for any optional field are accepted.
Responses and streaming
A normal response has object: "chat.completion", the selected profile ID
as model, and one assistant message in choices. finish_reason is
length when the output limit was reached, otherwise stop.
usage contains prompt_tokens, completion_tokens and total_tokens
only when the provider reported them.
With stream: true, the response is Server-Sent Events. Each event is
data: <json>\n\n, without an event: line. Chunks use
object: "chat.completion.chunk". The sequence is:
- An assistant role delta with empty content.
- Text deltas; thinking and tool blocks are omitted.
- An empty delta with the final
finish_reason. - If requested and reported, a usage chunk with
choices: []. data: [DONE]\n\n.
While the stream is open, Cloud sends the SSE comment line : keepalive every
5 seconds so that idle connections stay open. OpenAI clients ignore comment
lines.
Quota rejection and provider failure before the first text arrive as HTTP JSON
errors. A provider failure after text arrives as one data: {"error": {...}}
event; the stream then closes without [DONE]. Cloud keeps the request open
while the provider makes progress. With and without streaming, a provider that
sends nothing for 60 seconds before or during its answer fails the request with
upstream_error. The gateway waits at most 4 minutes for response headers, so
a non-streaming answer must be complete within that time. Use stream: true
for longer answers. Client disconnection cancels the provider call. Missing
usage after cancellation is booked as an estimate through the
normal inference accounting.
Limits and usage
Requests use the person's model grants, account category, account expiry and
chat allowance. The usage report attributes each call to task openai-api.
No request or response content is stored in the inference ledger.
- Output: the requested output limit is capped by the model profile's
maxOutputTokensand the remaining priced chat budget. - Context: estimated input must fit the provider's context window.
- Body size: 16 bytes times the largest context window among your allowed chat models. The estimate assumes about four characters per token and up to four UTF-8 bytes per character. A profile with no explicit window counts as 1,000,000 tokens, the largest supported adapter default.
- Rate: the existing
security.rate_limit_per_secondlimit applies per IP. - Concurrency:
ai.openai_api_max_concurrent_requestsdefaults to four simultaneous requests per person across all instances. Typical clients use one or two connections. Four leaves room for a second client while limiting scripts that would crowd out other people. A Cloud process runs up to eight queued chat turns; these synchronous requests bypass that queue. Operators with more provider capacity can raise the limit to 64.
A request takes its slot after authentication, before Cloud reads the request
body, and holds it until the response ends; a request that is then rejected
releases it at once. Slots refresh every 30 seconds and expire after two
minutes if a process crashes. A concurrency refusal returns Retry-After: 1.
The limit relies on Valkey: if Valkey is unreachable for more than two minutes,
running requests lose their slots but continue, so the limit is best-effort
until they end.
Models without reference prices have no usage limit. As in Assistant, chat allowances do not cover them. Only rate and concurrency limits protect their use through this API. See Usage and feedback.
Errors
All errors use {"error":{"message":"...","type":"...","param":null,"code":"..."}}.
Messages follow the request locale (English or German); codes and types remain
stable. param names the offending field when applicable.
| Status | Type | Code |
|---|---|---|
| 400 | invalid_request_error |
invalid_request, unsupported_parameter, unsupported_content, context_length_exceeded |
| 401 | authentication_error |
invalid_api_key |
| 403 | permission_error |
openai_api_disabled, ai_disabled, model_access_denied |
| 404 | invalid_request_error |
model_not_found, not_found |
| 413 | invalid_request_error |
request_too_large |
| 429 | insufficient_quota |
insufficient_quota |
| 429 | rate_limit_error |
rate_limit_exceeded, too_many_concurrent_requests |
| 502 | api_error |
upstream_error, missing_provider_credential |
| 500 | api_error |
internal_error |
A request for a model you cannot use returns 404 model_not_found. This
covers unknown models, models without your grant, models whose provider has no
credential, and AI turned off. ai_disabled, model_access_denied and
missing_provider_credential appear only when an administrator changes these
settings during the request.
Key isolation and revocation
Models-only keys are personal API keys with the reserved ai:inference scope.
Only personal key creation with access: "models" can issue that scope.
Central authentication rejects it on every other Cloud surface; only this
endpoint authenticates it directly. Other scopes alongside it do not bypass
the rejection. Its secret is stored with a separate hash, so applications on
an older @k2b/cloud version, which do not know the scope, reject the key
instead of granting full access.
Revoke the key like any personal key in your profile or with
cld account api-keys revoke <id-or-prefix-or-name> --yes.
Turning off ai.openai_api_enabled stops new requests with existing keys too.
It does not cancel provider calls already running.