# Create an API key and wire up your test suite

> Create a key, use it from CI, rotate it without downtime and revoke it. What it reaches, and what it will never reach.
> https://facteur.eu/en/help/cles-api

An API key lets your tests call the API with no browser and no session. It belongs to
the **organization**, which means it survives the departure of whoever created it: your
CI does not break because somebody changed jobs.

You have to be an **owner or an administrator** of the organization to create one.

## Create a key

Open [**Settings → API keys → Create a key**](https://app.facteur.eu/settings/api-keys)
and fill in four fields.

1. **A name.** It is what will tell you which one to revoke in six months: "GitHub CI",
   "Camille's staging".
2. **What it can do.** Read-only by default, which is what a test suite needs. A second
   checkbox opens workspace management, for a provisioning script.
3. **Where.** If you say nothing, the key only reaches your main workspace. The response
   always reminds you of the scope you got.
4. **A duration.** 90 days by default, one year at most.

The secret is shown **once only**, right after creation. We keep nothing but a hash of
it: neither you nor we can retrieve it afterwards. Copy it straight into your CI's
secrets.

## Use it

One `Authorization` header, and that is all:

```bash
curl -H "Authorization: Bearer fk_your_key" \
  https://api.facteur.eu/v1/inboxes
```

The `Basic` form works too, with the key as the username and an empty password: that is
what some SDKs built for other platforms send.

**Do not send a session cookie alongside a key.** The request is refused with
`AMBIGUOUS_CREDENTIALS` rather than resolved arbitrarily: two identities in one request
would give you a test that proves something about the wrong one.

Allow **600 requests per minute per key**. Past that, `429 KEY_RATE_LIMITED`.

Every route, its scope and its refusals are listed in the API reference, on
[developers.facteur.eu](https://developers.facteur.eu/reference/). It opens in your
language, and it is generated from the specification the API is held to.

## Choose the scope

A key answers two questions separately, which lets you hand out little at a time.

**What it can do**, in four scopes and two families:

| Scope | What it opens |
|---|---|
| `ws:read` | Read the messages of the workspaces it targets |
| `ws:write` | Create and delete inboxes there |
| `org:read` | List the workspaces, and see who reaches each one |
| `org:write` | Create one, rename it, tag it, and add or remove the colleagues who reach it |

**Where it can do it**: all your workspaces, or only the ones you name.

Write implies read **within a family**, and nothing crosses families. `org:write`
therefore gives no access to your mail: a script that provisions workspaces has no
business reading the messages inside them.

If you choose "all workspaces", the scope also covers the ones you create later. A list
frozen at creation would be a key that silently stops covering your new workspaces.

## What a key never reaches

Whatever its scope.

| What the key tries | The refusal | What you do |
|---|---|---|
| Create or revoke a key | Route unreachable | Go through [the application](https://app.facteur.eu/settings/api-keys). A stolen key that rotates itself would make its own revocation meaningless |
| Touch your account: password, second factor, passkeys | Route unreachable | A leaked key does not take the account |
| Write while it is read-only | `403 SCOPE_INSUFFICIENT` | The response names the missing scope and the ones the key carries |
| List workspaces without `org:*` | `403 SCOPE_INSUFFICIENT` | Create a key with `org:read` |
| Bring somebody into the organization | `403 KEY_CANNOT_INVITE` | Invite from [the application](https://app.facteur.eu/settings/members). See [Members and seats](membres-et-sieges) |
| Delete a workspace | `403 KEY_CANNOT_DELETE_WORKSPACE` | Delete from [the application](https://app.facteur.eu/settings/workspaces): the operation reassigns usage history and needs somebody to read it |
| Read the mail of a workspace outside its scope | `404` | A key targeting "staging" does not read the main workspace. Same boundary as members, see [Workspaces](espaces-de-travail) |

A support session cannot create a key either, even with your explicit consent: the key
would afterwards be indistinguishable from one you had created yourself.

## Rotate without downtime

**Rotating** creates a fresh key and lets the old one live for 24 hours. Deploy the new
secret, watch a green build go by, and the old one goes out on its own. A rotation that
cuts is a rotation nobody performs.

An already revoked key cannot be rotated: that would keep it alive for a few more
hours, which would undo the revocation.

## Revoke

Immediate, with no cache window: the next call fails, including one from a pipeline
already running.

Before revoking, read the **"last used"** column: it tells you whether the key is still
in service.

Afterwards, the refusal separates three cases, because the actions they call for are
different:

| Code | What it means | What you do |
|---|---|---|
| `KEY_REVOKED` | Somebody withdrew it | Create a new one |
| `KEY_EXPIRED` | It reached its date | Rotate it |
| `KEY_UNKNOWN` | This key never existed | Check what your CI is sending |

## Delete a workspace a key targets

Refused, with `409 WORKSPACE_HAS_ACTIVE_KEYS` and the names of the keys that block it.

Two possible moves before trying again: revoke the key, or remove that workspace from
its scope. An already revoked or expired key blocks nothing, since it opens nothing.

## Provision one workspace per branch

That is what `org:write` is for:

```bash
# Open a workspace for the current branch
curl -X POST https://api.facteur.eu/v1/workspaces \
  -H "Authorization: Bearer fk_your_key" \
  -H "content-type: application/json" \
  -d '{"name": "staging-PR-1234"}'
```

Two things to know:

- **The workspace it creates is not in the key's scope.** A key does not widen itself.
  If your tests have to read the mail of that new workspace, target "all workspaces"
  when you create the key.
- **The plan's ceiling applies.** A plan that sells only one workspace answers `402`,
  naming the plan from which it becomes possible.

## Manage who reaches a workspace

A key carrying `org:write` can list a workspace's members, add one and remove one.

**That brings nobody into your organization.** The person you name has to be a member
already; anybody else is refused with `NOT_AN_ORGANIZATION_MEMBER`. What changes is
which workspaces a colleague already present can open, and it uses no seat.

Inviting remains the act a key never performs.

## Known limits

- **No IP address restriction.** An allowlist, for runners that leave through a fixed
  address, is announced on the screen as coming.
