ClearStaq
Log inBook a DemoGet started

Plans from $99/month. True revenue, positions, and 27 fraud signals included.

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.

ScopeGrants
analyses:writeSubmit documents (POST /api/v1/analyses, POST /api/v1/clients), create businesses (POST /api/v1/businesses), and manage webhook subscriptions, except creating submission.completed subscriptions, which requires analyses:read.
analyses:readRead analysis results, client profiles, and businesses (GET /api/v1/analyses/{id}, GET /api/v1/clients/{id}, GET /api/v1/clients/{id}/open-banking, GET /api/v1/businesses, GET /api/v1/businesses/{id}/submissions, GET /api/v1/people/{id}), list webhook subscriptions, and create submission.completed subscriptions (Available when the client portal launches).
applications:write · applications:read · documents:writeThe applications API for lead sources: create applications, ask their owners to sign, read them, add their documents (see Applications API below), and subscribe to the application webhooks.
partner_responses:writeOnly on a key an organization gives a partner: answer that organization's sends to the partner (POST /api/v1/partner-responses).

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
Node.js
Python
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

callback_url accepts HTTP or HTTPS without URL credentials. All resolved IP addresses must be public. Invalid or non-public targets receive 422 invalid_callback_url before the analysis is accepted. Webhook subscriptions continue to require HTTPS.

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
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.

Node.js
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
Node.js
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
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, poor_scan_quality (the scan is too poor to read; upload a clearer copy), 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, with the 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 }.

Client open-banking envelope

Fetch https://app.clearstaq.com/api/v1/clients/{id}/open-banking to get a client's entire open-banking picture (institutions, accounts, transactions, and the client-level revenue, MCA, and fraud rollup) in one response, instead of fetching and merging one analysis per document. Requires the analyses:read scope.

Query parameterMeaning
includeComma-separated subset of institutions, accounts, transactions, analysis. Default: everything except transactions, which must be requested explicitly. Unknown values are ignored. Sections not requested are omitted from the response.
transactions_limitPage size. Default 500, maximum 2000 (larger values are capped).
transactions_offsetTransactions to skip. Default 0, maximum 100000; a larger value returns 400 invalid_transactions_offset.
cURL
Node.js
curl "https://app.clearstaq.com/api/v1/clients/<client_id>/open-banking" \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"

# Transactions are opt-in and paginated (newest first):
curl "https://app.clearstaq.com/api/v1/clients/<client_id>/open-banking?include=transactions&transactions_limit=500&transactions_offset=0" \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"
{
  "schema_version": "1.0",
  "generated_at": "2025-03-04T12:00:00.000Z",
  "client": { "id": "8f1c...", "name": "Acme LLC" },
  "institutions": [{ "id": "chase", "name": "Chase" }],
  "accounts": [
    {
      "id": "acct_...",
      "institution_id": "chase",
      "mask": "4412",
      "label": null,
      "type": null,
      "is_composite": false,
      "source_account_count": null,
      "period": { "start": "2025-01-01", "end": "2025-02-28" },
      "balances": { "opening": 10250.4, "closing": 12980.11, "average": 11402.37 },
      "documents": ["d1...", "d2..."]
    }
  ],
  "transactions": [
    {
      "id": "t1...",
      "account_id": "acct_...",
      "document_id": "d2...",
      "date": "2025-02-27",
      "description": "ACH DEPOSIT",
      "counterparty": null,
      "amount": 1520.0,
      "direction": "credit",
      "category": "Revenue"
    }
  ],
  "analysis": {
    "revenue": { "true_revenue": 168500.24, "estimated_monthly": 84250.12 },
    "monthly": [{ "account_id": "acct_...", "month": "2025-01", ... }],
    "mca_positions": [],
    "fraud": { "score": 12, "risk_level": "low", "findings": [] },
    "checks": [],
    "excluded_documents": { "failed": 0, "processing": 0, "no_org": 0 },
    "ambiguous_accounts": 0,
    "unassigned_accounts": 0,
    "monthly_replacements": 0,
    "excluded_transactions": 0,
    "recompute_pending": false,
    "fraud_pending": false
  }
}

