Skip to main content

API Key Lifecycle

API keys are the primary authentication method for the Soku Public API. This page explains how keys work from creation to revocation.


Overview


Creating a key

From the web app

  1. Go to Settings > API Keys
  2. Click Create Key
  3. Optionally enter a name (e.g., "Production", "Staging")
  4. Copy the key immediately — it is shown only once

From the API

Create keys programmatically using a Firebase ID token:

curl -X POST "https://<base-url>/api/v1/api-keys" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <firebase-id-token>" \
-d '{ "name": "My Production Key" }'

Response:

{ "apiKey": "sk_live_..." }

Key format

Keys follow the pattern: sk_live_ + 24 random bytes (base64url encoded).

Example: sk_live_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6


How keys are stored

When a key is created:

  1. The raw key (e.g., sk_live_...) is hashed using SHA-256
  2. The hash is stored as a document ID in the apiKeys collection
  3. The document contains:
    • userId — the owner's Firebase UID
    • name — optional human-readable label (1-100 characters)
    • createdAt — server timestamp
    • revokedAtnull (set later if revoked)

The raw key is never stored. Only the hash exists in the database. If you lose your key, you must create a new one.


How authentication works

When you send a request with soku-api-key: sk_live_...:

  1. The authenticateApiKey middleware extracts the header value
  2. It computes SHA-256(raw_key) to get the hash
  3. It looks up apiKeys/{hash} in Firestore
  4. If the document doesn't exist → 401 unauthorized (invalid key)
  5. If revokedAt is set → 401 unauthorized (key revoked)
  6. If valid, req.auth is set to { userId, keyHash, name }

After authentication, the requireActiveSubscription middleware checks:

  • The user has a subscription document at users/{uid}/subscription/current
  • The status is trialing or active
  • currentPeriodEnd is in the future

If the subscription check fails → 403 forbidden.

Reference: apps/functions/middleware/auth.js (both authenticateApiKey and requireActiveSubscription).


Using your key

Include the key in every Public API request:

curl -X POST "https://<base-url>/api/v1/posts" \
-H "Content-Type: application/json" \
-H "soku-api-key: sk_live_your_key_here" \
-d '{ ... }'

Important:

  • The header name is soku-api-key (lowercase)
  • Do not include the key in the URL or request body
  • Keep your key secret — treat it like a password

Listing keys

List all keys for your account (requires Firebase ID token):

curl "https://<base-url>/api/v1/api-keys" \
-H "Authorization: Bearer <firebase-id-token>"

Response:

{
"keys": [
{
"id": "a1b2c3d4e5...",
"name": "Production",
"createdAt": "2025-01-15T10:00:00Z",
"revokedAt": null,
"tokenPreview": "sk_live_********c3d4"
}
]
}

The tokenPreview shows a masked version so you can identify which key is which. The id is the SHA-256 hash.


Revoking a key

Revoke a key when it's compromised or no longer needed:

curl -X DELETE "https://<base-url>/api/v1/api-keys/<key-hash-id>" \
-H "Authorization: Bearer <firebase-id-token>"

Response:

{ "revoked": true }

What happens:

  • revokedAt is set to the current server timestamp
  • All future API requests with this key immediately fail with 401 unauthorized
  • In-flight requests that already passed authentication are not affected
  • The document is kept for audit purposes (not deleted)

You can only revoke your own keys. Attempting to revoke another user's key returns 403 forbidden.


Security best practices

  1. Never commit keys to source control. Use environment variables or a secrets manager.
  2. Use separate keys per environment (development, staging, production).
  3. Name your keys so you can identify them later (e.g., "CI/CD Pipeline", "Zapier Integration").
  4. Revoke unused keys immediately.
  5. Rotate keys periodically — create a new key, update your systems, then revoke the old one.
  6. Monitor usage — check users/{uid}/apiRequests in Firestore for unexpected activity.