API Reference
Bank statement analysis over HTTPS
Submit a PDF, receive structured revenue, fraud, and debt analysis via signed webhook. Generate credentials at app.clearstaq.com.
New to the API? Start with the conceptual guide: API & Webhooks overview.
Overview
Submit a bank-statement PDF to https://app.clearstaq.com/api/v1/analyses. The endpoint returns 202 Accepted with an analysis id; the full analysis result is delivered asynchronously to your callback_url as a signed webhook.
API access is available on StaqCore and above. Free-tier orgs cannot generate API keys.
Authentication
Every request must include a bearer token issued from your Integrations settings page. Keep both the cg_live_* key and the whsec_* webhook secret out of version control.
Authorization: Bearer cg_live_YOUR_KEY_HERE
API key scopes
Keys carry scoped permissions, so an integration can be granted only the access it needs. Each endpoint states the scope it requires; a request made with a key missing that scope receives 403 scope_required with required_scope and granted_scopes in the error body.
| Scope | Grants |
|---|---|
| analyses:write | Submit documents (POST /api/v1/analyses, POST /api/v1/clients) and manage webhook subscriptions. |
| analyses:read | Read analysis results and client profiles (GET /api/v1/analyses/{id}, GET /api/v1/clients/{id}). |
OAuth 2.0 access tokens (used by the Zapier integration) are accepted on the same endpoints and follow the same scope checks.
Submit a statement
Multipart upload. Maximum size 25 MB. Only application/pdf is accepted.
document_kind is required. Allowed values are bank_statement, tax_return, and application_form. A missing or invalid value returns HTTP 400 with error invalid_document_kind.
ClearStaqAI Fraud Detection (Gemini visual second-pass) is opt-in. Pass clearstaq_ai_enabled=true on a submission to enable it for that request. Defaults to off; the deterministic metadata fraud score always runs.
curl -X POST https://app.clearstaq.com/api/v1/analyses \
-H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
-F "file=@./statement.pdf" \
-F "document_kind=bank_statement" \
-F "callback_url=https://your-app.example.com/hooks/clearstaq" \
-F "client_ref=customer-123" \
-F 'metadata={"loan_id":"LN-999"}' \
# Opt in to the ClearStaqAI visual fraud second-pass (default off):
# -F "clearstaq_ai_enabled=true"Receive the result
When the analysis completes, ClearStaq POSTs a signed JSON envelope to your callback_url. Respond with any 2xx status within 10s to acknowledge. Transient failures (5xx, network timeouts, 408, and 429) are retried after 1m, 1m, 5m, and 15m; the fifth attempt is marked dead after about 22m total. Other 4xx responses are permanent and are not retried.
POST /hooks/clearstaq HTTP/1.1
Content-Type: application/json
X-ClearStaq-Event: analysis.completed
X-ClearStaq-Delivery: 2f8b0...
X-ClearStaq-Signature: sha256=<hex hmac>
{
"id": "cb_...",
"event": "analysis.completed",
"client_ref": "customer-123",
"status": "completed",
"result": { "bank_name": "Chase", "true_revenue": 84250.12, ... }
}Poll an analysis
Without a callback_url, poll https://app.clearstaq.com/api/v1/analyses/{id}. A completed bank statement returns the analysis.completed envelope with status and result. A completed tax return or application form with persisted extraction rows returns its extraction envelope with event and data. A completed tax return with no extracted rows uses the legacy analysis.completed envelope with status and result; it does not fabricate data: []. If an application-form extraction row is unavailable, the terminal fallback is its extraction envelope with an empty data array.
curl https://app.clearstaq.com/api/v1/analyses/<analysis_id> \ -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" # For a completed bank statement, the response has status/result. # For a completed extraction with persisted rows, it has event/data. # An empty tax return uses the analysis.completed envelope.
Verify the signature
Compute HMAC-SHA256 over the raw request body using your webhook signing secret and compare (timing-safe) to the X-ClearStaq-Signature header.
import crypto from "node:crypto"
// In your webhook handler:
export default function handler(req, res) {
const signature = req.headers["x-clearstaq-signature"]
const raw = req.rawBody // the exact JSON bytes ClearStaq sent
const expected =
"sha256=" + crypto.createHmac("sha256", process.env.CLEARSTAQ_WEBHOOK_SECRET).update(raw).digest("hex")
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature ?? ""))
if (!ok) return res.status(400).send("bad signature")
// req.body is the WebhookEnvelope — act on it, then respond 2xx.
res.status(200).send("ok")
}Client profiles (multiple statements)
To analyze a client across several statements at once, POST 1–25 PDFs to https://app.clearstaq.com/api/v1/clients. This creates a ClearStaq client in your org (visible in your webapp), parses every statement, and computes an aggregated profile over the valid statements. Incomplete or non-bank statements are excluded from the profile and listed with a failure reason. Billing is per document (same credits as a single analysis), scaled by file count.
Repeat the file field for each PDF (or use files).document_kind is required and must be bank_statement for this client-profile endpoint. Use /api/v1/analyses for tax_return or application_form; missing or invalid values return HTTP 400 with error invalid_document_kind. Max 25 MB per file. client_name is optional — when omitted, the client is named from the dominant account holder once parsed. clearstaq_ai_enabled is opt-in, same as a single submission.
curl -X POST https://app.clearstaq.com/api/v1/clients \ -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \ -F "file=@./statement-jan.pdf" \ -F "file=@./statement-feb.pdf" \ -F "document_kind=bank_statement" \ -F "client_name=Acme LLC" \ # Optional: opt in to the ClearStaqAI visual fraud second-pass (default off): # -F "clearstaq_ai_enabled=true"
The endpoint returns 202 Accepted with a client_id, a batch_id, and per-document queue state.
{
"client_id": "8f1c...",
"batch_id": "2a4d...",
"documents": [
{ "id": "d1...", "filename": "statement-jan.pdf", "status": "queued" },
{ "id": "d2...", "filename": "statement-feb.pdf", "status": "queued" }
]
}Poll a client profile
Fetch https://app.clearstaq.com/api/v1/clients/{id} to read the aggregated profile and per-document statuses. Poll until counts.completed + counts.failed === counts.total and profile is non-null — the profile is computed asynchronously shortly after the documents finish parsing.
curl https://app.clearstaq.com/api/v1/clients/<client_id> \ -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" # Poll until counts.completed + counts.failed === counts.total # AND profile is non-null (the profile is computed asynchronously # shortly after the documents finish parsing).
{
"id": "8f1c...",
"name": "Acme LLC",
"status": "completed",
"profile": { "true_revenue": 168500.24, "fraud_score": 12, ... },
"documents": [
{
"id": "d1...",
"filename": "statement-jan.pdf",
"status": "completed",
"bank_name": "Chase",
"period_start": "2025-01-01",
"period_end": "2025-01-31",
"fraud_score": 8,
"failure_reason": null,
"pages_present": 6,
"pages_expected": 6,
"detected_bank": "Chase"
},
{
"id": "d2...",
"filename": "statement-feb.pdf",
"status": "failed",
"failure_reason": "missing_pages",
"pages_present": 3,
"pages_expected": 6
}
],
"counts": { "total": 2, "completed": 1, "failed": 1 }
}failure_reason values include missing_pages, not_a_bank_statement:<type>, or another reason string; null when the document parsed successfully.
Prefer webhooks? Subscribe to the batch.completed event (via POST /api/v1/webhooks) and ClearStaq POSTs the computed profile to your endpoint once per submission — same HMAC X-ClearStaq-Signature scheme and 5-step retry backoff as analysis.completed. The payload is { event, batch_id, client_id, org_id, counts, profile, created_at }.
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or unrecognised API key. |
| 402 | plan_excluded | Org is on the Free plan. |
| 402 | insufficient_credits | No credits remaining and auto top-up is off. |
| 413 | file_too_large | PDF exceeds 25 MB. |
| 415 | unsupported_media_type | Non-PDF upload. |
| 422 | invalid_callback_url | callback_url is invalid. |
| 429 | rate_limited | Rate limit exceeded — honor Retry-After. |
| 502 | parser_unavailable | Analysis service could not accept the job. |
Rate limits
Requests are limited to 120 per minute per bearer token. Every /api/v1 response carries the standard RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, and RateLimit-Policy headers; exceeding the window returns 429 rate_limited with a Retry-After header. Self-throttle on RateLimit-Remaining rather than retrying into a 429. StaqScale plans can lift the limit.
Versioning & deprecation policy
The API is versioned in the URL path (/api/v1). Backwards-compatible additions — new optional fields, new endpoints, new enum values on fields documented as extensible — ship without a version bump. Breaking changes ship only as a new path version (/api/v2); the prior version then keeps working for at least 12 months, during which responses on deprecated surfaces carry Deprecation and Sunset headers and the change is announced by email to every organization with an active key.
OpenAPI spec & agent access
Download the machine-readable spec to generate SDKs or import into Postman/Insomnia: openapi.json · openapi.yaml
AI agents can call ClearStaq natively via the MCP server at https://clearstaq.com/mcp (Streamable HTTP; manifest at /.well-known/mcp.json), or start from the LLM-friendly index at /llms.txt.