Overview
The ReelMirror API supports two authentication methods:
- API keys — For programmatic/server-side access
- JWT tokens — For browser-based access via Supabase Auth
Every request must include an Authorization header:
Authorization: Bearer <token>
API Keys
API keys are the recommended way to authenticate programmatic access.
Creating a key
Create an API key from the dashboard or via the API using your JWT token:
curl -X POST https://reelmirror.com/api/v1/api-keys \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Production Key", "scopes": ["personas:read", "content:read"]}'
Response:
{
"id": "uuid",
"name": "Production Key",
"key": "rm_abc123...",
"scopes": ["personas:read", "content:read"],
"created_at": "2025-01-01T00:00:00Z"
}
The key field is only returned once at creation time. Store it securely — you cannot retrieve it later.
API keys follow the format rm_ followed by 32 random characters. Keys are stored as SHA-256 hashes in the database.
Listing keys
curl https://reelmirror.com/api/v1/api-keys \
-H "Authorization: Bearer YOUR_TOKEN"
Revoking a key
curl -X DELETE https://reelmirror.com/api/v1/api-keys/KEY_ID \
-H "Authorization: Bearer YOUR_TOKEN"
JWT Tokens
JWT tokens are Supabase session tokens issued when a user signs in via the dashboard. They are primarily used for frontend/browser access.
JWT users automatically have all scopes — no scope restrictions apply.
Scopes
API keys can be restricted to specific scopes. If no scopes are specified when creating a key, all scopes except billing:read, billing:write, feedback:read, and feedback:write are granted by default. Billing and feedback scopes must be explicitly requested.
| Scope | Description |
|---|
personas:read | Read personas |
personas:write | Create, update, delete personas |
sources:read | List sources |
sources:write | Add, remove sources |
content:read | Read content, poll generation status |
content:write | Generate content, clone URLs, toggle public, approve/reject posts |
sync:write | Trigger content sync |
billing:read | View balance and transactions |
billing:write | Top up balance and configure auto-reload |
publishing:read | List connected accounts, view outgoing posts and slots |
publishing:write | Connect accounts, create/update/publish outgoing posts |
profile:read | Read your profile |
profile:write | Update your profile |
uploads:write | Upload avatar images and voice samples |
feedback:read | Read your feedback tickets and responses |
feedback:write | Submit feedback and post replies |
Scope requirements by endpoint
| Endpoint | Method | Required scope |
|---|
/v1/api-keys | POST, GET, DELETE | None (always allowed) |
/v1/personas | GET | personas:read |
/v1/personas | POST, PATCH, DELETE | personas:write |
/v1/personas/:id/sources | GET | sources:read |
/v1/personas/:id/sources | POST, DELETE | sources:write |
/v1/personas/:id/sync | POST | sync:write |
/v1/personas/:id/source-posts | GET | content:read |
/v1/personas/:id/generated-posts | GET | content:read |
/v1/generate | POST | content:write |
/v1/generate/media-item | POST | content:write |
/v1/generate/voice-convert | POST | content:write |
/v1/generate/regenerate-post | POST | content:write |
/v1/clone-url | POST | content:write |
/v1/generate/toggle-public | POST | content:write |
/v1/generated-posts/:id | DELETE | content:write |
/v1/generated-media-items/:id | DELETE | content:write |
/v1/sync-jobs/:id | GET | content:read |
/v1/generated-posts/:id | GET | content:read |
/v1/generated-media-items/:id | GET | content:read |
/v1/billing/balance | GET | billing:read |
/v1/billing/balance | PATCH | billing:write |
/v1/billing/topup | POST | billing:write |
/v1/billing/setup-intent | POST | billing:write |
/v1/billing/transactions | GET | billing:read |
/v1/publishing/accounts | GET | publishing:read |
/v1/publishing/accounts/connect | GET | publishing:write |
/v1/publishing/accounts/slots | GET | publishing:read |
/v1/publishing/accounts/slots | POST | publishing:write |
/v1/publishing/accounts/{id} | GET | publishing:read |
/v1/publishing/accounts/{id} | DELETE | publishing:write |
/v1/publishing/posts | GET | publishing:read |
/v1/publishing/posts | POST | publishing:write |
/v1/publishing/posts/{id} | GET | publishing:read |
/v1/publishing/posts/{id} | PATCH, DELETE | publishing:write |
/v1/publishing/posts/{id}/publish | POST | publishing:write |
/v1/publishing/posts/{id}/schedule | POST | publishing:write |
/v1/publishing/posts/{id}/retry | POST | publishing:write |
/v1/publishing/posts/{id}/cancel | POST | publishing:write |
/v1/publishing/caption | POST | publishing:write |
/v1/generated-posts/{id}/approve | POST | content:write |
/v1/generated-posts/{id}/reject | POST | content:write |
/v1/generated-posts/{id}/send-to-compose | POST | content:write |
/v1/generated-media-items/{id}/reject | POST | content:write |
/v1/profile | GET | profile:read |
/v1/profile | PATCH | profile:write |
/v1/uploads | POST | uploads:write |
/v1/feedback | GET | feedback:read |
/v1/feedback | POST | feedback:write |
/v1/feedback/{id} | GET | feedback:read |
/v1/feedback/{id}/responses | POST | feedback:write |
/v1/auth/signup | POST | None (public) |
/v1/publishing/accounts/connect requires a browser. The authUrl returned must be opened in a browser to complete the OAuth flow. The provider redirects to /api/internal/publishing/accounts/callback — this is an HTML redirect, not a JSON callback. API clients (curl, scripts) cannot complete this flow. Connect accounts once via the dashboard; all publishing operations are fully automatable thereafter.
Rate Limiting
Rate limits are applied per authentication identity:
| Auth type | Limit |
|---|
| API key | 60 requests/minute |
| JWT | 120 requests/minute |
Rate limit information is included in every response:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1704067260
When the limit is exceeded, the API returns a 429 status with the RATE_LIMITED error code.
Monthly Spending Limits
Each API key can have an optional monthly spending limit. When set, the API will reject requests that would cause the key’s total spending for the current calendar month (UTC) to exceed the limit.
Setting a limit
Set monthly_limit_cents when creating or updating a key:
# Create a key with a $50/month limit
curl -X POST https://reelmirror.com/api/v1/api-keys \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Limited Key", "monthly_limit_cents": 5000}'
# Update an existing key's limit
curl -X PATCH https://reelmirror.com/api/v1/api-keys/KEY_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthly_limit_cents": 10000}'
# Remove the limit (unlimited)
curl -X PATCH https://reelmirror.com/api/v1/api-keys/KEY_ID \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthly_limit_cents": null}'
How it works
- Limits are checked before every billable operation (sync, generate, clone-url, voice conversion)
- The limit resets on the 1st of each month (UTC)
- When exceeded, the API returns a
429 status with the KEY_SPENDING_LIMIT_EXCEEDED error code
- The
monthly_spent_cents field on the list endpoint shows current-month spending per key
- JWT/cookie auth (dashboard usage) is never subject to key spending limits
- Valid range: 1.00(100cents)to10,000.00 (1,000,000 cents)
Checking current spending
The list API keys endpoint includes monthly_spent_cents for keys that have a limit configured:
{
"data": [
{
"id": "uuid",
"name": "Limited Key",
"monthly_limit_cents": 5000,
"monthly_spent_cents": 1250,
...
}
]
}
Security Best Practices
- Never expose API keys in client-side code. Use them only in server-side applications.
- Use the minimum scopes necessary. Create separate keys for different services with only the scopes they need.
- Set spending limits on API keys. Protect against abuse or key theft by capping monthly spend per key.
- Rotate keys regularly. Delete old keys and create new ones periodically.
- Monitor usage. Check
last_used_at on your API keys via the list endpoint.