Skip to content
Radient LogoRadient Documentation

Radient API Reference

The Radient API is one HTTP surface over one balance. Inference, tools, usage and billing calls all live under a single base URL and are paid for from your Radient Pass:

https://api.radienthq.com/v1

Everything except the health check is under /v1. Responses are JSON; request bodies are JSON unless stated otherwise.

bash
# The smallest useful call: check the API is up.
curl https://api.radienthq.com/health
# {"msg":"OK"}

#Authentication at a glance

Send exactly one credential in the Authorization header:

Authorization: Bearer YOUR_API_KEY
  • API key (rad-prod-…) — for servers and tools calling inference and tool endpoints. Create one in the console's Applications page.
  • User token — for anything tenant- or account-scoped: keys, usage, billing, members, plans, transfers.
  • Client token — only for the public contact form.

A missing credential answers 401 {"error":"missing Authorization header"}. Full detail, including which credential reaches which routes and the key-revocation behaviour: Authentication.

#Response conventions

Two success shapes exist:

  • Raw JSON (OpenAI-shaped, no envelope): chat completions, completions, embeddings, decisions, models, the account usage summary, and the tools endpoints that answer JSON directly (search, media). Transcription answers the result envelope, and speech answers audio with X-Radient-Cost-* headers.
  • Envelope {"msg": "…", "result": …}: everything else — applications, tenants, usage records and rollups, billing, ledger, auto-reload, transfers, members, invites, plans, billing sources, agents and teams, notifications, prices, client-token.

Billed calls also answer with a versioned cost object stating what the call charged — top level on raw bodies, inside result on the enveloped tool responses. See Cost reporting.

Error responses are one shape everywhere:

json
{ "error": "insufficient credits" }

Some errors add a machine-readable code and optional details:

json
{ "code": "idempotency_key_required",
  "error": "an Idempotency-Key header of 16-128 [A-Za-z0-9_-] characters is required" }
json
{ "code": "transfer_insufficient_giftable", "details": { "giftable": 0 },
  "error": "granted credits can no longer be transferred" }

The error string is stable for a given cause; match on status and code where present, and treat error as human-readable text.

#Status codes and common bodies

StatusTypical bodies (as returned)Meaning
400Invalid request format (unparseable JSON); Validation failed; Invalid start_time format; Invalid rollup value (must be 'daily', 'monthly', or 'annual'); Radient client ID is required; Unsupported grant_type: <x>Bad request or parameters
401missing Authorization header; invalid Authorization header format; invalid API key format; API key is not a Radient key; invalid API key; invalid or expired tokenCredential problem — see Authentication
402insufficient credits; billing account is not active; billing account not found for tenantThe credit gate refused the call (below)
403not_a_member; insufficient_role; team_plan_required; email_mismatch; Tenant ID in path does not match JWTNot allowed for this account
404agent_not_found, invite_not_found, transfer_not_found, media_not_found, team_not_found, …Unknown object (unknown and foreign ids answer identically)
409already_member, last_owner, idempotency_key_reused, transfer_not_pending, media_already_completed, …Conflicting state
413The decision request body is too large; transcription File size exceeds the 25MB limitBody over the limit
422transfer_insufficient_credits, transfer_insufficient_giftable, … ; the decision model's own rejectionUnderstood but not acceptable
429Too many publish requests. Wait a moment and try again. + Retry-AfterA write rate limit (see Rate limits) — not emitted by inference routes
500Failed to process chat completions request (and the embeddings/transcription equivalents); Internal server errorServer-side failure. Upstream model errors are flattened into these bodies; the detail is logged, not returned
502The decision model upstream failed with status NA decision-model upstream failure
503billing is temporarily unavailable; auto_reload_unconfigured; media_unavailableA dependency is down or not configured; the credit gate fails closed

#The credit gate

Paid routes are admitted by a credit gate before any work is sent to a model. It applies to:

  • POST /v1/chat/completions, POST /v1/completions, POST /v1/embeddings, POST /v1/decisions
  • every route under /v1/tools/… except GET /v1/tools/media/status, GET /v1/tools/media/result, POST /v1/tools/media/cancel, GET /v1/tools/media/models, and GET /v1/tools/speech/voicing

