This is the technical reference for applications integrating with SingleAPI. For customer steps, start with the user guide index or quickstart. This page keeps exact URLs, fields, response shapes, limits and errors separate from the task guides.
SingleAPI is a small prepaid LLM gateway with an OpenAI-compatible inference surface. It provides:
- account and wallet visibility;
- generated SingleAPI API keys;
- model catalogue and model-scope control;
- managed-provider inference through LiteLLM;
- explicitly selected user-owned BYOK provider keys;
- client-executed function tool calling;
- OpenAI-compatible server-sent event streaming;
- usage history, analytics and billing statements;
- optional Stripe credit purchases.
It deliberately does not provide a general-purpose agent runtime, arbitrary code execution, automatic provider routing, or every OpenAI API feature.
1. API boundaries
There are two authentication domains:
Dashboard and management APIs → Clerk session token
Inference APIs → generated SingleAPI API key
Stripe webhook → Stripe-Signature header
Health checks → no authenticationThe main ownership boundary is:
| Concern | Owner |
|---|---|
| User identity for the dashboard | Clerk + SingleAPI local user |
| Inference authentication | SingleAPI API key |
| Model access and key scopes | SingleAPI |
| Provider adapter and transport | SingleAPI + LiteLLM |
| Function implementation and execution | Client |
| Provider key in BYOK mode | User |
| Provider-side bill in BYOK mode | User/provider account |
| SingleAPI wallet and customer charge | SingleAPI |
2. Base URLs and conventions
The API origin configured for this documentation is:
https://staging.singleapi.ai/apiThe OpenAI-compatible inference base URL is:
https://staging.singleapi.ai/api/v1Use the API origin for your deployment. Local development normally uses http://localhost:8010; the OpenAI SDK base URL includes /v1 exactly once. When connecting to another server, replace the origin while retaining the endpoint paths.
Content types
Normal JSON endpoints use:
Content-Type: application/jsonStreaming chat completions use:
Content-Type: text/event-streamBilling statements use:
Content-Type: application/pdfTime and money
- Timestamps are timezone-aware UTC timestamps.
- Time range
fromvalues are inclusive. - Time range
tovalues are exclusive. - API money values are decimal strings such as
"2.50". - Internal money accounting uses integer nanos:
1 USD = 1,000,000,000 nanos. - Floating-point values are not used for wallet or charge calculations.
- The dashboard rounds displayed amounts to two decimal places. A displayed
$0.00can still be a nonzero sub-cent wallet debit; use usage detail for the precise charge.
Request IDs
Every response receives an X-Request-ID header. Inference errors also include that ID in the JSON error body. Use it when reporting a failed request or correlating logs and usage.
For an admitted inference request, the response ID, X-Request-ID and usage-history request ID refer to the same request. A request rejected before admission does not create an inference-history row.
3. Authentication
3.1 Clerk session authentication
Dashboard and account-management endpoints require:
Authorization: Bearer <Clerk session JWT>The backend verifies the Clerk token signature, issuer, expiry, subject and configured authorized party. It then resolves the subject to a local user.
On first authenticated access, SingleAPI creates the local user and zero-balance wallet. A later login does not reset the wallet, keys or account status.
3.2 SingleAPI key authentication
Inference endpoints require:
Authorization: Bearer llm_v1_<public-identifier>_<secret>The key is generated by SingleAPI. The full secret is shown only when the key is created or rotated. It is never returned by list or update endpoints.
The key must be:
- syntactically valid;
- owned by an active user;
- active and not expired;
- allowed to access the requested model.
3.3 Stripe webhook authentication
The Stripe webhook is called by Stripe and requires:
Stripe-Signature: <Stripe signature>The raw request body is verified with the configured webhook secret. It is not a customer endpoint and does not use a Clerk or SingleAPI bearer token.
4. Endpoint index
Operations
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/healthz |
None | Process liveness |
GET |
/readyz |
None | API and database readiness |
Account and dashboard
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/account |
Clerk | Read/provision current user |
GET |
/account/overview |
Clerk | Balance and activity summary |
GET |
/catalogue |
Clerk | Enabled models, limits and prices |
GET |
/billing/transactions |
Clerk | Wallet ledger entries |
GET |
/billing/statement.pdf |
Clerk | Download customer-facing statement |
API keys
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/api-keys |
Clerk | List owned key metadata |
POST |
/api-keys |
Clerk | Create a key and return its secret once |
PATCH |
/api-keys/{key_id} |
Clerk | Edit key metadata and limits |
POST |
/api-keys/{key_id}/rotate |
Clerk | Replace the secret |
POST |
/api-keys/{key_id}/revoke |
Clerk | Permanently revoke a key |
BYOK provider credentials
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/byok |
Clerk | List BYOK providers and owned key metadata |
POST |
/byok |
Clerk | Store an encrypted provider key |
POST |
/byok/{credential_id}/rotate |
Clerk | Replace an active provider key |
DELETE |
/byok/{credential_id} |
Clerk | Revoke a provider key |
Inference
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/v1/models |
SingleAPI key | List enabled models allowed to that key |
POST |
/v1/chat/completions |
SingleAPI key | Normal or streaming chat completion |
Usage
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/usage |
Clerk | Cursor-paginated request history |
GET |
/usage/content-logs |
Clerk | List your unexpired prompt and response logs |
GET |
/usage/content-logs/export |
Clerk | Download filtered prompt logs as Excel-compatible CSV |
GET |
/usage/analytics |
Clerk | Aggregate usage and charge analytics |
GET |
/usage/traffic |
Clerk | Monitor finalized requests by minute or hour |
GET |
/usage/{request_id} |
Clerk | Detailed usage and accounting record |
GET |
/usage/{request_id}/content |
Clerk | Read opted-in prompt and response content for 30 days |
Payments
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/payments/checkout |
Clerk | Create Stripe Checkout session |
GET |
/payments/{payment_id} |
Clerk | Read local payment status |
POST |
/webhooks/stripe |
Stripe signature | Fulfill or expire Stripe payments |
5. Operations endpoints
GET /healthz
Checks whether the API process is alive.
Response:
{"status":"ok"}This does not verify PostgreSQL or provider configuration.
GET /readyz
Checks whether the API can execute a basic database query.
Healthy response:
{"status":"ok"}If the database check fails:
503 Service Unavailable{"status":"unavailable"}6. Account and dashboard APIs
All endpoints in this section use Clerk authentication.
GET /account
Returns the authenticated local user. It also provisions the local user and zero-balance wallet on first access.
Response:
{
"id": "user-uuid",
"email": "user@example.com",
"status": "active"
}Possible account statuses are active, suspended and closed. Inactive accounts cannot use authenticated APIs.
GET /account/overview
Returns wallet balance and current activity. Without filters, the time range defaults to the current UTC month.
Query parameters:
| Parameter | Type | Meaning |
|---|---|---|
from |
datetime | Inclusive UTC start |
to |
datetime | Exclusive UTC end |
from must be earlier than to when both are supplied.
Response shape:
{
"stripe_enabled": true,
"balance": {
"available_usd": "9.50",
"held_usd": "0.25",
"total_usd": "9.75"
},
"active_key_count": 2,
"request_count": 14,
"charged_usd": "1.83",
"starts_at": "2026-09-01T00:00:00Z",
"ends_at": "2026-10-01T00:00:00Z"
}held_usd is reserved for in-flight requests and is not the same as final charged spend.
GET /catalogue
Optional classification=open|closed filters models independently of upstream provider. Models include classification, presentation_label, supports_text, supports_streaming and supports_function_tools alongside the existing limits, JSON flags and prices. The response uses Cache-Control: private, no-store. Presentation labels do not change model IDs or provider identifiers.
Returns enabled providers and models with current customer-facing prices and reviewed JSON-format support.
Response:
{
"providers": [
{
"id": "provider-uuid",
"code": "openai",
"display_name": "OpenAI",
"models": [
{
"id": "model-uuid",
"public_model_id": "openai/gpt-4o-mini",
"display_name": "GPT-4o mini",
"context_window_tokens": 128000,
"max_output_tokens": 16384,
"default_max_output_tokens": 1024,
"supports_json_object": true,
"supports_json_schema": true,
"input_price_usd_per_million_tokens": "0.150000000",
"cached_input_price_usd_per_million_tokens": "0.075000000",
"output_price_usd_per_million_tokens": "0.600000000"
}
]
}
]
}Only enabled models with a current price are returned. The prices shown here are the customer rates used at admission. An admitted request keeps its applied price even if the catalogue changes later.
GET /billing/transactions
Returns the newest wallet ledger entries.
Query parameters:
| Parameter | Type | Default | Rules |
|---|---|---|---|
limit |
integer | 10 |
Minimum 1, maximum 100 |
Response:
{
"items": [
{
"id": "reserve:request-uuid",
"request_id": "request-uuid",
"kind": "reserve",
"amount_usd": "-0.020000000",
"available_delta_usd": "-0.020000000",
"held_delta_usd": "0.020000000",
"created_at": "2026-09-18T10:00:00Z"
}
]
}Ledger kinds are credit, reserve, settle, release and adjustment. Temporary reservation and release entries are retained in the API ledger view; the customer PDF omits temporary reserve/release noise.
GET /billing/statement.pdf
Downloads a PDF containing customer-facing wallet credits, charges and adjustments.
The response is a file attachment. Temporary reservations and releases are omitted from the customer-facing statement.
7. API key APIs
POST /api-keys
Creates a SingleAPI inference key. The secret is returned once.
Request:
{
"name": "demo key",
"expires_at": null,
"scope_mode": "restricted",
"model_ids": ["model-uuid"],
"budget_period": "monthly",
"budget_limit_usd": "10.00",
"prompt_logging_enabled": false
}Fields:
| Field | Rules |
|---|---|
name |
Required, 1–255 characters |
expires_at |
Optional timezone-aware timestamp |
scope_mode |
all or restricted; default all |
model_ids |
UUIDs of catalogue models; must be empty in all mode |
budget_period |
onetime, daily, weekly or monthly; default onetime |
budget_limit_usd |
Optional nonnegative decimal string with at most 9 decimal places |
prompt_logging_enabled |
Optional boolean; defaults to false. When true, prompts and model responses for requests made with this key are encrypted and retained for 30 days. |
all grants access to all currently and subsequently enabled models. restricted with no model IDs grants access to no models. Budget weeks begin Monday in UTC. A null budget limit is unlimited; zero is not unlimited.
The response contains key metadata plus:
{
"key": "llm_v1_..."
}The full key must be saved by the client. It is not returned again by list or update endpoints.
GET /api-keys
Lists owned key metadata and current allowance. Full secrets are never listed.
Each key includes:
- record UUID;
- public key identifier;
- name and status;
- expiry;
- scope mode and model IDs;
- budget period and limit;
- whether prompt/response content logging is enabled;
- current budget window;
- last-used and creation timestamps.
The current window contains:
{
"starts_at": "2026-09-01T00:00:00Z",
"ends_at": "2026-10-01T00:00:00Z",
"spent_usd": "1.20",
"held_usd": "0.10",
"remaining_usd": "8.70"
}remaining_usd is null for an unlimited key budget. Reducing a key's budget limit does not reset its existing spending or in-flight holds.
PATCH /api-keys/{key_id}
Updates selected key metadata. Omitted fields are preserved.
Supported fields:
name;expires_at;scope_mode;model_ids;budget_limit_usd;prompt_logging_enabled.
The budget period cannot be changed after key creation. Revoked keys cannot be reactivated.
POST /api-keys/{key_id}/rotate
Replaces the secret immediately and returns a new full secret. The previous secret becomes invalid. The key identity, scope and spending history remain associated with the same key record. Requests authenticated before rotation may finish with the old secret; new authentication cannot use it.
Rotating a revoked key returns 409.
POST /api-keys/{key_id}/revoke
Permanently prevents new authentication with the key. Historical usage remains visible.
8. BYOK provider credential APIs
BYOK means a user-owned provider key used for an explicitly selected request. It does not replace the SingleAPI API key.
POST /byok
Stores a provider credential encrypted at rest.
Request:
{
"provider": "openai",
"name": "Personal OpenAI key",
"credential": "provider-secret"
}The credential field is treated as a secret. The response contains metadata only:
{
"id": "credential-uuid",
"provider_id": "provider-uuid",
"provider": "openai",
"provider_display_name": "OpenAI",
"name": "Personal OpenAI key",
"status": "active",
"created_at": "2026-09-18T10:00:00Z",
"rotated_at": null,
"revoked_at": null
}The plaintext key is never returned.
GET /byok
Lists enabled BYOK providers and keys owned by the authenticated user. You can store multiple keys for the same provider; each has a distinct UUID. The dashboard's BYOK page shows and copies active credential IDs. This endpoint returns metadata, never ciphertext or plaintext.
POST /byok/{credential_id}/rotate
Request:
{"credential":"new-provider-secret"}Rotation keeps the credential UUID and replaces the encrypted value. Revoked credentials cannot be rotated.
DELETE /byok/{credential_id}
Revokes the credential, clears its stored encrypted secret and blocks future requests that select it. This is a logical revocation rather than a customer-visible hard delete.
Selecting BYOK on inference
Send the credential UUID with the inference request:
X-SingleAPI-Provider-Credential-ID: <credential UUID>Save the id returned by POST /byok, or obtain an existing ID from GET /byok. Use the copy control on the dashboard BYOK page to copy a credential UUID. The BYOK guide shows the request header through the OpenAI SDK.
SingleAPI checks:
- the credential belongs to the authenticated SingleAPI user;
- it is active;
- its provider matches the selected model’s provider.
In BYOK mode:
billing_modeisbyok;- SingleAPI reservation is zero;
- SingleAPI wallet charge is zero;
- provider usage and request status are still recorded;
- there is no automatic fallback to a platform-managed key.
The provider key is decrypted in memory only when building the upstream call.
9. Inference API
The inference API is intentionally a small OpenAI Chat Completions subset.
GET /v1/models
Optional classification=open|closed filters the enabled models within this key's scope. Each model includes additive classification and presentation_label fields. owned_by remains the upstream provider code (for example cerebras) even when the presentation label is SingleAPI. Model IDs and BYOK provider matching remain unchanged. The response uses Cache-Control: private, no-store.
Requires a SingleAPI API key. Returns only enabled models permitted by that key’s scope.
Response:
{
"object": "list",
"data": [
{
"id": "openai/gpt-4o-mini",
"object": "model",
"created": 0,
"owned_by": "openai"
}
]
}Use this endpoint before making a completion instead of assuming a model is enabled.
POST /v1/chat/completions
Requires:
Authorization: Bearer <SingleAPI API key>
Content-Type: application/jsonOptional BYOK header:
X-SingleAPI-Provider-Credential-ID: <credential UUID>Supported request fields
| Field | Type | Rules |
|---|---|---|
model |
string | Required public model ID; must be enabled and allowed for the key |
messages |
array | Required, non-empty, maximum 256 messages |
temperature |
number | Optional, from 0 through 1 |
top_p |
number | Optional, greater than 0 and at most 1 |
max_tokens |
integer | Optional positive output limit |
max_completion_tokens |
integer | Optional alias for the output limit |
stop |
string or string array | Optional; array maximum 4 strings |
tools |
array | Optional; maximum 128 function/hosted-tool definitions |
tool_choice |
string or object | Optional tool selection policy |
response_format |
object | Optional text, json_object, or json_schema; JSON modes require model support |
stream |
boolean | Optional; defaults to non-streaming behavior |
stream_options |
object | Optional; requires stream=true |
max_tokens and max_completion_tokens cannot be sent together. If neither is sent, the model’s configured default output limit is used, subject to the model maximum and context window.
Unknown top-level fields are rejected. The API does not silently ignore unsupported parameters.
For response_format, { "type": "text" } is accepted and has the same effect as omitting the field. { "type": "json_object" } requests JSON mode, but does not enforce a schema; ask for JSON in the prompt. JSON Schema mode uses { "type": "json_schema", "json_schema": { "name": "answer", "schema": { "type": "object", ... }, "strict": true } }. The root schema must be an object. name is required; description and strict are optional. The schema is forwarded to the provider and included in the input-token reservation estimate. Unsupported model/format combinations return 400 unsupported_response_format before admission. A provider may still reject a schema it does not support; the API returns a safe provider_rejected_request error without exposing the provider's raw response.
Supported message roles
| Role | Content rules |
|---|---|
system |
Plain-text content required |
user |
Plain-text content required |
assistant |
Plain-text content, or tool_calls when requesting a function |
tool |
Plain-text content and a matching tool_call_id |
Message content must be a plain string. Multimodal content arrays are not supported.
Normal response
Request:
{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "user", "content": "Hello"}
],
"max_tokens": 100
}Response:
{
"id": "request-uuid",
"object": "chat.completion",
"created": 1789650000,
"model": "openai/gpt-4o-mini",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello!"},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 3,
"total_tokens": 13
}
}If the provider does not provide complete valid usage, usage is null and the request is not billed from invented numbers.
10. Tool calling support
SingleAPI supports portable client-executed function tools. For a short request flow, see Use a function tool.
Tool definition
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": false
},
"strict": true
}
}Rules:
- function names are 1–64 characters;
- allowed name characters are letters, digits,
_and-; - names must be unique within the request;
parametersmust be an object when supplied;- only
type: "function"is supported; tool_choicesupportsauto,none,required, or a named declared function;requiredand named choices require at least one matching declared tool.
Tool-call response
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Delhi\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}The client owns execution. It sends the result in a new request:
{
"role": "tool",
"tool_call_id": "call_123",
"content": "{\"city\":\"Delhi\",\"temperature_c\":28}"
}SingleAPI validates that the tool result references an earlier assistant tool call, that IDs are unique, and that each call receives at most one result.
The initial request and follow-up request are separate inference requests and are billed separately under managed billing.
11. Streaming support
Set:
"stream": trueThe API returns text/event-stream instead of one JSON response.
Usage option
"stream_options": {"include_usage": true}stream_options is invalid unless stream=true. When requested, the gateway emits a final usage chunk before [DONE] when valid provider usage is available.
SSE response
Each event is formatted as:
data: {"id":"request-uuid","object":"chat.completion.chunk",...}
The stream ends successfully with:
data: [DONE]
Content and tool-call arguments may arrive incrementally in choices[].delta. A client must accumulate tool-call argument fragments before parsing the final JSON.
Example with curl:
curl -N https://staging.singleapi.ai/api/v1/chat/completions \
-H "Authorization: Bearer $SINGLEAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [{"role":"user","content":"Explain databases in three paragraphs."}],
"stream": true,
"stream_options": {"include_usage": true}
}'With the OpenAI Python SDK, iterate over the stream after creating client as shown in the quickstart:
response = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Explain databases."}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in response:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("Usage:", chunk.usage)The SDK response is a Stream, not a normal object with response.choices.
Streaming and billing
Streaming is not priced per chunk. Managed billing works as follows:
- estimate input and maximum output cost;
- reserve that amount before the provider call;
- relay chunks;
- capture final provider usage;
- calculate actual cost from provider input/output usage;
- cap the charge at the original reservation;
- release the unused reservation.
If the provider stream errors, times out or ends without valid usage, the current safe behavior is to release the reservation without inventing partial usage.
If the client disconnects, SingleAPI currently stops delivering events to that client but continues draining the provider stream. There is no explicit upstream cancellation endpoint or cancellation protocol in the current API.
12. Inference lifecycle and billing
Every admitted request follows:
validate
→ authenticate
→ check model/key scope
→ check BYOK if selected
→ estimate and reserve
→ create request record
→ dispatch one provider attempt
→ record outcome and usage
→ settle or releaseNo database transaction remains open across the provider call.
Managed billing
The reservation uses the estimated input tokens, configured output limit, model rates and rounding buffer. Final cost uses valid provider usage:
uncached input tokens × input rate
+ cached input tokens × cached-input rate
+ output tokens × output rateThe final charge is capped at the original reservation. Failures, unknown outcomes and missing/invalid usage are not billed.
BYOK billing
BYOK requests have:
- zero SingleAPI reservation;
- zero SingleAPI wallet charge;
- provider usage recorded where available;
billing_mode: byokin usage history.
Recovery and idempotent finalization
The recovery worker can finalize abandoned requests but never sends another provider request. A request has at most one finalization ledger ID, and the financial state prevents double settlement or double release.
Possible request statuses are:
reserved, running, succeeded, failed, unknownPossible financial states are:
held, settled, released13. Usage APIs
GET /usage
Returns newest inference requests first.
Query parameters:
| Parameter | Type | Rules |
|---|---|---|
from |
datetime | Inclusive |
to |
datetime | Exclusive |
key_id |
UUID | Owned API key record UUID |
model_id |
string | Public model ID |
limit |
integer | Default 50, maximum 100 |
cursor |
string | Opaque next_cursor from the previous response |
Response item:
{
"request_id": "request-uuid",
"created_at": "2026-09-18T10:00:00Z",
"api_key_name": "demo key",
"public_key_identifier": "public-id",
"model": "openai/gpt-4o-mini",
"status": "succeeded",
"billing_mode": "managed",
"billing_status": "charged",
"total_tokens": 42,
"charged_usd": "0.000019000"
}total_tokens is null when complete token usage is unavailable. The API does not convert unknown usage into zero.
GET /usage/{request_id}
Returns detailed usage and billing information:
- request status and financial state;
- billing mode and billing status;
- usage source;
- input, cached-input and output tokens;
- reserved, calculated and charged amounts;
- applied input, cache and output prices;
- adjustment amount;
- provider/error code;
- latency when dispatch and completion timestamps exist.
- hosted search count, errors and price when search was requested.
Prompt and response content are not returned by this endpoint. When the request's API key had content logging enabled at admission, use GET /usage/{request_id}/content to read the separately stored encrypted record.
GET /usage/content-logs
Lists your available prompt and response logs, newest first. Each item includes the request ID, creation and expiry times, API key name, model, and request status. Content is returned only when you open an individual log. Optional from (inclusive), to (exclusive), and repeated key_id parameters filter by UTC creation time and your API keys. Either time bound can be used alone. Use limit (1–100, default 20) and the returned next_cursor for more pages. Expired logs and other users' logs are excluded.
In the dashboard, open Prompt logs to filter by key, date, or time and view these records. Enable content logging on an API key to capture future requests; it does not record earlier requests.
GET /usage/content-logs/export
Downloads every unexpired log matching the same from, to, and repeated key_id filters, including matches outside the current list page. The dashboard's Export CSV button applies its current filters. The file is Excel-compatible UTF-8 CSV, not a native .xlsx workbook. It includes request metadata and prompt, response, and error JSON; treat it as sensitive. The export is owner-scoped, redacts provider continuation state, escapes spreadsheet formula prefixes, and uses Cache-Control: no-store. More than 1,000 matching logs returns 400 export_too_large; narrow the filters and retry.
GET /usage/{request_id}/content
Returns the accepted inference request JSON and full response JSON for an opted-in API key. The record expires 30 days after request admission. Streamed responses are reconstructed from deltas, including every choice and tool call. A stream that fails after returning content includes both the partial response and an error object, with response_status: "partial". Requests with content logging disabled, expired records, and requests owned by another user all return 404 content_not_found.
Example response:
{
"request_id": "request-uuid",
"expires_at": "2026-10-18T10:00:00Z",
"response_status": "complete",
"prompt": {
"model": "openai/gpt-4o-mini",
"messages": [{"role": "user", "content": "Hello"}]
},
"response": {
"id": "request-uuid",
"object": "chat.completion",
"created": 1792317600,
"model": "openai/gpt-4o-mini",
"choices": [{"index": 0, "message": {"role": "assistant", "content": "Hi"}, "finish_reason": "stop"}],
"usage": {"prompt_tokens": 4, "completion_tokens": 1, "total_tokens": 5}
},
"error": null
}response_status is pending while the provider is running, complete after a normal result, partial when a stream failed after returning content, or failed when no completion was captured. response is null when no completion was captured; error is the safe API error object when the call failed. A streamed response is reconstructed rather than a literal replay of SSE chunks. Usage and billing details remain available in the ordinary usage record. Content is encrypted at rest; the endpoint decrypts it only after checking request ownership. Earlier message-only logs are returned in their original format until they expire.
GET /usage/analytics
Aggregates admitted requests over a UTC time range.
Query parameters:
| Parameter | Type | Rules |
|---|---|---|
from |
datetime | Must be supplied with to |
to |
datetime | Must be supplied with from |
key_id |
UUID array | Repeat to filter by owned keys |
model_id |
string array | Repeat to filter by public model IDs |
If no range is provided, the default is the previous 30 days. The maximum range is 366 days.
Granularity is selected automatically:
- up to 24 hours: hourly;
- up to 90 days: daily;
- longer ranges: monthly.
The response contains totals, time series, model breakdowns, API-key breakdowns and filter dimensions. Empty time buckets contain zero requests and charge, with null token totals because no tokens were measured. Every admitted request is counted, including failed and unknown outcomes; requests rejected before admission are excluded. Model dimensions can include models that have since been disabled, and key dimensions can include revoked keys.
token_counted_request_count must be compared with request_count. Token totals can be partial when some admitted requests have missing usage; charges still sum persisted charged_nanos.
GET /usage/traffic
Returns minute or hourly UTC buckets for finalized requests owned by the signed-in user. The default is the last 60 minute buckets; set granularity=hour for the last 24 hourly buckets. Optional from and to must be supplied together, align to the selected bucket boundary, and span at most 24 hours for minutes or 30 days for hours. key_id and model_id filter to one key or public model ID.
Each bucket includes request count, requests per minute, error count, token-counted request count, input/output tokens, tokens per minute, average latency in milliseconds, and the SingleAPI charge in USD. Search fields include requests that asked for search (search_request_count), requests with a known search count (search_counted_request_count), completed provider search actions (web_search_requests), and search errors (web_search_errors). Compare the first two counts before treating search totals as complete; unknown search usage is null, not zero. Hourly RPM and TPM are averages over 60 minutes. Empty buckets return zero counts; token rate and latency are null when there is no known usage or duration. This traffic view counts finalized requests, while /usage/analytics counts admitted requests, including requests still running.
14. Payments and wallet credits
POST /payments/checkout
Creates a local pending payment and Stripe-hosted Checkout session.
Request:
{
"amount_usd": "10.00",
"success_url": "http://localhost:3000/dashboard?payment=success",
"cancel_url": "http://localhost:3000/dashboard?payment=cancel"
}The amount must have at most two decimal places and must be within configured minimum and maximum top-up limits.
Response:
{
"payment_id": "payment-uuid",
"checkout_url": "https://checkout.stripe.com/..."
}The browser should navigate to checkout_url. Returning to success_url alone does not credit the wallet.
GET /payments/{payment_id}
Returns the authenticated user’s local payment state:
{
"payment_id": "payment-uuid",
"status": "succeeded",
"amount_usd": "10.00",
"balance": {
"available_usd": "10.00",
"held_usd": "0.00",
"total_usd": "10.00"
}
}Possible payment states are pending, succeeded, failed and expired.
POST /webhooks/stripe
Stripe calls this endpoint for:
checkout.session.completed;checkout.session.async_payment_succeeded;checkout.session.expired.
Only a verified paid session credits the wallet, and fulfillment is idempotent. If Stripe is not configured, the endpoint returns 503.
Checkout supports prepaid credits. Subscriptions, refund handling, tax handling and generated tax invoices are not part of the current API.
15. Error format
Platform errors use the same envelope:
{
"error": {
"code": "invalid_request",
"type": "invalid_request_error",
"message": "stream_options requires stream=true",
"param": "stream_options",
"request_id": "request-id"
}
}The param field may be null when the error does not map to one request field.
HTTP status mapping
| Status | Error type | Meaning |
|---|---|---|
400 |
invalid_request_error |
Malformed or unsupported request |
401 |
authentication_error |
Missing, invalid, expired or revoked credentials |
402 |
billing_error |
Insufficient wallet credits |
403 |
permission_error |
Inactive account or model scope denial |
404 |
not_found_error |
Model, credential, payment or usage record not found |
409 |
api_error |
State conflict such as revoked credential/key rotation |
429 |
rate_limit_error |
Key budget exceeded |
500 |
server_error |
Unexpected internal failure |
502 |
api_error |
Provider or upstream failure |
503 |
service_unavailable_error |
Configuration, readiness or finalization problem |
504 |
timeout_error |
Provider request timeout |
Inference validation failures use 400, not FastAPI’s normal 422, so clients receive the same inference error envelope.
Expected errors do not expose provider payloads, credentials, stack traces or exception text.
An unknown request outcome or 503 request_finalization_pending does not establish whether the provider processed the call. Use the request ID to check usage history or ask support before retrying; the API does not deduplicate a new request automatically.
16. Supported limits
Current important limits include:
| Area | Limit |
|---|---|
| Request body | 1 MiB default maximum |
| Messages | 256 messages |
| Text content | 100,000 aggregate characters |
| Tools | 128 function definitions |
| Function name | 1–64 characters |
| Function description | 10,000 characters |
| Stop sequences | 4 strings |
| Usage page | 100 items maximum |
| Billing transaction page | 100 items maximum |
| Analytics range | 366 days maximum |
| Provider timeout | Up to 120 seconds |
| Request deadline | 150 seconds by default |
Model-specific context and output limits are enforced from the catalogue.
17. Explicitly supported versus unsupported
Supported
- Clerk-authenticated management APIs;
- SingleAPI-key-authenticated inference APIs;
- model listing and per-key model scope;
- plain-text system, user, assistant and tool messages;
temperature,top_p,stopand output token limits;- client-executed function tools;
- model-scoped JSON object and JSON Schema response formats;
- tool choices
auto,none,requiredand named function choice; - streamed text deltas;
- streamed tool-call deltas;
- optional final streaming usage chunk;
- managed provider credentials from server configuration;
- explicitly selected encrypted user-owned BYOK credentials;
- usage history and aggregate analytics;
- wallet reservations, settlement and release;
- Stripe Checkout credits when Stripe is configured;
- PDF wallet statements;
- safe request-ID-based error responses.
Unsupported
The current API rejects or does not implement:
- multimodal message content, images, audio or video;
developeror legacyfunctionmessage roles;- legacy
function_callfields; - arbitrary custom/non-function tool types other than
singleapi:web_searchandsingleapi:web_fetch; - provider-hosted tools other than explicitly enabled web search and Anthropic web fetch (computer use and provider code execution remain unsupported);
- server-managed tool execution;
- arbitrary customer Python, JavaScript, shell or URL execution;
- tool registry, sandbox, secret injection or human approval workflows;
- automatic agent loops after a tool call;
- server-managed tool retries, idempotency, concurrency or circuit breakers;
parallel_tool_calls;- JSON response formats on models without verified support; only
text,json_object, and object-rootjson_schemaare accepted; - provider capability discovery before dispatch;
- automatic provider fallback or model substitution;
- automatic request retries;
- response caching;
- customer-supplied idempotency keys;
- explicit upstream cancellation API;
- prompt/response retention unless explicitly enabled on the API key;
- conversation memory;
- model hosting or fine-tuning;
- subscriptions or organizations;
- separate tool-runtime billing;
- guaranteed partial usage after a cancelled provider stream.
Unsupported inference fields are rejected rather than silently forwarded. A rejected request does not create a provider call, reservation, inference-history row or charge.
18. Provider and compatibility boundary
LiteLLM is the provider adapter. SingleAPI normalizes provider responses into its supported OpenAI-shaped contract, but it does not promise that every provider supports every feature.
The public model catalogue is the source of enabled model IDs, limits and prices. A model must be enabled, priced and allowed by the current API key.
Compatibility means the official OpenAI SDK can call the supported endpoint shape. It does not mean that every OpenAI endpoint, parameter, model behavior or usage field is implemented.
19. Useful client sequence
For runnable OpenAI SDK and HTTP examples, see the quickstart.
Managed inference
1. Create or obtain SingleAPI key
2. GET /v1/models
3. POST /v1/chat/completions
4. Read response usage
5. GET /usage/{request_id} if detailed accounting is neededBYOK inference
1. Authenticate with Clerk
2. POST /byok
3. Save returned credential UUID
4. Create/use a SingleAPI inference key
5. GET /v1/models
6. POST /v1/chat/completions with X-SingleAPI-Provider-Credential-ID
7. Confirm billing_mode=byok and zero SingleAPI charge in /usageClient tool calling
1. POST completion with tools
2. Receive assistant tool_calls
3. Execute the function in the client
4. Append assistant tool-call message
5. Append role=tool result
6. POST a new completion request
7. Receive final responseStreaming
1. POST completion with stream=true
2. Iterate SSE/OpenAI SDK chunks
3. Accumulate text or tool-call deltas
4. Read usage chunk when include_usage=true
5. Stop at data: [DONE]These examples use the same customer request and response contract as the endpoint reference above.
Provider web search
The chat endpoint accepts tools: [{"type":"singleapi:web_search","max_uses":1}] for models whose supported_tools metadata enables it. You choose max_uses per request (1–10; omitted defaults to 3), up to the model's published supported_tools[].limits.max_uses. A larger value is rejected before any provider call or credit reservation. The development GPT-4.1 Mini model currently allows 1. Each separate request gets its own search limit and credit reservation; API-key budgets limit spending across requests.
allowed_domains and blocked_domains are optional. Omit both for no domain filter; empty lists are invalid. Check the model's published limits before using filters, streaming, forced search, or continuation. Search adds citations and hosted usage. Managed search charges include search fees and token costs; BYOK charges zero SingleAPI credits. See the web search guide and SDK example for the complete behavior.
Provider web fetch
For an Anthropic model with singleapi:web_fetch in supported_tools, send tools: [{"type":"singleapi:web_fetch","max_uses":1,"max_content_tokens":8192}] and exactly one HTTPS URL in the latest user message. You choose max_uses per request, up to the model's published limit. The current API supports automatic tool choice and non-streaming text responses. Fetch cannot be mixed with search or client functions. The API restricts fetching to the URL's host; an optional allowed_domains list must include that host or a parent domain and cannot widen the native filter.
hosted_tool_usage.fetch_requests reports the provider's fetch count. Anthropic has no separate fetch fee; fetched content counts as input tokens. Managed requests reserve the remaining model context and settle actual provider usage; BYOK charges zero SingleAPI credits. The text content limit does not cap PDFs. See the user example.