API Reference

All endpoints use https://api.kubbi.ai as the base URL. Requests and responses use JSON. All /api/v1/* responses include the header API-Version: v1.


Health

GET/health

Liveness and readiness check. Verifies application and database connectivity.

Auth: None

Response 200· json
{
  "status": "ok",
  "database": "connected",
  "timestamp": "2026-03-28T12:00:00.000Z"
}
Response 503· json
{
  "status": "degraded",
  "database": "unreachable",
  "timestamp": "2026-03-28T12:00:00.000Z"
}

Registration & Login

POST/auth/register

Register a new user with email and password. Returns a JWT.

Auth: None

Request body· json
{
  "email": "user@example.com",
  "password": "your_password",
  "name": "Jane Doe"
}
Response 201· json
{
  "token": "eyJhbG...",
  "user": {
    "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
    "email": "user@example.com",
    "name": "Jane Doe",
    "created_at": "2026-03-28T12:00:00.000Z"
  }
}
POST/auth/login

Email/password login. Returns a JWT.

Auth: None

Request body· json
{
  "email": "user@example.com",
  "password": "your_password"
}

Response has the same shape as register (token + user).


API Key Management

POST/auth/api-keys

Create an API key for the authenticated user. Requires a label. Max active keys depends on your plan tier.

Auth: JWT (Bearer token)

Request body· json
{ "label": "my-key" }
Response 201· json
{
  "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
  "api_key": "kb_live_abc123...",
  "key_prefix": "kb_abc",
  "label": "my-key",
  "created_at": "2026-03-28T12:00:00.000Z",
  "note": "Store this API key securely — it will not be shown again."
}
GET/auth/api-keys

List the authenticated user's API keys with pagination.

Auth: JWT (Bearer token)

Response 200· json
{
  "keys": [
    {
      "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "key_prefix": "kb_abc",
      "label": "my-key",
      "created_at": "2026-03-28T12:00:00.000Z",
      "revoked_at": null
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "total_pages": 1
  }
}

kubbi Operations

All producer routes are under /api/v1/kubbis. Auth: API key as Bearer token. Rate limited per plan tier.

POST/api/v1/kubbis

Create a kubbi. The request must contain exactly one of content (single-content) or files (multi-file package), never both.

Auth: API key (Bearer token)

Single-Content Parameters

FieldTypeRequiredDescription
contentany JSON valueYesThe data to encrypt (max size depends on your plan)
content_typestringYestext/plain, application/json, text/markdown, or text/csv
ttl_secondsintegerYesTime-to-live in seconds (60 – max depends on your plan)
max_retrievalsintegerNo≥ 1 if set; max depends on your plan. Omit or null for unlimited
metadataobjectNoArbitrary JSON (≤ 1 KB serialized)
Single-content response 201· json
{
  "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
  "claim_url": "https://api.kubbi.ai/r/abc123xyz",
  "claim_token": "abc123xyz",
  "status": "active",
  "content_type": "application/json",
  "max_retrievals": 1,
  "metadata": { "source": "my-app" },
  "created_at": "2026-03-28T12:00:00.000Z",
  "expires_at": "2026-03-28T13:00:00.000Z"
}

Multi-File (Package) Parameters

FieldTypeRequiredDescription
filesarrayYesFile objects (max count and total size depend on your plan)
ttl_secondsintegerYes60 – max depends on your plan
max_retrievalsintegerNo≥ 1 if set
metadataobjectNo≤ 1 KB serialized

Each file object

FieldTypeRequiredDescription
namestringYes≤ 255 chars, alphanumeric start, unique (case-insensitive)
contentstringYesFile content; valid base64 if encoding is "base64"
content_typestringYesValid MIME format (type/subtype)
rolestringNoinstructions, data, context, config, or attachment
encodingstringNoOnly valid value: "base64" (for binary files)
Package response 201· json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "claim_url": "https://api.kubbi.ai/r/pkg456xyz",
  "claim_token": "pkg456xyz",
  "status": "active",
  "file_count": 3,
  "total_size_bytes": 1234,
  "max_retrievals": 2,
  "metadata": null,
  "created_at": "2026-03-28T12:00:00.000Z",
  "expires_at": "2026-03-28T13:00:00.000Z"
}
GET/api/v1/kubbis

List kubbis owned by the authenticated API key. Paginated with page and limit query params (default 20, max 100).

Auth: API key (Bearer token)

