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 JWT tokens issued during portal sign-in.

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

Authentication

Include your 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", "upload"]
  }'

Response (201)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "production-key",
  "key": "sk_live_abc123def456...",
  "scopes": ["search", "upload"],
  "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", "upload"],
      "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", "upload"],
  "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",
  "records": {
    "used": 12500,
    "included": 100000,
    "overage": 0,
    "overage_cost": 0.0
  },
  "searches": {
    "used": 8200,
    "included": null,
    "overage": 8200,
    "overage_cost": 8.20
  },
  "rate_limit": {
    "requests_per_minute": 200,
    "search_per_minute": 100,
    "max_collections": -1
  },
  "allows_overage": true,
  "estimated_overage_total": 8.20
}
FieldDescription
records.usedTotal active records stored (capped on Free/Pro, unlimited on Scale+)
searches.usedSearch queries this period
searches.overage_costEstimated search overage cost (€1.50/1K on Pro)
estimated_overage_totalTotal estimated search overage cost
allows_overageWhether the plan allows usage beyond free allowance

Analytics

GET /portal/analytics

Get provider analytics — how AI agents are querying your public data.

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

Response (200)

{
  "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
  "period": "2026-03",
  "total_open_searches": 47200,
  "collections": [
    {
      "collection": "manufacturing",
      "record_count": 2450,
      "public_count": 2100,
      "open_searches": 31500,
      "last_updated": "2026-03-15T08:30:00Z"
    },
    {
      "collection": "hotels",
      "record_count": 1200,
      "public_count": 1200,
      "open_searches": 15700,
      "last_updated": "2026-03-14T22:00:00Z"
    }
  ]
}
FieldDescription
total_open_searchesTotal times your public data was queried via QANATIX Open this month
collections[].open_searchesPer-collection search count (when per-collection tracking is available)
collections[].public_countNumber of records with visibility=public
collections[].last_updatedMost recent record update in this collection

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": "upload_data",
      "title": "Upload sample data",
      "completed": false,
      "completed_at": null
    },
    {
      "id": "first_search",
      "title": "Run your first search",
      "completed": false,
      "completed_at": null
    }
  ],
  "all_completed": false
}

Data Discovery

These endpoints power the dashboard's data browser. They mirror the main API endpoints but use JWT auth instead of API keys.

GET /portal/collections

List all collections for your tenant with record counts, indexing stats, and replace status.

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

Response (200)

{
  "collections": [
    {
      "name": "manufacturing",
      "record_types": ["product"],
      "total_records": 2500,
      "indexed": 2500,
      "pending": 0,
      "processing": 0,
      "failed": 0,
      "has_pending_replace": false
    }
  ],
  "plan": "pro",
  "record_limit": 100000,
  "total_records": 2500
}

GET /portal/records

List records in a collection with pagination.

curl "https://api.qanatix.com/api/v1/portal/records?collection=manufacturing&limit=20&offset=0" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Query parameters

ParameterTypeDefaultDescription
collectionstringrequiredCollection to list records from
record_typestringFilter by record type
limitinteger20Max results (1–100)
offsetinteger0Pagination offset

Response (200)

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Linear Bearing LM16UU",
      "description": "16mm linear motion bearing",
      "collection": "manufacturing",
      "record_type": "product",
      "indexing_status": "indexed",
      "collection_data": { "sku": "LM16UU", "price_eur": 12.50 },
      "created_at": "2026-03-07T10:00:00Z",
      "updated_at": "2026-03-07T10:05:00Z"
    }
  ],
  "total": 2500
}

POST /portal/search/{collection}

Search within a collection from the dashboard. Same behavior as POST /search/{collection} but uses JWT auth.

curl -X POST https://api.qanatix.com/api/v1/portal/search/manufacturing \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{ "query": "linear bearing", "limit": 10 }'

POST /portal/upload/{collection}/{record_type}/upload

Upload a file from the dashboard. Same behavior as POST /upload/{collection}/{record_type}/upload but uses JWT auth.

curl -X POST https://api.qanatix.com/api/v1/portal/upload/manufacturing/product/upload \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -F "file=@catalog.csv"

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