Read the data carefully:

  • Account ids beginning with acct_tmp_ are provisional and not stable: the same account can come back under a different id later, so never store them as a durable key. All other account ids are stable.
  • institution_id has two distinct empty-looking states. "unknown" is a value: the statement was read but its bank could not be identified. null means no institution is linked to the account at all. Treat them differently: neither one means the field is missing.
  • Transaction amount is always positive; direction (credit = money in, debit = money out) gives the sign.
  • analysis.recompute_pending covers only the revenue and MCA rollups (true = may be stale, null = no signal). Fraud freshness is reported separately in analysis.fraud_pending (true while fraud analysis is still running on any document).
  • analysis.excluded_documents counts documents left out of the envelope: failed (parsing failed), processing (not finished yet), and no_org (completed but not attributable to an organization, permanently excluded).
  • Transactions are ordered newest first. To page through them, advance transactions_offset by transactions_limit and stop on an empty page. Transactions with no amount or type are never returned (they are left out before paging, so they do not shorten a page), and their number is reported in analysis.excluded_transactions.
HTTPCodeMeaning
400invalid_idMalformed client id.
400id_requiredClient id missing from the path.
400invalid_transactions_offsettransactions_offset is above 100000.
403scope_requiredKey is missing the analyses:read scope.
404not_foundClient not found. Also returned for a client in another organization, so the response never reveals whether an id exists.
500internal_errorThe envelope could not be built. Safe to retry.

Businesses & submissions

A business is a company your organization works with: legal name, DBA, EIN, address, and industry. Every document for a business lands in one of its submissions, numbered #1, #2, and so on. A business has at most one open submission at a time, and new documents go into it. When your team marks that submission funded or lost in the webapp, it stops receiving documents and the next document opens the next number.

A submission is a client. Its id is the client_id the endpoints below return, and every client endpoint accepts it: GET /api/v1/clients/{id} for the aggregated profile and GET /api/v1/clients/{id}/open-banking for the open-banking envelope. POST /api/v1/clients works as before and creates a client that is not linked to any business yet; it appears in the webapp under Applications › Needs review, where your team can link it to a business.

EndpointScopeWhat it does
GET /api/v1/businessesanalyses:readLists your businesses, newest first, each with its open submission and its number of submissions. Query: page (from 1), per_page (default 25, maximum 100), search (case-insensitive match on the legal name or DBA).
POST /api/v1/businessesanalyses:writeCreates a business together with its open submission #1 and returns it with 201. JSON body: legal_name (required), dba, ein, address, industry, start_date (YYYY-MM-DD); text fields up to 500 characters.
GET /api/v1/businesses/{id}/submissionsanalyses:readLists every submission of a business, newest first, with its number, client_id, status, source, and open and close dates.
GET /api/v1/people/{id}analyses:readReturns a person (an owner or officer) with the businesses they are linked to, their title, and their ownership percentage.
POST /api/v1/analysesanalyses:writeWith the optional business_id field, files the document into that business's open submission.
cURL
# List businesses, newest first (search matches the legal name or DBA)
curl "https://app.clearstaq.com/api/v1/businesses?search=acme&page=1&per_page=25" \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"

# Create a business together with its submission #1
curl -X POST https://app.clearstaq.com/api/v1/businesses \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"legal_name":"Acme LLC","ein":"12-3456780","address":"100 Main St, Springfield, IL 62701"}'
{
  "businesses": [
    {
      "id": "5b0e...",
      "legal_name": "Acme LLC",
      "dba": null,
      "ein_last4": "6780",
      "address": "100 Main St, Springfield, IL 62701",
      "industry": null,
      "created_at": "2025-01-10T09:30:00.000Z",
      "open_submission": { "client_id": "9c3a...", "number": 2, "status": "open" },
      "submission_count": 2
    }
  ],
  "page": 1,
  "per_page": 25,
  "total": 1
}

Read the data carefully:

  • The full EIN is never returned; ein_last4 has its last four digits. The EIN you send is 9 digits (dashes allowed); placeholder numbers are rejected with 400 invalid_ein.
  • open_submission is null when every submission of the business is funded or lost. The next document sent with its business_id opens a new one.
  • An EIN already used by one of your businesses returns 409 business_exists with that business_id; reuse it instead of creating a copy.
  • Without an EIN, a business with the same name and address returns 409 possible_duplicate with the matching candidates. Repeat the request with ?force=true to create it anyway; force never bypasses the EIN check.

List a business's submissions to find the one you need, then read it with the client endpoints.

