API

Public gateway only. DownloadOpenAPI,Postman, orInsomnia. Auth is Bearer YOUR_API_KEY or, on website routes, thekv_session cookie.

Production
https://kasva.dev
Local
http://127.0.0.1:8787

Meta

GET /health

Liveness

GET /v1

Gateway index

Keys

POST /v1/keys

Legacy issue key by email

One-shot email key without a password account. Prefer `POST /v1/auth/register`. IP limit 5/hour. Second issue for the same email is 409. Rotate with `POST /v1/auth/rotate-key`.

{
  "email": "you@example.com"
}

GET /v1/auth/keys

List named Kasva keys

Mask only. Never returns the full `kv_…` secret.

POST /v1/auth/keys

Create a named Kasva key

`name` required, 1–64 characters. Full `api_key` once. Duplicate active name → 409. Max 10 active keys → 400.

{
  "name": "cursor"
}

GET /v1/auth/keys/{id}

One named key (mask only)

DELETE /v1/auth/keys/{id}

Revoke a named key

Last remaining key → 400. Canonical rotate remains `POST /v1/auth/rotate-key`.

Auth

POST /v1/auth/register

Create account and gateway key

{
  "email": "you@example.com",
  "password": "at-least-8-chars",
  "name": "Optional",
  "accepted_terms": true
}

POST /v1/auth/login

Sign in

{
  "email": "you@example.com",
  "password": "at-least-8-chars"
}

POST /v1/auth/logout

Sign out

POST /v1/auth/logout-all

Revoke other sessions

Keeps the current cookie.

GET /v1/auth/sessions

List active sessions

GET /v1/auth/me

Current user

POST /v1/auth/forgot-password

Request password reset

Valid-shaped emails always get the same generic 200.

{
  "email": "you@example.com"
}

POST /v1/auth/reset-password

Complete password reset

{
  "token": "…",
  "password": "new-password-8+"
}

POST /v1/auth/verify-email/request

Send email verification link

API stays usable unverified. No-op when mail is unset.

POST /v1/auth/verify-email

Confirm email

{
  "token": "…"
}

POST /v1/auth/rotate-key

Rotate gateway key

Session cookie or Bearer. Returns a new `api_key` once; the previous key stops immediately. Stored BYOK moves to the new key. Limit 5/hour.

POST /v1/auth/claim-account

Claim a legacy key into an account

Same `kv_…` key is kept. `accepted_terms` required.

{
  "email": "you@example.com",
  "api_key": "kv_…",
  "password": "at-least-8-chars",
  "accepted_terms": true
}

GET /v1/auth/oauth/github

Start GitHub OAuth

POST /v1/auth/oauth/github

Start GitHub OAuth

GET /v1/auth/oauth/google

Start Google OAuth

POST /v1/auth/oauth/google

Start Google OAuth

GET /v1/auth/oauth/{provider}/callback

OAuth return

BYOK

GET /v1/byok/keys

Which provider keys are stored

Never returns secrets or last-4.

POST /v1/byok/keys

Upsert or clear provider keys

Omit a field to leave it unchanged. Send `""` to delete that provider. `{ "enabled": { "openai": false } }` disables a stored key without resending it. Per-request provider headers are not accepted.

{
  "groq": "gsk_...",
  "openai": "sk-..."
}

Models

GET /v1/models

Model list

OpenAI-compatible list. BYOK ids appear only for providers you have registered and not disabled. Hosted ids are `kasva/<catalog-id>`.

Chat

POST /v1/chat/completions

Chat (OpenAI-compatible)

`stream: true` pipes OpenAI-compatible SSE. Response includes a `kasva` routing object. Image and video model ids are rejected here — use `POST /v1/images` or `POST /v1/videos`.

{
  "model": "kasva/auto",
  "messages": [
    {
      "role": "user",
      "content": "Hello"
    }
  ],
  "temperature": 0.7,
  "max_tokens": 1024,
  "stream": false
}

POST /v1/messages

Chat (Anthropic Messages)

Anthropic-shaped request and response. Anthropic SDKs use base URL `https://kasva.dev`. The path is `/v1/messages`. Auth is Bearer and/or `x-api-key` with the Kasva `kv_…` key (not a provider `sk-ant-…` key). Bearer wins if both are sent. `anthropic-version` is optional. Required: `model`, `messages`, `max_tokens`.

{
  "model": "kasva/auto",
  "max_tokens": 1024,
  "messages": [
    {
      "role": "user",
      "content": "Hello"
    }
  ]
}

Images

POST /v1/images

Generate or edit images

Hosted `kasva/image` and `kasva/image-pro` (operator, credits debit after success) plus BYOK generate. Prompt-only hosted generate (`n=1`). BYOK may add `input_references` to edit. Image models are not accepted on chat.

{
  "model": "kasva/image",
  "prompt": "A product photo of a ceramic mug"
}

POST /v1/images/generations

Alias of POST /v1/images

{
  "model": "byok/fal/flux-schnell",
  "prompt": "A ceramic mug"
}

Videos

POST /v1/videos

Create a video job

Hosted `kasva/video`. Returns `queued` with id prefix `vid_`. Poll `GET /v1/videos/{id}` then fetch bytes from `GET /v1/videos/{id}/content` when `completed`. Rate-limit applies to create only. Optional `seconds` is an integer 4–15 (default 5).

{
  "model": "kasva/video",
  "prompt": "A ceramic mug spinning on a table",
  "seconds": 5
}

GET /v1/videos/{id}

Poll a video job

Owner-only. Unknown or other-user ids are 404. Terminal jobs return stored status with no upstream poll. Queued and in-progress jobs poll once per GET.

GET /v1/videos/{id}/content

Download completed video bytes

MP4 bytes for the owner when status is `completed`. Otherwise 404. No JSON body.

Usage

GET /v1/usage

Per-key usage stats

Billing

GET /v1/credits

Credit balance

GET /v1/credits/history

Credit pack grant history

POST /v1/credits/checkout

Credits pack checkout URL

POST /v1/billing/pro

Pro $9/month checkout URL

MCP

GET /v1/mcp/tools

Tool catalog

Catalog only; no remote execution.