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.
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 and fill in four fields.
- A name. It is what will tell you which one to revoke in six months: “GitHub CI”, “Camille’s staging”.
- 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.
- Where. If you say nothing, the key only reaches your main workspace. The response always reminds you of the scope you got.
- 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:
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. 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. 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. See Members and seats |
| Delete a workspace | 403 KEY_CANNOT_DELETE_WORKSPACE | Delete from the application: 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 |
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:
# 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.
Checked on Is this article wrong or incomplete?