API reference · v1
Tarazu API
APIs that turn Indian bank statements into clean transactions, risk signals with reasons, tamper checks and verified account details.
On this page
Overview
One REST API, four capabilities: statement parsing (PDF, XLS, XLSX, CSV into clean transactions), a suspicious-activity risk score with weighted reasons, statement integrity checks, and account detail extraction. All requests and responses are JSON over HTTPS, amounts are in INR and timestamps are ISO 8601 in IST (+05:30).
https://api.tarazu.example/v1Risk output is a set of indicators, not a verdict. Responses carry risk.disclaimer: “Indicators only. Not a determination of fraud or mule activity.” Show it wherever you display the score.
Authentication
Send your secret key as a Bearer token on every request. Keys are created in the dashboard. Sandbox keys look like tz_test_… and only ever return mock data; live keys look like tz_live_… and respect your IP allowlist.
Authorization: Bearer tz_live_••••••••••••••••Never ship keys in mobile apps or browsers. Call the API from your backend. For safe retries on POST, send an Idempotency-Key header (any unique string, kept for 24 hours).
Endpoints
POST/statements/analyze
Upload a statement as multipart/form-data. Small files usually complete within the request (status: completed); larger ones return status: processing and finish via webhook or polling.
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | yes | PDF, XLS, XLSX or CSV. Max 25 MB, 500 pages. |
| password | string | no | Password for protected PDFs. Used once, never stored. |
| checks | string[] (JSON) | no | Any of parse, risk, integrity, account. Default: all four. |
| consent_ref | string | yes | Your reference to the account holder's recorded consent. 6 to 64 chars. |
| purpose | string | no | Declared purpose, e.g. loan_underwriting. Stored with the consent reference. |
| declared_profile | string | no | salaried, self_employed or business. Improves INCOME_MISMATCH. |
| external_ref | string | no | Your application or customer id, echoed back in responses and webhooks. |
curl -X POST https://api.tarazu.example/v1/statements/analyze \
-H "Authorization: Bearer tz_live_••••••••" \
-F "file=@statement_apr_sep.pdf" \
-F "password=•••••" \
-F 'checks=["parse","risk","integrity","account"]' \
-F "consent_ref=cns_8F2K19QX"GET/statements/{id}
Fetch the full result for a statement: account, summary, integrity and risk. Transactions are paginated separately.
curl https://api.tarazu.example/v1/statements/stm_01JD4Q7Y3W \
-H "Authorization: Bearer tz_live_••••••••"GET/statements/{id}/transactions
Cursor-paginated parsed rows in statement order.
| Query | Type | Description |
|---|---|---|
| limit | integer | 1 to 500, default 100. |
| cursor | string | Value of next_cursor from the previous page. |
| flagged | boolean | Only rows that contributed to a reason code. |
| direction | cr | dr | Credits or debits only. |
| from, to | date | Inclusive date range inside the statement period. |
curl "https://api.tarazu.example/v1/statements/stm_01JD4Q7Y3W/transactions?limit=100&flagged=true" \
-H "Authorization: Bearer tz_live_••••••••"{
"data": [
{
"id": "txn_3fd0a1",
"date": "2026-04-02",
"time": "23:41",
"mode": "UPI",
"direction": "cr",
"amount_inr": 4500,
"balance_inr": 5120.4,
"narration": "UPI/CR/412790110482/upi•••12@okbank",
"flags": [
"NIGHT_VELOCITY"
]
}
],
"next_cursor": "c_9Qm2",
"has_more": true
}DELETE/statements/{id}
Permanently erases the derived report, transactions and any cached artefacts for a statement. Use it when a data principal withdraws consent or asks for erasure. The audit-log entry (who, when, consent reference) is kept; statement content is not.
curl -X DELETE https://api.tarazu.example/v1/statements/stm_01JD4Q7Y3W \
-H "Authorization: Bearer $API_KEY"POST/webhooks/test
Sends a signed sample event to your configured webhook URL so you can test signature verification end to end.
curl -X POST https://api.tarazu.example/v1/webhooks/test \
-H "Authorization: Bearer tz_test_••••••••" \
-H "Content-Type: application/json" \
-d '{"event":"statement.completed"}'Response schema
POST /statements/analyze and GET /statements/{id} return the same object. Sections you did not request in checks are null.
{
"id": "stm_01JD4Q7Y3W",
"status": "completed",
"processing_ms": 4180,
"account": {
"holder_name": "R•••• K•••• S••••",
"bank": "Sample Co-operative Bank",
"account_no": "XXXXXXXX4417",
"ifsc": "SMPL0001234",
"type": "savings",
"period": {
"from": "2026-04-01",
"to": "2026-09-30"
}
},
"summary": {
"transactions": 1284,
"credits_inr": 4821330.5,
"debits_inr": 4809112,
"closing_balance_inr": 18630.25
},
"integrity": {
"verdict": "review",
"checks_passed": 11,
"checks_total": 13,
"findings": [
"font_mismatch_page_4",
"running_balance_break_row_612"
]
},
"risk": {
"score": 78,
"band": "high",
"label": "suspicious_activity_indicators",
"reasons": [
{
"code": "PASS_THROUGH",
"weight": 0.31,
"detail": "92% of credits leave the account within 30 minutes"
},
{
"code": "MANY_TO_ONE",
"weight": 0.22,
"detail": "214 distinct UPI senders, 3 recurring beneficiaries"
},
{
"code": "LOW_RESIDUAL",
"weight": 0.14,
"detail": "Median end-of-day balance below ₹600"
}
],
"disclaimer": "Indicators only. Not a determination of fraud or mule activity."
}
}| Field | Description |
|---|---|
| status | processing, completed or failed. |
| account | Extracted holder name (masked), bank, masked account number, IFSC, type and period. |
| summary | Row count and INR totals for credits, debits and closing balance. |
| integrity.verdict | pass, review or fail. findings lists check codes that did not pass with location. |
| risk.score | 0 to 100. Higher means more suspicious-activity indicators were observed. |
| risk.band | low (0 to 39), medium (40 to 69), high (70 to 100). |
| risk.reasons[] | Reason code, its weight in the score (0 to 1) and a plain-language detail. |
| risk.disclaimer | Fixed text to display with the score. |
Risk reason codes
Each code describes an observed pattern in the statement. None of them on its own, or together, establishes that an account is used for fraud.
| Code | Name | What it means |
|---|---|---|
| PASS_THROUGH | Rapid pass-through | A large share of incoming funds is moved out shortly after arrival, leaving little retained balance. |
| MANY_TO_ONE | Many senders, few beneficiaries | Credits arrive from a wide set of counterparties while debits concentrate on a small recurring set. |
| ONE_TO_MANY | Fan-out of debits | Funds from a few sources are distributed to an unusually wide set of beneficiaries. |
| LOW_RESIDUAL | Low residual balance | End-of-day balance stays very low relative to the volume flowing through the account. |
| ROUND_AMOUNT_BURST | Burst of round amounts | Clusters of identical or round-figure transfers within a short window. |
| NIGHT_VELOCITY | Late-night velocity | Elevated transaction count between 23:00 and 05:00 IST compared with the account's daytime pattern. |
| NEW_ACCOUNT_HIGH_VOLUME | High volume soon after opening | Turnover in the first months is high compared with typical accounts of the same type. |
| CASH_SPLIT_DEPOSITS | Split cash deposits | Several cash deposits of similar size on the same or adjacent days. |
| DORMANT_REACTIVATION | Reactivation after dormancy | A long inactive period followed by a sudden rise in activity. |
| INCOME_MISMATCH | Inflow pattern differs from declared profile | Credit pattern does not resemble salary or business receipts expected for the declared profile, if one is supplied. |
| CIRCULAR_FLOW | Circular flow | Amounts sent to a counterparty return in similar size within a short period. |
Integrity check codes
Checks that do not apply to the input (for example PDF metadata on a CSV) are reported as pass with a skipped note. Any fail makes the verdict fail; otherwise any review makes it review.
| Code | Check | Description |
|---|---|---|
| PDF_PRODUCER | Metadata and producer | PDF producer/creator matches the issuing bank's known generators; flags consumer editors. |
| MODIFIED_AFTER_CREATE | Edit timestamp | Modification date is not later than creation date. |
| FONT_CONSISTENCY | Font consistency | Glyph sets and font objects are consistent across rows and pages. |
| TEXT_LAYER | Text layer present | Statement has a native text layer, not only scanned images. |
| TEMPLATE_MATCH | Layout matches bank template | Column layout, headers and footers match the issuing bank's format. |
| PAGE_COUNT | Page count and sequence | Page numbering is continuous and matches the declared total. |
| RUNNING_BALANCE | Running balance continuity | Each row's balance equals the previous balance plus credit minus debit. |
| OPENING_CLOSING | Opening and closing totals | Opening balance plus net movement equals the closing balance. |
| DATE_ORDER | Date order | Transaction dates are monotonic and fall inside the statement period. |
| DUPLICATE_TXN_ID | Duplicate transaction IDs | Reference numbers (UTR/RRN) are not repeated across rows. |
| ARITHMETIC_TOTALS | Summary arithmetic | Printed totals equal the sum of parsed rows. |
| IFSC_VALID | IFSC and branch | IFSC format is valid and consistent with the bank name on the statement. |
| DIGITAL_SIGNATURE | Digital signature | Embedded signature, when present, validates and covers the whole document. |
Errors
Errors use standard HTTP status codes and a stable machine-readable code.
{
"error": {
"code": "password_required",
"message": "The PDF is encrypted. Send the password field.",
"request_id": "req_7Hk2PzQ"
}
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed. The message names the field. |
| 401 | unauthorized | Missing, malformed or revoked API key. |
| 403 | ip_not_allowed | Request came from an IP outside the key's allowlist. |
| 403 | env_mismatch | A sandbox key was used against live data, or the reverse. |
| 404 | not_found | No statement with this id in your workspace, or it has passed retention. |
| 409 | duplicate_upload | Same file hash was submitted within 10 minutes; the earlier id is returned. |
| 413 | file_too_large | File exceeds 25 MB or 500 pages. |
| 415 | unsupported_type | Only PDF, XLS, XLSX and CSV are accepted. |
| 422 | password_required | The PDF is encrypted and no password was sent. |
| 422 | wrong_password | The supplied password did not open the PDF. |
| 422 | consent_missing | consent_ref is required for every analysis. |
| 422 | unreadable_statement | No transaction table could be located (for example a blank scan). |
| 429 | rate_limited | Too many requests. Retry after the seconds given in Retry-After. |
| 500 | internal_error | Unexpected failure on our side. Safe to retry with the same Idempotency-Key. |
| 503 | temporarily_unavailable | Planned maintenance or overload. Retry with backoff. |
Webhooks and signatures
Events: statement.completed, statement.failed. We retry non-2xx responses with exponential backoff for up to 24 hours. Respond within 10 seconds and process asynchronously.
{
"id": "evt_01JD4Q8A2C",
"type": "statement.completed",
"created_at": "2026-11-07T14:32:04+05:30",
"data": {
"id": "stm_01JD4Q7Y3W",
"status": "completed",
"risk": {
"score": 78,
"band": "high"
},
"integrity": {
"verdict": "review"
}
}
}Every delivery carries an X-Signature header: a timestamp and an HMAC-SHA256 of {timestamp}.{raw body} using your webhook signing secret. Verify against the raw request body before parsing JSON, compare in constant time, and reject timestamps older than five minutes.
import crypto from "node:crypto";
// header: X-Signature: t=1762506124,v1=5f2c...e9
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) return false; // reject replays older than 5 minutes
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Rate limits
| Scope | Default | Notes |
|---|---|---|
| All endpoints | 120 req / min | Per workspace, across keys. |
| POST /statements/analyze | 20 req / min | Concurrent processing capped at 10 statements. |
| Sandbox | 30 req / min | Mock data only. |
Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. On 429, wait for Retry-After seconds. Higher limits are available on request.
Consent and data retention
Statements contain personal data of the account holder. Under the Digital Personal Data Protection Act, 2023 you act as the Data Fiduciary and Tarazu processes the data on your behalf.
- 01
consent_refis required.Every analysis must reference a consent record you hold: who consented, when, for what purpose. Requests without it fail with consent_missing.
- 02
Purpose limitation.
Use results only for the purpose the holder consented to, such as credit underwriting or onboarding checks.
- 03
Raw file deleted after processing.
The uploaded statement and any password are discarded as soon as processing finishes. They are never used for training.
- 04
Derived data is configurable.
Parsed transactions, scores and checks are kept for 30 days by default, configurable from 0 to 180 days per workspace.
- 05
Erasure on request.
Call
DELETE /statements/{id}when a holder withdraws consent; derived data is removed within 24 hours. - 06
Data stays in India.
Processing and storage happen in Indian data centres.
Grievances and data-principal requests: grievance@tarazu.example. Security reports: security@tarazu.example.