# Authentication and API keys

> When to sign in (OAuth) and when to use an account API key, what a key looks like, its abilities, product-scoped keys, limits, revocation and error codes.

## Request header

Every call authenticates with `Authorization: Bearer <key>`. The canonical host of the public API is `api.gujo.ai`.

```bash
curl https://api.gujo.ai/api/products \
  -H "Authorization: Bearer $GUJO_API_KEY"
```

Older clients call `gujo.ai/api/...`, which is forwarded to `api.gujo.ai`, but that path only passes the `authorization`, `content-type`, `accept`, `accept-language` and `user-agent` headers plus `X-*` headers. New code should always call `api.gujo.ai`.

## Signing in (OAuth) or an API key

To connect an agent to the customer MCP, sign in first. Use a key where nobody can sign in through a browser.

| | Signing in (OAuth) | Account API key |
|---|---|---|
| Works with | The customer MCP (`https://api.gujo.ai/api/mcp`) only | The whole account API and the customer MCP |
| Use it for | MCP clients people use, such as Claude Code, Codex, Cursor, VS Code and Claude.ai | CI, scripts on a server, headless agents and direct calls such as `curl` |
| How you get it | Give your client the server URL, then sign in and approve its permissions in the browser | Create it in Developer settings and put the value, shown once, into an environment variable |
| Permissions | What you approved (by default `mcp.read` and `products.read`) | The abilities you picked at creation |
| Lifetime | Access token one hour, refreshed by the client (expires after 30 days without use) | Until the expiry you set (or until revoked) |
| Disconnect | **Connected apps** in your account security settings | Revoke it in the key list in Developer settings |

Both use the same permission names and the same checks, so the ability table below applies to sign-in permissions too. Tokens you get by signing in are accepted only by the customer MCP, so calling other account API endpoints with them returns 401 `invalid_api_key`. Connection steps and token lifetimes are in [Connect MCP](mcp/index.md).

If you build your own MCP client, follow the standard OAuth flow (MCP 2025-11-25 authorization). Start from the protected resource metadata at `https://api.gujo.ai/.well-known/oauth-protected-resource/api/mcp` and the authorization server metadata at `https://account.gujo.ai/.well-known/oauth-authorization-server`. Only the authorization code flow with PKCE (S256) is accepted. Register your app with a client ID metadata document (CIMD) or dynamic registration (`POST /oauth/register`). Send `resource=https://api.gujo.ai/api/mcp` in the authorization and token requests.

## What a key looks like

- Keys start with `gujo_`.
- The server stores only a SHA-256 hash of the key. The plain value is shown once at creation, and after that not even Gujo can show it again.
- The key list on the Developer settings page shows each key's name, its first 14 characters (the prefix) and its abilities. Use the prefix to tell keys apart.

## Abilities

Each key lists the abilities it may use. The ability each endpoint needs is in the `ability` column of the [API reference](api-reference.md). When it is empty, any valid key will do.

| Ability | Opens | Keys that have it from the start |
|---|---|---|
| `products.read` | Library, product list and detail, update checks, install plans, setup progress, product license | Keys you create, device sign-in keys |
| `orders.read` | Your orders, invoices and payment methods | None (pick it when you create a key) |
| `subscriptions.read` | Your subscriptions | None (pick it when you create a key) |
| `mcp.read` | Customer MCP (`POST /api/mcp`) | Keys you create |
| `setup-progress.write` | Recording product setup progress | None (pick it when you create a key) |
| `device.install` | Install records, app distribution feeds and downloads | Keys you create, device sign-in keys |
| `device.entitlements` | Owned products, license checks, Pass checks | Device sign-in keys |
| `device.activate` | Bulk app activation | Device sign-in keys |
| `device.usage` | Product usage upload | Device sign-in keys |
| `support.report` | App problem reports | Device sign-in keys |

- A key you create starts with `products.read`, `mcp.read` and `device.install`.
- A key from device sign-in in gujoctl or Gujo Cloud Apps gets six: `products.read`, `device.entitlements`, `device.activate`, `device.install`, `device.usage` and `support.report`. If you need `orders.read`, `subscriptions.read` or `mcp.read`, create a separate key on the Developer settings page.
- `support.staff`, `commerce.staff` and `design.staff` are abilities for Gujo operators. They do nothing on an ordinary account's key.

## Product-scoped keys

Most keys cover the whole account. Some are bound to one product, such as the key Gujo Cloud Apps receives when it activates an app.

- `GET /api/library` with a product-scoped key returns only that product.
- Calling another product's path returns 403 `product_api_key_scope_required`.

## Limits and expiry

- You can hold up to 3 live keys per scope. Account-wide keys and the keys bound to each product are counted separately.
- If there is no free slot when you create a key, issuing fails and the page shows the limit. Revoking a key frees a slot.
- Device sign-in with no free slot revokes the least recently used key of the same client and issues a new one.
- A key you create works until the expiry date you set (or until you revoke it, if you set none). A device sign-in key expires after 90 days without use, and each use pushes that date back.

## Revocation

- Revoke a key at any time from the key list on the Developer settings page.
- `gujoctl logout` revokes the gujoctl key on the server, then deletes it from the device.
- A revoked or expired key gets 401 `invalid_api_key` from the next request on.

If you think a key has leaked, revoke it first, then create a new one.

## Errors

Authentication and permission errors have the body `{"message": "...", "error": "<code>"}`. `/api/*` never redirects to a sign-in page. It always answers with JSON.

| Status | `error` | Meaning |
|---|---|---|
| 401 | `missing_api_key` | No `Authorization` header |
| 401 | `invalid_api_key` | Unknown, revoked or expired key |
| 403 | `missing_api_key_ability` | The key lacks the ability this endpoint needs |
| 403 | `product_entitlement_required` | You have no entitlement to that product |
| 403 | `product_api_key_scope_required` | A key bound to one product called another product |
| 404 | — | The target does not exist |
| 422 | — | Validation failed. The body is `{message, errors}` |
| 429 | — | Rate limit exceeded. The whole API allows 60 requests a minute, and some endpoints add a tighter limit of their own |

The ability check runs before the target is looked up, so a key without the ability cannot learn whether an id exists (it gets 403, not 404).

## Handling keys

- Send keys only in the header. Never put them in a URL query or path.
- Keep environment variable names, not key values, in config files and repositories. Every key config in [Connect MCP](mcp/index.md) points at `GUJO_API_KEY`.
- Don't send the key to the signed download URL an install plan returns. Keys go to the API host only.
- For an MCP client a person uses, connect by signing in instead of using a key. Then no key needs to sit in a config at all.
