flexway
FRENTalk to an expert

API keys

GuideOperationsSince version 0.1.0

Markdown

Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.

For a known integration - a partner, a batch, a server with no user - and not for an agent (which gets an OAuth token, Agents and MCP). A key is bound to one tenant, to named operations and to an end date; it never reaches the upstream.

Command - create a key:

docker run --rm -it ghcr.io/flexwiz/flexway:<version> api-key   # or the binary: flexway api-key

The command asks for the id, the tenant, the operations and the lifetime (90 days by default), then prints the key once (fwk_ + 43 characters, 256 random bits) and the entry to add to the store - its SHA-256 digest, never the key:

# api-keys.yaml - in a cluster, a Secret synchronised by ESO, mounted read-only
keys:
  - id: acme-billing-2026-10
    hash: sha256:9f2c...
    tenant: acme
    operations: [listInvoices, getInvoice]
    expires_at: 2027-01-01T00:00:00Z

The configuration names the store; the contract says where a key is accepted, with an apiKey alternative next to the token's:

# flexway.yaml
auth:
  api_keys: { store: "file:./api-keys.yaml" }
# contract
components:
  securitySchemes:
    key: { type: apiKey, in: header, name: Authorization }   # always Authorization: ApiKey <key>
paths:
  /invoices:
    get:
      operationId: listInvoices
      security: [ { customers: [invoices:read] }, { key: [] } ]   # a token, or a bound key

Check:

Call Response
Authorization: ApiKey <key> on a bound operation relayed; the upstream gets the key's X-Tenant, without the key
A valid key, an operation it does not name 403
A key on an operation with no apiKey alternative 401, WWW-Authenticate: Bearer
An unknown or expired key, or one without the fwk_ prefix 401, WWW-Authenticate: ApiKey; the log names an expired key by its id, never the key
A key in another header (X-Api-Key) or in the URL ignored; an apiKey scheme outside Authorization is refused at load

Lifecycle: the store is re-read at every reload (Changing the configuration without a restart). Issue = add an entry; revoke = remove it; rotate = two entries while the integration switches, then remove the old one. An entry naming an operation no contract declares is refused at load, like a policy. Rate limits count per key (apikey:<id>).

Next steps