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
    • revokedAt — null (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.