ClearStaq API
Submit a PDF, receive structured revenue, fraud, and debt analysis via signed webhook. Credentials are generated at app.clearstaq.com.
Submit a bank-statement PDF to https://app.clearstaq.com/api/v1/analyses. The endpoint returns 202 Accepted with an analysis id; the full parsed 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.
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
Multipart upload. Maximum size 25 MB. Only application/pdf is accepted.
curl -X POST https://app.clearstaq.com/api/v1/analyses \
-H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
-F "file=@./statement.pdf" \
-F "callback_url=https://your-app.example.com/hooks/clearstaq" \
-F "client_ref=customer-123" \
-F 'metadata={"loan_id":"LN-999"}'When the analysis completes, ClearStaq POSTs a signed JSON envelope to your callback_url. Respond with any 2xx status within 10s to acknowledge. Non-2xx responses are retried up to 5 times with exponential backoff (1m, 5m, 30m, 2h, 12h).
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, ... }
}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")
}| 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 could not be parsed. |
| 502 | parser_unavailable | Downstream parser could not accept the job. |
Download the machine-readable spec to generate SDKs or import into Postman/Insomnia. openapi.yaml