API
Authentication
Public API keys — their shape, their two roles, expiry, the organization header, revocation and rate limits.
On this page
The public API manages your organization from code: databases, their users and network allowlists, API keys, and the usage you bill on. Every request carries a key:
Authorization: Bearer fvx_<43 characters>A key is fvx_ followed by 32 random bytes in base64url — 43 characters, 256 bits. A key belongs to one organization and has one role, and it only ever reaches that organization: ids of another organization's databases, users, keys or operations answer 404, exactly like ids that do not exist.
The console's own keys (fnx_…) do not work here, and fvx_ keys do not work on the console's routes.
The two shell variables#
Every example in this section uses these two. Keep the key in a secret store and read it from there; a key written in a command ends up in your shell history.
export FENVIX_API=https://api.fenvix.dev/v1/public
export FENVIX_KEY="$(read_from_your_secret_store)" # never written in a commandAnswers are JSON, and every answer — errors included — carries an X-Request-Id header.
Get your first key#
The first key is made by a person who is an owner or admin of the organization. A key cannot make the first one.
- Sign in to the console as an owner or admin of the organization.
- Do not use the console's API keys page for this. It makes
fnx_keys, which the public API refuses with401. Public keys start withfvx_. - Make the first
fvx_key with one request toPOST /api-keys. It is authenticated with your signed-in console session instead of a key, and it names the organization withX-Fenvix-Workspace:
curl -sS -X POST "$FENVIX_API/api-keys" \
-H "Authorization: Bearer $FENVIX_SESSION_TOKEN" \
-H "X-Fenvix-Workspace: <organization id>" \
-H "Content-Type: application/json" \
-d '{"name":"first key","role":"admin"}'- Copy
tokenfrom the answer and store it. It is shown once and never again.
Keep the first key safe. It stops working if the person who made it leaves the organization or drops below admin (see Revocation).
The two roles#
A key's role is applied exactly as the console applies an organization role to a person.
| Role | Acts like | Can do |
|---|---|---|
admin |
an organization admin | everything the public API offers, including creating read_only keys and revoking keys |
read_only |
an organization member | every GET except listing API keys; every change answers 403 |
Two rules are worth knowing before you design your automation:
- Only a person creates an
adminkey. An owner or admin signed in to the console can; a key cannot. A key that tries gets403. So a stolen key cannot mint a stronger one. - An
adminkey can createread_onlykeys, which is how you hand narrower rights to a service.
Expiry#
expiresAt is optional and may be at most 3 years ahead. An expired key answers 401, like a key that was never valid.
A key created by a key may never outlive its creator: if the creating key expires, expiresAt is required on the new key and must be on or before the creator's expiry, otherwise the answer is 400 with field: "expiresAt".
How many keys#
At most 100 active keys per organization. Revoked and expired keys do not count; the 101st active key is a 409.
Naming the organization#
| Caller | Header |
|---|---|
A key (fvx_…) |
Not needed. The key names its own organization. Sending X-Fenvix-Workspace with a different organization is a 404. |
| A console session | Required: X-Fenvix-Workspace: <organization id>. There is no default organization, and without it the answer is 400. |
A console session is accepted on the key routes (/api-keys) only — so the first key can be made, because the console has no page for public keys yet. Every other route takes keys only.
Revocation#
curl -sS -X DELETE "$FENVIX_API/api-keys/$KEY_ID" \
-H "Authorization: Bearer $FENVIX_KEY"- It takes effect on the next request. Revoking twice changes nothing.
- Keys made by the revoked key are revoked with it — and keys made by those, and so on down the chain.
- A revoked or expired key is refused with
401, and the refusal is written to the organization's audit log.
Some keys are revoked without anyone calling that route. This is enforced in the database, so it holds however the change is made:
| What happens | What is revoked |
|---|---|
| A person leaves the organization, is removed from it, or their account is deleted | the admin keys they created in it |
| A person's role drops below admin (owner or admin to member) | the admin keys they created in it |
| A key is revoked, by anyone or by one of the rules above | every key it created, and so on down |
read_only keys a person created stay when that person leaves or is demoted; an owner or admin can revoke them. An owner who becomes an admin keeps their keys. When a whole organization is deleted, its keys go with it and nothing is recorded.
What is recorded#
The key itself is never stored, logged or shown again — only its SHA-256 and its display prefix (fvx_ and the next 8 characters, which is how keys are told apart in a list). The Authorization header is redacted in logs, and refusals log the reason only.
The organization's audit log records:
| Event | When |
|---|---|
api_key.public.create |
a key is created (its name, role and expiry) |
api_key.public.revoke |
a key is revoked (with the reason when a rule did it: creator_removed, creator_demoted, creator_revoked, not_delivered) |
api_key.public.auth_failed |
a revoked or expired key was used (with the method and path) |
api_key.public.denied |
a read_only key tried to change something |
Changes made with a key are recorded with the key's id, like the console's own audit rows. The two refusal events are written at most once a minute per key; the refusals in between are counted, not lost, and the count appears in the next row, or in a summary row once the key goes quiet.
Rate limits#
| Limit | Default | Applies to |
|---|---|---|
| Per key | 120 requests a minute | every key, on top of the organization's own limit |
| Per address, failed or missing keys | 30 a minute | wrong, malformed or missing keys — shared with failed console sign-ins |
| Per address, keys known to be valid | 600 a minute | a key the server instance created, or saw work in the last 15 minutes |
Database user routes have their own, smaller limits, as on the console.
Over a limit, the answer is 429 with Retry-After in seconds. Wait that long and retry — see Errors.
Two details that matter when several services share one outbound address (a NAT, a shared CI runner):
- Once an address is over the failed-key limit, a key is refused with
429before it is looked up, so a flood of made-up keys costs no database work. - A key the instance already knows to be valid is still looked up, on the separate and higher per-address budget, so other traffic from the same address does not lock out a working key. A valid key the instance has not seen before, used from an address that is already locked out, waits like any other until the minute is over. The lookup still decides: a revoked key is refused at once.
What is not in this version#
Fork, restore, export, tags, keep-awake, billing and invoices, the overview, the data explorer, network blocks and members are on the console only. The public API sends no CORS headers for other origins, so call it from a server, not from a browser.