QANATIX
API Reference

Portal API

JWT-authenticated endpoints for the developer portal.

Portal API

The Portal API powers the QANATIX developer dashboard. Unlike the main API (which uses API keys), the Portal API authenticates with Supabase JWT tokens issued during portal sign-in.

All endpoints are under /api/v1/portal/.

Authentication

Include your Supabase JWT in the Authorization header:

curl https://api.qanatix.com/api/v1/portal/account \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

The JWT is issued when you sign in via the portal. It contains your user ID and tenant ID — no X-Tenant-Id header is needed.

API key management

POST /portal/keys

Create a new API key for your tenant.

curl -X POST https://api.qanatix.com/api/v1/portal/keys \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-key",
    "scopes": ["search", "ingest"]
  }'

Response (201)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "production-key",
  "key": "sk_live_abc123def456...",
  "scopes": ["search", "ingest"],
  "created_at": "2026-03-08T12:00:00Z",
  "message": "Store this key securely — it cannot be retrieved again."
}

The raw key is returned once. Store it immediately.

GET /portal/keys

List all API keys for your tenant.

curl https://api.qanatix.com/api/v1/portal/keys \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "keys": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "production-key",
      "scopes": ["search", "ingest"],
      "created_at": "2026-03-08T12:00:00Z",
      "last_used_at": "2026-03-08T14:30:00Z",
      "expires_at": null
    }
  ]
}

Key values are never returned in list responses.

DELETE /portal/keys/{key_id}

Revoke an API key. The key stops working immediately.

curl -X DELETE https://api.qanatix.com/api/v1/portal/keys/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (204)

No content. The key is permanently revoked.

POST /portal/keys/{key_id}/rotate

Rotate an API key. Generates a new key value with the same name, scopes, and expiration. The old key is invalidated immediately.

curl -X POST https://api.qanatix.com/api/v1/portal/keys/550e8400-e29b-41d4-a716-446655440000/rotate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "production-key",
  "key": "sk_live_new789ghi012...",
  "scopes": ["search", "ingest"],
  "created_at": "2026-03-08T12:00:00Z",
  "message": "Store this key securely — it cannot be retrieved again."
}

Account

GET /portal/account

Get account information for the authenticated user.

curl https://api.qanatix.com/api/v1/portal/account \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "developer@example.com",
  "tenant_id": "t_abc123",
  "display_name": "Jane Developer",
  "company": "Acme Corp",
  "created_at": "2026-03-01T10:00:00Z"
}

PATCH /portal/account

Update your profile information.

curl -X PATCH https://api.qanatix.com/api/v1/portal/account \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Jane D.",
    "company": "Acme Industries"
  }'

Updatable fields

FieldTypeDescription
display_namestringYour display name
companystringCompany or organization name

Response (200)

{
  "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "email": "developer@example.com",
  "tenant_id": "t_abc123",
  "display_name": "Jane D.",
  "company": "Acme Industries",
  "created_at": "2026-03-01T10:00:00Z"
}

Usage

GET /portal/usage

Get usage and cost summary for your tenant.

curl https://api.qanatix.com/api/v1/portal/usage \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
  "plan": "pro",
  "period": "2026-03",
  "entities": {
    "used": 12500,
    "included": 100000,
    "overage": 0,
    "overage_cost": 0.0
  },
  "searches": {
    "used": 8200,
    "included": null,
    "overage": 8200,
    "overage_cost": 8.20
  },
  "ingestions": {
    "used": 1500,
    "included": null,
    "overage": 1500,
    "overage_cost": 12.00
  },
  "rate_limit": {
    "requests_per_minute": 200,
    "search_per_minute": 100,
    "max_verticals": -1
  },
  "allows_overage": true,
  "estimated_overage_total": 20.20
}
FieldDescription
entities.usedTotal entities stored (capped on Free/Pro, unlimited on Scale+)
searches.usedSearch queries this period
searches.overage_costEstimated search overage cost ($1.50/1K on Pro)
ingestions.overage_costEstimated ingestion overage cost ($6/1K on Pro)
estimated_overage_totalSum of search + ingestion costs
allows_overageWhether the plan allows usage beyond free allowance

Quickstart

GET /portal/quickstart

Get the quickstart checklist status for your tenant. Each step is marked as completed or pending based on your actual usage.

curl https://api.qanatix.com/api/v1/portal/quickstart \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Response (200)

{
  "steps": [
    {
      "id": "create_api_key",
      "title": "Create an API key",
      "completed": true,
      "completed_at": "2026-03-08T12:00:00Z"
    },
    {
      "id": "define_schema",
      "title": "Define a schema",
      "completed": true,
      "completed_at": "2026-03-08T12:05:00Z"
    },
    {
      "id": "ingest_data",
      "title": "Ingest sample data",
      "completed": false,
      "completed_at": null
    },
    {
      "id": "first_search",
      "title": "Run your first search",
      "completed": false,
      "completed_at": null
    }
  ],
  "all_completed": false
}

Error responses

Portal API errors follow the same format as the main API:

{
  "detail": "Invalid or expired token"
}
StatusMeaning
401Missing or invalid JWT token
403Token valid but insufficient permissions
404Resource not found
422Validation error (invalid request body)
429Rate limit exceeded

On this page