Skip to content
Facteur
FRGet started

API keys — wiring a test suite to your inboxes

Create a key, use it from CI, rotate it without downtime, revoke it. What a key reaches, and what it will never reach.

An API key is what lets your tests call the API with no browser and no session. You create one from Settings → API keys, at organization level.

Two questions, never one

A key answers two questions separately, and that is what makes it possible to grant little at a time:

What it may do — four scopes, in two families:

ScopeWhat it opens
ws:readread the messages of the workspaces it aims at
ws:writecreate and delete inboxes there
org:readlist the workspaces
org:writecreate one, rename it, mark it

Where it may do it — all your workspaces, or only the ones you name.

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

Creating a key

Settings → API keys → Create a key. Four choices:

  • A name. This is what will tell you which one to revoke in six months. “GitHub CI”, “Camille’s staging”.
  • What it may do. Read-only by default: a test suite reads messages, it rarely needs to create or delete inboxes. A second checkbox opens workspace management, for a provisioning script.
  • Where. If you say nothing, the key aims at your main workspace only — the narrowest reading, so a forgotten field can never grant too much. The response always states the scope you got.
  • A lifetime. 90 days by default, a year at most.

The secret is shown once, right after creation. We keep only a digest of it: neither you nor we can recover it afterwards. Copy it straight into your CI secrets.

Using it

One Authorization header, and that is all:

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 — which 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 settled arbitrarily. Two identities in one request means a test that proves something about the wrong one.

Every route, its scope and its refusals are listed in the API reference at developers.facteur.eu. It is in English only, and it is generated from the specification the API is held against.

What a key reaches

The inboxes of the workspaces it aims at, and their messages. Nothing else.

A key aimed at “staging” does not read the main workspace’s mail — that is the border workspaces exist to draw, and it is the same one that applies to members (see espaces-de-travail).

If you choose “all workspaces”, the scope covers the ones you create later too: that is what you asked for, and a list frozen at creation time would be a key that quietly stops covering your new workspaces.

It belongs to the organization, not to the person who created it and not to a workspace: it outlives its author’s departure. A key tied to someone would break your continuous integration on the day of a leaving party, with nobody making the connection.

What a key never reaches

  • Key management itself. A key does not mint keys. Otherwise a stolen key would renew itself and revoking it would mean nothing.
  • Your account. Password, second factor, passkeys, organization: a leaked key does not take the account over.
  • Writes, if it is read-only. The answer is 403 SCOPE_INSUFFICIENT, which names the missing scope and the ones the key does hold.
  • The shape of your organization, without org:*. A key holding only ws:read gets 403 SCOPE_INSUFFICIENT on the workspace list — naming the missing scope, not a bare “forbidden”.
  • Letting anybody in. Never, whatever the scope. Neither an addressed invitation nor an organization link: 403 KEY_CANNOT_INVITE. Human access granted by a key would outlive that key’s revocation — take the key away, the invitee is still a member.
  • Deleting a workspace. 403 KEY_CANNOT_DELETE_WORKSPACE. Deletion reattributes usage history and can be refused if a key still aims at the workspace: that is a judgement call needing somebody to read it. Delete from the application.

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

Rotating without downtime

Rotate creates a fresh key and leaves the old one alive for 24 hours. You deploy the new secret, watch one green build, and the old one dies on its own.

That is the whole point of the function: a rotation that cuts is a rotation nobody performs.

An already-revoked key cannot be rotated — that would keep it alive for hours, undoing the revocation.

Revoking

Immediate, with no cache window: the next call fails. Including one from a pipeline in flight, which is the intended behaviour.

The message tells three cases apart, because they call for different actions:

CodeWhat it means
KEY_REVOKEDsomebody took it away — create a new one
KEY_EXPIREDthe clock did its job — rotate
KEY_UNKNOWNthis key never existed — check what your CI is sending

The “last used” column tells you whether a key is still in service. That is what makes it possible to revoke without wondering what you are about to break.

Who can do this

An owner or an admin of the organization. A key reads every message in its workspace, which is more than a member does by hand.

Deleting a workspace a key aims at

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

Two possible gestures before retrying: revoke the key, or remove that workspace from its scope. The two other imaginable behaviours are worse — detaching silently would leave a key whose scope is empty, and disabling the key would break a pipeline without anybody connecting the failure to a deleted workspace.

An already revoked or expired key blocks nothing: it opens nothing.

Provisioning a workspace per branch

This is what org:write is for:

# 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 does not enter the key’s scope. A key does not widen itself. If your tests need to read that new workspace’s mail, aim the key at “all workspaces” when you create it — that scope covers future workspaces too.
  • The plan ceiling still applies. A plan that sells one workspace answers 402, with the plan from which it becomes possible.

What does not exist yet

  • Member management — listing, changing a role, removing, from a script. That needs routes the API does not expose yet.
  • IP restriction — an allowlist of addresses, for runners that egress from a fixed one.

The screen announces both as coming.