Last updated 2 September 2026

Error handling

Every Lovelio API response - success or failure - carries a consistent envelope.

Success

{
  "success": true,
  "data": { ... },
  "meta": { "request_id": "req_abc123def456" },
  "error": null
}

Failure

{
  "success": false,
  "data": null,
  "meta": { "request_id": "req_abc123def456" },
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "email is required.",
    "field": "email",
    "docs": "https://docs.lovelio.ai/errors/VALIDATION_ERROR",
    "request_id": "req_abc123def456"
  }
}

error.field is present only when a specific field is at fault. error.docs always points at the docs for that code.

Always log request_id

Every response includes meta.request_id (and repeats it on error.request_id for failures). Log it on every API call you make. If you file a support ticket or need the team to trace an issue, the request ID is the fastest path from your log line to our traces.

HTTP header form: X-Request-ID. Both are set on every response.

Error codes

Branch on error.code, never on the message text.

error.codeHTTPWhen it happensWhat to do
VALIDATION_ERROR422Missing or malformed fieldFix and retry. error.field points at the offender.
AUTHENTICATION_REQUIRED401Missing or invalid API keyCheck the Authorization: Bearer header. Keys are regional - the message says when a valid key hit the wrong region's domain.
INSUFFICIENT_SCOPE403API key scope does not cover this actionUse a key with the named scope or narrow the request.
RESOURCE_NOT_FOUND404Resource does not exist, or belongs to another accountThe ID is wrong or not yours. Do not retry.
CONFLICT409Duplicate resource or state mismatchHandle as a no-op or pick a different identifier.
IDEMPOTENCY_KEY_REUSED409Same Idempotency-Key, different requestUse a fresh key for each distinct operation.
MISSING_IDEMPOTENCY_KEY400POST to an idempotent endpoint without the headerAdd an Idempotency-Key header.
RATE_LIMIT_EXCEEDED429Too many requests this minuteRead Retry-After, back off, retry.
INTERNAL_ERROR500Our faultRetry with backoff. If persistent, file with the request_id.

Codes you will meet on specific paths: TRIAL_EXPIRED (403), EMAIL_NOT_VERIFIED (403, trial keys must verify email before writes), PREMIUM_REQUIRED (402), QUOTA_EXCEEDED (403, over a volume allowance - talk to us rather than wait), PAYLOAD_TOO_LARGE (413), ACCOUNT_NOT_FOUND (404), MARKETPLACE_DISABLED (403).

Idempotency

POST endpoints that create or send things require an Idempotency-Key header - the OpenAPI spec marks the header required on each one, and posting without it returns 400 MISSING_IDEMPOTENCY_KEY. Use a UUID per logical operation. Retrying with the same key within 24 hours returns the original response (with an X-Idempotency-Replayed: true header) instead of running the operation again.

curl -X POST $LOVELIO_HOST/api/v1/applications/app_01HXXX/stage \
  -H "Authorization: Bearer $LOVELIO_API_KEY" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{ "stage": "yes", "reason": "Strong screen - moving forward" }'

Rules:

  • Keys apply to POST only. PATCH and DELETE ignore the header.
  • Keys are scoped to your account and live for 24 hours. After that a retry runs as a fresh operation.
  • A key is bound to the exact request it was first used for (method, path, and body). Reusing it for a different request returns 409 IDEMPOTENCY_KEY_REUSED - it never replays the wrong response.

Retry rules

Safe to retry: any 5xx, any 429, any network error.

Safe to retry on POST because of the idempotency key: the retry returns the original response instead of creating a duplicate.

Never retry: 4xx other than 429. They will not succeed no matter how many times you try.

Exponential backoff starting at 1 second, doubling to a max of 60. Stop after 5 attempts.

Rate limits

Every response includes:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 598
X-RateLimit-Reset: 1730000000

Limits are per API key, per minute window: 600 requests/minute on an active plan, 60 requests/minute for trial, past-due, and cancelled accounts. There is no burst allowance - the minute window is the whole rule.

When you hit 429, Retry-After is the seconds until the window resets. Respect it.

Bulk operations

If you find yourself making many calls in a row, switch to POST /api/v1/batch. One call dispatches up to 100 canonical actions and counts as a single rate-limit unit.

curl -X POST $LOVELIO_HOST/api/v1/batch \
  -H "Authorization: Bearer $LOVELIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "operations": [
      { "op": "move_stage", "payload": { "application_id": "app_1", "new_stage": "rejected" }, "idempotency_key": "batch-001" },
      { "op": "move_stage", "payload": { "application_id": "app_2", "new_stage": "yes" }, "idempotency_key": "batch-002" }
    ]
  }'

Operations run independently - one failing never rolls back the others, and each carries its own idempotency_key so you can retry a single failed op without re-running the ones that succeeded. The full action catalogue is on the POST /batch operation in the API reference.

Debugging checklist

When a call fails and the error message is not obvious:

  1. Check error.docs - points to the docs for that error code.
  2. Copy the request_id. Paste it into any support message.
  3. Verify the key is live: GET /api/v1/accounts/me. If that returns 200, your auth is fine; the issue is in the specific endpoint.
  4. Verify the resource exists and belongs to the authed account: GET /api/v1/{resource}/{id}. A 404 here means wrong account or deleted resource.
  5. For async tasks, check the task state: GET /api/v1/tasks/{task_id}. Error details live there, not on the originating call.