# Authentication

> One header, one kind of credential, and four refusals that mean different things.

Every call carries an API key as a bearer token:

```bash
curl https://api.formalogic.io/v1/oracles \
  -H "Authorization: Bearer fk_live_…"
```

There is no other scheme. No query parameter, no cookie, no signed URL — a
credential in a URL ends up in a proxy log, a browser history and a bug report,
and offering it as an option means it will eventually be used.

## Getting a key

Keys are created on the [API keys](https://app.formalogic.io/keys) page. The token is shown **once**, at
the moment it is created, and is not recoverable afterwards — we store a hash of
it, so there is nowhere to read it back from.

[Create or revoke a key](https://app.formalogic.io/keys)

Give each caller its own key. Quota is per organisation, so a second key does not
buy a second allowance; what it buys is the ability to revoke one integration
without breaking the others, and a usage log that tells you which one is
spending.

## Two identities, and they never mix

An **API key** authenticates a machine against the oracle API. It knows nothing
about people and has no role. The only thing it can read about your account is
[which organisation it belongs to](https://docs.formalogic.io/me) — no members, no usage, no billing,
and nothing it could change.

A **console session** authenticates a person against the console — minting keys,
changing a plan, reading usage. It cannot call an oracle.

The two run in separate processes with separate credentials against separate
database roles. A key that leaks cannot be used to raise your plan or list your
team; a console session that leaks cannot be used to spend your credits.

## What each refusal means

All four are `401`, and the code says which — because the remedies differ and a
client that cannot tell them apart will retry something that is never going to
start working.

| Code | What happened | What to do |
|---|---|---|
| `KEY_MISSING` | No `Authorization` header, or not a bearer token | Send the header |
| `KEY_UNKNOWN` | The token is not one of ours | Check for a truncated paste |
| `KEY_REVOKED` | It was valid and someone revoked it | Mint a new one; do not retry |
| `KEY_EXPIRED` | It carried an expiry and passed it | Mint a new one; do not retry |

`KEY_REVOKED` and `KEY_EXPIRED` are permanent. Backing off and retrying will
produce the same answer forever.

One refusal is not about the key at all: `403 ORG_DISABLED` means the key is
fine and the organisation it belongs to is not active. Keep the key — minting
another would be refused the same way — and write to support@formalogic.io.
The console answers the same code to every member of that organisation.

Note what is **not** in that list: a spent allowance answers `402
QUOTA_EXCEEDED`, not `401`. Your credential is fine. See
[Limits and credits](https://docs.formalogic.io/limits).

## Rotating a key

Mint the replacement first, move traffic to it, then revoke the old one. Revoking
takes effect on the next request — there is no cache to wait out and no grace
period, which is what makes it useful when a key has leaked.
