API reference
Every endpoint on the public API your code calls and the management API the dashboard uses. Both are documented because people automate against both.
Public API
Bearer-authenticated with a gateway key. OpenAI-shaped, which is the entire reason switching is a base URL change. Reading with a machine? /llms.txt is all of this in one plain-text file.
Inference
The OpenAI-compatible endpoint. Any OpenAI SDK works by changing the base URL.
POST /v1/chat/completions
Send a chat completion, streamed or not.
{ "model": "...", "messages": [...], "models": ["fallback"], "stream": false }
Extends OpenAI's body with an optional models array, tried in order when a provider fails in a way that would not repeat.
GET /v1/models
The catalogue, with the price of every model per million tokens.
Unauthenticated. Region-restricted models are hidden from requests originating in a restricted country.
Scoring
Attach a rating to a request after the fact, from a person or from a grader.
POST /v1/feedback
Score one request by its id.
{ "request_id": "...", "name": "thumbs", "value": true, "comment": "...", "source": "user" }
A boolean becomes 1 or 0 so everything averages. Scoring the same name twice corrects rather than duplicates.
Management API
What the dashboard runs on, authenticated by the session cookie rather than a gateway key. The two are never interchangeable: one identifies a person, the other identifies a program.
Account
You, rather than your organisation. One sign-in covers every Vatan product.
POST /api/auth/signup
Create an account and its personal organisation.
{ "email": "...", "password": "...", "name": "..." }
The wallet opens empty. Nothing is granted on signup alone, because submitting a form is not scarce.
POST /api/auth/login
Sign in.
{ "email": "...", "password": "..." }
POST /api/auth/verify
Confirm an email address from the link that was sent.
{ "token": "..." }
Idempotent: clicking the link twice is not a failure.
POST /api/auth/resend-verification
Send the verification link again.
POST /api/auth/password
Change your password.
{ "currentPassword": "...", "newPassword": "..." }
Requires the current password even though you are signed in, and signs out every other session.
GET /api/auth/me
The signed-in user and the organisations they belong to.
PATCH /api/auth/me
Change your display name.
{ "name": "..." }
GET /api/auth/sessions
Where this account is signed in.
DELETE /api/auth/sessions/:jti
Sign one session out.
Scoped to your own sessions, so knowing a jti is not enough to sign somebody else out.
POST /api/auth/logout
Sign out of this session.
POST /api/auth/logout-all
Sign out everywhere, across every product.
Keys
The credentials your code uses. Stored only as a hash, so a lost key is rotated rather than recovered.
GET /api/orgs/:orgId/gateway/keys
Every key, with its prefix, status and monthly cap.
POST /api/orgs/:orgId/gateway/keys
Create a key.
{ "name": "production", "monthlyLimit": 100, "rpmLimit": 60, "tpmLimit": 60000 }
The only time the key itself is returned. It is stored as a hash, so a lost key is rotated rather than recovered. The rate limits are optional and inherit the plan's when omitted.
PATCH /api/orgs/:orgId/gateway/keys/:keyId
Change a live key's spend cap and rate limits without rotating it.
{ "monthlyLimit": 250, "rpmLimit": null, "tpmLimit": 120000 }
Null clears a limit; an omitted field leaves it alone. The two are deliberately different, or a limit could never be removed once set.
DELETE /api/orgs/:orgId/gateway/keys/:keyId
Revoke a key immediately.
There is no key cache, so revocation takes effect on the very next request.
Money
Prepaid credit, the ledger that explains it, and buying more.
GET /api/orgs/:orgId/gateway/plan
What your plan allows, and how much of each allowance is used.
GET /api/orgs/:orgId/gateway/wallet
Balance, credit held against in-flight requests, and what is spendable.
GET /api/orgs/:orgId/gateway/ledger
Every charge and top-up, with the balance after it.
GET /api/gateway/billing/config
Whether card top-ups are available, the fee, and the limits.
POST /api/orgs/:orgId/gateway/checkout
Start a card payment and get a checkout URL.
{ "amountUsd": 50 }
Credit lands when the payment confirms, never on the redirect back. A redirect is a claim, not a payment.
POST /api/orgs/:orgId/gateway/credit
Add or claw back credit by hand.
{ "amount": 25, "reason": "..." }
Staff only. A negative amount is how a refund or a correction is made, audited the same way as a grant.
Usage
What happened, what it cost, and what it cost us.
GET /api/orgs/:orgId/gateway/overview
Headline numbers for the last 30 days.
GET /api/orgs/:orgId/gateway/analytics
Spend over time and a breakdown, under one set of filters.
Filter by range, model, provider, key, status, route, prompt and tag; group by any of those. Every panel is computed under the same filters, so they cannot disagree.
GET /api/orgs/:orgId/gateway/usage
Usage totals for the period.
GET /api/orgs/:orgId/gateway/logs
Individual requests, newest first.
Metadata only. Prompts and completions are never stored: that is a different product with different data-protection consequences.
GET /api/orgs/:orgId/gateway/filter-options
What the filter controls can offer, scoped to the window on screen.
GET /api/orgs/:orgId/gateway/sessions
Requests grouped into conversations.
GET /api/orgs/:orgId/gateway/users
What each of your end users costs.
GET /api/orgs/:orgId/gateway/scores
Ratings by name, by model, and the comments people left.
Prompts
Templates with a history. Versions are append-only, so the text behind an old answer still says what it said.
GET /api/orgs/:orgId/gateway/prompts
Every prompt, with its latest version and variables.
GET /api/orgs/:orgId/gateway/prompts/:slug
One prompt and its full version history.
PUT /api/orgs/:orgId/gateway/prompts/:slug
Save a prompt, or add a version to one that exists.
{ "messages": [{ "role": "system", "content": "... {{variable}}" }], "note": "..." }
Variables are read out of the template, so the declared list cannot drift from the text.
POST /api/orgs/:orgId/gateway/prompts/:slug/render
Fill a stored template in and get the messages back.
{ "version": 3, "variables": { "customer": "Acme" } }
An unsupplied variable is reported and left visible rather than blanked.
DELETE /api/orgs/:orgId/gateway/prompts/:slug
Delete a prompt and every version of it.
Cache
Opt-in response caching, and what it has saved.
GET /api/orgs/:orgId/gateway/cache
Hit rate, entries, and an estimate of what was saved.
The rate is over requests that asked to be cacheable, not over all traffic.
DELETE /api/orgs/:orgId/gateway/cache
Purge every entry. All or nothing, because selecting one would mean showing prompts.
Security
What the prompt-injection and personal-data checks found, and which mode they run in.
GET /api/orgs/:orgId/gateway/security
Findings by kind, how many requests were blocked, and the current mode. Takes ?days=, up to 90.
Kinds and counts only. The matched text is never stored, so nothing here can leak what was found while reporting that it was.
PATCH /api/orgs/:orgId/gateway/security
Set the organisation's default mode.
{ "mode": "block" }
off, flag or block. A per-request x-vatan-security header overrides it.
Webhooks
Events pushed to you, signed so you can prove they are ours.
GET /api/orgs/:orgId/gateway/webhooks
Your endpoints, with delivery and failure counts.
POST /api/orgs/:orgId/gateway/webhooks
Add an endpoint and receive its signing secret.
{ "url": "https://...", "events": ["request.completed", "spend.alert"] }
The secret is returned once and never again.
PATCH /api/orgs/:orgId/gateway/webhooks/:id
Enable, disable, or change which events an endpoint wants.
{ "enabled": false }
DELETE /api/orgs/:orgId/gateway/webhooks/:id
Remove an endpoint.
GET /api/orgs/:orgId/gateway/webhooks/:id/deliveries
Every attempt, with its status, response code and error.
A webhook that stops silently is the classic failure, and this is what makes it answerable.
POST /api/orgs/:orgId/gateway/webhooks/:id/test
Send a test event now and get the result inline.
Team
Who else can see and manage this organisation.
GET /api/orgs/:orgId/members
Members and outstanding invites.
POST /api/orgs/:orgId/invites
Invite somebody by email.
{ "email": "...", "role": "MEMBER" }
DELETE /api/orgs/:orgId/invites/:userId
Withdraw an invite.
PATCH /api/orgs/:orgId/members/:userId
Change somebody's role.
{ "role": "ADMIN" }
DELETE /api/orgs/:orgId/members/:userId
Remove somebody.
PATCH /api/orgs/:orgId
Rename the organisation.
{ "name": "..." }
POST /api/invites/:token/accept
Accept an invite you were sent.
Support
Writing in about the Gateway. Separate from the Vatan contact form, which is for consultancy enquiries.
POST /api/support
Ask for help.
{ "kind": "bug", "subject": "...", "message": "...", "requestId": "...", "email": "..." }
Works signed out, because somebody locked out of their account is exactly who needs it. Include the request id: it turns a description into a diagnosis.
Saved views
A filter combination worth keeping. Stored as the query string the usage page already speaks, so a new filter never needs a migration.
GET /api/orgs/:orgId/gateway/saved-queries
Every saved view.
POST /api/orgs/:orgId/gateway/saved-queries
Save a view, or update one with the same name.
{ "name": "Errors this week", "query": "days=7&status=error" }
Saving an existing name replaces it and does not count against your plan limit again.
DELETE /api/orgs/:orgId/gateway/saved-queries/:id
Remove a saved view.
Reports
A recurring summary that arrives whether or not anybody thought to look, carrying spend, margin, top models and top end users.
GET /api/orgs/:orgId/gateway/reports
Your reports and the schedules available.
POST /api/orgs/:orgId/gateway/reports
Schedule one.
{ "name": "Weekly spend", "schedule": "weekly", "email": "..." }
PATCH /api/orgs/:orgId/gateway/reports/:id
Pause one or change its schedule.
{ "enabled": false }
DELETE /api/orgs/:orgId/gateway/reports/:id
Remove one.
GET /api/orgs/:orgId/gateway/reports/preview
Read what would be sent, right now.
Returns the rendered text as well as the numbers, so you can see exactly what lands rather than guessing.
POST /api/orgs/:orgId/gateway/reports/:id/send
Send one immediately.
Does not delay the scheduled send, which is usually the thing being tested.
Alerts
Told when spend crosses a threshold, by email and by webhook.
GET /api/orgs/:orgId/gateway/alerts
Your spend alerts.
POST /api/orgs/:orgId/gateway/alerts
Add one.
{ "threshold": 100, "window": "month", "email": "..." }
DELETE /api/orgs/:orgId/gateway/alerts/:alertId
Remove one.
Your own provider keys
Stored encrypted. Not yet used to serve requests, so a key here changes nothing until the billing side ships.
GET /api/orgs/:orgId/gateway/provider-keys
Which providers have a key stored, never the key itself.
PUT /api/orgs/:orgId/gateway/provider-keys/:provider
Store or replace a key.
{ "key": "sk-..." }
DELETE /api/orgs/:orgId/gateway/provider-keys/:provider
Remove a stored key.