cURL
curl https://app.clearstaq.com/api/v1/businesses/<business_id>/submissions \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"
{
  "business_id": "5b0e...",
  "legal_name": "Acme LLC",
  "submissions": [
    {
      "number": 2,
      "client_id": "9c3a...",
      "status": "open",
      "source": "api",
      "opened_at": "2025-03-04T12:00:00.000Z",
      "closed_at": null
    },
    {
      "number": 1,
      "client_id": "8f1c...",
      "status": "funded",
      "source": "webapp",
      "opened_at": "2025-01-10T09:30:00.000Z",
      "closed_at": "2025-02-28T17:05:00.000Z"
    }
  ]
}
  • status is one of open, submitted, awaiting_signatures, complete, funded, or lost. Only funded and lost stop a submission from receiving documents, and they are set only by your team in the webapp; closed_at is filled then.
  • For portal applications, submitted means the merchant signed and submitted the application; awaiting_signatures means other required owners still need to sign; complete means all required signatures were collected and the application PDF was generated. These are submission states, separate from analysis processing statuses. An open_submission can have any of these states and still receive documents.
  • Merchant edits revoke signatures and return the submission to open. Signing again generates a new PDF version. Portal files and PDFs are not exposed through the public API.
  • source is the channel that opened the submission: webapp, email, api, or portal; null when it was not recorded.

To add a document to a business, send its business_id with POST /api/v1/analyses. The document goes into the open submission, or into a new one with the next number when none is open, and the 202 response adds a submission object (created is true when this request opened it). The business is checked before anything is stored or billed. Without business_id, the endpoint behaves exactly as described in “Submit a statement”.

cURL
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 "business_id=<business_id>"
{
  "id": "d3...",
  "status": "queued",
  ...
  "submission": { "client_id": "9c3a...", "number": 2, "created": false }
}

People are the owners and officers linked to your businesses. Only the businesses of your organization are listed.

cURL
curl https://app.clearstaq.com/api/v1/people/<person_id> \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"
{
  "id": "4e7d...",
  "email": "[email protected]",
  "full_name": "Jane Doe",
  "phone": null,
  "businesses": [
    { "business_id": "5b0e...", "legal_name": "Acme LLC", "title": "Owner", "ownership_pct": 60 }
  ]
}

A key reads and writes only its own organization. A business or person of another organization answers 404, exactly like one that does not exist.

HTTPCodeMeaning
400invalid_jsonThe POST /api/v1/businesses body is not valid JSON (invalid_body when it is not a JSON object).
400legal_name_requiredlegal_name is missing, empty, not a string, or longer than 500 characters.
400invalid_fieldAn optional field is not a string or is longer than 500 characters; field names it.
400invalid_einein is not 9 digits or is a placeholder number.
400invalid_start_datestart_date is not a YYYY-MM-DD date.
400invalid_idMalformed business or person id.
400invalid_business_idbusiness_id sent to POST /api/v1/analyses is not a valid UUID.
403scope_requiredKey is missing the scope in the table above. Sending business_id always needs analyses:write.
404not_foundBusiness or person not found in your organization.
404business_not_foundbusiness_id is not a business of your organization; nothing is stored or billed.
409business_existsThe EIN already belongs to one of your businesses; business_id identifies it.
409possible_duplicateA business with the same name and address exists; candidates lists it. Retry with ?force=true to create anyway.
500internal_errorThe businesses, submissions, or person could not be read or created. On POST /api/v1/analyses the codes are business_lookup_failed and submission_create_failed.
503business_create_unavailableThe business or its submission could not be created right now (submission_unavailable on POST /api/v1/analyses). Retry.

Applications API (lead sources)

Connect a lead source (your website form, a referral partner) and create complete applications: the business, its owners, the application's answers, then its documents. Create one key per lead source in Settings › Integrations › API & webhooks: its name is the Source of every application it creates. Give it the scopes it needs:

applications:writeCreate applications and ask their owners to sign.
documents:writeAdd documents to applications.
applications:readRead applications.

Create an application

POST /api/v1/applications. The business is your organization's business with that EIN, or a new one; its next application gets one or two owners (the ones who sign, owner 1 then owner 2) and the answers. The SSN is accepted, kept encrypted and never returned. answers takes your own application form's questions by their id. Everything is created in one transaction.

