API Reference
API reference
Every endpoint the gateway actually serves. The base URL is https://api.celon.ai (http://localhost:8787 for local dev), and authentication uses the Authorization: Bearer <key> or x-api-key header. GET /v1/models and GET /v1/endpoints are open without a key; the self-serve console (/v1/me) authenticates with a Supabase session token, and admin endpoints with CELON_MASTER_KEY.
Error format
Every error is returned in an OpenAI-compatible envelope. If an upstream error occurs mid-stream, the same error object shape is delivered as SSE data.
{
"error": {
"message": "Monthly budget exceeded",
"type": "insufficient_quota",
"code": "budget_exceeded"
}
}| Status | code | Meaning |
|---|---|---|
| 400 | — | The body is not JSON, or a required field is missing |
| 400 | guardrail_violation | Blocked by a guardrail — a block-mode violation or a banned word (action: block) was detected |
| 401 | invalid_api_key | Key missing, invalid, or disabled |
| 402 | budget_exceeded | Monthly budget exceeded (organization, team, or key reject policy) |
| 403 | model_blocked | Requested a model blocked by the organization or key model policy |
| 403 | master_key_required | Called an admin endpoint with a regular key |
| 404 | model_not_found | A model id not in the catalog |
| 502 | upstream_unavailable | Every candidate in the fallback chain failed |
POST /v1/chat/completions
OpenAI-compatible chat completions. OpenAI parameters not listed in the table below are passed through the adapter unchanged, without validation.
| Field | Type | Description |
|---|---|---|
model | string · required | celon/auto · celon/code · celon/quality · celon/nitro · celon/floor · a catalog id (vendor/model) |
messages | Message[] · required | OpenAI chat format. Supports text and image_url content parts |
temperature | number | Passed through to the upstream. Must be ≤ 0.3 to be cacheable (see Caching) |
top_p · stop · presence_penalty · frequency_penalty · seed · user | passthrough | Passed through to the upstream |
max_tokens / max_completion_tokens | number | Output token cap |
stream | boolean | SSE streaming (see the format below) |
tools · tool_choice | object | Function calling — excludes the request from caching, and signals structured output to the classifier |
response_format | text | json_object | json_schema | Structured output — factored into the classifier score |
celon | object | Routing/cache options — dial, anchor, session_id, context_compression, tier, no_cache, no_pii_mask, fallbacks (see Smart routing) |
curl https://api.celon.ai/v1/chat/completions \
-H "Authorization: Bearer sk-celon-…" \
-H "Content-Type: application/json" \
-d '{
"model": "celon/auto",
"messages": [{ "role": "user", "content": "Hello!" }],
"temperature": 0.2,
"stream": false
}'The response carries a celon metadata field and x-celon-* headers (x-celon-model · x-celon-tier · x-celon-cache · x-celon-dial · x-celon-sticky · x-celon-guardrail-findings, and more) — for the full header table and field definitions, see Response metadata.
SSE streaming format
With stream: true, OpenAI-compatible chunks are delivered as text/event-stream. The final data chunk has an empty choices array and carries the usage and celon metadata, followed by data: [DONE] to terminate.
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","created":1750000000,"model":"meta-llama/llama-4-maverick","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","created":1750000000,"model":"meta-llama/llama-4-maverick","choices":[{"index":0,"delta":{"content":"Hi there"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","created":1750000000,"model":"meta-llama/llama-4-maverick","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","created":1750000000,"model":"meta-llama/llama-4-maverick","choices":[],"usage":{"prompt_tokens":21,"completion_tokens":96,"total_tokens":117},"celon":{"model":"meta-llama/llama-4-maverick","provider":"deepinfra","tier":2,"classifier_score":0.41,"cache_hit":null,"cost_usd":0.000122,"saved_usd":0.003886,"latency_ms":842,"pii_masked":0,"attempts":[],"dial":"balanced","sticky":false,"guardrails":{"mode":"mask","findings":0,"blocked":false}}}
data: [DONE]POST /v1/embeddings
OpenAI-compatible embeddings. model defaults to openai/text-embedding-3-small when omitted, and accepts both catalog ids and raw OpenAI names (text-embedding-3-large). input is a string or string[]. The response's celon field carries provider · cache_hit · cost_usd · latency_ms.
curl https://api.celon.ai/v1/embeddings \
-H "Authorization: Bearer sk-celon-…" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/text-embedding-3-small",
"input": ["first sentence", "second sentence"]
}'POST /v1/images/generations
OpenAI-compatible image generation. model defaults to openai/dall-e-3 when omitted, and accepts prompt (required), n (≤ 10), size, quality, style, and response_format. The prompt goes through the same guardrail input checks as chat, and a block-mode violation is rejected with 400 guardrail_violation. Billing is per call, not per token — perCallUsd × n. The response's celon metadata carries model · provider · cost_usd · latency_ms.
curl https://api.celon.ai/v1/images/generations \
-H "Authorization: Bearer sk-celon-…" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/dall-e-3",
"prompt": "Seoul at night, minimalist illustration",
"n": 1,
"size": "1024x1024"
}'{
"created": 1750000000,
"data": [{ "url": "https://…/img.png" }],
"celon": {
"model": "openai/dall-e-3",
"provider": "openai",
"cost_usd": 0.04,
"latency_ms": 4120
}
}POST /v1/audio/transcriptions
OpenAI-compatible audio transcription (multipart/form-data). The form fields are file (required, the audio part), model (defaults to openai/whisper-1 when omitted), and language. Billing is by duration — the playback length is parsed directly from the audio container, rounded up to whole minutes (with a 1-minute floor), and multiplied by perCallUsd. If parsing fails, the request is not failed; it falls back to a bytes/32k estimate. The response's celon metadata carries the actual billed minutes (billed_minutes) and how the duration was determined (duration_source: "parsed" | "estimated").
curl https://api.celon.ai/v1/audio/transcriptions \
-H "Authorization: Bearer sk-celon-…" \
-F file=@meeting.mp3 \
-F model=openai/whisper-1 \
-F language=ko{
"text": "Full meeting transcript…",
"celon": {
"model": "openai/whisper-1",
"provider": "openai",
"cost_usd": 0.012,
"latency_ms": 1830,
"billed_minutes": 2,
"duration_source": "parsed"
}
}GET /v1/models
Returns the virtual models (celon/auto, celon/code, celon/quality, celon/nitro, celon/floor) and the full catalog. Each entry's celon metadata reports the tier, $/1M pricing, context window, modality, and whether it can currently be served (available — whether that provider's key is configured). This endpoint is public — it can be called without an API key, so the web catalog reads it directly from the browser.
# Public endpoint — no key required
curl https://api.celon.ai/v1/models{
"object": "list",
"data": [
/* celon/* package labels are served in Korean */
{ "id": "celon/auto", "object": "model", "owned_by": "celon",
"celon": { "tier": null, "pricing": null, "modality": "text", "available": true } },
{ "id": "celon/quality", "object": "model", "owned_by": "celon",
"celon": { "virtual": true, "package": { "label": "최고 품질", "strategy": "frontier", "dial": "strict-quality" } } },
{ "id": "celon/nitro", "object": "model", "owned_by": "celon",
"celon": { "virtual": true, "package": { "label": "최고 속도", "strategy": "nitro", "dial": "low-latency" } } },
{ "id": "celon/floor", "object": "model", "owned_by": "celon",
"celon": { "virtual": true, "package": { "label": "최저가", "strategy": "floor", "dial": "max-savings" } } },
{ "id": "anthropic/claude-fable-5", "object": "model", "owned_by": "anthropic",
"celon": { "tier": 1, "pricing": { "inputPerM": 10, "outputPerM": 50 },
"context_window": 500000, "modality": "text", "available": true } }
]
}GET /v1/endpoints
Per-model provider endpoint statistics — returns the provider, $/1M pricing and its source (pricingSource), measured latency/throughput/uptime and their sources, and the sample count. It's a public endpoint, so the web catalog reads it directly from the browser. The response has the shape { modelId: EndpointStat[] }, and GET /v1/endpoints/:vendor/:model queries a single model and returns that model's EndpointStat[] (an unregistered model returns 404).
# Public endpoint — no key required
curl https://api.celon.ai/v1/endpoints
# A single model only: /v1/endpoints/deepseek/deepseek-v3.2{
"deepseek/deepseek-v3.2": [
{
"modelId": "deepseek/deepseek-v3.2",
"provider": "deepinfra",
"inputPerM": 0.27,
"outputPerM": 0.40,
"pricingSource": "openrouter-endpoints",
"latencyMs": 640,
"latencySource": "measured",
"throughputTps": 78.2,
"uptimePct": 0.998,
"uptimeSource": "measured",
"samples": 128
}
]
}Self-serve console — /v1/me
GET /v1/me returns the logged-in user and their organization (org: null if no organization exists yet). POST /v1/me/provision idempotently bootstraps the organization and issues a default key once (the plaintext key is shown only in this response). From there, GET · POST /v1/me/keys list and issue keys, and DELETE /v1/me/keys/:id revokes one (soft delete — ledger attribution is preserved). GET /v1/me/dashboard?days=, GET /v1/me/billing/balance, and POST /v1/me/billing/checkout each serve the same data as /v1/dashboard and /v1/billing/* below, scoped to the organization resolved from the JWT.
# Every /v1/me call authenticates with a Supabase session token (JWT)
curl https://api.celon.ai/v1/me \
-H "Authorization: Bearer <supabase-access-token>"
# → { "userId": "…", "email": "dev@acme.co.kr", "org": null | {…} }{
"org": { "id": "org_abc123", "name": "acme", "plan": "prepaid", "billingMode": "prepaid" },
"created": true,
"defaultKey": {
"id": "key_7f3d",
"name": "default",
"prefix": "sk-celon-…1a2b",
"monthlyBudgetUsd": null,
"overBudgetBehavior": "reject",
"disabled": false,
"createdAt": "2026-07-16T02:00:00.000Z",
"key": "sk-celon-<48 hex — shown only in this response>"
}
}# Issue a key (the plaintext key is shown only in this response)
curl -X POST https://api.celon.ai/v1/me/keys \
-H "Authorization: Bearer <supabase-access-token>" \
-H "Content-Type: application/json" \
-d '{ "name": "prod-backend", "monthlyBudgetUsd": 1000 }'
# List (masked view — hash and BYOK keys are never returned)
curl https://api.celon.ai/v1/me/keys \
-H "Authorization: Bearer <supabase-access-token>"
# Revoke (soft delete — ledger attribution is preserved)
curl -X DELETE https://api.celon.ai/v1/me/keys/key_7f3d \
-H "Authorization: Bearer <supabase-access-token>"GET /v1/dashboard
The organization dashboard read model — returns spend, savings, and baseline; cache hit rate; request count; per-tier aggregates; per-key and per-team budget gauges; daily trends; recent usage (metadata only); and a Korean briefing. Prepaid organizations also get the credit balance (balanceUsd), billing mode (billingMode), and the platform fee rate (platformFeePct, default 5). Query days (1–365, default 30). A regular key sees only its own organization; a master key can view any organization via ?orgId=.
curl "https://api.celon.ai/v1/dashboard?days=30" \
-H "Authorization: Bearer sk-celon-…"{
"orgId": "org_abc123",
"windowDays": 30,
"spendUsd": 12.4,
"savedUsd": 63.1,
"baselineUsd": 75.5,
"cacheHitRate": 0.23,
"requests": 1284,
"balanceUsd": 42.5,
"billingMode": "prepaid",
"platformFeePct": 5,
"byTier": { "1": 96, "2": 512, "3": 676 },
"keys": [ { "name": "prod-backend", "prefix": "sk-celon-…1a2b",
"spentUsd": 8.2, "budgetUsd": 1000, "overBudgetBehavior": "downgrade" } ],
"teams": [ { "id": "team_9a2b", "name": "Marketing", "spentUsd": 5.1,
"budgetUsd": 500, "overBudgetBehavior": "reject", "keys": 3 } ],
"daily": [ { "date": "2026-07-15", "spendUsd": 0.62, "savedUsd": 3.1, "baselineUsd": 3.72 } ],
"recent": [ /* metadata only — no bodies (ZDR) */ ],
/* briefing text is generated by the gateway in Korean */
"briefing": { "summary": "지난 30일 동안 1284건 요청, …", "highlights": ["캐시 적중률 23.0% …"] }
}Billing and balance
Billing is based on prepaid credits. The 5% platform fee is charged once, at top-up — top up $100 and you pay $105, leaving a $100 balance. The ledger records the gross payment (toss/polar) and the fee (fee) as separate rows, so the charge explains itself.
Requests then draw the model's raw cost (a usage row), and a request served from cache — which made no upstream call — draws nothing. Postpaid organizations never top up, so their fee accrues per request as a fee row instead and lands on the monthly invoice.
GET /v1/billing/balance
Returns the current prepaid balance along with the plan, billing mode, and fee rate.
curl https://api.celon.ai/v1/billing/balance \
-H "Authorization: Bearer sk-celon-…"
# → { "balanceUsd": 42.5, "plan": "prepaid", "billingMode": "prepaid", "platformFeePct": 5 }POST /v1/billing/checkout
Creates a hosted top-up session — amountUsd (required), provider (polar | toss), successUrl. Polar uses a redirect checkout; Toss links to our /billing/topup widget page. Redirect to the url in the response. On a gateway with no payment provider configured (no TOSS_CLIENT_KEY/TOSS_SECRET_KEY or Polar keys), 400 checkout_unavailable is returned.
curl -X POST https://api.celon.ai/v1/billing/checkout \
-H "Authorization: Bearer sk-celon-…" \
-H "Content-Type: application/json" \
-d '{ "amountUsd": 50, "provider": "toss",
"successUrl": "https://celon.ai/billing/topup" }'{
"url": "https://celon.ai/billing/topup?orderId=ord_…",
"provider": "toss",
"ref": "ord_…"
}POST /v1/billing/toss/confirm
Confirms a Toss widget payment server-side and grants credit — pass paymentKey, orderId, and amount (the KRW integer Toss returned to the success URL). The grant is idempotent — reconfirming the same payment returns alreadyApplied: true and does not double-grant.
curl -X POST https://api.celon.ai/v1/billing/toss/confirm \
-H "Authorization: Bearer sk-celon-…" \
-H "Content-Type: application/json" \
-d '{ "paymentKey": "…", "orderId": "ord_…", "amount": 65000 }'
# → { "granted": 48.5, "alreadyApplied": false, "balanceUsd": 91.0 }Payment providers also reflect top-ups through a signature-verified webhook (POST /v1/billing/webhook/:provider, public and always 200).
Admin API
POST /admin/orgs · GET /admin/orgs · PATCH /admin/orgs/:id
Create, read, and update organizations. Set zdr (the no-body-storage policy), monthlyBudgetUsd (a hard cap, null = unlimited), and plan (byok | prepaid | enterprise), along with the organization's default governance policy (governance — custom PII patterns, banned words, model policy; see the governance docs). GET /admin/orgs returns the full organization list, and GET /admin/orgs/:id returns a single organization.
Changing billingMode to "postpaid" via PATCH /admin/orgs/:id switches to month-end postpaid settlement — requests are not blocked even at a zero balance, and usage accrues as a negative balance (debt). It's a contact-based, master-only switch and is not exposed to self-serve.
curl -X POST https://api.celon.ai/admin/orgs \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "acme", "zdr": true, "monthlyBudgetUsd": 5000 }'curl -X PATCH https://api.celon.ai/admin/orgs/org_abc123 \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"governance": {
"guardrails": {
"mode": "mask",
"pii": {
"entities": { "EMAIL": false },
"custom": [{ "name": "EMPLOYEE_ID", "pattern": "EMP-\\d{6}" }]
},
"bannedWords": [
{ "word": "Project Falcon", "action": "block" },
{ "word": "Internal codename" }
]
},
"models": {
"blockedModels": ["xai/*"],
"allowedModels": []
}
}
}'# Switch to postpaid settlement (contact-based, master only) — no blocking
# even at zero balance; debt accrues as a negative balance and is settled at month-end close.
curl -X PATCH https://api.celon.ai/admin/orgs/org_abc123 \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "billingMode": "postpaid" }'POST /admin/teams · GET /admin/teams · PATCH·DELETE /admin/teams/:id
Manage departments (teams). A team is the budget unit between the organization and the key — set a per-team monthly budget (monthlyBudgetUsd) and an over-budget policy (overBudgetBehavior: reject = deny with 402 / downgrade = switch to Tier 3). Keys join via teamId, and GET /admin/teams?orgId= returns each team's spend this month (spentUsd) and key count. Deleting a team moves its keys to no team, and past spend attribution is unchanged.
# Create a team (budget $500, rejects with 402 when exceeded)
curl -X POST https://api.celon.ai/admin/teams \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "orgId": "org_abc123", "name": "Marketing",
"monthlyBudgetUsd": 500, "overBudgetBehavior": "reject" }'
# List (includes this month's spend + key count)
curl "https://api.celon.ai/admin/teams?orgId=org_abc123" \
-H "Authorization: Bearer $CELON_MASTER_KEY"
# Assign a key to a team
curl -X PATCH https://api.celon.ai/admin/keys/key_7f3d \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "teamId": "team_9a2b" }'POST /admin/keys
Issue a virtual key (sk-celon- + 48 hex). Configure, per key: the budget (monthlyBudgetUsd, overBudgetBehavior), team membership (teamId), PII masking, ZDR, the routing policy (routing — the default intent dial and the anchorModel ceiling), the guardrail policy (guardrails — mode block | mask | shadow, per-detector detectors, custom pii and bannedWords, output inspection streamOutput), the model policy (modelPolicy — blockedModels/allowedModels), and BYOK upstream keys (byokKeys). Key policy takes precedence over the organization's governance, and celon.dial / celon.anchor in the request body take precedence over key policy.
curl -X POST https://api.celon.ai/admin/keys \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"orgId": "org_abc123",
"name": "prod-backend",
"monthlyBudgetUsd": 1000,
"overBudgetBehavior": "downgrade",
"piiMasking": true,
"zdr": true,
"routing": {
"dial": "strict-quality",
"anchorModel": "anthropic/claude-fable-5"
},
"guardrails": {
"mode": "mask",
"detectors": { "pii": true, "secrets": true, "injection": true, "presidio": false },
"streamOutput": true
},
"byokKeys": {}
}'
# The plaintext key (sk-celon-…) is shown only in this responsePOST /admin/credits · GET /admin/credits
Manually grant or adjust prepaid credit and read the ledger history. POST adds a ledger entry with amountUsd (positive = top-up, negative = deduction) and source (admin-grant · promo · adjustment · polar · toss), and GET ?orgId= returns the current balance (balanceUsd) and credit history (credits). Polar/Toss top-ups and usage deductions are recorded in the same ledger.
# Manual credit grant/adjustment (positive = top-up, negative = deduction)
curl -X POST https://api.celon.ai/admin/credits \
-H "Authorization: Bearer $CELON_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "orgId": "org_abc123", "amountUsd": 100, "source": "admin-grant" }'
# Ledger history + current balance
curl "https://api.celon.ai/admin/credits?orgId=org_abc123" \
-H "Authorization: Bearer $CELON_MASTER_KEY"
# → { "balanceUsd": 142.5, "credits": [ { "id": "…", "ts": "…",
# "orgId": "org_abc123", "amountUsd": 100, "source": "admin-grant" } ] }GET /admin/audit
Returns the immutable, append-only audit log of guardrail decisions as an events array, newest first. Each event carries only id, ts, orgId, keyId, endpoint, model, dial, the outcome decision (allowed · blocked · masked · shadow · error), the detection count guardrailFindings, the per-category tally findingsByCategory (pii · secret · injection · presidio · banned), and the response status — no prompt or response bodies (ZDR). Query parameters: orgId (required), limit (1–1000). For the policy and mode descriptions, see Governance — Audit Trail.
curl "https://api.celon.ai/admin/audit?orgId=org_abc123&limit=50" \
-H "Authorization: Bearer $CELON_MASTER_KEY"{
"events": [
{
"id": "audit_9f2a",
"ts": 1750000000000,
"orgId": "org_abc123",
"keyId": "key_7f3d",
"endpoint": "chat",
"model": "meta-llama/llama-4-maverick",
"dial": "balanced",
"decision": "masked",
"guardrailFindings": 2,
"findingsByCategory": { "pii": 1, "secret": 1 },
"status": 200
}
]
}GET /admin/usage/summary
An organization-level usage summary — returns the request count, total cost, total savings, cache hit rate, and per-tier and per-model aggregates.
curl "https://api.celon.ai/admin/usage/summary?orgId=org_abc123" \
-H "Authorization: Bearer $CELON_MASTER_KEY"{
"requests": 1284,
"totalCostUsd": 12.4,
"totalSavedUsd": 63.1,
"cacheHitRate": 0.23,
"byTier": { "1": 96, "2": 512, "3": 676 },
"byModel": {
"google/gemini-3-flash": { "requests": 512, "costUsd": 4.2 }
}
}GET /admin/health
An operational status snapshot — exposes the active health-probe configuration, the last run time, and the real state of fail-open integrations. Optional guardrails (presidio, promptGuard) show whether they're configured (configured) and, when configured but failing open (passing through), the failure count and last error (failures, lastError) — without this, “configured but dead and simply passing through” would be indistinguishable from “nothing detected.” It also carries the cache backend (upstash | memory), whether the semantic cache is enabled, and whether the mock provider is enabled.
curl https://api.celon.ai/admin/health \
-H "Authorization: Bearer $CELON_MASTER_KEY"{
"enabled": true,
"intervalMinutes": 15,
"lastRun": "2026-07-16T02:00:00.000Z",
"guardrails": {
"presidio": { "configured": false },
"promptGuard": { "configured": true }
},
"cache": {
"backend": "upstash",
"semantic": { "enabled": true }
},
"mock": { "enabled": false }
}POST /admin/health/probe · POST /admin/endpoints/refresh
POST /admin/health/probe runs the active health probe once immediately and returns a per-provider success/failure and latency report. POST /admin/endpoints/refresh re-fetches the endpoint stats feed (OpenRouter) and returns a refresh summary (model/row counts, how many were priced from the feed, how many have measured stats).
# Run one active health probe immediately (per-provider ok/failed/latency)
curl -X POST https://api.celon.ai/admin/health/probe \
-H "Authorization: Bearer $CELON_MASTER_KEY"
# Re-fetch the endpoint stats feed (OpenRouter)
curl -X POST https://api.celon.ai/admin/endpoints/refresh \
-H "Authorization: Bearer $CELON_MASTER_KEY"
# → { "refreshedAt": "…", "models": 41, "rows": 128, "feedPriced": 96, "measured": 54 }GET /healthz
A liveness check. Used by the health probe in deployment environments.
curl https://api.celon.ai/healthz
# → { "status": "ok" }GET /metrics
An in-process metrics snapshot (JSON) — total requests, requests per minute, per-endpoint and per-tier counts, cache hit rate, and p50/p95 latency (over the most recent 1,000 samples).
curl https://api.celon.ai/metrics{
"uptimeSeconds": 3600,
"requests": { "total": 1284, "ok": 1279, "error": 5, "perMinute": 21 },
"byEndpoint": { "chat": 1201, "embeddings": 83 },
"byTier": { "1": 96, "2": 512, "3": 676 },
"cache": { "exactHits": 214, "semanticHits": 81, "hitRate": 0.23 },
"latencyMs": { "p50": 640, "p95": 2210, "avg": 812, "samples": 1000 }
}