CiteFlow API

Errors

Stable envelope and the full code catalog.

Every non-2xx response from /api/v1/* carries the same JSON envelope:

{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Balance below required 150 credits for type=all",
    "doc_url": "https://docs.citeflow.io/errors/insufficient-credits",
    "required": 150
  },
  "request_id": "req_01jbq6t8tjk0vfgz5gd9p4d4qa"
}

Code against error.code, not the HTTP status. Status mappings may relax (e.g., we might add a new 4xx) but codes are versioned with the API path (/api/v1).

The request_id is echoed back in the X-Request-Id response header. Include it in support tickets.

Code catalog

StatusCodeDescription
400INVALID_URLBody isn't JSON or a URL field isn't http(s).
400INVALID_TYPEtype not in all/seo/aeo/geo.
400INVALID_TIERtier not in starter/growth/scale.
400CONSENT_REQUIREDURL requires consent the partner did not grant.
401UNAUTHORIZEDMissing Authorization header.
401INVALID_API_KEYKey not recognized.
401KEY_REVOKEDKey revoked in the dashboard.
402INSUFFICIENT_CREDITSBalance below the required amount. Includes error.required.
403IP_NOT_ALLOWEDCaller IP outside the key's allowlist.
403SCOPE_INSUFFICIENTEndpoint requires a scope the key lacks.
404AUDIT_NOT_FOUNDAudit id missing or not owned by the key.
404WEBHOOK_DELIVERY_NOT_FOUNDDelivery id missing or not owned.
409AUDIT_CANNOT_CANCELCancel attempted in an invalid state (reserved — terminal states currently no-op).
409IDEMPOTENCY_CONFLICTSame Idempotency-Key reused with a different body.
409WEBHOOK_ENDPOINT_DISABLEDReplay refused — endpoint disabled in dashboard.
413BODY_TOO_LARGEBody > 1MB.
429RATE_LIMITEDPer-key per-endpoint rate limit hit. Includes error.retryAfter.
500INTERNAL_ERRORUnexpected server error. Quote request_id to support.
503ENGINE_BUSYEngine saturated. Honor Retry-After (default 30s).

Distinguishing failures

404 is returned for both "audit does not exist" and "audit belongs to a different workspace" — CiteFlow doesn't leak existence of other partners' resources.

Retry policy

  • 429, 503: respect Retry-After / error.retryAfter. Exponential back-off if missing.
  • 5xx (other): retry up to 3 times with jittered exponential back-off (250ms, 1s, 4s).
  • 4xx: do not retry — the request is wrong; fix and resubmit.

Both official SDKs implement this policy.

Common pitfalls

  • INSUFFICIENT_CREDITS after a top-up — Stripe webhook may lag the buyer redirect by a few seconds. Show "processing" until your next GET /balance reflects the new total.
  • IDEMPOTENCY_CONFLICT on the same payload — concurrent in-flight request raced with the same key. Sleep ~100ms and retry; the original call will have completed.
  • AUDIT_NOT_FOUND immediately after POST /audit — replication lag (rare). Sleep 250ms and retry.

On this page