Go to Dashboard → Partner API. Fill in the company name + billing email, accept the Terms, and click Enable. Activation creates your first production API key instantly.
2
Save your production key
The plaintext key is shown ONCE on activation. Copy it into your secret manager — we hash it server-side and cannot show it again. If lost, revoke and generate a new one from the API Keys tab.
3
Add credits
Open the Billing tab and pick a tier — Starter ($49 = 4,900 credits) is enough for ~32 full audits. Payment runs through Stripe Checkout. Credits land on your balance the moment Stripe confirms the charge.
4
Make your first audit call
POST /api/v1/audit with { "url": "https://example.com", "type": "all" }. You will get back an audit_id immediately while the engine scans in the background. See "Receiving results" below for the three ways to get the final scan back.
Skip writing HTTP by hand for the first pass. Download the Postman collection or the OpenAPI spec, plug in your key, and run real requests against the live API in under a minute.
Download the collection above and open Postman → File → Import → drop the .json file.
2
Open the imported collection ("CiteFlow API v1") and switch to the Variables tab.
3
Override Current Value for baseUrl (e.g. https://www.citeflow.io/api/v1 in prod) and apiKey (your ckf_… key from /dashboard/api/keys).
4
Open Balance → Get balance → Send. Expect 200 + your current credit balance.
5
Open Audits → Create audit, edit the body to your real URL + type ("all" | "seo" | "aeo" | "geo"), Send. Expect 202 + audit_id. Postman auto-saves audit_id as a collection variable for the next request.
6
Open Audits → Get audit → Send every 3-5 seconds. The status field walks queued → processing → complete (or failed). When complete, scores, dimensions, and issues appear in the response.
7
Once happy, port the same sequence to your code — or paste the AI prompt below into Claude / Cursor / ChatGPT and let it scaffold the whole integration.
Receiving audit results
Audits run asynchronously. POST /audit returns a 202 with the audit_id immediately while the engine scans in the background (typically 15-60s, longer for heavy sites). Two ways to receive the final result — pick by where you're calling from. (For ad-hoc manual testing, the 7-step Postman walkthrough above shows the polling path.)
Recommended
Webhooks
CiteFlow pushes the final result to your endpoint the moment the engine finishes. Zero polling, lowest latency.
When: Production integrations with a server (or serverless function) you can point at.
// Your /webhooks/citeflow endpoint receives a slim summary:
{
"id": "evt_…",
"type": "audit.completed",
"data": {
"audit_id": "…",
"url": "…",
"scores": { "overall": 73, "seo": 78, … },
"credits": { "charged": 80, "refunded": 0 }
}
}
// Need dimensions / issues / fixes? Call
// GET /audit/{audit_id} from your handler.
Setup: Register your URL in the Webhooks tab and verify signatures with the SDK helper (HMAC-SHA256, ±5 min skew, 7-day rotation grace).
Recommended
SDK helper
waitForCompletion() polls under the hood with sensible backoff and returns the final audit. One line of code.
When: Node.js or Python clients — scripts, batch tooling, server-to-server calls that genuinely need to block.
Setup: npm install @citeflow/sdk · pip install citeflow-python. Pass your API key on construction.
Why not sync? Holding an HTTP connection for 60s × thousands of audits/minute is how production APIs collapse. Async returns control instantly and lets webhooks deliver the result when it's actually ready — that's the same pattern Stripe, OpenAI batches, and AWS Textract use.
Build with AI — no code required
Pick the prompt that matches your integration shape, copy it, paste it into Claude, Cursor, ChatGPT, or any coding assistant, and edit only the <<< … >>> block to describe the feature you want built. The AI will scaffold a working integration — endpoints, retry, idempotency, tests, README — without you writing HTTP by hand.
Prompt 1 — Synchronous flow (SDK + polling)
Builds a complete "user enters URL → spinner → result" experience. Code fires the audit, polls via the SDK's waitForCompletion helper, and renders the scores when done. Simplest path — no webhook endpoint to host, no signing secret to manage. Good for MVPs and any volume up to a few hundred scans per day.
You are integrating the CiteFlow Partner API into <MY APP NAME>. This integration FIRES audits from my code and reads results synchronously — I do NOT run a webhook endpoint. Use ONLY the info below; do not invent endpoints or behaviour.
## API basics
- Base URL: https://www.citeflow.io/api/v1
- Auth: Authorization: Bearer ckf_<my-key>
- Content-Type: application/json on POST
- Every success response: { "data": { ... }, "request_id": "req_..." }
- Every error response: { "error": { "code": "...", "message": "...", "doc_url": "..." }, "request_id": "..." }
- Every POST MUST include header Idempotency-Key: <uuid-v4>. The SAME key MUST be reused for every retry of the same logical operation — do NOT mint a fresh key per retry, or you risk double-charging on a network blip.
## Endpoints I will use
### POST /audit — create an audit
Body: { "url": "https://example.com", "type": "all" | "seo" | "aeo" | "geo", "metadata"?: { ... } }
Returns 202: { "data": { "audit_id": "...", "status": "queued", "type": "...", "credits": { "charged": number /* 80 for seo|aeo|geo, 150 for all */ } } }
### GET /audit/{id} — poll status / read result
Returns 200: { "data": { "audit_id": "...", "status": "queued" | "processing" | "complete" | "failed" | "cancelled", "scores"?: { "overall": 73, "seo": 78, "aeo": 65, "geo": 70 }, "dimensions"?: { "technical": 80, "crawlers": 95, "schema": 70, "citability": 74, "platform": 80, "content": 78, "brandMentions": 65 }, "issues"?: [ ... ], "failure_reason"?: "timeout" | "engine_error" | "blocked_by_policy" | "partner_input" | "cancelled_by_user", "credits": { "charged": number, "refunded": number } } }
Treat credits.charged and credits.refunded as plain numbers. Typical values: charged 80 or 150; refunded 0 / 50% on engine-side failure / 100% on cancel. Do NOT model them as a closed enum — the refund matrix may evolve.
### POST /audit/{id}:cancel — abort while still queued / processing
Refunds 100% of charged credits.
### GET /balance — current credit balance
Returns: { "data": { "balance": 4900, "balance_usd": "$49.00", "lifetime_purchased": 4900, "lifetime_consumed": 0 } }
## Credit costs per audit
- type=all: 150 credits ($1.50, full SEO+AEO+GEO bundle)
- type=seo | aeo | geo: 80 credits ($0.80) each
## Reliability patterns
1. Auto-retry on 429 and 5xx with exponential backoff. Honor Retry-After if present.
2. For each logical POST, generate ONE fresh UUIDv4 Idempotency-Key and reuse it across ALL retries of that operation. Do NOT generate a new key per retry.
3. Polling helper: call GET /audit/{id} every 3 seconds until status is terminal (complete | failed | cancelled). Overall timeout 90 seconds; surface a clean timeout error to my caller if exceeded.
4. Log request_id on every API error. Never log API keys.
## Official SDKs — prefer these over hand-rolling HTTP
- Node.js: `npm install @citeflow/sdk`. Exposes a Citeflow class with .audits.create() / .audits.waitForCompletion() / .audits.get() / .audits.cancel() / .balance.retrieve().
- Python: `pip install citeflow-python`. Same surface, snake_case (audits.wait_for_completion(), etc).
### SDK return shape
- On success, SDK methods return the UNWRAPPED "data" object (e.g. `client.audits.create()` returns { audit_id, status, credits, ... } directly — NOT the { data, request_id } envelope).
- On failure, methods RAISE a typed CiteflowError with .code / .message / .requestId (Node) or .request_id (Python) / .docUrl / .status. Log those fields, never the raw exception.
### Use SDK helpers, don't re-implement
The SDKs already ship: auto-retry on 429+5xx with backoff + Retry-After, auto Idempotency-Key (with proper reuse across SDK-internal retries), and waitForCompletion polling. USE THOSE. Fall back to raw fetch / requests only for behaviour the SDK does not provide.
## What I want you to build
<<< DESCRIBE THE FEATURE — for example:
"Add a Scan button to my Next.js admin page that scans a URL via the SDK's waitForCompletion helper and renders overall + seo + aeo + geo scores in a result card. Include a Jest test that mocks the SDK."
"Write a Python script that reads urls.txt, scans each URL with type=all in parallel (max 4 concurrent) using the SDK's wait_for_completion, and writes results to results.csv." >>>
## Constraints
- Use the official SDK if one exists; otherwise raw fetch / requests.
- Read CITEFLOW_API_KEY from environment — never hardcode.
- Log request_id on every error so I can quote it in a support ticket.
- Write at least one happy-path test that mocks the SDK / fetch (no live API calls).
- Include a short README section with env-var setup + a demo command.
- When an SDK method signature, package import, or return shape is not explicitly stated above, make the smallest reasonable assumption, tag it with a "// ASSUMPTION:" comment, and isolate it behind a tiny adapter so it can be corrected in one place. Do NOT invent endpoints, response fields, or behaviours.
Also builds a complete "user enters URL → eventually sees result" experience, but the trigger returns immediately and CiteFlow webhooks the result to your backend when ready. Frontend gets updated via a DB poll or a realtime channel of your choice. More moving parts (webhook URL, signing secret, idempotent processing) but scales smoother at high volume and lower latency.
You are integrating the CiteFlow Partner API into <MY APP NAME> using the ASYNC pattern: my server fires audits, CiteFlow scans in the background, then CiteFlow webhooks the final result to MY server, which surfaces it to the user. Build the COMPLETE flow — outbound trigger + inbound webhook + a path to deliver results to the frontend — so a user in my product can submit a URL and eventually see the scan results. Use ONLY the info below; do not invent endpoints or behaviour.
(Pick this pattern when scans run in the background and the user doesn't sit waiting on a single request — also better when scan volume scales. If the user is staring at a spinner expecting an immediate response, use the Outbound polling prompt instead.)
## API basics (for outbound calls FROM my code TO CiteFlow)
- Base URL: https://www.citeflow.io/api/v1
- Auth: Authorization: Bearer ckf_<my-key>
- Content-Type: application/json on POST
- Success: { "data": { ... }, "request_id": "req_..." }
- Error: { "error": { "code": "...", "message": "...", "doc_url": "..." }, "request_id": "..." }
- Every POST MUST include header Idempotency-Key: <uuid-v4>. The SAME key on every retry of the same logical operation. Do NOT mint a new key per retry attempt.
## Endpoint I will call to TRIGGER audits
### POST /audit
Body: { "url": "https://example.com", "type": "all" | "seo" | "aeo" | "geo", "metadata"?: { ... } }
Returns 202: { "data": { "audit_id": "...", "status": "queued", "type": "...", "credits": { "charged": number /* 80 for seo|aeo|geo, 150 for all */ } } }
Return the audit_id to my caller immediately and STOP. Do NOT poll GET /audit/{id} from this path — the webhook will deliver the final result.
## Endpoint I may call as a fallback safety net
### GET /audit/{id}
Use ONLY if a webhook is suspiciously late (e.g. > 5 minutes past queued without a delivery). If the audit shows a terminal status here but my scans row is still pending, mark the scan as status='webhook_missing' and surface an alert. Do NOT try to auto-replay — the replay endpoint needs a delivery_id which is not available from GET /audit/{id}.
### POST /webhooks/deliveries/{delivery_id}/replay (informational — usually NOT called from partner code)
Authorization: Bearer ckf_<my-key>. delivery_id is the per-delivery attempt identifier from the CiteFlow dashboard webhook log — it is NOT the same as event.id, audit_id, or X-CiteFlow-Webhook-Id. Do NOT guess or derive it. Replay is usually driven from the dashboard UI by a human operator. Only implement automatic replay if my feature description below explicitly provides a delivery_id source.
## Credit costs per audit
- type=all: 150 credits
- type=seo | aeo | geo: 80 credits each
## What I receive via webhook (inbound)
CiteFlow POSTs to MY endpoint URL (registered manually at /dashboard/api/webhooks — not an API call) when an audit terminates or balance crosses a threshold. Event types fall in two groups:
### Audit events (audit.completed | audit.failed | audit.cancelled)
{
"id": "evt_...",
"type": "audit.completed",
"created_at": "2026-06-02T10:00:00Z",
"data": {
"audit_id": "...",
"url": "...",
"scores"?: { "overall": 73, "seo": 78, "aeo": 65, "geo": 70 },
"failure_reason"?: "timeout" | "engine_error" | "blocked_by_policy" | "partner_input" | "cancelled_by_user",
"credits": { "charged": number, "refunded": number }
}
}
Treat credits.charged and credits.refunded as plain numbers, not a closed enum.
The webhook payload is a SLIM summary — it does NOT include dimensions, issues, or fixes. If my feature needs those details, call GET /audit/{audit_id} ONCE from the webhook handler (or a queued job) after verifying the signature, and store the full result.
### Balance event (balance.low)
Fires once when my credit balance crosses below the low-balance threshold. The data shape is DIFFERENT from audit events — do NOT assume audit_id, url, scores, failure_reason, or credits.charged/refunded exist on this event. If my feature doesn't explicitly handle balance.low, just log it and return 200. Do NOT route balance.low through the audit_results upsert path.
## Webhook verification (CRITICAL — HMAC-SHA256, constant-time compare)
Headers on every delivery:
- X-CiteFlow-Webhook-Id: evt_...
- X-CiteFlow-Webhook-Timestamp: <unix-seconds>
- X-CiteFlow-Webhook-Signature: v1=<base64-hmac-sha256>
Algorithm:
1. Reject if timestamp drift > 300 seconds (5 minutes). Prevents replay.
2. signed_payload = byte concat of `${webhook_id}.${timestamp}.${raw_request_body}` where webhook_id is the exact value of the X-CiteFlow-Webhook-Id HEADER (NOT body.id — the body must not be parsed before verification) and timestamp is the exact value of X-CiteFlow-Webhook-Timestamp. raw_request_body is the raw request body bytes BEFORE any JSON parsing.
3. HMAC-SHA256 with my endpoint's signing secret. Base64-encode the digest.
4. Constant-time compare against the v1= value.
5. Header may carry "v1=old,v1=new" during a 7-day secret rotation grace period. Accept ANY matching value.
## Idempotent processing — MUST IMPLEMENT
Webhook deliveries may be retried by CiteFlow or replayed manually. Process every event idempotently:
- Use event.id as the dedup key (UNIQUE constraint on a processed_events table OR check-then-upsert in my audit_results table).
- For audit events, data.audit_id is also a valid dedup key — only one terminal event fires per audit.
- Duplicate deliveries must produce ZERO side effects. Design DB writes as UPSERTs.
- If a delivery is recognised as a duplicate (already-processed event), return 200 with no body. Do NOT return 409 or 500 for duplicates — that would make CiteFlow's delivery retries hammer the endpoint.
## App-level idempotency for scan triggers (different from CiteFlow's Idempotency-Key)
CiteFlow's Idempotency-Key only deduplicates retries of the SAME outbound POST /audit call. It does NOT protect against a user double-clicking the Scan button or my frontend retrying my own POST /api/scan endpoint — both of those produce two DIFFERENT logical operations from my server's perspective and would create two CiteFlow audits, charging me twice.
Add a second layer of dedup on MY trigger endpoint:
- Accept a client-supplied request_id (UUID from the browser) OR derive a server-side key from (user_id, url, type, time_bucket).
- Store it on the partner scans table with a UNIQUE constraint. If the same logical scan arrives twice within a short window, return the existing audit_id instead of firing a second POST /audit.
- This is separate from, and required IN ADDITION TO, CiteFlow's Idempotency-Key on the outbound call.
## Partner-owned scan status lifecycle
Use these statuses on MY scans table so the frontend, DB queries, and downstream logic agree:
- pending: I called POST /audit and got 202; waiting for the webhook.
- complete: audit.completed processed; scores stored.
- failed: audit.failed processed; failure_reason stored.
- cancelled: audit.cancelled processed.
- webhook_missing: fallback check (see below) found a terminal audit at CiteFlow but no webhook ever stored a result for me. Surface this to an operator.
## Reliability for my endpoint
- Return 200 within 5 seconds of successful verify + idempotent process. Heavier downstream work → enqueue then ACK 200 immediately, process async.
- On signature mismatch, return 400 and do NOT enqueue, store, or internally retry the event. Treat it as a security failure.
- CiteFlow auto-disables the endpoint after 10 consecutive non-2xx responses (400s from signature mismatch count). Alert on signature-mismatch spikes — repeated mismatches will get the endpoint auto-disabled and silently drop legitimate deliveries until I re-enable in the dashboard.
## Raw body capture — framework-specific gotchas
- Express: `app.post('/webhooks/citeflow', express.raw({ type: 'application/json' }), handler)`
- Next.js Route Handler: `const raw = await req.text(); /* verify with raw */; const event = JSON.parse(raw);`
- FastAPI: `raw = await request.body()`
- Most frameworks auto-JSON-parse the body and break verification. Always grab raw bytes first.
## Getting the result to MY frontend (closing the loop)
The webhook handler writes the result to my server-side DB. To surface it to a user in my UI, pick ONE:
- DB poll: frontend polls a partner-owned endpoint like GET /my-backend/audits/{audit_id} every few seconds until status is terminal. Simple, no realtime infra. Recommended default.
- Realtime push: backend broadcasts to a channel (WebSocket / SSE / Pusher / Ably / Supabase Realtime) keyed by audit_id or user_id, frontend subscribes after receiving the audit_id. Lower latency, more setup.
Do NOT have the frontend poll CiteFlow's GET /audit/{id} directly — that's what the webhook eliminates, and it would leak my API key into the browser.
## Logging
- BEFORE verifying a webhook: log X-CiteFlow-Webhook-Id and X-CiteFlow-Webhook-Timestamp on error paths. Do NOT JSON-parse the body just to log event.id — parsing before verifying defeats the security check.
- AFTER successful verify + parse: log event.id and event.type alongside any downstream-processing errors.
- For outbound calls I make (POST /audit, replay): log CiteFlow request_id from the response on errors.
- Never log CITEFLOW_API_KEY, CITEFLOW_WEBHOOK_SECRET, Authorization headers, or raw signature header values.
## Official SDKs — prefer them on both directions
- Node.js: `npm install @citeflow/sdk`. Citeflow class with .audits.create() for the outbound trigger + top-level parseWebhookEvent() / verifyWebhookSignature() for the inbound handler. .webhooks.replay(deliveryId) on the client for outbound replay.
- Python: `pip install citeflow-python`. Same surface, snake_case (audits.create(), parse_webhook_event() / verify_webhook_signature() at module level).
### SDK return shape
- Outbound SDK calls return the UNWRAPPED "data" object on success (not the { data, request_id } envelope). On failure they RAISE a CiteflowError carrying .code / .message / .requestId (Node) or .request_id (Python) / .docUrl / .status.
- parseWebhookEvent() / parse_webhook_event() verifies + JSON-parses + returns the typed event in one call. If it throws, return 400.
### Use SDK helpers, don't re-implement
The SDKs ship: auto-retry on 429+5xx with backoff + Retry-After, auto Idempotency-Key with proper reuse across SDK-internal retries, and full webhook signature verification (timestamp drift, raw body bytes, constant-time compare, rotation grace). USE THEM. Fall back to raw fetch / hand-rolled HMAC only for behaviour the SDK doesn't provide.
## What I want you to build
<<< DESCRIBE THE FULL FEATURE — for example:
"In my Next.js 14 App Router app:
- POST /api/scan accepts { url, type } from the user, fires the audit via @citeflow/sdk, stores { audit_id, url, status: 'pending', user_id } in my Postgres scans table, and returns audit_id immediately.
- POST /webhooks/citeflow verifies the signature with parseWebhookEvent, then UPSERTs the matching scans row with scores on audit.completed (or status='failed' on failures), and ACKs 200.
- The frontend SWR-fetches /api/scans/{audit_id} with a 3-second refresh until status is terminal, then renders the result card.
Include a Jest test that mocks both client.audits.create() and parseWebhookEvent()." >>>
## Constraints
- Use the SDK in BOTH directions (outbound .audits.create + inbound parseWebhookEvent) if available; otherwise raw HTTP + hand-rolled HMAC per the spec above.
- Read CITEFLOW_API_KEY and CITEFLOW_WEBHOOK_SECRET from environment. Never hardcode.
- Capture the RAW request body BEFORE any JSON parsing on the webhook route.
- Implement idempotent processing using event.id (or data.audit_id as fallback dedup key).
- Write at least one happy-path test that does NOT hit the live API and does NOT require a real HMAC signature. Mock client.audits.create() for the outbound side and parseWebhookEvent() for the inbound side.
- Include a short README: env-var setup, how to expose the webhook URL locally (e.g. ngrok / cloudflared) for testing, and a curl that simulates a scan trigger.
- When something is unspecified (DB driver, frontend bridge choice, framework details), make the smallest reasonable assumption, tag with "// ASSUMPTION:", and isolate behind a tiny adapter. Do NOT invent CiteFlow API endpoints, payload fields, event types, header names, or signing rules beyond what's listed above. You MAY create partner-owned application endpoints (e.g. POST /api/scan, GET /api/scans/{audit_id}, POST /webhooks/citeflow) — those belong to my codebase, not CiteFlow's API surface.
Tip Pick ONE prompt — don't run both. Each builds a complete end-to-end flow on its own. If you start with Prompt 1 and your volume later outgrows polling, you can re-run Prompt 2 to rebuild the same feature on the async pattern. Mixing both in one codebase in one shot tends to produce a tangled file.
Frequently asked questions
How are credits charged per audit type?+
all costs 150 credits (the full SEO + AEO + GEO bundle). seo, aeo, and geo each cost 80 credits when ordered separately. Credits are deducted atomically at create-time — your balance won't go negative. Every audit type includes a paste-ready fix snippet on each issue — the same remediation the CiteFlow dashboard shows.
What happens if an audit fails on our engine?+
You get 50% of credits back automatically when the failure is on our side (timeout, engine_error). Refunds appear under credits.refunded in both the audit.failed webhook payload and the GET /audit/{id} response, and the credit posts in the Billing tab transaction history. Failures caused by your input (bad URL, blocked_by_policy) are not refunded.
I get 402 INSUFFICIENT_CREDITS — what now?+
Your balance is below the cost of the audit type you tried to run. Top up in Billing. The response body includes a required field telling you exactly how many credits short you are.
My webhook endpoint stopped receiving deliveries.+
We auto-disable endpoints after 10 consecutive failures. Open the Webhooks tab — if the endpoint shows "disabled", re-enable it once your endpoint is healthy again. Use the replay button to re-fire missed deliveries.
How do I verify a webhook signature?+
Both official SDKs ship a verifier (verifyWebhookSignature in @citeflow/sdk, verify_webhook_signature in citeflow-python). It handles the HMAC-SHA256 check, ±5 minute timestamp tolerance, and the 7-day secret rotation grace window — three things easy to get wrong by hand.
I hit 429 — am I being rate-limited?+
Yes. Rate limits scale with your lifetime spend tier. The response includes a Retry-After header in seconds; the SDKs honor it automatically. If you need a higher ceiling for a one-off backfill, email support@citeflow.io with the window + expected RPS and we can lift it temporarily.
Can I run audits without writing code?+
Yes — every endpoint is in the API reference and a Postman collection ships with the docs. For one-off scans the dashboard itself is faster — use the main Dashboard page, not the Partner API.
How do I rotate an API key without downtime?+
Click Rotate on a key in the API Keys tab. You get a new key plaintext (shown once) — the old key keeps working for 7 days. Swap the new key into your secret manager, deploy, then revoke the old one. No traffic interruption.
What is Idempotency-Key for?+
Pass a unique key on every POST /audit so a network retry never creates two audits. The SDKs auto-generate one per call. If you build raw HTTP, mint a UUID per audit and pass it in the Idempotency-Key header.
How do I cancel a running audit?+
POST /api/v1/audit/{id}:cancel while status is queued or processing. We refund the full 100% of credits because the engine never started work. Audits already complete cannot be cancelled.
Troubleshooting by error code
Code
What it means / how to fix
401 UNAUTHORIZED
Missing or invalid API key
Check the Authorization header reads exactly "Bearer ckf_…" (no quotes, no extra spaces). If the key was rotated or revoked, generate a fresh one in API Keys.
401 KEY_REVOKED
Key was revoked
Someone on your team revoked this key — likely after a leak. Generate a new key, replace it in your secret manager, redeploy.
402 INSUFFICIENT_CREDITS
Not enough credits
Top up in Billing. The response includes a "required" field with the exact credit shortfall.
403 IP_NOT_ALLOWED
Caller IP outside the allowlist
If you configured an IP allowlist on the key, add the caller IP (CIDR or single address) under API Keys → Edit. Remove the allowlist entirely if you want to allow all sources.
404 AUDIT_NOT_FOUND
Audit ID does not belong to your partner
Confirm you are using the right key (a key from another partner cannot read this audit). Audit IDs are scoped per partner.
413 BODY_TOO_LARGE
Request body over 1 MB
Drop any custom metadata you might be cramming into the audit request — only url + type + small metadata are needed.
429 RATE_LIMITED
Hit your tier rate limit
Back off and retry after Retry-After seconds. SDKs do this automatically. For sustained higher throughput, email support@citeflow.io.
503 ENGINE_BUSY
Engine saturated
Transient — retry after the Retry-After interval (typically 30s). If you see sustained 503s for more than a few minutes, email support@citeflow.io with a request_id from a failed call.
Glossary
audit_id
Unique per audit. Use it to poll GET /audit/{id} or cancel. Format: UUID (e.g. 019eb58e-0a61-7308-bc45-6a819f06a670).
status
queued → processing → one of { complete, failed, cancelled }. Only the four terminal/active values appear in webhooks.
failure_reason
When status=failed: timeout (engine ran past the budget), blocked_by_policy (target site blocked us), partner_input (bad URL), engine_error (our side), cancelled_by_user.
credits.charged / credits.refunded
How much you paid for the audit and how much we refunded on failure. balance_after shows the resulting balance, if available.
low_balance_threshold
Default 1,000 credits ($10). When your balance crosses below, we send a balance.low webhook + email so you can top up before depletion.
X-Request-Id
Returned on every response (success or error). Always quote it when emailing support — it is how we trace your call in our logs.
Need more credits or a custom plan?
Email support@citeflow.io with your expected monthly volume. Enterprise tiers above Scale ($499 / 49,900 credits) are quoted per partner.
Setting up webhooks
Register your endpoint URL under the Webhooks tab. CiteFlow signs every delivery with HMAC-SHA256 — verify with our SDK helpers, not by hand. Deliveries auto-disable after 10 consecutive failures and are replayable from the dashboard for 30 days.