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.

HeaderMeaning
X-RateLimit-LimitSustained requests per second
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (seconds) when the window resets
Retry-AfterSeconds 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).

ScopeGrants
contacts:readGET /api/v1/contacts, GET /api/v1/contacts/{id}
contacts:writecreate / update / delete contacts
events:writePOST /api/v1/events
transactional:sendPOST /api/v1/transactional
topics:writeadd / 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