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

# Authentication

> Create API keys, choose their scopes, and send them from your servers.

Every request authenticates with an API key in the `Authorization` header:

```bash theme={null}
curl https://api.zerogen.com/v1/me \
  -H "Authorization: Bearer $ZEROGEN_API_KEY"
```

## Create a key

Create keys in ZeroGen under **Settings → API keys**. Any member of an
organization in the private beta can create one. ZeroGen shows the full key
once, when you create it, so store it in your server's secret store right away.

When you create a key, you choose:

* **Live or test.** A live key runs real generations and charges your
  organization. A [test key](https://docs.zerogen.com/test-keys.md) runs the same requests on placeholder
  media at no charge.
* **Permissions.** Give the key all scopes, read-only scopes, or a restricted
  set that you choose resource by resource.
* **Expiry.** A key can expire after 1, 7, 30, or 90 days, after any number of
  days up to a year, or never. New keys default to 90 days, and Settings warns
  you two weeks before a key expires.

You can later rename a key, change its expiry, or change its permissions. Only
a key's owner can add access or extend its expiry. Organization admins can see
every member's keys, remove access from any of them, or make them expire
sooner. Revoking a key takes effect on its next request.

## Key prefixes

| Prefix | Mode |
| - | - |
| `zg_` | Live key |
| `zg_test_` | Test key |

The prefixes let secret scanners recognize ZeroGen keys. ZeroGen stores each
key's mode with the key and doesn't infer it from the prefix: a key sent with
the other mode's prefix is rejected.

## What a key can do

A key belongs to the member who created it and acts as that member in the one
organization it was created in. A key can never do more than its owner's
current role allows, and it stops working when its owner leaves the
organization.

Each request also needs the endpoint's scope. Existing keys never gain new
scopes automatically when ZeroGen adds them.

| Scope | Endpoints |
| - | - |
| `models:read` | `GET /v1/models`, `GET /v1/models/{vendor}/{model}/{operation}` |
| `generations:read` | `GET /v1/generations`, `GET /v1/generations/{id}` |
| `generations:create` | `POST /v1/generations`, `POST /v1/generations/quote` |
| `generations:cancel` | `POST /v1/generations/{id}/cancel` |
| `uploads:create` | `POST /v1/uploads`, `GET /v1/uploads/{id}`, `POST /v1/uploads/{id}/complete` |
| `account:read` | `GET /v1/balance` |

`GET /v1/me` needs no scope. A generation or quote with an `https` URL input
also needs `uploads:create`, because ZeroGen imports the URL into your Library.
For details, see [Uploads and URL inputs](https://docs.zerogen.com/uploads.md).

A request without the scope it needs fails with `403 permission_denied`, and
`details.scope` names the missing scope.

## Read your key's access

`GET /v1/me` returns the calling key, its organization, and its owner:

```json theme={null}
{
  "apiKey": {
    "id": "7c6b5a4f-3e2d-4c1b-9a0f-8e7d6c5b4a3f",
    "name": "Render server",
    "mode": "test",
    "scopes": ["models:read", "generations:read", "generations:create"],
    "createdAt": "2026-10-21T16:00:00.000Z",
    "expiresAt": "2027-01-19T16:00:00.000Z"
  },
  "organization": {
    "id": "1f2e3d4c-5b6a-4987-8a6b-5c4d3e2f1a0b",
    "name": "Example Studio"
  },
  "user": {
    "id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d"
  },
  "sandbox": {
    "projectId": "3b4c5d6e-7f8a-4b9c-8d0e-1f2a3b4c5d6e"
  }
}
```

`scopes` lists the scopes the key can use now: the ones it holds that its
owner's role still allows. `expiresAt` is `null` for a key that never expires.
`sandbox` is `null` for a live key; for a test key, it names the
[API sandbox project](https://docs.zerogen.com/test-keys.md#the-api-sandbox).

## Keep keys on your servers

API keys belong on servers. ZeroGen rejects any request that carries a browser
`Origin` header with `403 permission_denied`, and the API sends no CORS
headers. Don't embed a key in a web page, a mobile app, or any other code that
runs on someone else's device.

## Authentication errors

| Status | Code | Cause |
| - | - | - |
| `401` | `invalid_api_key` | The key is missing, malformed, unknown, revoked, or expired, or its prefix names the wrong mode. The response doesn't say which, and carries a `WWW-Authenticate: Bearer` header. |
| `403` | `permission_denied` | The API isn't enabled for the organization, the key lacks the endpoint's scope, or the request came from a browser. |
| `503` | `unavailable` | ZeroGen can't check API keys right now. Retry the request. |

For the full list of codes, see [Errors](https://docs.zerogen.com/errors.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.