Skip to content

API reference

Exact endpoints, authentication, parameters, responses and errors.

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:

text
Dashboard and management APIs → Clerk session token
Inference APIs                → generated SingleAPI API key
Stripe webhook                → Stripe-Signature header
Health checks                 → no authentication

The 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:

text
https://staging.singleapi.ai/api

The OpenAI-compatible inference base URL is:

text
https://staging.singleapi.ai/api/v1

Use 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:

text
Content-Type: application/json

Streaming chat completions use:

text
Content-Type: text/event-stream

Billing statements use:

text
Content-Type: application/pdf

Time and money

  • Timestamps are timezone-aware UTC timestamps.
  • Time range from values are inclusive.
  • Time range to values 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.00 can 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:

http
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:

http
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:

http
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:

json
{"status":"ok"}

This does not verify PostgreSQL or provider configuration.

GET /readyz

Checks whether the API can execute a basic database query.

Healthy response:

json
{"status":"ok"}

If the database check fails:

http
503 Service Unavailable
json
{"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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "provider": "openai",
  "name": "Personal OpenAI key",
  "credential": "provider-secret"
}

The credential field is treated as a secret. The response contains metadata only:

json
{
  "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:

json
{"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:

http
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:

  1. the credential belongs to the authenticated SingleAPI user;
  2. it is active;
  3. its provider matches the selected model’s provider.

In BYOK mode:

  • billing_mode is byok;
  • 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:

json
{
  "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:

http
Authorization: Bearer <SingleAPI API key>
Content-Type: application/json

Optional BYOK header:

http
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:

json
{
  "model": "openai/gpt-4o-mini",
  "messages": [
    {"role": "user", "content": "Hello"}
  ],
  "max_tokens": 100
}

Response:

json
{
  "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

json
{
  "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;
  • parameters must be an object when supplied;
  • only type: "function" is supported;
  • tool_choice supports auto, none, required, or a named declared function;
  • required and named choices require at least one matching declared tool.

Tool-call response

json
{
  "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:

json
{
  "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:

json
"stream": true

The API returns text/event-stream instead of one JSON response.

Usage option

json
"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:

text
data: {"id":"request-uuid","object":"chat.completion.chunk",...}

The stream ends successfully with:

text
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:

bash
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:

python
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:

  1. estimate input and maximum output cost;
  2. reserve that amount before the provider call;
  3. relay chunks;
  4. capture final provider usage;
  5. calculate actual cost from provider input/output usage;
  6. cap the charge at the original reservation;
  7. 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:

text
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 release

No 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:

text
uncached input tokens × input rate
+ cached input tokens × cached-input rate
+ output tokens × output rate

The 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: byok in 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:

text
reserved, running, succeeded, failed, unknown

Possible financial states are:

text
held, settled, released

13. 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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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:

json
{
  "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, stop and output token limits;
  • client-executed function tools;
  • model-scoped JSON object and JSON Schema response formats;
  • tool choices auto, none, required and 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;
  • developer or legacy function message roles;
  • legacy function_call fields;
  • arbitrary custom/non-function tool types other than singleapi:web_search and singleapi: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-root json_schema are 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

text
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 needed

BYOK inference

text
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 /usage

Client tool calling

text
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 response

Streaming

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

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.

Need a hand?

Manage your keys, credits and usage from the dashboard.

Open the dashboard