Authentication

sfmapi has two authentication modes selected by the SFMAPI_AUTH_MODE environment variable.

auth_mode=none (default — dev / single-tenant)

Every request resolves to the default tenant. No Authorization header is required; admin routes are open. Use this for local development, single-user deployments, and the ephemeral mode demo (SFMAPI_EPHEMERAL=true).

Operationally this is the equivalent of “trust whatever’s on the other side of the socket” — terminate at a reverse proxy or front-end auth layer if you need a perimeter.

auth_mode=api_key (multi-tenant)

Every tenant-scoped non-public request must carry a bearer API key.

Authorization: Bearer sfm_<26-char-ULID>_<random>

Public routes (no key required): GET /healthz, GET /readyz, GET /version, GET /spec, GET /metrics.

Keys are scoped to a single tenant_id and resolved on every request through app.core.tenancy.current_tenant. Cross-tenant reads and writes use the same problem+json error shape as other API errors.

Issuing keys

The /v1/admin/api-keys endpoints mint and revoke tenant API keys, but they are not tenant-scoped and are not protected by sfmapi’s tenant API-key dependency. Production deployments must put these routes behind an admin-only control-plane layer such as a private network, mTLS, ingress policy, or deploy-time master key.

# Mint a new key (returns the raw key ONCE — store it).
curl -X POST http://localhost:8000/v1/admin/api-keys \
     -d '{"tenant_id": "acme", "name": "ci-bot"}'

# List keys (raw_key field is null for non-just-issued rows).
curl http://localhost:8000/v1/admin/api-keys

# Revoke (returns the row with revoked_at set; key stops working).
curl -X DELETE http://localhost:8000/v1/admin/api-keys/$KEY_ID

raw_key is the only time you see the secret. The DB stores a content-addressed digest; lose the raw key, mint a new one.

Tenant boundaries

The web layer never trusts a caller-provided tenant_id — it pulls the tenant from current_tenant() and adds it to every query in the service layer. A row that exists under a different tenant looks identical to “not present” (404).

For implementation details see multi-tenancy.

SDK usage

All three SDKs accept an api_key parameter (or env var):

# Python
from sfmapi_client_gen import Client
client = Client(base_url="https://api.example.com", token="sfm_...")
// TypeScript after building/installing the repository package
import { createSfmApiClient } from "@sfmapi/client/generated";
const client = createSfmApiClient({
  baseUrl: "https://api.example.com",
  apiKey: process.env.SFMAPI_KEY,
});
// C++
sfmapi::Client client({"https://api.example.com", api_key, transport});