Chat Completions
Send messages to the Staffi 1.0 model and get a response back. The endpoint uses the industry-standard chat completions format, so any OpenAI-compatible client library works out of the box. Just point base_url and api_key at Staffi.
The model
staffi-1.0Fast, versatile chat model with vision and tool calling. Ideal for chatbots, agents and content generation. 128k context window.
Input pricing
€0.552 / 1M tokens
Output pricing
€2.208 / 1M tokens
Context
128k
Max output
16,384
Vision
yes
Tools
yes
Quick start
Any OpenAI-compatible client works. Only base_url and api_key change.
curl https://api.staffifyai.com/api/v1/chat/completions \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "staffi-1.0",
"messages": [
{ "role": "user", "content": "Say hi in one word." }
]
}'/api/v1/chat/completionsCreates a model response for the given chat conversation. The full chat completions request body shape is supported. Any parameter not listed below is forwarded to the model untouched.
| Field | Type | Required | Description |
|---|---|---|---|
| model | string | Must be "staffi-1.0". Any other value returns 400 UNSUPPORTED_MODEL. | |
| messages | array | Chat history. Roles: system, user, assistant, tool. Content can be a string or an array of parts (text + image_url for vision). | |
| stream | boolean | optional | If true, chunks are sent as Server-Sent Events. Usage counts arrive in the final chunk. |
| temperature | number | optional | 0 to 2, default 1. Higher = more random. |
| top_p | number | optional | 0 to 1, default 1. Nucleus sampling. |
| max_tokens | integer | optional | Cap on output tokens. Used for the credits pre-check (worst case). |
| max_completion_tokens | integer | optional | Alias for max_tokens. Takes precedence if both are provided. |
| tools | array | optional | Standard function tool definitions (OpenAI-compatible schema). |
| tool_choice | string | object | optional | "auto" | "none" | { type: "function", function: { name } } |
| response_format | object | optional | { type: "json_object" } or { type: "json_schema", ... } for structured output. |
| seed | integer | optional | Best-effort deterministic sampling. |
| stop | string | string[] | optional | Up to 4 stop sequences. |
| n | integer | optional | Number of completions to generate. Default 1. |
| logprobs | boolean | optional | Return log probabilities of the output tokens. |
| presence_penalty | number | optional | -2 to 2, default 0. |
| frequency_penalty | number | optional | -2 to 2, default 0. |
| user | string | optional | End-user identifier for abuse monitoring on your side. |
Example response (non-stream)
{
"id": "chatcmpl-9a1B2c3D4e...",
"object": "chat.completion",
"created": 1751812345,
"model": "staffi-1.0",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hi!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 2,
"total_tokens": 14
}
}Streaming
Set stream: true to receive chunks as they are generated. Frames follow the standard SSE format: data: {...} lines separated by blank lines, ending with data: [DONE]. Token usage is included in the final chunk before [DONE].
curl -N https://api.staffifyai.com/api/v1/chat/completions \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "staffi-1.0",
"messages": [{ "role": "user", "content": "Count from 1 to 5." }],
"stream": true
}'Vision
Send images as content parts alongside text. Either an HTTPS URL or a base64 data URL.
curl https://api.staffifyai.com/api/v1/chat/completions \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "staffi-1.0",
"messages": [{
"role": "user",
"content": [
{ "type": "text", "text": "What is in this image?" },
{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
]
}]
}'Function calling (tools)
Define tools in the standard chat completions format. The assistant may reply with tool_calls instead of content. Send the tool result back as a role: "tool" message and call the endpoint again to continue.
curl https://api.staffifyai.com/api/v1/chat/completions \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "staffi-1.0",
"messages": [{ "role": "user", "content": "What is the weather in Amsterdam?" }],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}]
}'JSON mode & structured output
Force the model to return valid JSON, optionally matching a JSON Schema.
curl https://api.staffifyai.com/api/v1/chat/completions \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "staffi-1.0",
"messages": [{ "role": "user", "content": "Return the JSON object { \"ok\": true }" }],
"response_format": { "type": "json_object" }
}'/api/v1/modelsReturns the list of models available on this API. Currently a single entry.
{
"object": "list",
"data": [{
"id": "staffi-1.0",
"object": "model",
"owned_by": "staffify",
"display_name": "Staffi 1.0",
"context_window": 128000,
"max_output_tokens": 16384,
"supports_vision": true,
"supports_tools": true,
"supports_json_mode": true,
"input_cost_per_million_tokens_eur": 0.552,
"output_cost_per_million_tokens_eur": 2.208
}]
}Billing
Every request is deducted from your Staffify credits wallet based on the actual token usage. To prevent concurrent bursts from overspending, we use an atomic reservation model:
- Reserve. At request start we atomically reserve the worst-case cost (input tokens plus max output tokens) from your wallet. If your balance cannot cover it, the request fails immediately with
402 INSUFFICIENT_CREDITS. This runs under a row lock, so N concurrent requests cannot all pass the same balance snapshot. - Finalize. When the response completes, the reservation is released and the actual cost is deducted (based on
usage.prompt_tokens+usage.completion_tokens). Net wallet delta = actual cost only. - Refund on error. If the upstream call errors or the stream closes without usage counts, the full reservation is released. You are never charged for a request you did not receive tokens for.
For streaming requests, usage arrives in the final chunk and finalization runs fire-and-forget after the stream closes. Enterprise tier is post-pay and skips the reservation step.
Retries with the same Idempotency-Key are billed exactly once, even across streaming attempts. See Idempotency below.
Transactions appear in /api/v1/credits history with these types:
ai_prededuction: worst-case reservation, negative delta.ai_prededuction_release: reservation released (positive delta) on finalize or refund.ai_completion: actual cost, negative delta.
Rate limits
Chat completions have their own limits, separate from the standard v1 REST limits. Every response includes X-AI-RateLimit-* headers.
| Tier | Requests / min | Concurrent | Max input tokens / request |
|---|---|---|---|
| PAYG | 60 | 5 | 32,000 |
| Starter | 200 | 15 | 64,000 |
| Growth | 500 | 40 | 128,000 |
| Scale | 1,000 | 80 | 128,000 |
| Enterprise | Unlimited | Unlimited | Unlimited |
Requests that exceed the per-tier input token ceiling get 413 INPUT_TOO_LARGE. Requests that exceed the model allowlist for your tier get 403 MODEL_NOT_IN_PLAN.
The request body itself is capped at 2 MB. Requests larger than that are rejected before authentication with 413 REQUEST_TOO_LARGE.
Idempotency
Chat completions supports the industry-standard Idempotency-Key header. SDK auto-retries on transient 5xx errors are safe: two requests with the same key are billed exactly once and return the same response body.
How to use it
Set the Idempotency-Key request header to a value unique per logical request. A UUID per user action is the standard pattern. Keys are scoped to your API key and cached for 24 hours.
curl https://api.staffifyai.com/api/v1/chat/completions \
-H "Authorization: Bearer sfy_live_YOUR_KEY" \
-H "Idempotency-Key: 8a1e6b9f-3c2d-4d5f-9e2b-1a0b2c3d4e5f" \
-H "Content-Type: application/json" \
-d '{
"model": "staffi-1.0",
"messages": [{ "role": "user", "content": "Say hi." }]
}'Behavior
- First request: the response is cached for 24 hours together with a SHA-256 hash of the request body.
- Retry with the same key + same body: the cached response is replayed and the response includes
Idempotency-Replay: true. No second billing. - Retry with the same key + different body: returns
409 IDEMPOTENCY_KEY_CONFLICT. Use a fresh key or match the original body. - Streaming (stream: true): SSE responses cannot be replayed. A retry with a key that was previously used on a streaming request returns
409 IDEMPOTENCY_STREAMING_REPLAY. Ledger deduplication still runs, so streaming retries are billed once even without response replay.
Limits
- Key length: up to 128 characters. Longer keys return
400 IDEMPOTENCY_KEY_TOO_LONG. - Volume: up to 10,000 unique keys per API key per 24 hours. Exceeding this returns
429 IDEMPOTENCY_KEY_QUOTA_EXCEEDED. - Cache TTL: 24 hours. After that a re-used key runs a fresh request.
- Header is optional. If omitted, no replay cache is written for that request.
Errors
Error envelope
Every 4xx and 5xx response returns an OpenAI-compatible envelope. This shape is consistent across all failure modes, so client SDK error handlers work without modification.
{
"error": {
"message": "Insufficient credits. Estimated worst-case cost €0.0362 exceeds balance €0.0100.",
"type": "invalid_request_error",
"code": "INSUFFICIENT_CREDITS",
"param": null,
"request_id": "chatreq-8a1e6b9f-3c2d-4d5f-9e2b-1a0b2c3d4e5f"
}
}type: one ofinvalid_request_error,authentication_error,permission_error,rate_limit_error,upstream_error,server_error.code: the stable machine-readable identifier. Match on this programmatically, never onmessage.request_id: unique per attempt. If you supplied anIdempotency-Key, that value is used; otherwise a UUID is synthesized. The same id is echoed in theX-Request-Idresponse header for log correlation.
Error codes
| Code | HTTP | Type | Description |
|---|---|---|---|
| MISSING_API_KEY | 401 | authentication_error | No Authorization or X-Api-Key header provided. |
| INVALID_API_KEY | 401 | authentication_error | API key not found, revoked, or malformed. |
| AUTH_RATE_LIMITED | 429 | rate_limit_error | Too many failed authentication attempts from this IP. |
| READ_ONLY_KEY | 403 | permission_error | API key has read-only scope and cannot perform write operations. |
| UNSUPPORTED_MODEL | 400 | invalid_request_error | model must be "staffi-1.0". |
| MODEL_NOT_IN_PLAN | 403 | permission_error | The requested model is not available on your tier. |
| INVALID_MESSAGES | 400 | invalid_request_error | messages missing or not a non-empty array. |
| INPUT_TOO_LARGE | 413 | invalid_request_error | Prompt exceeds the per-tier input token ceiling. |
| REQUEST_TOO_LARGE | 413 | invalid_request_error | Request body larger than 2 MB. |
| INSUFFICIENT_CREDITS | 402 | invalid_request_error | Worst-case request cost exceeds wallet balance, or wallet is paused. |
| WALLET_MISSING | 402 | invalid_request_error | No credits wallet exists for this organization. Contact support. |
| RATE_LIMIT_EXCEEDED | 429 | rate_limit_error | General REST per-minute request limit exceeded for this tier. |
| AI_RATE_LIMIT_EXCEEDED | 429 | rate_limit_error | Chat completions per-minute limit exceeded for this tier (stricter than general REST). |
| AI_CONCURRENT_LIMIT_EXCEEDED | 429 | rate_limit_error | Too many simultaneous in-flight requests. Reduce parallelism. |
| IDEMPOTENCY_KEY_TOO_LONG | 400 | invalid_request_error | Idempotency-Key header exceeds 128 characters. |
| IDEMPOTENCY_KEY_CONFLICT | 409 | invalid_request_error | Same key already used with a different request body. |
| IDEMPOTENCY_STREAMING_REPLAY | 409 | invalid_request_error | Same key already used on a streaming request. Streaming responses cannot be replayed. |
| IDEMPOTENCY_KEY_QUOTA_EXCEEDED | 429 | rate_limit_error | Exceeded 10,000 unique Idempotency-Keys for this API key in 24 hours. |
| INVALID_REQUEST | 400 | invalid_request_error | Upstream provider rejected the request as invalid. |
| UPSTREAM_FORBIDDEN | 403 | permission_error | Upstream provider refused the request. |
| UPSTREAM_RATE_LIMIT | 429 | rate_limit_error | Upstream provider is throttling. Retry with backoff. |
| UPSTREAM_UNAVAILABLE | 502 | upstream_error | Upstream provider temporarily unavailable. Retry. |
| UPSTREAM_ERROR | 502 | upstream_error | Upstream provider returned an unexpected error. |
| UPSTREAM_AUTH_FAILURE | 502 | server_error | Internal issue reaching the upstream provider. Contact support. |
| UPSTREAM_NOT_FOUND | 502 | upstream_error | Upstream provider returned an unexpected 404. |
Note: for security reasons the message field never contains fragments of your request. Full upstream error details are logged server-side against request_id. Contact support with that id to get the full context.
Good to know
- The response
modelfield always echoes back"staffi-1.0". Internal provider names are never exposed. - High-availability failover is built-in. If the primary upstream fails, requests are transparently routed to a fallback provider. Some advanced parameters (
response_format,seed,logprobs) are best-effort during a failover event and may be ignored. - The base URL is
https://api.staffifyai.com/api/v1. When configuring an OpenAI-compatible client, that value goes into thebase_url/baseURLfield.