> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beltic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> API keys, environments, scopes, rate limits, and retry guidance for the Beltic API.

## API Keys

Every request to `https://api.beltic.com/v1` must include an API key in the `X-Api-Key` header:

```http theme={null}
X-Api-Key: sk_production_...
```

API keys are created and managed in the [Beltic Console](https://console.beltic.com) under **Settings → API Keys**. The raw key value is shown exactly once at creation — copy it immediately and store it in a secrets manager. It cannot be recovered after you leave the page.

## Environments

Beltic has two environments, each with its own API key prefix:

| Environment | Key prefix          | Base URL                    |
| ----------- | ------------------- | --------------------------- |
| Production  | `sk_production_...` | `https://api.beltic.com/v1` |
| Staging     | `sk_staging_...`    | `https://api.beltic.com/v1` |

The base URL is the same for both environments — the key prefix determines which environment your requests target. **Never use a production key in development or test code.** Staging credentials are isolated from production data and can be freely reset.

```bash theme={null}
# Staging
export BELTIC_API_KEY=sk_staging_...

# Production
export BELTIC_API_KEY=sk_production_...
```

## Key Scopes

Keys carry a fixed permission set. Request only the scopes your service actually needs — a key used only to verify credentials shouldn't also be able to revoke them.

**Workflow API**

| Scope                       | Required for                                                                                                                                    |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `workflows:read`            | `GET /v1/workflows`, `GET /v1/workflows/{id}` (metadata; full definition requires `workflows:editor:read`)                                      |
| `workflows:editor:read`     | Viewing a workflow's full definition in `GET /v1/workflows/{id}` (the editor / preview). Implied by `workflows:write`                           |
| `workflows:write`           | `POST /v1/workflows`, `PATCH /v1/workflows/{id}`, `PUT /v1/workflows/{id}/state`, `POST /v1/workflows/{id}/deploy`, `DELETE /v1/workflows/{id}` |
| `workflows:execute`         | `POST /v1/workflows/execute`                                                                                                                    |
| `workflows:executions:read` | `GET /v1/workflows/executions`, `GET /v1/workflows/{id}/executions`, `GET /v1/workflows/status/{executionId}`                                   |

**Credentials API**

| Scope                | Required for                                                                       |
| -------------------- | ---------------------------------------------------------------------------------- |
| `credentials:write`  | `POST /v1/credentials`, `POST /v1/credentials/batch-issue`                         |
| `credentials:read`   | `GET /v1/credentials`, `GET /v1/credentials/{id}`                                  |
| `credentials:verify` | `POST /v1/credentials/verify`                                                      |
| `credentials:revoke` | `POST /v1/credentials/{id}/revoke`                                                 |
| `credentials:delete` | `DELETE /v1/credentials/{id}`                                                      |
| `audit:read`         | `GET /v1/audit/events`                                                             |
| `audit:write`        | `POST /v1/audit/events`                                                            |
| `audit:streams`      | `POST /v1/audit/streams`, `GET /v1/audit/streams`, `DELETE /v1/audit/streams/{id}` |

**Document API**

| Scope                      | Required for                                                       |
| -------------------------- | ------------------------------------------------------------------ |
| `documents:write`          | `POST /v1/documents`                                               |
| `documents:read`           | `GET /v1/documents`, `GET /v1/documents/{id}`                      |
| `document_templates:write` | `POST /v1/document-templates`, `PATCH /v1/document-templates/{id}` |
| `document_templates:read`  | `GET /v1/document-templates`, `GET /v1/document-templates/{id}`    |

**Business & Screening APIs**

| Scope                  | Required for                                    |
| ---------------------- | ----------------------------------------------- |
| `businesses:write`     | `POST /v1/businesses`                           |
| `businesses:read`      | `GET /v1/businesses`, `GET /v1/businesses/{id}` |
| `screenings:write`     | `POST /v1/screenings`                           |
| `screenings:read`      | `GET /v1/screenings`, `GET /v1/screenings/{id}` |
| `website_checks:write` | `POST /v1/website-checks`                       |

**Webhooks**

| Scope            | Required for                                                                                                |
| ---------------- | ----------------------------------------------------------------------------------------------------------- |
| `webhooks:write` | `POST /v1/webhooks`, `PATCH /v1/webhooks/{id}`, `DELETE /v1/webhooks/{id}`, `POST /v1/webhooks/{id}/resend` |
| `webhooks:read`  | `GET /v1/webhooks`, `GET /v1/webhooks/{id}`                                                                 |

Requests with a valid key but missing scope return `403 Forbidden`.

## Error Responses

Authentication errors return standard HTTP codes with a JSON body:

```json theme={null}
{
  "error": "unauthorized",
  "message": "API key is invalid or has been revoked"
}
```

| HTTP  | Code           | Cause                              |
| ----- | -------------- | ---------------------------------- |
| `401` | `unauthorized` | Key missing, malformed, or revoked |
| `403` | `forbidden`    | Key valid but lacks required scope |
| `429` | `rate_limited` | Too many requests — see below      |

## Rate Limits

Rate limits are applied per API key:

| Tier               | Requests / minute | Burst |
| ------------------ | ----------------- | ----- |
| Default            | 300               | 500   |
| Workflow execution | 60                | 100   |
| Batch issue        | 10                | 10    |

When a limit is exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header indicating how many seconds to wait:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 3
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716292800
```

## Retries

The Beltic API is safe to retry on transient errors. Follow these conventions:

* **Retry on:** `429`, `500`, `502`, `503`, `504`
* **Do not retry on:** `400`, `401`, `403`, `404`, `422` — these are deterministic failures that won't resolve on retry
* **Use exponential backoff** with jitter, starting at 1 second, capping at 30 seconds
* **Honour `Retry-After`** on `429` responses — don't backoff shorter than what the header specifies

For mutation requests (`POST`, `DELETE`), use [idempotency keys](#idempotency) to ensure retries don't produce duplicate side effects.

## Idempotency

Mutation endpoints (`POST /v1/credentials`, `POST /v1/workflows/execute`) accept an `Idempotency-Key` header. Submitting the same key twice within 24 hours returns the original response without re-executing:

```bash theme={null}
curl -X POST https://api.beltic.com/v1/credentials \
  -H "X-Api-Key: $BELTIC_API_KEY" \
  -H "Idempotency-Key: your-unique-request-id" \
  -H "Content-Type: application/json" \
  -d @payload.json
```

Concurrent requests with the same key return `409 Conflict`. Use a UUID or a hash of the request payload as the key.

## Public Endpoints (No Auth)

The following endpoints do not require authentication — they are used by third-party verifiers who don't have a Beltic account:

| Endpoint                              | Purpose                                    |
| ------------------------------------- | ------------------------------------------ |
| `GET /.well-known/jwks.json`          | Issuer's public signing keys               |
| `GET /.well-known/did.json`           | Issuer DID document                        |
| `GET /.well-known/status-lists/v1`    | Revocation bitstring                       |
| `POST /v1/credentials/_public/verify` | Verify a credential JWT without an API key |

See [Public Endpoints](/guides/credentials/well-known-endpoints) for details.
