# API authentication

Use an organization-scoped Hoplite API key beginning with `hop_`.

Send it either as `X-Api-Key: hop_…` or `Authorization: Bearer hop_…`.

## Create a service account

Sign in as an owner or admin, select the team workspace, and open Settings → Workspace → API keys → Service accounts → New service account. Name the account. Under Access, leave the project scope on All projects only for organization-wide access; to restrict access, choose Selected and add each project. Set each permission scope (Projects, Threads, Automations, Repositories, MCP servers, Webhooks, Model keys, Usage) to None, Read, or Write; a new account starts with Read on every scope. Select Create service account and save the one-time key as HOPLITE_API_KEY in backend secret storage. See [factory setup](/docs/factory#before-running) for the required grants.

Read grants the scope's `read` permission, such as `project:read`. Write grants every permission in the scope: for Projects, `project:create`, `project:read`, `project:update`, and `project:delete`. Model keys and Usage offer Read only.

Service credentials begin with `hop_svc_`, expire after 90 days, and support rotation with up to 24 hours of overlap. Current grants, project allowlists, revocation, and disabled accounts are checked on every request.

Account administration requires a current owner/admin session; service credentials can inspect their own identity and rotate themselves. Credentials do not inherit a creator's personal entitlements.

Published permissions: `automation:create`, `automation:delete`, `automation:read`, `automation:run`, `automation:update`, `mcpServer:create`, `mcpServer:delete`, `mcpServer:read`, `mcpServer:update`, `modelKey:read`, `project:create`, `project:delete`, `project:read`, `project:update`, `repo:read`, `repo:update`, `thread:create`, `thread:read`, `thread:retry`, `thread:stop`, `thread:update`, `usage:read`, `webhook:create`, `webhook:delete`, `webhook:read`, `webhook:update`.

Send a stable Idempotency-Key on every service-account write and reuse the exact request when retrying. Receipts last at least seven days. A changed payload returns 409. Only validation rejections are replayed; other 4xx responses, such as 402, 404, 409 state conflicts and 429, can be retried under the same key. Retrying an uncertain outcome settles it from the resource the first request created, or dispatches again once Hoplite proves nothing was created. `409 operation_outcome_unknown` means neither could be established: inspect /api/operations/{id} and the resource before using a new key. One-time secrets are not returned on replay.

## Rate limits

Each API key and each service credential has its own quota of 600 requests per fixed 60-second window. Requests without a valid credential are limited to 300 per window for each client IP address. Responses carry `RateLimit` (for example `"ingress";r=599;t=60`), `RateLimit-Policy` (`"ingress";q=600;qu="requests";w=60`), `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (a Unix timestamp in seconds).

A request over the limit is not processed and answers `429` with `{"ok":false,"error":"rate_limited"}`, `Retry-After`, and `X-Retry-After`, both in seconds. Wait that long, then retry; a write keeps its original Idempotency-Key. Lower your request rate if `429` repeats, and follow the [event feed](/docs/factory/events) rather than polling each resource. If the limiter itself is unavailable, the API answers `503` with `rate_limit_unavailable` and `Retry-After: 1`.