Send an Idempotency-Key: a retry with the same key and body returns the first application (200, Idempotent-Replayed: true) instead of creating a second. Keys are kept 24 hours.

curl -X POST https://app.clearstaq.com/api/v1/applications \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
  -H "Idempotency-Key: lead-000481" \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "LEAD-000481",
    "business": {
      "legal_name": "Bayside Tire & Wheel LLC",
      "dba": "Bayside Tire",
      "ein": "12-3458214",
      "entity_type": "llc",
      "industry": "Auto Repair",
      "start_date": "2016-12",
      "address": { "street": "88 Bay St", "city": "Tampa", "state": "FL", "zip": "33602" },
      "phone": "8135550188"
    },
    "owners": [
      { "full_name": "Rosa Alvarez", "email": "[email protected]", "title": "Owner",
        "ownership_pct": 100, "ssn": "123-45-6789", "date_of_birth": "1981-04-02" }
    ],
    "application": {
      "amount_requested": 80000,
      "use_of_funds": "Second location fit-out",
      "monthly_revenue": 61000,
      "answers": { "your_question_id": 6 }
    }
  }'

201 Created:

{
  "id": "9f2c…",
  "business_id": "41a7…",
  "number": 1,
  "status": "open",
  "source": "api",
  "source_name": "Website form",
  "external_ref": "LEAD-000481",
  "missing": ["bank_statements", "signatures"],
  "links": {
    "self": "/api/v1/applications/9f2c…",
    "documents": "/api/v1/applications/9f2c…/documents",
    "signature_invites": "/api/v1/applications/9f2c…/signature-invites"
  }
}
422validation_failedWith errors: [{field, message}], e.g. owners[0].ownership_pct missing. Values are never echoed.
409possible_duplicateNo EIN, and a business with the same name and address exists (candidates). Send its EIN, or retry with ?force=true.
409application_openThe business already has an open application (application_id): add documents to it.
422idempotency_key_reusedThe same key was used with another body.
409idempotency_in_progressThe first request with this key is still running. Retry shortly.

Add documents

Bank statements and tax returns as PDF; the other document types of your client portal (driver's license, voided check, …) as PDF, PNG or JPEG, and your own types (custom:<id>) in the formats you chose for them. The format is read from the file's bytes. The files go through the antivirus, and bank statements through the automatic analysis, like your client portal's files. Available once your client portal is live (409 documents_unavailable before).

Up to 10 MB per request, as multipart:

curl -X POST https://app.clearstaq.com/api/v1/applications/<id>/documents \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
  -F "doc_type=bank_statements" \
  -F "file=@./march.pdf" \
  -F "file=@./april.pdf"

Bigger files (statements up to 50 MB) go straight to storage with upload URLs, valid 2 hours:

# 1. Ask for upload URLs
curl -X POST https://app.clearstaq.com/api/v1/applications/<id>/documents/uploads \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"doc_type":"bank_statements","files":[{"name":"2025.pdf","size_bytes":31457280,"mime_type":"application/pdf"}]}'

# 2. PUT each file to its upload_url
curl -X PUT "<upload_url>" -H "Content-Type: application/pdf" --data-binary @./2025.pdf

# 3. Finish it
curl -X POST https://app.clearstaq.com/api/v1/applications/<id>/documents/<document_id>/complete \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"

Ask the owners to sign

POST /api/v1/applications/{id}/signature-invites emails each owner who signs and hasn't signed yet to review and sign the application in your client portal. Signing is always the owners'. An owner already asked gets the same request again; its email goes out at most once every 24 hours. Optional body: {"emails": ["[email protected]"]}.

curl -X POST https://app.clearstaq.com/api/v1/applications/<id>/signature-invites \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE"

Read an application

GET /api/v1/applications/{id}: status (open, awaiting_signatures, signed, submitted_to_partners, funded, lost), what's missing, the owners (never an SSN or a date of birth) and whether each signed, the documents by state, the analysis summary once bank statements are analyzed, and the outcome. Or listen to the webhooks.

Application webhooks

Events application.created, application.signed, application.funded, application.lost, partner_send.status_changed and partner_offer.received. Subscribe with POST /api/v1/webhooks and an applications scope: a key's subscriptions hear only the applications that key created, never the partner events. Endpoints added in Settings › Integrations › API & webhooks hear every application and the partner events. The payload holds ids, numbers, statuses and amounts (never a person's data) and is signed like every event (X-ClearStaq-Signature). In the application events data.status is the application's status, as in the GET; in partner_send.status_changed, the send's.

