Skip to main content
Pre-launch. fremai is not open for signups yet. These docs describe the launch API and are subject to change. fremai.eu →
Management API

Management API

The management API is how you administer fremai without a browser: issue and revoke inference keys, read usage, read the credit ledger, and rotate your own credentials.

It is a separate surface from the inference API, with a separate base URL and a separate kind of credential.

Inference APIManagement API
Base URLhttps://api.fremai.eu/v1https://app.fremai.eu/_api/v1
Credentialsk-fremai-…fma_…
PurposeRun modelsAdminister the account

The two credentials are deliberately not interchangeable. Presenting an sk-fremai-… inference key here returns a named error telling you so, rather than a generic 401 — that mistake is common enough to be worth its own message.

Getting a token

Management tokens are created in the console: Settings → Management tokens. The first one has to be made there; after that a token with tokens:read can enumerate your tokens so automation can rotate itself.

The token value is shown once, at creation. Only its SHA-256 is stored, so it cannot be recovered — if you lose it, revoke it and mint another.

Scopes

A token carries an explicit set of scopes. They are coarse on purpose: a fine-grained scope is a promise about enforcement at every call site, and an unenforced scope is worse than an absent one, because it is believed.

ScopeGrants
keys:readList inference keys
keys:writeIssue and revoke inference keys
usage:readRead usage
billing:readRead the credit ledger and receipts
tokens:readList your own management tokens

keys:write does not imply keys:read. Implication is a rule the reader has to know, and the only thing it buys is a shorter token definition. A CI token that may rotate keys but not enumerate them is a legitimate and useful shape, so grant exactly what you need.

Authentication

curl https://app.fremai.eu/_api/v1/keys \
  -H "Authorization: Bearer $FREMAI_MANAGEMENT_TOKEN"

There is no tenant in the path. A management token is issued for one tenant and carries it, so a slug in the URL would be a second source of truth and a class of “token for A used against B’s path” bugs to defend against. There is nothing to defend if the path cannot express it.

Endpoints

GET /keys

Requires keys:read. Lists the tenant’s inference keys. Never returns key material — only the last four characters.

{
  "data": [
    { "id": "…", "name": "ci", "last4": "AB12", "status": "active", "createdAt": "2026-08-01T09:00:00.000Z" }
  ]
}

status is one of active, revoked, expired. A revoked key reports revoked even if it had also expired — revocation is a deliberate act and the more useful thing to know about a key that stopped working.

POST /keys

Requires keys:write. Issues a new inference key.

curl -X POST https://app.fremai.eu/_api/v1/keys \
  -H "Authorization: Bearer $FREMAI_MANAGEMENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "ci"}'
{
  "data": { "id": "…", "name": "ci", "key": "sk-fremai-…", "last4": "AB12" },
  "note": "Store `key` now — it is hashed on our side and cannot be shown again."
}

Returns 201. key is returned exactly once, for the same reason management tokens are: only the hash is stored.

An optional profile selects a key profile where your plan offers them.

DELETE /keys/{id}

Requires keys:write. Revokes a key. Returns 204 with no body.

A key belonging to another tenant returns 404, not 403. Confirming that an id exists but belongs to someone else is a cross-tenant existence oracle, and the difference is not something you could act on anyway.

GET /usage

Requires usage:read. Returns the tenant’s usage summary.

GET /billing

Requires billing:read. Returns the current credit balance, the credit ledger, and receipts.

{
  "data": {
    "balanceCents": 4200,
    "entries": [
      { "type": "topup", "amountCents": 5000, "source": "mollie", "ref": "tr_…", "createdAt": "2026-08-01T09:00:00.000Z" }
    ],
    "invoices": [
      { "period": "2026-08", "amountCents": 5000, "status": "paid", "dineroNumber": 1234, "createdAt": "2026-08-01T09:00:00.000Z" }
    ]
  }
}

All amounts are integer cents, never floats. Ledger entries are signed: a top-up is positive, a debit negative.

GET /tokens

Requires tokens:read. Lists the tenant’s management tokens — this one included.

Never returns a token value or its hash. The raw token existed once, at creation. This endpoint answers “what credentials does my tenant have, and which are stale”, which is what rotation needs:

{
  "data": [
    {
      "id": "…", "name": "ci", "last4": "9F3A",
      "scopes": ["keys:read", "keys:write"],
      "createdAt": "2026-08-01T09:00:00.000Z",
      "lastUsedAt": "2026-08-18T06:00:00.000Z",
      "expiresAt": null,
      "revokedAt": null
    }
  ]
}

lastUsedAt is what makes rotation safe: mint the replacement, move your automation over, then confirm the old token has stopped being used before revoking it.

Errors

Every error carries a stable machine-readable code:

{ "error": { "code": "insufficient_scope", "message": "This token lacks the scope required for this endpoint." } }
StatuscodeMeaning
400name_requiredname is required when issuing a key
401missing_credentialNo Authorization: Bearer fma_… header
401invalid_tokenNot a token we recognise
401token_expiredThe token has expired
401token_revokedThe token was revoked
401inference_key_not_acceptedAn sk-fremai-… key was presented here
403insufficient_scopeThe token is valid but lacks the scope
404not_foundNo such key

invalid_token deliberately covers both an unknown token and a malformed one. Telling a caller that a well-formed token was merely not found confirms the format to anyone probing it.

Responses are JSON only. Unlike the console, this surface does no content negotiation — it has exactly one kind of caller.

Rotating a token

The pattern the API is built for:

  1. Mint the replacement in the console, with the same scopes.
  2. Move your automation to the new token.
  3. GET /tokens and check the old token’s lastUsedAt has stopped advancing.
  4. Revoke the old token in the console.

Step 3 is the one worth not skipping. Revoking a credential you believe is unused is how a pipeline breaks at 03:00.