Documentation

DevAI Gateway API

One OpenAI-compatible endpoint that runs on your own provider keys (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Mistral), with automatic failover, parallel routing, key rotation and usage tracking.

Base URLhttps://devai-gateway.onrender.com/v1
On this page

Overview

DevAI speaks the OpenAI Chat Completions format, so any OpenAI client works by changing the base URL and the API key. It is bring-your-own-key: requests run only on provider keys you add yourself. Behind that one endpoint the gateway picks one of your providers and keys, retries on the next key or provider when one fails, and records tokens and latency for the dashboard.

EndpointAuthPurpose
POST /v1/chat/completionsdvai_ keyChat completion on your own provider keys, JSON or streaming (SSE)
GET /v1/modelsdvai_ keyModels of the providers your key routes over
GET /v1/usagedvai_ keyUsage summary for the calling key’s project
GET /v1/healthnoneGateway, database and Redis health (no provider needed)
/user/*Clerk sessionYour provider keys, key health, dvai_ keys and hackathon kits
/admin/*adminProviders, keys, projects, analytics (used by the dashboard)
All requests and responses are JSON. Every response carries an X-Request-ID header (e.g. req_3f9c2a1b7d4e8f60); include it when reporting a problem. The gateway runs on a free hosting tier, so the first request after a quiet period can take longer while it wakes up.

Bring your own key

DevAI has no provider keys of its own to lend. Every signed-in user adds their own provider keys under Dashboard → Your keys, creates a DevAI API key there, and calls the gateway with it. The gateway routes that key's requests only over the keys its owner added. Provider usage is billed by each provider to you, and free tiers (Gemini, Groq, OpenRouter :free models) work too.

Getting started
# 1. Sign in → Dashboard → Your keys → "Add your first provider key"
#    (OpenAI, Anthropic, Gemini, Groq, OpenRouter or Mistral; stored encrypted)
# 2. Same page → "New key" → copy your dvai_ key (shown once)
# 3. Call the gateway; it routes only over the keys you added:
curl https://devai-gateway.onrender.com/v1/chat/completions \
  -H "Authorization: Bearer $DEVAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "auto", "messages": [{ "role": "user", "content": "hi" }] }'

# Check your keys without spending tokens (Clerk session token, as the dashboard does):
#   POST /user/pool/health           → every key
#   POST /user/pool/keys/:id/test    → one key

With no provider key (or every key disabled), chat requests fail fast with 400 no_provider_keys: the gateway never falls back to anyone else's keys. If you ask for a model none of your keys serves, you get 400 no_provider_for_model; use "model": "auto", pick a provider with devai.providers, or add a key for that provider.

400 Bad Request
{
  "error": {
    "type": "no_provider_keys",
    "message": "no_provider_keys: add your own provider key in the dashboard (Your keys → Add a key), then retry. DevAI never uses shared keys for your requests.",
    "request_id": "req_3f9c2a1b7d4e8f60"
  }
}
Providerid when adding a keyDefault model for "auto"
OpenAIopenaigpt-4o-mini
Anthropicanthropicclaude-3-5-haiku-20241022
Google Geminigeminigemini-2.5-flash
Groqgroqllama-3.1-8b-instant
OpenRouteropenrouteropenrouter/auto (any model id)
Mistralmistralmistral-small-latest (any model id)
You can set your own default model per provider when you add a key. The gateway admin can still run shared providers (Dashboard → Shared providers) for the admin's own projects; those keys never serve other users' requests.

Authentication

Every /v1 endpoint except /v1/health needs a DevAI API key in the Authorization header. Create one under Dashboard → Your keys; keys start with dvai_ and are shown in full only once.

Header
Authorization: Bearer dvai_your_key

A missing header, an unknown or revoked key, or an inactive project returns 401 with error.type = authentication_error. Keep the key on your server: browser requests are only accepted from origins on the gateway's CORS allowlist.

Chat completions

POST/v1/chat/completionsBearer dvai_…

Creates a model response for a conversation. With "stream": true the answer is sent as Server-Sent Events (see Streaming).

curl https://devai-gateway.onrender.com/v1/chat/completions \
  -H "Authorization: Bearer $DEVAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [
      { "role": "system", "content": "You are a concise assistant." },
      { "role": "user", "content": "Explain binary search in one line." }
    ],
    "temperature": 0.3,
    "max_tokens": 200
  }'

Request body

FieldTypeDescription
messagesarray, requiredList of { role, content }. role is system, user or assistant; content is a string.
modelstringModel id, e.g. gpt-4o-mini or llama-3.1-8b-instant. Defaults to auto: any of your providers, each using its default model.
temperaturenumberSampling temperature, passed to the provider.
max_tokensnumberMaximum tokens to generate.
top_pnumberNucleus sampling, passed to the provider.
stopstring | string[]Stop sequence(s).
streambooleanStream the answer as SSE.
stream_options.include_usagebooleanWith streaming: send a final chunk with token usage.
userstringYour end-user id (optional).
devaiobjectDevAI routing overrides for this request; never forwarded to providers. See Routing.

The devai object

FieldTypeDescription
modesingle | fastest | bestRouting mode (default single).
providersarray (max 10)Providers to use, in priority order. Each entry is a provider id, name or type (google, openai, anthropic, groq, ollama), optionally with a model: "openai:gpt-4o-mini" or { "provider": "groq", "model": "llama-3.1-8b-instant" }.
judgestring | objectModel that picks the winner in best mode, same format as a provider entry.
timeoutMsinteger 1000–120000Per-provider call timeout. Default 30000.
keyStrategyfailover | round_robin | random | least_usedHow a provider's keys are used. See Multi-key strategies.

An invalid body returns 400 invalid_request with per-field details.

Response

The standard OpenAI chat.completion object, plus a devai object describing how the request was routed: the mode, the provider, model and masked key that answered, how many providers (fallbacks) and keys (key_fallbacks) were tried first, and one entry per provider call in candidates. In best mode it also has a judge object (method, reason, provider, model, latency_ms). Keys are always masked to a prefix and the last four characters.

200 OK
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1792310400,
  "model": "llama-3.1-8b-instant",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Binary search halves a sorted range each step..." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 14, "completion_tokens": 22, "total_tokens": 36 },
  "devai": {
    "mode": "single",
    "provider": "Groq",
    "model": "llama-3.1-8b-instant",
    "fallbacks": 0,
    "key": "gsk_…DyMg",
    "key_fallbacks": 1,
    "latency_ms": 912,
    "candidates": [
      { "provider": "Groq", "provider_type": "groq", "model": "llama-3.1-8b-instant",
        "status": "error", "latency_ms": 120, "key": "gsk_…k3Qa", "error": "429 rate limited" },
      { "provider": "Groq", "provider_type": "groq", "model": "llama-3.1-8b-instant",
        "status": "success", "latency_ms": 780, "key": "gsk_…DyMg", "selected": true,
        "usage": { "prompt_tokens": 14, "completion_tokens": 22, "total_tokens": 36 } }
    ]
  }
}

Streaming (SSE)

Set "stream": true to receive OpenAI-compatible Server-Sent Events: a series of chat.completion.chunk objects, a final chunk with finish_reason, and data: [DONE]. With "stream_options": { "include_usage": true } a usage chunk (empty choices) is sent before [DONE].

curl -N https://devai-gateway.onrender.com/v1/chat/completions \
  -H "Authorization: Bearer $DEVAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "stream": true,
    "stream_options": { "include_usage": true },
    "messages": [{ "role": "user", "content": "Write a haiku about failover" }]
  }'
What arrives on the wire
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1792310400,"model":"llama-3.1-8b-instant","choices":[{"index":0,"delta":{"role":"assistant","content":"Keys"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1792310400,"model":"llama-3.1-8b-instant","choices":[{"index":0,"delta":{"content":" rotate quietly"},"finish_reason":null}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1792310400,"model":"llama-3.1-8b-instant","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","created":1792310400,"model":"llama-3.1-8b-instant","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":17,"total_tokens":26}}

data: [DONE]
Failover finishes before the first byte is sent, so a request where every provider fails still gets a normal JSON error with an HTTP status. If a provider breaks after streaming has started, the gateway sends an event {"error":{"type":"stream_error",…}} followed by a final chunk with finish_reason: "error" and [DONE]. In fastest mode the first provider to produce text streams and the others are cancelled.

Routing modes

ModeBehaviour
singleDefault. One provider at a time in priority order. On failure the next key of the same provider is tried, then the next provider.
fastestSends the request to several providers in parallel (up to 5). The first valid answer wins; slower calls are aborted.
bestCollects every answer, then a judge model picks the most accurate and complete one. The decision is returned in devai.judge.

Precedence: the request body devai field, then the x-devai-mode header, then the project's routing defaults (set in the dashboard), then single. With model: "auto" and no provider list, every provider you have a key for takes part, ordered by its configured priority and then recent latency.

# Race every provider; first valid answer wins
curl https://devai-gateway.onrender.com/v1/chat/completions \
  -H "Authorization: Bearer $DEVAI_KEY" \
  -H "Content-Type: application/json" \
  -H "x-devai-mode: fastest" \
  -d '{ "model": "auto",
        "messages": [{ "role": "user", "content": "Summarise RAFT in 2 lines" }] }'

# Ask two providers, let a judge pick the best answer
curl https://devai-gateway.onrender.com/v1/chat/completions \
  -H "Authorization: Bearer $DEVAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "messages": [{ "role": "user", "content": "Is 2^61-1 prime? Explain briefly." }],
    "devai": {
      "mode": "best",
      "providers": ["openai:gpt-4o-mini", "groq:llama-3.1-8b-instant"],
      "judge": "openai:gpt-4o-mini",
      "timeoutMs": 20000
    }
  }'

Multi-key strategies

A provider can hold several API keys. The key strategy only changes the order keys are tried in: a failing key always falls through to the next key of the same provider before DevAI moves to another provider, and keys that are cooling down are skipped.

StrategyOrder
failoverKeys in priority order (oldest first); the first healthy key is used.
round_robinDefault for your keys. Rotates the starting key on every request to spread load evenly.
randomShuffles the healthy keys for each request.
least_usedKey with the fewest recent requests first.

Set it in the dashboard (Your keys → Rotation), or override per request with devai.keyStrategy in the body or the x-devai-key-strategy header. The body takes precedence over the header, the header over the project default, and that over the provider setting.

Per-request override
{
  "model": "auto",
  "messages": [{ "role": "user", "content": "hi" }],
  "devai": { "keyStrategy": "round_robin" }
}

# or as a header (body wins if both are set):
x-devai-key-strategy: least_used

Headers

Request headers

HeaderDescription
AuthorizationRequired: Bearer dvai_… (or the admin credential on /admin).
Content-Typeapplication/json for POST requests.
x-devai-modesingle, fastest or best. Unknown values are ignored.
x-devai-key-strategyfailover, round_robin, random or least_used. Unknown values are ignored.

Response headers (chat completions)

HeaderDescription
X-Request-IDUnique id of this request (on every response).
x-devai-modeRouting mode that was used.
x-devai-providerProvider and model that answered, e.g. OpenAI/gpt-4o-mini.
x-devai-fallbacksHow many providers failed before this one answered.
x-devai-keyName and masked hint of the key that answered, e.g. Key 2 (sk-p...DyMg).
x-devai-key-fallbacksHow many keys of the provider failed before this one.
X-CacheHIT or MISS for cacheable requests (see Caching).

Browsers on allowlisted origins can read all of these headers (they are exposed via CORS).

Caching

Plain requests on the admin's own projects (mode single and no devai field) are cached in Redis for one hour. Requests on your own keys are never served from, or written to, the shared cache; an identical request is answered from the cache with X-Cache: HIT. Identical requests that arrive at the same time share a single upstream call. To skip the cache, send any devai field or use a parallel mode. Streaming requests are cached when they finish normally.

Models

GET/v1/modelsBearer dvai_…

Lists the models your key can use, in the OpenAI list format: each of your providers' default model plus any models registered for them. The list is empty until you add a provider key.

curl
curl https://devai-gateway.onrender.com/v1/models -H "Authorization: Bearer $DEVAI_KEY"

# {
#   "object": "list",
#   "data": [
#     { "id": "gpt-4o-mini", "object": "model", "created": 1792310400, "owned_by": "OpenAI" },
#     { "id": "llama-3.1-8b-instant", "object": "model", "created": 1792310400, "owned_by": "Groq" }
#   ]
# }
# Only models of the providers YOUR key routes over; empty until you add a provider key.

Usage

GET/v1/usage?period=today|week|monthBearer dvai_…

Token and request totals for the project that owns the key. period defaults to today; cacheHitRate is a percentage.

curl
curl "https://devai-gateway.onrender.com/v1/usage?period=week" -H "Authorization: Bearer $DEVAI_KEY"

# {
#   "requestCount": 42, "inputTokens": 5120, "outputTokens": 8730, "totalTokens": 13850,
#   "avgLatencyMs": 840, "cacheHitRate": 12, "period": "week"
# }

Health

GET/v1/healthpublic

No authentication, and no provider is needed: it reports the gateway, database and Redis. status is unhealthy when the database is down and degraded when Redis is down. Provider keys belong to users, so they never change this status; providers lists only the admin's shared providers from the last background check (key counts only) and is usually empty. Check your own keys in the dashboard or with POST /user/pool/health.

curl
curl https://devai-gateway.onrender.com/v1/health

# {
#   "status": "healthy",            // healthy | degraded | unhealthy (gateway, database, Redis)
#   "gateway": "healthy",
#   "database": "healthy",
#   "redis": "healthy",
#   "byok": true,
#   "providers": [],                // shared providers only, if any; never affects status
#   "providers_checked_at": null,
#   "uptime": 5321.4,
#   "timestamp": "2026-10-18T08:00:00.000Z"
# }

Your provider keys

Signed-in users add their own provider keys (OpenAI, Anthropic, Gemini, Groq, OpenRouter, Mistral) under Dashboard → Your keys. A DevAI API key (dvai_…, created on that page) is routed only over that user's keys, with their rotation strategy. A key that is rate-limited cools down and the next key answers. This traffic is never cached and never uses shared keys.

PlanProvider keys
Free2
Student10
ProUnlimited
TeamUnlimited

The pool is managed with the user's Clerk session token (Authorization: Bearer <session JWT>); admin keys and project keys are not accepted. Adding a key beyond the plan limit returns 403 plan_limit_reached. With no enabled key, chat requests return 400 no_provider_keys.

EndpointDescription
GET /user/poolPlan, limit, key strategy, keys (masked, with health: healthy, unused, cooling_down, quota_exhausted, error, disabled, and lastCheck), onboarding (nextStep: add_provider_key, create_api_key or done) and 30-day stats: served, tokens, rateLimitsAbsorbed, stretchFactor.
POST /user/pool/keysAdd a key: provider (openai, anthropic, gemini, groq, openrouter, mistral), apiKey, optional name, defaultModel, quotaLimit. Stored encrypted.
POST /user/pool/healthTest every enabled key with a cheap authenticated call (no tokens). Returns status (healthy, degraded, unhealthy, no_keys), healthy, total and one entry per key. A key that fails is skipped by routing for 5 minutes.
POST /user/pool/keys/:id/testTest one of your keys the same way.
PATCH /user/pool/keys/:idUpdate enabled or name.
DELETE /user/pool/keys/:idRemove a key from the pool.
PUT /user/pool/strategy{ "keyStrategy": "round_robin" | "least_used" | "failover" | "random" }
POST /user/pool/api-keysCreate your dvai_ key (shown once). DELETE /user/pool/api-keys/:id revokes it.
stretchFactor = answers served by all your keys ÷ answers served by your busiest single key. 3× means your keys served three times what one free key managed on its own.

Hackathon kits

Team plan users can create kits under Dashboard → Hackathon kits. A kit is a set of temporary dvai_ keys for a team, with a request cap and/or token cap, an end time (up to 30 days), an optional max team size and an optional allowed-model list. Kit traffic runs on the owner's own provider keys, and the keys are used like any other API key. With an allowed-model list, "model": "auto" uses the first allowed model.

Every kit has a public page at /kit/<shareToken> that shows the quota left and the end time. Its data comes from GET /public/kits/:shareToken (no auth, never includes keys). Caps are counted in Redis across all of the kit's keys. Tokens are added when a response finishes, so the last request before a token cap can go slightly over it.

Statuserror.typeWhen
400kit_model_not_allowedThe requested model is not on the kit’s allowed list (the message lists the allowed models).
403kit_revokedThe owner revoked the kit.
403kit_expiredThe kit’s end time has passed.
429kit_request_cap_reachedThe kit has used all of its requests.
429kit_token_cap_reachedThe kit has used its token cap.
Endpoint (Clerk session)Description
GET /user/kitsYour kits with status (active, expired, revoked) and usage (requests, tokens, requestsRemaining, tokensRemaining).
POST /user/kitsname, expiresAt (ISO 8601), requestCap and/or tokenCap, optional maxTeamSize, keyCount (1–50, ≤ team size), allowedModels. Returns the keys once. Requires Team (403 plan_required otherwise).
POST /user/kits/:id/revokeRevoke a kit; its keys stop working immediately.

Errors

Errors use one shape: an error object with type, message and, on /v1 routes, request_id.

Statuserror.typeWhen
400invalid_requestBody is not JSON or fails validation (details lists the fields).
400no_provider_keysYou have not added a provider key yet (or all are disabled). Add one under Your keys. Shared keys are never used instead.
400no_provider_for_modelNone of your provider keys serves the requested model. Use auto, devai.providers, or add a key for that provider.
400no_eligible_providersNone of the providers in devai.providers are among yours.
400validation_errorAdmin API: a required field is missing or invalid.
401authentication_errorMissing/invalid Authorization header, unknown or revoked key, inactive project, invalid admin key or expired session.
403permission_errorAdmin API: valid Clerk session, but the user is not on the admin allowlist.
404not_foundUnknown route, or an admin resource that does not exist.
409conflictAdmin API: the model already exists for this provider.
500server_errorUnexpected gateway error.
502all_providers_failedEvery provider and key failed or was skipped. Includes retry_after (seconds) and one attempts entry per call.
400kit_model_not_allowedHackathon kit: model not allowed (see kits).
403kit_revoked / kit_expiredHackathon kit was revoked or has ended.
403plan_limit_reached / plan_requiredKey pool: plan key limit reached. Kits: Team plan required.
429kit_request_cap_reached / kit_token_cap_reachedHackathon kit used its cap.
502 Bad Gateway
{
  "error": {
    "type": "all_providers_failed",
    "message": "All 1 provider attempt(s) failed",
    "request_id": "req_3f9c2a1b7d4e8f60",
    "retry_after": 30,
    "attempts": [
      { "provider": "Groq", "provider_type": "groq", "model": "llama-3.1-8b-instant",
        "status": "skipped", "latency_ms": 0, "key": "gsk_…DyMg", "error": "rate limited (cooling down)" }
    ]
  }
}

Rate limits & quotas

The gateway does not add its own per-project request limit; the limits that apply are your providers' limits. DevAI tracks them per key and routes around keys that hit them:

Provider responseWhat DevAI does
429 rate limitKey cools down for 30 s (or the provider's Retry-After, 5 s – 1 h); next key is tried.
429 / 402 quota or billingKey cools down for 10 minutes (or the provider hint); next key is tried.
401 / 403 invalid keyKey cools down for 5 minutes; next key is tried.
5xx, timeout, networkThat key + model cools down for 60 s; next key is tried.
Other 4xxReturned as a failure without trying other keys (they would fail the same way).

Each provider key can also have a token quota (set when the key is added). A key over its quota is skipped until the quota resets (every 30 days). Google per-model quotas only cool down that key + model, so other models on the same key keep working. When nothing is left to try you get 502 all_providers_failed with retry_after.

Admin API

The admin part of the dashboard (shared providers, projects, analytics) uses /admin/*. Regular users never need it: their keys live under /user/*. These endpoints accept either the gateway's ADMIN_API_KEY or a Clerk session token of a user on the gateway's admin allowlist (ADMIN_EMAILS / ADMIN_USER_IDS), as Authorization: Bearer …. A Clerk user who is not on the list gets 403. Project keys (dvai_…) do not work here. Responses wrap results in data.

curl
# With the gateway's ADMIN_API_KEY (server-side only, never in a browser)
curl https://devai-gateway.onrender.com/admin/me -H "Authorization: Bearer $DEVAI_ADMIN_KEY"
# { "data": { "method": "admin_key" } }

# With a Clerk session token of an allowlisted user (what the dashboard sends)
# { "data": { "method": "clerk", "userId": "user_…", "email": "you@example.com" } }

# Create a project, then an app key for it
curl -X POST https://devai-gateway.onrender.com/admin/projects \
  -H "Authorization: Bearer $DEVAI_ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "My app", "description": "Chat feature" }'

curl -X POST https://devai-gateway.onrender.com/admin/api-keys/project/PROJECT_ID \
  -H "Authorization: Bearer $DEVAI_ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "production" }'
# → data.key is the full dvai_ key. It is returned only once.
EndpointDescription
GET /admin/meWho is calling: { method: "admin_key" } or { method: "clerk", userId, email }.
GET /admin/providersList providers with key strategy, key count and model count.
POST /admin/providersAdd a provider: type (openai, anthropic, google, groq, ollama), name, optional baseUrl, config.
PATCH /admin/providers/:idUpdate name, baseUrl, status or config (keyStrategy, keyOrder, priority; merged with the existing config).
DELETE /admin/providers/:idDelete a provider.
POST /admin/providers/:id/testTest the provider connection.
GET /admin/providers/:id/modelsList the provider’s models.
POST /admin/providers/:id/modelsAdd a model (modelId required).
DELETE /admin/providers/:id/models/:modelIdRemove a model.
POST /admin/providers/:id/sync-modelsFetch the model list from the provider.
GET /admin/credentials/provider/:providerIdList a provider’s keys (masked).
POST /admin/credentials/provider/:providerIdAdd a key: name, apiKey, optional quotaLimit. Stored encrypted (AES-256-GCM).
PATCH /admin/credentials/:idUpdate name, enabled or status.
DELETE /admin/credentials/:idDelete a key.
POST /admin/credentials/:id/testTest one key and update its status (clears its rate-limit cooldown).
GET /admin/projectsList projects.
POST /admin/projectsCreate a project: name, optional description.
PATCH /admin/projects/:idUpdate name, description, status or routing (same fields as the devai object).
DELETE /admin/projects/:idDelete a project.
GET /admin/api-keys/project/:projectIdList a project’s keys (prefix only).
POST /admin/api-keys/project/:projectIdCreate a dvai_ key (name required). The full key is returned only in this response.
DELETE /admin/api-keys/:idRevoke a key.
GET /admin/analytics/overviewTotals for ?period=today|week|month.
GET /admin/analytics/by-providerUsage per provider (same period parameter).
GET /admin/analytics/by-modelUsage per model.
GET /admin/analytics/by-projectUsage per project.
GET /admin/analytics/recent-requestsLatest requests (?limit=, default 50).
GET /admin/audit-logsAudit log of admin changes.
Never put the admin key in browser code. In the hosted dashboard you sign in with Clerk and the dashboard sends your session token automatically.

Ready to make a request?

Sign in, add your first provider key, create your dvai_ key, and call the endpoint.