Response 200· json
{
  "kubbis": [
    {
      "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "status": "active",
      "content_type": "application/json",
      "max_retrievals": 1,
      "retrieval_count": 0,
      "first_retrieved_at": null,
      "last_retrieved_at": null,
      "metadata": null,
      "created_at": "...",
      "expires_at": "..."
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 5, "total_pages": 1 }
}

Single-content items include content_type. Package items include file_count and total_size_bytes instead.

GET/api/v1/kubbis/:id

Fetch detail for a kubbi owned by the caller. Same shape as a list item. Does not return the payload.

Auth: API key (Bearer token)

DELETE/api/v1/kubbis/:id

Delete a kubbi. The encrypted payload is wiped immediately. Returns 410 Gone on subsequent claim attempts.

Auth: API key (Bearer token)

Response 200· json
{ "status": "deleted" }

Returns 409 if the kubbi is already inactive (expired, burned, or deleted).


Consumer Routes

All consumer routes use the claim_token from the claim URL. No API key required — the claim URL is the credential.

GET/r/:claim_token

Preview a kubbi. Returns metadata plus a claim object with the URL and method to claim. Does not count as a retrieval.

Auth: None (claim URL is the credential)

Single-content response 200· json
{
  "status": "active",
  "content_type": "text/plain",
  "max_retrievals": 1,
  "retrieval_count": 0,
  "metadata": null,
  "created_at": "...",
  "expires_at": "...",
  "claim": {
    "url": "https://api.kubbi.ai/r/abc123xyz/claim",
    "method": "POST",
    "description": "POST to this URL to retrieve the content. This counts as a retrieval."
  }
}
Package response 200· json
{
  "type": "kubbi_package",
  "status": "active",
  "kubbi_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "file_count": 3,
  "total_size_bytes": 1234,
  "max_retrievals": 2,
  "retrieval_count": 0,
  "remaining_reads": 2,
  "metadata": null,
  "files": [
    { "name": "config.json", "content_type": "application/json", "size_bytes": 128, "role": "config" }
  ],
  "created_at": "...",
  "expires_at": "...",
  "claim": {
    "url": "https://api.kubbi.ai/r/pkg456xyz/claim",
    "method": "POST",
    "message": "POST to this URL to retrieve all file contents."
  }
}
GET/r/:claim_token/inspect

Inspect a kubbi. Same as the preview endpoint above, but without the claim object. Does not count as a retrieval.

Auth: None (claim URL is the credential)

POST/r/:claim_token/claim

Claim a kubbi. Returns the decrypted content. No request body needed (empty POST). Counts as a retrieval. If this is the last allowed retrieval, the payload is permanently wiped.

Auth: None (claim URL is the credential)

Single-content response 200· json
{
  "content": "sensitive data here",
  "content_type": "text/plain",
  "metadata": null,
  "created_at": "...",
  "expires_at": "..."
}

For application/json, content is parsed JSON (an object or array). For text/plain, text/markdown, and text/csv, it is a string.

Package response 200· json
{
  "type": "kubbi_package",
  "file_count": 3,
  "metadata": null,
  "files": [
    {
      "name": "config.json",
      "content_type": "application/json",
      "size_bytes": 128,
      "role": "config",
      "content": "{\"env\":\"prod\"}"
    },
    {
      "name": "image.png",
      "content_type": "image/png",
      "size_bytes": 4096,
      "role": "attachment",
      "content": "iVBORw0KGgo...",
      "encoding": "base64"
    }
  ],
  "created_at": "...",
  "expires_at": "..."
}

Text files (text/* and application/json) have content as a UTF-8 string with no encoding field. Binary files have content as base64 with "encoding": "base64".


Error Responses

All errors follow a consistent shape:

json
{
  "error": "<error_code>",
  "message": "<human-readable message>",
  "messages": ["<detail>"]
}

The messages array is only present on validation_error (400) responses.

StatusCodeWhen
400validation_errorInvalid request body (details in messages[])
401unauthorizedMissing or invalid API key / JWT
404not_foundResource not found or not owned by caller
409conflictResource state conflict (already deleted, duplicate email)
410gonekubbi expired, burned, or deleted
429rate_limitedToo many requests — rate limit depends on your plan tier
429quota_exceededDaily quota, active kubbi limit, or max API keys reached
500internal_errorUnexpected server error
501not_implementedAPI version not available

Rate limiting

All API key-authenticated endpoints are rate-limited per API key. The exact limit depends on your plan tier. If you exceed it, you will receive a 429 Too Many Requests response with error code rate_limited. Wait and retry.