# API keys

> Organization API keys (eb_sk_…) authenticate servers and SDKs acting as the whole organization. Org-scoped, least-privilege, shown once — and why they're stored as SHA-256, not Argon2id.

An **organization API key** authenticates a server or SDK acting as the **whole organization** — not on behalf of any one signed-in user. It is the server-side counterpart to the user-facing [OAuth flow](/docs/oauth/overview/): where OAuth answers "which user is this?", an API key answers "which organization is this?".

- **Shape:** `eb_sk_…` — the `sk` means *secret key*.
- **Created in:** **Settings → API keys** by an org admin.
- **Scopes:** least-privilege [`org:*`](/docs/oauth/scopes/) permissions.
- **Calls:** [`/api/v1/org/*`](/docs/api-reference/organization/).

> **A key is a server secret — never ship it in an app.** An `eb_sk_…` key acts as your entire organization and can be extracted from any downloadable binary (APK, SPA bundle). Client apps use an OAuth [`client_id`](/docs/get-started/authentication/) (public, PKCE-protected) instead. If a key was ever embedded or shared, [revoke it](/docs/api-keys/create-and-manage/).

## What a key carries

Each key is minted with:

- **Least-privilege scopes** — pick only the `org:*` scopes the integration needs (`org:passes:read`, `org:passes:write`, `org:facilities:read`). See [Scopes](/docs/oauth/scopes/).
- **An optional expiry** — after which the key stops working.
- **An optional source-IP allowlist** — requests from other IPs are rejected.

The key is **shown exactly once**, at creation. EntryBit stores only its hash and can never display it again — if it's lost, revoke it and mint a new one. See [Create & manage keys](/docs/api-keys/create-and-manage/).

## Why keys are stored as SHA-256, not Argon2id

User passwords at EntryBit are hashed with **Argon2id**. API keys are hashed with plain **SHA-256** — and that is deliberate, not a shortcut.

An API key is **256 bits of CSPRNG output**. There is no dictionary, no pattern, no human-memorable structure — so an offline preimage or brute-force search against *any* hash is computationally infeasible. Argon2id exists to slow down guessing of **low-entropy** secrets (passwords people choose); it buys nothing against a full-entropy random key.

Meanwhile, a memory-hard KDF on the **hot, per-request verify path** would be actively harmful:

- **Latency** — every authenticated API call would pay Argon2's deliberate cost.
- **A DoS lever** — an attacker POSTing garbage keys would force expensive Argon2 work per request, turning the verification step into a CPU-exhaustion amplifier.

A fast hash is therefore the *correct* choice for high-entropy machine credentials — the same doctrine EntryBit applies to OAuth client secrets and refresh tokens. Argon2id for what humans choose; SHA-256 for what a CSPRNG generates.

## Keys vs OAuth, at a glance

| | API key | OAuth access token |
|---|---|---|
| Acts as | The organization | A signed-in user |
| Credential | `eb_sk_…` (long-lived secret) | JWT (~15 min, from a `client_id` + PKCE) |
| Endpoints | `/api/v1/org/*` | `/api/v1/*` |
| Scopes | `org:passes:*`, `org:facilities:read` | `passes:*`, `invites:read`, … |
| Lives | On your server, in a vault | In the app, refreshed as needed |

## Next steps

- [Create & manage keys](/docs/api-keys/create-and-manage/) — mint, reveal, and revoke.
- [Authenticating requests](/docs/api-keys/authenticating/) — how to send the key and the challenges you'll get.
- [Organization API](/docs/api-reference/organization/) — the endpoints keys can call.