# Authentication

> The Authorization header, the three credential types, the scope each endpoint needs, public access token rules, CORS, and authentication errors.

This page is the contract: what the header looks like, what each credential
reaches, and what comes back when it cannot. Creating, rotating, and capping
keys is on [authentication](https://mynth.io/docs/authentication.md).

## The header

Every authenticated request sends exactly one bearer token:

```text
Authorization: Bearer <credential>
```

The scheme is case-insensitive. The value must be the scheme, whitespace, and
the token, with nothing after it. Anything else counts as no credential.
There is no query parameter, no `X-API-Key` header, and no Basic auth.

The API decides what the token is from its prefix:

| Prefix    | Credential          | Checked as                                    |
| --------- | ------------------- | --------------------------------------------- |
| `mak_`    | API key             | A live key on your account                    |
| `pat_`    | Public access token | A signed token for one task                   |
| any other | OAuth access token  | A mynth.io session. Your code never sends one |

A key pasted without its `mak_` prefix is checked as an OAuth token and fails
with `401 UNAUTHORIZED`. Send the key exactly as it was shown.

## Credentials

| Credential          | Format                | Issued by                               | Lifetime              | Reaches                                  |
| ------------------- | --------------------- | --------------------------------------- | --------------------- | ---------------------------------------- |
| API key             | `mak_` + 48 hex chars | You: dashboard, CLI, or `POST /api-key` | Until you delete it   | Every endpoint its scopes allow          |
| Public access token | `pat_` + a signed JWT | Mynth, in a task's create response      | 1 hour                | `/status` and `/result` of that one task |
| OAuth access token  | JWT                   | mynth.io when you sign in               | Your mynth.io session | The dashboard and playground             |

## Scope per endpoint

An API key needs one of the scopes listed for the endpoint. OAuth sessions
pass every scope check.

| Endpoint                                             | API key scope                       | `pat_`            |
| ---------------------------------------------------- | ----------------------------------- | ----------------- |
| `POST /image/*`, including `/estimate` and `/upload` | `generate`                          | No                |
| `POST /video/*`, including `/estimate`               | `generate`                          | No                |
| `GET /tasks`, `GET /tasks/{id}`                      | `generate`                          | No                |
| `GET /tasks/{id}/status`, `GET /tasks/{id}/result`   | `generate`                          | Yes, its own task |
| `/webhook`, `/destinations`, `GET /balance`          | `manage`                            | No                |
| `/api-key`                                           | `keys`                              | No                |
| `GET /me`                                            | any of `generate`, `manage`, `keys` | No                |
| `GET /models`, `GET /health`                         | none, public                        | n/a               |

A key without the scope gets `403`, and the body says what was missing:

```json
{
  "code": "INSUFFICIENT_SCOPE",
  "message": "This endpoint requires the `manage` scope. This key has: generate. Add the scope to this key in the dashboard, or use a key that has it.",
  "scopes": { "required": ["manage"], "current": ["generate"] }
}
```

Read `scopes.required` rather than parsing `message`. Scopes can be changed
on an existing key in the dashboard, or with `PUT /api-key/{id}`.

### Which tasks a key can read

- `GET /tasks` lists only the tasks the calling key created. An OAuth session
  lists every task on the account.
- `GET /tasks/{id}`, `/status`, and `/result` check the account, not the key.
  Any key on your account with `generate` can read any of your tasks by id.
- Another account's task answers `404 TASK_NOT_FOUND`, never `403`, so the API
  never confirms that an id exists.

## Public access tokens

`POST /image/generate`, `POST /image/remove-background`, and
`POST /video/generate` return a token for the task they create:

```json
{
  "data": {
    "taskId": "tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP",
    "estimatedCost": "0.03",
    "access": { "publicAccessToken": "pat_eyJhbGciOiJIUzI1NiJ9..." }
  }
}
```

- It works as a bearer token on `GET /tasks/{id}/status` and
  `GET /tasks/{id}/result` for that task. Every other endpoint rejects it.
- It expires one hour after it is issued, and nothing refreshes it.
- Send `"access": { "pat": { "enabled": false } }` in the create body to skip
  it.
- Treat `data.access` as optional even when enabled. If signing fails, the
  task is still created and the field is missing.
- `POST /image/rate`, `/image/alt`, and `/image/review` never return one.

[Poll for results](https://mynth.io/docs/guides/poll-for-results.md#from-a-browser) has the
pattern end to end.

## CORS

| Paths                                              | Allowed origins                       |
| -------------------------------------------------- | ------------------------------------- |
| `GET /tasks/{id}/status`, `GET /tasks/{id}/result` | Any                                   |
| Everything else                                    | `https://mynth.io` and its subdomains |

The allowed request headers are `Authorization` and `Content-Type`.
Credentials mode is off, so cookies are never sent. A browser call to
`/image/generate` from your own origin fails the preflight, whatever it
sends.

## Failures

| Status | Code                 | When                                                             |
| ------ | -------------------- | ---------------------------------------------------------------- |
| 401    | `UNAUTHORIZED`       | No usable header, or a key that is unknown or deleted            |
| 401    | `INVALID_TOKEN`      | A `pat_` that is malformed or not signed by Mynth                |
| 401    | `TOKEN_EXPIRED`      | A `pat_` past its hour                                           |
| 403    | `INSUFFICIENT_SCOPE` | A valid key without a scope the endpoint accepts                 |
| 403    | `SCOPE_ESCALATION`   | An API key tried to create or edit a key with `manage` or `keys` |
| 404    | `TASK_NOT_FOUND`     | Another account's task, or a `pat_` for a different task         |

> **Warning**
>
> `/status` and `/result` treat a missing or invalid API key as no credential and answer `404
> TASK_NOT_FOUND` instead of `401`, so an unauthenticated caller cannot learn whether a task id
> exists. If polling a task you just created returns `404`, check the header before the id.

A `pat_` problem is never a `404` on those two paths. A malformed or expired
token fails with its own `401` first.

Every code, with what to do about it, is on [errors](https://mynth.io/docs/api-reference/errors.md).

## Next steps

- [Authentication](https://mynth.io/docs/authentication.md): create a key, set scopes, rotate, cap spending.
- [The task object](https://mynth.io/docs/api-reference/task-object.md): what the task endpoints return.
- [Errors](https://mynth.io/docs/api-reference/errors.md): every code the API can send.