A key sends each event to an address once, and to at most 5 addresses; with only an applications scope it lists and deletes its own subscriptions (deleting needs applications:write). Deliveries are retried up to 8 times over about 18 hours and can arrive out of order: rely on created_at and the GET for the current state. Webhooks are turned on per organization. Ask ClearStaq support (until then POST /api/v1/webhooks answers 404).

{
  "id": "5b1e…",
  "event": "application.signed",
  "created_at": "2026-10-11T12:00:00.000Z",
  "data": {
    "org_id": "7a2b…",
    "application_id": "9f2c…",
    "business_id": "41a7…",
    "number": 1,
    "source": "api",
    "source_name": "Website form",
    "external_ref": "LEAD-000481",
    "status": "signed",
    "at": "2026-10-11T12:00:00.000Z"
  }
}

For partners: answer a send

An organization that sends you applications can give you a response key (scope partner_responses:write). It answers only the sends made to you. Name the send with reference (ClearStaq's, a UUID sent as clearstaq_reference in the API request and as "Our reference" in the email) or with partner_reference, your application id. decision: approve with offers (a new answer replaces your previous offers), decline with reason, documents with documents and/or note, reviewing or funded.

curl -X POST https://app.clearstaq.com/api/v1/partner-responses \
  -H "Authorization: Bearer cg_live_THE_KEY_THE_BROKER_GAVE_YOU" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "<clearstaq_reference>",
    "decision": "approve",
    "offers": [{ "id": "OF-1", "funded_amount": 50000, "factor_rate": 1.35,
                 "term_months": 8, "payment_frequency": "daily", "product": "mca" }],
    "contact": { "name": "Uma Writer" }
  }'

Client portal webhook

Available when the client portal launches

Subscribe to submission.completed through the existing POST /api/v1/webhooks endpoint with the analyses:read scope. It reports when all required owner signatures have been collected and the application PDF has been generated. The submission continues accepting documents until your team marks it funded or lost. Other webhook event subscriptions still require analyses:write.

Before launch, subscribing returns 400 invalid_event_type. Demo organizations receive 403 demo_read_only; the existing organization integration gate also applies. included_phases is ignored for this event and is required only for analysis.completed.

cURL
curl -X POST https://app.clearstaq.com/api/v1/webhooks \
  -H "Authorization: Bearer cg_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"event_type":"submission.completed","target_url":"https://your-app.example.com/hooks/clearstaq"}'

The 201 response returns the subscription's secret once. Use it to verify the same signature as other events: X-ClearStaq-Signature: sha256=<hex>, the HMAC-SHA256 of the exact raw request body. Acknowledge a valid delivery with a 2xx response.

{
  "id": "evt_...",
  "event": "submission.completed",
  "created_at": "2026-10-03T12:00:00.000Z",
  "data": {
    "org_id": "7a2b...",
    "business_id": "5b0e...",
    "client_id": "9c3a...",
    "submission_number": 2,
    "status": "complete",
    "pdf_id": "6d4e...",
    "pdf_version": 1
  }
}

The envelope contains only identifiers, numbers, and status. Its id identifies the event. It includes no client_ref, metadata, application responses, personal data, files, or download URLs. pdf_id is a correlation identifier; portal files and PDFs are not exposed through the public API. This event is delivered by webhook and is not returned by the analysis polling endpoint.

Error codes

HTTPCodeMeaning
401unauthorizedMissing or unrecognised API key.
402plan_excludedThe org has no paid plan.
402payment_dueThe org's last invoice is unpaid.
402insufficient_creditsNo credits remaining and no paid plan to bill extra credits.
413file_too_largePDF exceeds 25 MB.
415unsupported_media_typeNon-PDF upload.
422invalid_callback_urlcallback_url must use HTTP or HTTPS, contain no URL credentials, and resolve only to public IP addresses. The analysis is not accepted.
429rate_limitedRate limit exceeded. Honor Retry-After.
502parser_unavailableAnalysis 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.

Plans from $99/month

Take back your time and automate loan underwriting

Join the lending teams using ClearStaq to parse statements, catch fraud, and verify income, all in under 5 seconds.

True revenue, positions, and 27 fraud signals included.