We are in official beta.

Troubleshooting

What the API is telling you when it says no — every error carries a machine-readable code and a reason, so most problems are one field away from an answer.

I am getting 404 on every request

You are calling the wrong host. The API lives on https://api.docbutterfly.com and every endpoint hangs off /api/… there. docbutterfly.com is this website — marketing pages, the portal, these docs — and it has no /api routes at all, so it answers 404 to every one of them whether your key is perfect, wrong, or absent.

A bad key answers 401, never 404. So a 404 on a request you are sure about is a host problem, not a credential problem — and a 401 is actually good news, because it proves you reached the API.
Two probes that settle it
# 1. Is the API there? No key needed; JSON back means the host is right.
curl -i "https://api.docbutterfly.com/api/health"

# 2. Is my key the problem? A deliberately WRONG key must answer 401.
curl -i -X POST "https://api.docbutterfly.com/api/ConvertHtmlToPdf" \
  -H "X-API-Key: not-a-real-key" -H "Content-Type: application/json" \
  -d '{"html": "<p>x</p>", "returnBase64": true}'
# 401 -> host correct, fix the key.   404 -> host wrong, fix the URL.

The full picture, including how a webhook URL is built, is on Base URL & hosts. If the host is right and you still get 404, check the endpoint spelling against the API Reference — routing ignores case, so /api/converthtmltopdf is fine, but a name that is not in the catalog is not.

Read the error envelope first

Every error response — from every endpoint — has the same shape:

Error response body
{
  "success": false,
  "error":   "Payment Required",        // short human label
  "message": "Insufficient tokens",     // human-readable detail
  "code":    "INSUFFICIENT_TOKENS",     // stable machine code โ€” branch on this
  "required": 50, "available": 12       // extra fields, per error
}

Branch your integration on code, not on message. Codes are stable UPPER_SNAKE strings; messages get reworded. The extra fields carry the part you can act on — required and available on a 402, pageCount and maxPagesPerRequest on a 413, and so on.

A failed call is not billed. Tokens are reserved before the operation runs and refunded in full if it throws or returns anything outside 2xx. If you see a charge for a failed call, that is a bug worth reporting — send us the correlation id.

Status codes you will actually see

StatusTypical codeWhat went wrongWhat to do
400 INVALID_JSON The request body is not valid JSON. The message quotes the parse error. Fix the body. Nothing was reserved, so nothing was billed.
400 INVALID_INPUT, BAD_REQUEST The JSON parsed but a parameter is missing, malformed, or out of range. Check the endpoint's parameters in the API Reference.
401 UNAUTHORIZED No API key, or a key that resolves to no account. See below.
402 INSUFFICIENT_TOKENS The balance cannot cover the reservation for this call. See below.
403 FORBIDDEN The key is valid but the account is deactivated. Contact us — a key change will not fix this.
404 NOT_FOUND Wrong host, wrong path, or a webhook slug that is no longer published. If every call 404s, see above — it is the host. Otherwise check the endpoint spelling; re-publish the workflow in the Orchestrator.
413 PAYLOAD_TOO_LARGE, DOC_AI_TIER_PAGE_LIMIT Document AI operations process at most 30 pages per request. The body returns pageCount and maxPagesPerRequest. Split the document first (/api/SplitPdf or /api/ExtractPdfPages) and submit page ranges.
422 ENCRYPTED_PDF The PDF is password-protected or encrypted, so it cannot be re-serialized as-is. See below.
429 RATE_LIMITED An upstream AI provider is throttling — not a limit we impose. Retry with backoff. There are no hard rate limits on the API itself.
500 INTERNAL_ERROR An unexpected fault. The reservation is refunded automatically. Retry once; if it persists, send us the X-Correlation-Id.
500 BILLING_ERROR Tokens could not be reserved (storage contention), so the operation never ran. Retry. Nothing was charged.
502 AI_UNREACHABLE, AI_REQUEST_FAILED An AI-backed endpoint could not reach or was rejected by its provider. Retry. Non-AI endpoints are unaffected.
503 SERVICE_UNAVAILABLE The feature is built but not configured in this environment — email, e-signature and AI endpoints each need their own credentials. The message names what is missing. Tell us which endpoint you need turned on.

