Customers & accounts
API Key Management
Generated from docs/plan/core/api-key-management/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
API Key Management lets merchants create, rotate, and revoke API keys with role-based scopes, enabling programmatic access for integrations and headless storefronts.
The problem
Developers need programmatic access but can’t securely rotate keys; leaked keys can’t be revoked without disrupting integrations.
What it does
- Create API key (auto-generate 32-byte random, hash in DB)
- Show plaintext key once on creation (never shown again)
- Assign role to key (Admin/Editor/Viewer/Fulfillment)
- Assign scopes to key (list of permissions)
- Key metadata: name, description, last used, created date, rate limit
- Rotate key: new key while old works for N days (grace period)
- Revoke key: immediately disable
- List active keys with creation date, last used, scopes
- Audit log: created, rotated, revoked, used
- Rate limiting: keys can be rate-limited
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Key delegation (key creates other keys) — only merchants create
- Scoped secrets (access only customer X’s orders) — role-based; row-level is separate
- Automatic key rotation (every 90 days) — manual only
- Key usage analytics (this key fetched 10k products) — metadata only
- Keys for specific IP ranges — bearer token auth; IP restriction separate
Data model
ApiKey: id, store_id, name, description, key_hash (SHA256), key_prefix (first 8 chars), role_id, scopes (JSON), rate_limit_requests, rate_limit_period_seconds, created_by, created_at, last_used_at, expires_at, revoked_at. ApiKeyRotation: id, api_key_id, old_key_hash, new_key_hash, grace_period_until, rotated_at. Migration: add api_keys and api_key_rotations tables with indexes.
API
- POST /admin/api-keys
- GET /admin/api-keys
- PATCH /admin/api-keys/{id}
- DELETE /admin/api-keys/{id}
- POST /admin/api-keys/{id}/rotate
- GET /admin/api-keys/{id}/audit
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
API keys page: table of key prefix, name, role, scopes, created date, last used, actions (rotate/revoke/delete). Create form: name, role, scope checkboxes, rate limit, ‘Generate key’. Key display: plaintext once + copy button. Rotate: new key + grace-period message.
The seam — why this is core
Core owns: ApiKey table, key generation/hashing, bearer auth middleware, scope enforcement, rate limiting. Paid module owns: nothing; core infrastructure.
Core owns the interface + honest key manager; API authentication is infrastructure, not a per-country obligation or credential.
Dependencies
- role-based-access-control
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Creating key generates 32-byte random; plaintext shown once, never again
- Key prefix (first 8 chars) shown in list (abc12345…); full key never displayed
- Using key in Authorization: Bearer succeeds with key’s role
- Revoking key causes next request to return 401
- Rotating: old key works 7 days, new key works immediately; after 7 days old returns 401
- Scope [orders.read] on key: GET /admin/orders succeeds, PATCH returns 403
- Rate limit 100 req/min: 101st returns 429
- Key created by staff_id=1; if staff deleted, audit still shows user 1
Risks
API key leaked causes attacker to impersonate merchant. Plaintext over HTTP causes MitM. Key prefix collision. Scope mismatch. Rate limiter state lost on restart. Double-rotation before grace ends.
Commercial context
| Suggested price | free (core) |
| Rival anchor | Shopify: included; Magento: included |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.