Authentication
Secure API access with organization-scoped API keys.
Secure API access with organization-scoped API keys. Each deployment serves a single organization, so a key is scoped to that one organization rather than to one of several tenants.
API key format
Owlat validates keys with a live prefix format:
lm_live_...
Sending auth headers
curl "https://<deployment>.convex.site/api/v1/contacts" \
-H "Authorization: Bearer lm_live_your_key"
Authorization: <api_key> (without Bearer) is also accepted, but Bearer is recommended.
Common auth failures
Missing header (401)
{
"error": {
"message": "Missing or invalid Authorization header. Use: Authorization: Bearer <api_key>",
"category": "unauthenticated"
}
}
Invalid format (401)
{
"error": {
"message": "Invalid API key format",
"category": "unauthenticated"
}
}
Invalid or revoked key (401)
{
"error": {
"message": "Invalid API key",
"category": "unauthenticated"
}
}
Insufficient scope (403)
{
"error": {
"message": "This API key is missing the required scope: contacts:write",
"category": "forbidden"
}
}
Rate limit exceeded (429)
{
"error": {
"message": "Rate limit exceeded. Maximum 10 requests per second.",
"category": "rate_limited"
}
}
The limit is a token bucket that sustains 10 requests per second but allows short bursts of up to 15 requests before throttling.
Authenticated responses (including the 429 itself) carry rate-limit headers so clients can back off precisely. A 429 additionally includes Retry-After. Authentication failures (401) do not include these headers.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Sustained requests per second |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
Retry-After | Seconds to wait before retrying (on 429 only) |
Scopes
Each key carries a set of scopes that gate which v1 endpoints it may call. A request with a key that lacks the required scope is rejected with 403 forbidden (see above).
| Scope | Grants |
|---|---|
contacts:read | GET /api/v1/contacts, GET /api/v1/contacts/{id} |
contacts:write | create / update / delete contacts |
events:write | POST /api/v1/events |
transactional:send | POST /api/v1/transactional |
topics:write | add / remove a contact on a topic |
Creating a key requires an explicit, non-empty scope list — omitting it is rejected, and unknown scope names are rejected too. Pre-existing keys with no stored scopes are treated as having none (deny-all), so grant the specific scopes each integration needs.
Best practices
- Keep keys server-side only
- Rotate and revoke keys on schedule
- Use separate keys per environment
- Monitor request patterns and failures