Note that read-only-looking tool calls such as GET /v1/tools/search and GET /v1/tools/images/providers are gated too: they are priced calls.

How it works:

  1. Estimate. The API estimates the maximum cost of the call from the model's published rates, your prompt and your max_tokens / max_completion_tokens (assumed 8192 when absent). When the cost cannot be estimated — the literal auto model id, tool submissions, bodies it cannot parse — the reservation is a flat $1.00. Decision requests reserve their own estimate, capped at $0.30.
  2. Reserve. The estimate is held against your account's in-flight budget (released after 10 minutes if the call never settles).
  3. Admit. The call is admitted only if balance − (others' in-flight holds) − floor ≥ estimate. The floor is −$1.00 for accounts that have ever been funded with real money, and $0 otherwise. A funded account can therefore go slightly negative once; a promotional-only balance cannot dip below zero.

Example: on an account holding $0.50, model: "auto" (flat $1.00 reservation) answers 402 {"error":"insufficient credits"}, while a named model with a small max_tokens is admitted.

Refusals:

StatusBodyCause
402{"error":"insufficient credits"}Balance cannot cover the estimate
402{"error":"billing account is not active"}Account not in an active state
402{"error":"billing account not found for tenant"}No billing account provisioned
503{"error":"billing is temporarily unavailable"}The reservation store is down — the gate fails closed

After the call, the actual cost is written to your ledger and the reservation is released. Streaming calls bill when the stream ends. Media generations are charged when their result is first observed — poll GET /v1/tools/media/status or /result, because a generation that is never fetched is never charged and its reservation expires after two hours.

#Cost reporting

Billed calls answer with a versioned cost object stating what the call charged, in USD. The object is additive: every field that existed before keeps its meaning, and a client that does not read it sees the same responses as always.

json
"cost": {
  "v": 1,
  "amount_usd": 0.0001627,
  "basis": "billed",
  "units": 1447,
  "unit": "token",
  "provider": "openrouter",
  "model": "openai/gpt-6-luna-pro",
  "usage_record_id": "6ac9dfbf705ad8633a87dfc4"
}

(The figure above is a chat completion's charge from a live call.)

#The cost object

FieldTypePresenceMeaning
vintegeralwaysContract version — 1 today. Additive fields do not bump it; ignore keys you do not recognise.
amount_usdnumberalwaysThe amount charged for this call; basis says what the amount is.
basisstringalwaysbilled, estimated or quote — see below.
unitsintegerwhen knownThe billed quantity: tokens, characters, seconds or searches, according to the surface's meter.
unitstringwhen knownWhat units counts: token, char, second, image, megapixel, search or email.
unit_price_usdnumbersingle-rate charges onlyThe per-unit price, present only where it reconstructs the amount exactly (amount_usd = units × unit_price_usd). Absent for mixed-rate charges — a chat turn with cached and uncached prompt legs — and for charges clamped by a ceiling.
providerstringwhen knownThe provider that served the call, in the usage surface's own vocabulary (openrouter, typesafe, elevenlabs, …).
modelstringwhen knownThe model this charge is for — the serving model, except on /v1/decisions, where it is the model the amount was priced from.
tool_typestringtool charges onlyWhich tool was billed: image_generation, video_generation, speech, transcription, web_search.
usage_record_idstringwhen a usage row existsThe id of the usage row the charge lands on (24 hex characters) — the join key for the usage and ledger surfaces. Absence never means the call was free or uncharged.
price_versionstringwhen a platform rate priced itProvenance of the figure, <rate-family>@<read-date> — e.g. decisions-rate@2026-09-19, speech-rates@2026-09-30. Omitted when the amount is the vendor's own reported figure; for the rate families that record no read date (search, email); and on the binding-quote echo path for streamed/rewritten bodies.
qualitystringwhen a marker appliesA marker on qualified figures — e.g. vendor_cost_suppressed, where an implausible vendor figure was withheld and the amount is the platform's own derivation. Ignore values you do not recognise.

Two reading rules. Amounts are the stored doubles, never re-rounded for display, so a figure can show representation noise (0.00030000000000000003 is ordinary): round for display and reconcile by numeric value, never by comparing decimal strings. And keys are omitted when they do not apply — absence is not zero; v, amount_usd and basis are always present when the object is.

basis says what the amount is:

ValueMeaning
billedThe amount IS the charge for this call: a vendor-quoted figure, a figure derived from a published rate, or a settled quote.
estimatedThe ledger bills this figure, but it came from a fallback estimate because no published rate was available for the call.
quoteA firm committed price that settles when the request completes — media submissions while unsettled.

#Where to read the charge, per surface

SurfaceWhere the object is
Chat and completions — non-streamingTop level, a sibling of usage — Chat API
Chat and completions — streamingOn the usage frames the server annotates. The last usage frame is the one that is billed; a stream that reports no usage frame carries no object anywhere and is reconciled through the usage surface — Chat API
EmbeddingsTop level — Embeddings API
DecisionsTop level. usage.cost on this route is the vendor's own informational figure — read cost — Decisions API
Media — image and video (generate, status, result, cancel)Top level; alongside the legacy cost_usd / units / unit pair where it exists (generate; status once settled). The quote while unsettled, the billed figure once settled (0 for a failure) — Image API
SpeechX-Radient-Cost-* response headers — the body is audio — Speech API
TranscriptionInside the result envelope — Transcription API
SearchTop level — Search API
Email — /v1/email/self/sendInside the result envelope; no usage_record_id by design — this path writes no usage row

How the object is emitted, in brief: charged calls carry it; error responses and credit-gate refusals (401 / 402) do not — nothing was admitted, nothing was charged. A settled zero (a failed media generation; a decision answered with no readable usage) is billed with amount 0 — a recorded zero, distinguishable from silence. And absence is never "free": a streamed turn with no usage frame is still billed, and the usage surface is the check.

#Reconciling a charge

cost.usage_record_id joins a response to the server-side record of its charge:

  1. take the id from the response;
  2. GET /v1/tenants/{tenant_id}/usage — the record whose id equals it; its cost equals amount_usd;
  3. GET /v1/tenants/{tenant_id}/billing/ledger — the debit whose reference_id equals the same id, with amount equal to the same figure.

Both routes need a user token, and the usage row is written asynchronously after the response — an id can briefly precede its row, and a missing row is not permission to read the call as uncharged. A zero-cost success writes no ledger row, and a team-paid call debits the payer's account rather than yours. The full set of caveats — including what a media quote's id means before settlement — is in Usage and credits API.

#Client correlation headers

Two optional headers let your logs point at the server-side record of a call: X-Radient-Client-Session-Id and X-Radient-Client-Request-Id.

  • The grammar is [A-Za-z0-9._:-]{1,64}; anything else is ignored — never a 400, because an optional telemetry header must not fail a paid request.
  • The values are opaque: never parsed as money, never used for routing, never logged. A session id means whatever your client means by it.
  • A valid value is stored on your own usage row (client_session_id, client_request_id — additive fields, omitted when absent) and is readable back only through the tenant-scoped usage surface. A media generation captures the values at submission and writes them with the settlement charge.

#Rate cards

GET /v1/prices (no auth) publishes the platform's rate cards for pre-call estimates — per-character speech prices, per-second transcription, per-search and per-email charges — alongside the promotional credit amounts. It is served with an ETag and Cache-Control: public, max-age=300: revalidate with If-None-Match after the TTL instead of pinning figures — a match (including the weak form a proxy may hand you) answers 304 Not Modified. Estimates are advisory; the cost object on the response is the record of what was charged. See Usage and credits API.

#Rate limits

What is enforced today:

  • There is no request-rate limit on the paid inference and tools routes (chat, completions, embeddings, decisions, search, speech, transcription, images, media). They are bounded only by the credit gate and by the serving models' own limits. No X-RateLimit-* headers are emitted; the only rate header is Retry-After on the routes below.
  • Publishing and uploading (agents and hub teams) is rate-limited: per-IP 1 request / 15 s (burst 10) and per-account 1 / 60 s (burst 10), answering 429 {"error":"Too many publish requests. Wait a moment and try again.","code":"rate_limited"} with Retry-After.
  • Signup verification is rate-limited per source address (claim: 1 per 5 s).
  • Invites and transfer invitations: reading an invite token is limited (1/s, burst 5), sending is capped at 3 emails per address per day, and a transfer resend has a 12-hour cooldown.

If you need higher throughput than a serving model allows, use a different model or spread requests across models.

#CORS

The API sets permissive CORS for browser callers:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Expose-Headers: Retry-After

Two consequences for browsers:

  • PATCH is not in the allowed methods and Idempotency-Key is not in the allowed headers. A cross-origin browser call using either fails its preflight. Server-to-server callers (which do not preflight) are unaffected.
  • Credentials are not used: the Authorization header is what authenticates, and Allow-Origin: * means any site can call the API with a key it already has — so never embed your key in a page. Keep keys server-side.

#Versioning

The path prefix /v1 is the version. Within it, changes are additive: new endpoints and new response fields may appear at any time, and clients should ignore fields they do not know. Behaviour that would break an existing integration is shipped under a new prefix.

#The route list

Every client-callable route, grouped by surface. “Gate” marks the credit gate; “Auth” column values: Key = application API key, User = user token, Both = either, None = anonymous, Optional = anonymous allowed with richer results when authenticated.

#Inference

MethodPathAuthGateGuide
POST/v1/chat/completionsBothyesChat API
POST/v1/completionsBothyesChat API
POST/v1/embeddingsBothyesEmbeddings API
POST/v1/decisionsBothyesDecisions API
GET/v1/modelsBoth—Chat API
GET/v1/usageBoth—Usage and credits

#Tools

MethodPathAuthGateGuide
POST/v1/tools/speechBothyesSpeech API
GET/v1/tools/speech/voicingNone—Speech API
POST/v1/tools/transcriptionsBothyesTranscription API
GET/v1/tools/searchBothyesSearch API
GET/v1/tools/search/providersBothyesSearch API
POST/v1/tools/media/generateBothyesVideo and media API
GET/v1/tools/media/statusBoth—Video and media API
GET/v1/tools/media/resultBoth—Video and media API
POST/v1/tools/media/cancelBoth—Video and media API
GET/v1/tools/media/modelsNone—Video and media API
POST/v1/tools/images/generateBothyesImage API (legacy)
GET/v1/tools/images/statusBothyesImage API (legacy)
GET/v1/tools/images/providersBothyesImage API (legacy)

#Usage and billing

MethodPathAuthGateGuide
GET/v1/tenants/{tenant_id}/usageUser—Usage and credits
GET/v1/tenants/{tenant_id}/usage/rollupUser—Usage and credits
GET/v1/tenants/{tenant_id}/usage/teamUser—Usage and credits
GET/v1/tenants/{tenant_id}/billingUser—Usage and credits
PATCH/v1/tenants/{tenant_id}/billingUser—Usage and credits
GET/v1/tenants/{tenant_id}/billing/creditsUser—Usage and credits
GET/v1/tenants/{tenant_id}/billing/ledgerUser—Usage and credits
GET · PUT/v1/tenants/{tenant_id}/billing/auto-reloadUser—Usage and credits
POST/v1/tenants/{tenant_id}/billing/auto-reload/chargeUser—Usage and credits
POST · GET/v1/tenants/{tenant_id}/billing/transfersUser—Usage and credits
DELETE/v1/tenants/{tenant_id}/billing/transfers/{transfer_id}User—Usage and credits
POST/v1/tenants/{tenant_id}/billing/transfers/{transfer_id}/resendUser—Usage and credits
GET/v1/transfers, /v1/transfers/{transfer_id}User—Usage and credits
POST/v1/transfers/{transfer_id}/accept, /v1/transfers/{transfer_id}/declineUser—Usage and credits
GET · POST/v1/transfer-invites/{token}, /v1/transfer-invites/{token}/attachNone · User—Usage and credits
GET/v1/me/billing-sourcesUser—Team pooling
PUT/v1/me/billing-sourceUser—Team pooling
GET/v1/me/billing-sources/usage, /v1/me/billing-sources/capacityUser—Team pooling
GET/v1/pricesNone——

#Account, applications and keys

MethodPathAuthGateGuide
POST · GET/v1/tenants/{tenant_id}/applicationsUser—Authentication
GET · PATCH · DELETE/v1/tenants/{tenant_id}/applications/{application_id}User—Authentication
GET/v1/meUser—Authentication
GET/v1/accounts/{account_id} · PATCHUser——
GET/v1/accounts/{account_id}/agentsUser——
GET/v1/accounts/{account_id}/notificationsUser——
GET · PATCH · DELETE/v1/notifications/{notification_id}User——
GET · PATCH/v1/account/notification-preferencesUser——
POST/v1/email/self/sendKey——

#Team and membership

MethodPathAuthGateGuide
GET/v1/tenants/{tenant_id}User——
GET/v1/tenants/{tenant_id}/membersUser—Team Plan
POST · GET/v1/tenants/{tenant_id}/invitesUser—Team Plan
DELETE/v1/tenants/{tenant_id}/invites/{invite_id}User—Team Plan
PATCH · DELETE/v1/tenants/{tenant_id}/members/{account_id}User—Team Plan
GET/v1/tenants/{tenant_id}/planUser—Team Plan
GET/v1/me/membershipsUser—Team Plan
POST/v1/invites/{token}/acceptUser—Team Plan
GET/v1/invites/{token}None—Team Plan

#Agents and hub teams

MethodPathAuthGateGuide
GET/v1/agentsNone—Agent Hub
GET/v1/agents/{agent_id}Optional—Agent Hub
POST · PATCH · DELETE/v1/agents, /v1/agents/{agent_id}Both—Agent Hub
POST · PUT/v1/agents/upload, /v1/agents/{agent_id}/uploadBoth—Agent Hub
GET/v1/agents/{agent_id}/download, /v1/agents/{agent_id}/download/countOptional—Agent Hub
POST · PUT/v1/agents/publish, /v1/agents/publish/preview, /v1/agents/{agent_id}/publish, /v1/agents/{agent_id}/publish/previewBoth—Agent Hub
POST · DELETE · GET/v1/agents/{agent_id}/like, /v1/agents/{agent_id}/favouriteUser—Agent Hub
GET/v1/agents/{agent_id}/like/count, /v1/agents/{agent_id}/favourite/countOptional—Agent Hub
POST · GET/v1/agents/{agent_id}/commentsUser · Optional—Agent Hub
PATCH · DELETE/v1/agents/{agent_id}/comments/{comment_id}User—Agent Hub
GET/v1/agent-name-availabilityOptional—Agent Hub
GET/v1/teams, /v1/teams/{team_id}None · Optional—Team Plan
POST · PUT/v1/teams/publish, /v1/teams/publish/preview, /v1/teams/{team_id}/publish, /v1/teams/{team_id}/publish/previewUser—Team Plan
DELETE/v1/teams/{team_id}User—Team Plan
GET · DELETE/v1/tenants/{tenant_id}/teams, …/teams/{team_id}User—Team Plan
GET · DELETE/v1/tenants/{tenant_id}/agents, …/agents/{agent_id}User—Agent Hub

Writes in the last two groups are rate-limited (see Rate limits).

#Sign-in, tokens and web

MethodPathAuthGateGuide
POST/v1/auth/google, /v1/auth/microsoftNone—Authentication
POST/v1/auth/google/refresh, /v1/auth/microsoft/refresh, /v1/auth/tokenNone—Authentication
POST/v1/auth/revokeNone—Authentication
GET · POST/v1/auth/oauth/request, /v1/auth/oauth/code, /v1/auth/oauth/tokenNone · User · None—Authentication
GET · POST/v1/auth/signup/verify, /v1/auth/signup/claim, /v1/auth/signup/resendNone · None · User——
POST/v1/web/client-tokenNone—Authentication
POST/v1/web/contactClient token——
GET/healthNone——

#Other endpoints

  • Personal tunnels (/v1/tunnels/…) are a separate bearer scheme used by the tunnel connector and your authorized browsers; they are managed from the Tunnels page and documented in Personal Tunnels. They are not part of the API-key surface.
  • Service-account and internal routes (operator billing, OAuth client registration, plan grants, Stripe webhooks) are intentionally not documented here and are not callable with customer credentials.
  • The console (console.radienthq.com) is the supported way to manage plans, payment methods, tunnels and team settings; its screens call a mix of these routes on your behalf.

#Where to go next