Skip to main content
SimpleFunctions issues per-user API keys of the form sf_live_<base64url>. Keys are shown exactly once at creation and stored hashed (Argon2) on the server. Every authenticated route accepts a key in Authorization: Bearer sf_live_.... There is no public GET /api/keys/{id} — once created, the raw secret is only available from the create response.

API keys

List keys

GET /api/keys
Auth: Authorization: Bearer sf_live_... or browser session. Response 200
{
  "keys": [
    {
      "id": "key_01J0...",
      "name": "my-agent",
      "keyPrefix": "sf_live_aB3D",
      "lastUsedAt": "2026-05-05T18:42:01.000Z",
      "revokedAt": null,
      "createdAt": "2026-04-22T10:11:08.000Z"
    }
  ]
}
keyPrefix is the first 12 characters and is safe to display. Revoked keys are returned with revokedAt set; they cannot authenticate any new request.

Create a key

POST /api/keys
Auth: required. Body
FieldTypeRequiredDefaultNotes
namestringoptional"Unnamed Key"Human label. Shown in the dashboard and GET /api/keys.
Response 201
{
  "id": "key_01J0...",
  "key": "sf_live_aB3D...XYZ",
  "keyPrefix": "sf_live_aB3D",
  "name": "my-agent",
  "message": "Save this key — it will not be shown again."
}
The key field is the only time the raw secret is returned. Store it now.

Revoke a key

DELETE /api/keys/{id}
Auth: required. The caller must own the key. Response 200
{ "success": true }
Response 404
{ "error": "Key not found or already revoked" }

Curl

# list
curl -H "Authorization: Bearer $SF_API_KEY" \
  "https://simplefunctions.dev/api/keys"

# create
curl -X POST -H "Authorization: Bearer $SF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-agent"}' \
  "https://simplefunctions.dev/api/keys"

# revoke
curl -X DELETE -H "Authorization: Bearer $SF_API_KEY" \
  "https://simplefunctions.dev/api/keys/key_01J0..."

CLI auth handshake (sf login)

The CLI cannot accept a Supabase password locally, so it runs a three-endpoint handshake that proxies the browser session into a fresh API key.
CLI                                        Browser                  Server
 │  1. POST /api/auth/cli  { sessionToken }  ─────────────────────▶  store session (5 min TTL)

 │  2. open https://app.simplefunctions.dev/auth/cli?token=...

 │                                            3. user logs in (Supabase)
 │                                            4. POST /api/auth/cli/complete { sessionToken }
 │                                                                  ▶ create API key, attach to session

 │  5. poll GET /api/auth/cli/poll?token=...  ──────────────────▶   { status: 'pending' | 'ready' }

 receive sf_live_..., persist locally

POST /api/auth/cli — initialize

Auth: none (the sessionToken is the shared secret). Body
FieldTypeRequiredNotes
sessionTokenstringyesAt least 32 characters. CLI generates this with a CSPRNG.
Response 200
{ "ok": true, "expiresAt": "2026-05-05T20:05:00.000Z" }
Errors
StatusBodyCause
400{ "error": "Invalid JSON" }Body is not valid JSON.
400{ "error": "Invalid session token" }Missing or shorter than 32 chars.
409{ "error": "Session already exists" }Token collision — generate a new one.
500{ "error": "Internal error" }Server failure.

GET /api/auth/cli/poll?token=... — poll for the key

Auth: none. The session token is the shared secret. Response 200 (pending)
{ "status": "pending" }
Response 200 (ready)
{ "status": "ready", "apiKey": "sf_live_..." }
The server deletes the session row immediately after returning a key, so a successful poll is a one-shot. Errors
StatusBodyCause
400{ "error": "token required" }?token= is missing.
410{ "status": "expired" }Session expired (5 min TTL) or already consumed.

POST /api/auth/cli/complete — browser callback

This is what the browser hits after Supabase login. CLI integrators don’t call it directly. Auth: Supabase session cookie (browser-side). Body
FieldTypeRequiredNotes
sessionTokenstringyesMust match the token created by POST /api/auth/cli and not yet have an apiKey attached.
Response 200
{ "ok": true }
Errors
StatusBodyCause
400{ "error": "Invalid JSON" } or { "error": "sessionToken required" }Bad body.
401{ "error": "Not authenticated" }No Supabase session.
410{ "error": "Session expired or already used" }Session expired or already converted.
The created key is named "CLI (browser login)" so users can see it in the dashboard and revoke it later.

Signup

POST /api/signup
Public endpoint for new account creation. Returns the Supabase session and a starter API key. Use the dashboard at https://simplefunctions.dev/signup for the standard flow; this endpoint is mainly for programmatic onboarding.

See also

Authentication

All auth flavors — API key, browser session, BYOK exchange creds.

Direct API access

First curl calls and language examples.

API keys (enterprise)

Rotation policy and per-tool restriction.

Errors

Full error envelope reference.