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

# Authentication

> How the Insito API authenticates SDK calls (project API keys) vs dashboard calls (Supabase JWTs).

The Insito API has two authentication modes — one for SDK traffic
from your mobile app, one for dashboard traffic from the
admin.insito.app web UI. They never overlap.

## SDK: project API keys

All `/v1/sdk/*` routes require a `Bearer` token in the
`Authorization` header. The token is your project's API key from
[admin.insito.app](https://admin.insito.app) → Project →
**Settings → API key**.

```http theme={null}
POST /v1/sdk/event HTTP/1.1
Host: api.insito.app
Content-Type: application/json
Authorization: Bearer proj_e014b2efbf474530955c48d40c5bb794

{ "event": "checkout_completed", "userId": "user-123" }
```

API keys:

* Format: `proj_` followed by a 32-character hex string (generated per
  project). The auth middleware accepts a `proj_<alphanumeric>` bearer token.
* One key per project — rotating creates a new key and invalidates
  the old one immediately.
* The key binds every request to a single project, so all SDK traffic is
  automatically scoped to that project. A missing, malformed, or rotated key
  returns `401 unauthorized`.

<Warning>
  API keys are project secrets. Treat them like a database password
  — never commit to public repos, never hard-code into your app
  source. For React Native, use `react-native-config` or
  `expo-constants`'s `extra` field.
</Warning>

## Dashboard: Supabase JWTs

`/v1/dashboard/*` routes require a Supabase auth JWT in the same
`Authorization: Bearer` shape.

```http theme={null}
GET /v1/dashboard/projects HTTP/1.1
Host: api.insito.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```

Get one by signing in via [Supabase Auth](https://supabase.com/docs/guides/auth)
against the same project the dashboard uses. The JWT is short-lived
(1 hour by default) — refresh via the refresh token flow.

Routes apply per-org RLS based on the JWT's `sub` (user ID) and the
`members` table. Cross-org access returns 404.

## Rate limits

All `/v1/sdk/*` routes share a **sliding-window limit of 120 requests per
minute per API key** (enforced in Upstash Redis). Exceeding it returns `429`
with `{ "error": "too_many_requests" }` and `X-RateLimit-Limit` /
`X-RateLimit-Remaining` response headers. There is no `Retry-After` header — the
window is one minute. Dashboard (`/v1/dashboard/*`) routes have no server-side
rate limit today. See [Plan limits](/concepts/plan-limits) for response caps.

## Error shape

Validation failures (Zod) return:

```json theme={null}
{
  "error": "bad_request",
  "issues": [{ "path": "userId", "message": "Required" }]
}
```

All other errors are a flat object with `error` plus any context fields at the
top level (there is no `details` wrapper):

```json theme={null}
{ "error": "response_limit_reached", "upgradeUrl": "https://app.insito.io/settings/billing", "projectId": "<uuid>" }
```

Common codes:

| Code                     | HTTP | Meaning                                                                    |
| ------------------------ | ---- | -------------------------------------------------------------------------- |
| `unauthorized`           | 401  | Missing, malformed, or rotated `Authorization` token                       |
| `not_found`              | 404  | Resource doesn't exist (or is in a different org — deliberately ambiguous) |
| `bad_request`            | 400  | Body failed Zod validation; see the `issues` array                         |
| `conflict`               | 409  | Unique constraint violation (e.g. duplicate key)                           |
| `too_many_requests`      | 429  | SDK rate limit hit (120 req/min per key)                                   |
| `response_limit_reached` | 402  | Plan exhausted; payload includes top-level `upgradeUrl` and `projectId`    |
| `internal_error`         | 500  | Bug on our end                                                             |

## Versioning

The API is versioned via URL prefix (`/v1`). We bump major versions
for breaking changes; backwards-compatible additions never get a new
version.
