> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cargovision.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cargovision.app/_mcp/server.

# Authentication

Cargovision has two separate authentication mechanisms. Use the one that matches the caller, and never substitute one for the other.

| Caller                                                       | Mechanism                                                            | Used for                                                       |
| ------------------------------------------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------- |
| People: web dashboard, mobile app, scripts acting for a user | JWT in the `token` cookie or an `Authorization: Bearer <jwt>` header | Every `/v1/*` endpoint except edge ingestion                   |
| Trusted edge devices                                         | Static API key in an `X-API-Key: <key>` header                       | `/v1/edge/*` writes and `POST /v1/tracking-data/stream-events` |

## JWT sessions

### Sign in

| Method | Endpoint                      | Purpose                                                                                            |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------------- |
| `POST` | `/v1/user/auth/email/login`   | Sign in with email and password                                                                    |
| `GET`  | `/v1/user/auth/google`        | Start Google sign-in; on success the API sets the session cookie and redirects back to the web app |
| `GET`  | `/v1/user/auth/refresh-token` | Issue a fresh token for the current session; returns the user's email and role                     |
| `GET`  | `/v1/user/auth/logout`        | Clear the session cookie                                                                           |

A successful sign-in sets an HttpOnly, `SameSite=Strict` cookie named `token` that holds the JWT (`Secure` in production, `Path=/`, `Max-Age` of 7 days). Browsers send it automatically. Non-browser clients can read the cookie and send its value as a bearer token instead:

```http
Authorization: Bearer <jwt>
```

Call `GET /v1/user/auth/refresh-token` to rotate the token. It requires a valid token and returns a new `token` cookie.

### Accounts

New accounts are created by invitation. Administrators issue invites from the dashboard, and the invite link lets the recipient set a password. Password resets use `POST /v1/user/auth/email/forgot-password` followed by `PUT /v1/user/auth/email/change-password` with the emailed token.

### Roles

Every user has one of three roles. The API enforces them on every request; the dashboard's role-based UI is a convenience, not the security boundary.

| Role         | Access                                                                                                      |
| ------------ | ----------------------------------------------------------------------------------------------------------- |
| `operator`   | Containers the user owns, plus shared registries such as cameras and topology                               |
| `supervisor` | Everything an operator can do, plus every container, the escalation queue, and manual review                |
| `admin`      | Everything a supervisor can do, plus `/v1/admin/*`: users, invites, edge devices, audit log, and email jobs |

A missing or invalid token returns `401`. A valid token without the required role returns `403`.

### Cookie-authenticated writes

Requests that change data (`POST`, `PUT`, `PATCH`, `DELETE`) and authenticate with the `token` cookie must come from a trusted origin, checked through the `Origin` or `Referer` header. Otherwise the API returns `403` with `Forbidden: untrusted request origin`. Requests that send an `Authorization: Bearer` header skip this check, so scripts and mobile clients should use the bearer header.

### Rate limits

Credential endpoints are rate limited per IP address over a 15-minute window:

| Endpoint                                                                               | Limit                                              |
| -------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `POST /v1/user/auth/email/login`                                                       | 5 failed attempts; successful sign-ins don't count |
| `verify`, `register`, `forgot-password`, `change-password` under `/v1/user/auth/email` | 5 requests                                         |
| Public invite preview and accept under `/v1/invites/{token}`                           | 30 requests                                        |

Exceeding a limit returns `429`. Standard `RateLimit-*` headers report the remaining budget.

## Edge device API keys

Edge devices authenticate with a static key configured on both the API and the device. Send it in the `X-API-Key` header:

```bash
curl -sS -X POST https://api.cargovision.app/v1/edge/containers/identify \
  -H "X-API-Key: $CARGOVISION_EDGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @identify.json
```

`Authorization: Bearer <api-key>` is accepted as an alternative. A missing key returns `401 MISSING_API_KEY`; a wrong key returns `401 INVALID_API_KEY`.

> **Warning**
>
> The edge API key grants write access to inspection data. Keep it on the edge device and on servers you control. Never ship it in browser or mobile code.