401 — the key is missing or does not resolve

Two different messages, two different causes:

  • “API key required” — no key arrived at all. Send it as X-API-Key: df_… or Authorization: Bearer df_…. A key in the query string is not accepted; that transport was removed on purpose, because full request URLs are written to diagnostic logs in plaintext.
  • “Invalid API key” — a well-formed key that matches no account. Usually a copy/paste artifact (trailing whitespace, a truncated value, a smart quote from a document) or a key that was replaced by a later regenerate.

Verify the exact key you are sending, in one free call that never touches your balance:

Key verification probe
curl "https://api.docbutterfly.com/api/account" -H "X-API-Key: $DOCBUTTERFLY_API_KEY"

# 200 { "authenticated": true, "clientId": "...", "isActive": true,
#       "tokenBalance": 940, "unlimited": false, "tier": "free" }
# 401 { "authenticated": false, "error": "UNAUTHORIZED", "message": "Invalid API key" }

If the probe says authenticated: false, the key is the problem — not the endpoint you were calling. Regenerate from Portal → API Key, remembering that the old key stops working the instant the new one is issued.

402 — insufficient tokens

The body tells you the arithmetic: required is what the platform tried to reserve, available is what you had, and operation is what you were calling.

The part that surprises people is which number fires the 402. Tokens are reserved before the operation runs, and for per-page operations the reservation is exact only when we can count the pages up front. If the page count cannot be determined from the request, the platform reserves a refundable worst case — the per-page rate multiplied by the 30-page cap — and the response says so:

402 on a per-page operation with an undeterminable page count
{
  "success": false, "code": "INSUFFICIENT_TOKENS",
  "required": 150, "available": 40, "operation": "pdf_analyze_ai",
  "perPageRate": 5, "maxPages": 30,
  "note": "\"required\" is a refundable worst-case hold (5 tokens/page x up to 30 pages),
           reserved because the page count could not be determined up front;
           the unused portion is refunded after processing."
}

Two ways out, in order of preference:

  1. Send the document in a field we can count. The reservation then matches the real document instead of the worst case, and a one-page extraction reserves one page's worth.
  2. Quote the job first. POST /api/quote is free and returns holdTokens — the number the 402 actually fires on — alongside affordable and shortfall. See Usage & Billing.

If the balance is genuinely spent, the monthly allowance refills on the first of the month, and purchased tokens can be added from Portal → Top Up.

422 — encrypted or password-protected PDFs

Bank statements, payroll runs and financial reports routinely ship with an owner password. They open without prompting, but they carry permission restrictions, and writing a new PDF from one produces a corrupt file. Rather than hand you that, those requests are refused with a 422 and the code ENCRYPTED_PDF.

Two fixes, both supported:

  • Pass the document password in the optional password field and it is decrypted for you.
  • Or strip the protection once with /api/UnlockPdf and work from the result.

Slow first call, then fast

Browser-backed endpoints — ConvertHtmlToPdf, CaptureWebPage and anything that renders a page — load a headless Chromium on a cold start. The first call after a quiet period can take 10–30 seconds; subsequent calls are seconds. This is expected, not a fault.

  • Set your HTTP client timeout to at least 120 seconds for those operations and for any pipeline that contains one. Power Automate and Logic Apps both default lower than you want here.
  • Retry on network errors and 5xx only. Do not blanket-retry on timeout — a retry of a slow-but-successful call runs the work again and bills it again.
  • A pipeline is billed once, up front, for the whole run — not per step.

Getting help — what to send us

Every response carries an X-Correlation-Id header, and that same id is on the log lines and the usage row for the request. It is the single most useful thing you can give us: with it we can find exactly what happened, including what the operation was charged and why.

You can also set the id yourself — send your own X-Correlation-Id request header (a Power Automate run id, for example) and we will use it, so a trace joins up across both systems.

Along with the correlation id, tell us the endpoint, the code from the response, and roughly when it happened. Your own call history is in Portal → Usage, and programmatically at GET /api/account/ledger.

Still stuck?

Reproduce it against your own input in the testbed, or send us the correlation id and we will look it up.

API Testbed Contact us