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

# API overview

> Authentication, base URL, and the endpoints SDKs use to fetch config and send events.

The Traffical SDK API is served from `sdk.traffical.io`. SDKs use it to fetch config bundles, send events, and (in `server` evaluation mode) request resolutions. You can also call it directly for server-to-server integrations.

This section covers the SDK runtime endpoints only: config, resolve, decide, and events. Project and organization management — creating parameters, layers, and policies — happens through the dashboard, the [CLI](/tools/cli), and the [MCP server](/tools/mcp-server).

## Base URL

```
https://sdk.traffical.io
```

You can override this in any SDK with the `baseUrl` option — useful for testing.

## Authentication

All requests require an API key in the `Authorization` header:

```
Authorization: Bearer traffical_sk_...
```

### API key types

Every key has the same `traffical_sk_...` prefix. What a key can do — and whether it is browser-safe — depends on its **scopes**, not its prefix:

| Scopes                                | Type           | Capabilities                                    | Browser-safe?             |
| ------------------------------------- | -------------- | ----------------------------------------------- | ------------------------- |
| `sdk:read`, `sdk:write`               | SDK key        | Fetch bundles, request resolutions, send events | Yes — ship in client code |
| `mgmt:read`, `mgmt:write`, or `admin` | Management key | Create and modify projects, layers, policies    | No — must stay secret     |

* **`sdk:read`** — fetch bundles, request resolutions
* **`sdk:write`** — send events
* **`mgmt:read` / `mgmt:write` / `admin`** — read and modify configuration

SDK keys are safe to include in client-side JavaScript: they can fetch the project's bundle and send events, but cannot modify configuration in any way. Management keys can change configuration and must never ship in client code.

A `mgmt:read` key gives AI agents a read-only connection to the [MCP server](/tools/mcp-server); write tools require separate write scopes.

### Managing keys

Create and revoke keys on your organization's **API Keys** page in the dashboard (from the account menu). Keys can be scoped to a project, given an expiry, or revoked at any time.

## Endpoints

| Method | Path                                         | Description                                                                  | Scope       |
| ------ | -------------------------------------------- | ---------------------------------------------------------------------------- | ----------- |
| `GET`  | [`/v1/config/:projectId`](/api/get-config)   | Fetch the config bundle                                                      | `sdk:read`  |
| `POST` | [`/v1/resolve`](/api/post-resolve)           | Server-side resolution (for `evaluationMode: "server"`)                      | `sdk:read`  |
| `POST` | [`/v1/decide/:policyId`](/api/post-decide)   | Per-entity adaptive decision                                                 | `sdk:read`  |
| `POST` | [`/v1/decide/batch`](/api/post-decide#batch) | Batch of per-entity decisions                                                | `sdk:read`  |
| `POST` | [`/v1/events`](/api/post-events)             | Send exposure, decision, and track events                                    | `sdk:write` |
| `POST` | [`/v1/events/batch`](/api/post-events#batch) | Identical to `/v1/events` — the path browser SDKs use to flush queued events | `sdk:write` |

## Caching

`GET /v1/config/:projectId` is served with `Cache-Control: public, max-age=60, must-revalidate` and an `ETag` per bundle version. SDKs send `If-None-Match` on subsequent requests — unchanged bundles return `304 Not Modified` with no body. Use the same pattern in any custom client that polls the bundle.

## Error responses

All errors share a consistent shape — an `error` object with a stable machine-readable `code` and a human-readable `message`:

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key"
  }
}
```

| Status | Code                                       | Description                                                                                                                              |
| ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `BAD_REQUEST`                              | Invalid payload or missing required fields                                                                                               |
| `401`  | `UNAUTHORIZED`                             | Missing, invalid, expired, or revoked API key                                                                                            |
| `403`  | `FORBIDDEN`                                | API key lacks the required scope, or a tenant header doesn't match the key                                                               |
| `404`  | `NOT_FOUND`                                | Resource does not exist (for example, no bundle for the project and environment)                                                         |
| `429`  | `RATE_LIMITED`                             | Too many requests — retry after the `Retry-After` header                                                                                 |
| `500`  | `INTERNAL_ERROR`                           | Internal server error                                                                                                                    |
| `503`  | `SERVICE_UNAVAILABLE`, `EVENTS_NOT_STORED` | Transient storage unavailability — retry after the `Retry-After` header when present (config and events set it); otherwise retry shortly |

`503` responses are always safe to retry: they mean nothing was stored or served, so a retry cannot duplicate data.

## Rate limits

Limits are generous for SDK traffic and scale with your plan. Rate-limited requests return `429` with a `RATE_LIMITED` error code and a `Retry-After` header (currently `10` seconds). Two things to know:

* `GET /v1/config/:projectId` responses are heavily cached at the edge — most SDK refreshes never reach an origin.
* `POST /v1/events` accepts batches of up to 1000 events per request. Always batch; never send one event per request from a server.
