Shared Inference (api.cyan.kiwi/v1)
cyankiwi Shared Inference serves a curated set of open models behind one OpenAI-compatible endpoint, billed per token. To make your first request with curl or the OpenAI SDK, see the API Usage guide.
Base URL: https://api.cyan.kiwi/v1 Zero data retention. Your prompts and the completions we return are not stored at any point in serving, and they are never used for training. We keep only usage metadata for each request (tokens, cost, timing and status) for billing. See the Privacy Notice for details.
Credits and rate limits
Requests are billed per token from your prepaid credits, and each API key has per-minute rate limits. Both are shown on the Account tab. A new account starts with a zero balance, so add credits there before your first request. Closing the connection cancels the request, whether streaming or non-streaming.
Models
The model catalog at GET /v1/models is public: no API key required (one sent along is simply ignored), and responses can be cached briefly. It lists only models available to customers.
The response is a standard OpenAI model list whose items carry extra metadata you can rely on programmatically:
| Field | Meaning |
|---|---|
id | the value to pass as model in requests |
context_length, max_output_length | token budgets of the model |
pricing | USD per token as exact decimal strings: prompt, completion, input_cache_read |
quantization | precision of the served weights (fp8, fp4, …) |
input_modalities, output_modalities | what the model accepts and returns |
supported_features, supported_sampling_parameters | what the model supports (for example tools, json_mode, reasoning) |
license | the model’s license |
Cached input. When a request hits the prompt cache, the cached input tokens bill at input_cache_read instead of prompt, and the hit shows up as usage.prompt_tokens_details.cached_tokens on the response. Caching granularity varies by model, and a hit is never guaranteed on any individual request.
The same ids are shown on the Inference tab of the workspace.
Streaming
Streams follow the standard OpenAI format and end in one of two ways:
- Success: a final usage chunk (empty
choices) followed bydata: [DONE], even withoutstream_options.include_usage. - Failure after the response has started: one
server_error/mid_stream_errorframe, then the stream closes without[DONE].
A stream that closes without [DONE] did not complete, even if no error frame arrived. During silent phases, SSE keep-alive comments (: keepalive) may arrive; SSE parsers skip them.
Request limits
A request that exceeds one of these is rejected, not trimmed. It never runs and it is never charged.
| limit | applies to | if exceeded |
|---|---|---|
| Request body 2 MiB | all routes | 413 request_too_large |
Chat messages must be a nonempty list of objects | chat | 400 invalid_request_body |
Non-null max_tokens / max_completion_tokens must be integers; stream boolean; temperature, top_p, presence_penalty, frequency_penalty finite numbers (not booleans) | chat + completions | 400 invalid_request_body |
Non-null n and best_of must be 1; omitted/null/1 allowed | chat + completions | 400 too_many_completions (invalid_request_body if not an integer) |
prompt_token_ids and input_ids are not accepted; on completions, prompt must be text (a string, or a list of strings) | chat + completions | 400 token_ids_not_supported |
chat_template_kwargs may not carry chat_template or tokenize | chat | 400 template_override_not_permitted |
A JSON schema must be an object, and each entry under properties (and similar keyword maps) a schema object or a boolean | response_format, tools | 400 invalid_json_schema |
regex, ebnf, guided_json, guided_regex, guided_grammar, guided_choice, guided_whitespace_pattern, guided_decoding_backend, structural_tag, prompt_embeds are not accepted | chat + completions | 400 unsupported_parameter |
All of these errors have type invalid_request_error. When a field is refused, the param key of the error names it, so an SDK surfaces it directly. For an invalid tool schema, param is the tool’s path, for example tools[0].function.parameters.
n or best_of above 1 is refused with 400, not silently reduced to one choice.
Text completions are not currently served. /v1/completions answers 400 completions_not_supported unless a model declares support for it, and none does today. Use /v1/chat/completions. The limits in the table above still apply to the route, and are checked before that refusal. /v1/embeddings likewise answers 400 embeddings_not_supported, because no model supports embeddings today.
Token log-probabilities are not supported. logprobs, top_logprobs and prompt_logprobs with truthy values are rejected with 400 logprobs_not_supported rather than silently ignored, on every model. False, zero and null are no-ops.
Other unsupported parameters are dropped. A sampling parameter a model does not support is ignored rather than rejected, so check supported_sampling_parameters in the model catalog for what each model honors.
Errors
Errors use the standard OpenAI envelope, so SDK exception handling works unchanged. The error object carries message, type, param and code, and transient failures also carry a Retry-After header.
For example, a single-choice refusal is:
{"error":{"message":"Only n=1 is supported; omit 'n' or set it to 1","type":"invalid_request_error","param":"n","code":"too_many_completions"}} An invalid-key response is:
{"error":{"message":"Invalid API key","type":"invalid_request_error","param":null,"code":"invalid_api_key"}} | Situation | Status | type | code | param |
|---|---|---|---|---|
| Malformed JSON / invalid field | 400 | invalid_request_error | invalid_request_body or specific validation code | field or null |
| Request rejected | 400 | invalid_request_error | invalid_request | field or null |
| Context too long | 400 | invalid_request_error | context_length_exceeded | messages |
| Content policy / refusal | 400 | invalid_request_error | content_policy_violation | null |
| Missing key | 401 | invalid_request_error | missing_api_key | null |
| Invalid / revoked key | 401 | invalid_request_error | invalid_api_key | null |
| Unknown or inaccessible model | 404 | invalid_request_error | model_not_found | model |
| Body too large | 413 | invalid_request_error | request_too_large | null |
| Credits exhausted | 429 | insufficient_quota | insufficient_credits | null |
| Key rate limit | 429 | rate_limit_error | rate_limit_exceeded | null |
| Server at capacity | 429 | rate_limit_error | server_overloaded | null |
| Model at capacity | 429 | rate_limit_error | capacity_unavailable | null |
| Internal failure | 500 | server_error | internal_error | null |
| Invalid response from the model | 502 | server_error | model_error | null |
| Service temporarily unavailable | 503 | service_unavailable_error | service_unavailable | null |
| Model timed out | 504 | server_error | timeout | null |
| Failure after the response started | 200 already committed | server_error | mid_stream_error | null |
insufficient_credits and content_policy_violation are not transient: add credit or revise the request instead of retrying. For the other 429 and 5xx errors, honor Retry-After and retry. We shed load instead of queueing: at capacity you get an immediate server_overloaded 429 rather than a slow response.
A failure detected after the response has started sends one error frame and closes, with no [DONE]:
data: {"error":{"message":"The model could not complete this request","type":"server_error","param":null,"code":"mid_stream_error"}} SDKs raise a base APIError on this frame, not an HTTP-status exception.
Key rotation. Rotating on the Account tab revokes the old key immediately and issues a new one. Your credits carry over, and the new key gets the current rate limits. Revoking without rotating just disables API access; remaining credits are kept for your next key.