BankScanPro API developer preview
This is the proposed v1 contract for accepted private-pilot customers. It documents the integration shape used for pilot planning; it is not a promise that self-serve credentials or every endpoint are currently live.
Status
Private pilot contract
The contract is intentionally versioned before general availability. Accepted pilot customers receive a pinned contract, credentials, allowance, support channel, and acceptance criteria.
- Contract version
- 0.9.0-preview
- Base URL
- https://api.bankscanpro.com/v1
- Authentication
- Bearer API key
- Usage metric
- Physical PDF pages
Quickstart
Submit one PDF as an asynchronous job
Use a non-sensitive idempotency key so a network retry cannot create duplicate billable work.
curl https://api.bankscanpro.com/v1/jobs \
--request POST \
--header "Authorization: Bearer $BANKSCANPRO_API_KEY" \
--header "Idempotency-Key: loan-application-1842" \
--form "file=@statement.pdf" \
--form 'options={"pages":"all","result_format":"canonical_json"}'const form = new FormData();
form.append("file", statementPdf);
form.append("client_reference", "loan-application-1842");
form.append("pages", "all");
form.append("result_format", "canonical_json");
const response = await fetch("https://api.bankscanpro.com/v1/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BANKSCANPRO_API_KEY}`,
"Idempotency-Key": "loan-application-1842",
},
body: form,
});
const job = await response.json();Authentication
Keep API keys on your server
Pilot credentials are scoped to one workspace and environment. Never expose them in browser code, mobile apps, statement filenames, analytics properties, or support screenshots.
Environment separation
Test and production credentials are issued and revoked independently.
Rotation
Applications should support overlapping keys during a controlled rotation window.
Log hygiene
Log request IDs and job IDs, never raw keys or financial document content.
Jobs
A small resource model
The v1 preview keeps ingestion, status, result retrieval, approved exports, cancellation, and usage reporting explicit.
| Method | Path | Purpose |
|---|---|---|
| POST | /jobs | Upload a PDF and create a conversion job |
| GET | /jobs/{job_id} | Read processing or terminal status |
| GET | /jobs/{job_id}/result | Retrieve a deliverable canonical result |
| POST | /jobs/{job_id}/exports | Create a format allowed by the delivery decision |
| DELETE | /jobs/{job_id} | Cancel running work or request artifact deletion |
| GET | /usage | Read allowance and page consumption |
Results
Treat delivery state as part of the data contract
A successful HTTP request does not mean the result is automatically complete. Your integration must branch on the terminal job status and delivery object.
completeThe requested page scope passed the automatic delivery gates. Review signals remain available.
partialSource-backed rows are available, but the supplied document or requested scope is incomplete.
needs_reviewThe result is retained for review instead of being silently promoted to a reliable conversion.
failedNo safe result is available. The error response explains whether the request can be retried.
Recommended integration rule
Only automate downstream accounting or underwriting actions after checking `status`, `delivery.automatic_complete`, `delivery.allowed_exports`, and any workflow-specific validation requirement.
Webhooks
Verify, deduplicate, then acknowledge
The preview sends terminal job events. Polling remains the recovery mechanism if a webhook is delayed or rejected.
Verify the raw body using the timestamp and signature headers before parsing JSON.
Reject stale timestamps and store event IDs so retries are processed idempotently.
Return a fast 2xx response, then perform downstream work asynchronously.
const timestamp = request.headers.get("BankScanPro-Timestamp");
const signature = request.headers.get("BankScanPro-Signature");
const signedPayload = `${timestamp}.${rawRequestBody}`;
verifyHmacSha256({
secret: process.env.BANKSCANPRO_WEBHOOK_SECRET,
payload: signedPayload,
signature,
});
// Reject stale timestamps, then process event.id idempotently.Errors
Retry only when the response says it is safe
Every error includes a stable code, human-readable message, retryable flag, and request ID. Do not infer retryability from the HTTP status alone.
{
"type": "api_error",
"code": "result_not_ready",
"message": "The job has not reached a deliverable terminal state.",
"retryable": true,
"request_id": "req_01JZ7M2V75B2D8XQ6M5S9F1J3C",
"details": {
"job_id": "job_01JZ7M2V6VB9K0Y5F3Q1W8A2DN",
"status": "processing"
}
}Limits & usage
Pilot limits are contractual, not universal defaults
File size, pages per job, concurrency, requests per second, result retention, and webhook retry windows are defined in the accepted pilot plan.
Billing unit
Physical PDF pages accepted for processing
Reservation
Pages may be reserved at job creation and settled at terminal state
Rate response
429 with Retry-After when a workspace limit is reached
Usage endpoint
Included, used, remaining, and overage page counts
OpenAPI
Preview schema 0.9.0-preview
Use the machine-readable contract for review and client generation experiments, but pin the version supplied in your pilot agreement.
{
"openapi": "3.1.0",
"info": {
"title": "BankScanPro API",
"version": "0.9.0-preview",
"summary": "Private pilot contract for bank statement conversion",
"description": "Preview contract for accepted BankScanPro API pilot customers. The API is not generally available and this contract may change before v1 general availability.",
"contact": {
"name": "BankScanPro API Pilot",
"url": "https://bankscanpro.com/contact/api"
}
},
"servers": [
{
"url": "https://api.bankscanpro.com/v1",
"description": "Reserved production hostname for accepted pilot customers"
}
],
"tags": [
{
"name": "Jobs",
"description": "Submit and inspect asynchronous conversion jobs."
},
{
"name": "Exports",
"description": "Create approved file exports from a deliverable result."
},
{
"name": "Usage",
"description": "Inspect the current API allowance and consumption."
}
],
"paths": {
"/jobs": {
"post": {
"tags": [
"Jobs"
],
"operationId": "createStatementConversionJob",
"summary": "Submit a bank statement conversion job",
"description": "Creates an asynchronous job from one digital or scanned PDF. Repeating a request with the same idempotency key returns the original job.",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"name": "Idempotency-Key",
"in": "header",
"required": true,
"description": "A unique, non-sensitive key for safely retrying this submission.",
"schema": {
"type": "string",
"minLength": 8,
"maxLength": 160
}
}
],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": [
"file"
],
"properties": {
"file": {
"type": "string",
"format": "binary",
"description": "One PDF bank or credit-card statement."
},
"client_reference": {
"type": "string",
"maxLength": 160,
"description": "Your non-sensitive identifier for correlating the job."
},
"pages": {
"oneOf": [
{
"type": "string",
"const": "all"
},
{
"type": "array",
"items": {
"type": "integer",
"minimum": 1
},
"minItems": 1
}
],
"default": "all"
},
"result_format": {
"type": "string",
"enum": [
"canonical_json"
],
"default": "canonical_json"
}
}
}
}
}
},
"responses": {
"202": {
"description": "Job accepted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Job"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"402": {
"$ref": "#/components/responses/AllowanceRequired"
},
"409": {
"$ref": "#/components/responses/Conflict"
},
"413": {
"$ref": "#/components/responses/PayloadTooLarge"
},
"429": {
"$ref": "#/components/responses/RateLimited"
}
}
}
},
"/jobs/{job_id}": {
"get": {
"tags": [
"Jobs"
],
"operationId": "getStatementConversionJob",
"summary": "Get job status",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/JobId"
}
],
"responses": {
"200": {
"description": "Current job state",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Job"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/RateLimited"
}
}
},
"delete": {
"tags": [
"Jobs"
],
"operationId": "cancelOrDeleteStatementConversionJob",
"summary": "Cancel a running job or request artifact deletion",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/JobId"
}
],
"responses": {
"202": {
"description": "Cancellation or deletion request accepted",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"id",
"status"
],
"properties": {
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"cancelling",
"deletion_requested"
]
}
}
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"$ref": "#/components/responses/Conflict"
}
}
}
},
"/jobs/{job_id}/result": {
"get": {
"tags": [
"Jobs"
],
"operationId": "getStatementConversionResult",
"summary": "Get the canonical result",
"description": "Available when the job is complete, partial, or needs_review.",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/JobId"
}
],
"responses": {
"200": {
"description": "Canonical statement result",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatementResult"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"$ref": "#/components/responses/ResultNotReady"
},
"429": {
"$ref": "#/components/responses/RateLimited"
}
}
}
},
"/jobs/{job_id}/exports": {
"post": {
"tags": [
"Exports"
],
"operationId": "createStatementExport",
"summary": "Create an allowed file export",
"description": "The requested format must appear in delivery.allowed_exports.",
"security": [
{
"bearerAuth": []
}
],
"parameters": [
{
"$ref": "#/components/parameters/JobId"
},
{
"name": "Idempotency-Key",
"in": "header",
"required": true,
"schema": {
"type": "string",
"minLength": 8,
"maxLength": 160
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"format"
],
"properties": {
"format": {
"type": "string",
"enum": [
"csv",
"excel",
"qbo",
"ofx"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "Export created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Export"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"409": {
"$ref": "#/components/responses/ExportNotAllowed"
},
"429": {
"$ref": "#/components/responses/RateLimited"
}
}
}
},
"/usage": {
"get": {
"tags": [
"Usage"
],
"operationId": "getApiUsage",
"summary": "Get current page allowance and usage",
"security": [
{
"bearerAuth": []
}
],
"responses": {
"200": {
"description": "Current usage period",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Usage"
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"429": {
"$ref": "#/components/responses/RateLimited"
}
}
}
}
},
"webhooks": {
"jobTerminal": {
"post": {
"operationId": "jobTerminalWebhook",
"summary": "A job reached a terminal state",
"description": "Sent for complete, partial, needs_review, failed, or cancelled jobs. Consumers verify the BankScanPro-Signature header.",
"parameters": [
{
"name": "BankScanPro-Signature",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WebhookEvent"
}
}
}
},
"responses": {
"200": {
"description": "Webhook accepted"
}
}
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "BankScanPro API key"
}
},
"parameters": {
"JobId": {
"name": "job_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"pattern": "^job_[A-Za-z0-9]+$"
}
}
},
"schemas": {
"JobStatus": {
"type": "string",
"enum": [
"queued",
"processing",
"complete",
"partial",
"needs_review",
"failed",
"cancelled"
]
},
"Job": {
"type": "object",
"required": [
"id",
"object",
"status",
"created_at",
"request_id"
],
"properties": {
"id": {
"type": "string",
"examples": [
"job_01JZ7M2V6VB9K0Y5F3Q1W8A2DN"
]
},
"object": {
"type": "string",
"const": "statement_conversion_job"
},
"status": {
"$ref": "#/components/schemas/JobStatus"
},
"progress": {
"type": [
"number",
"null"
],
"minimum": 0,
"maximum": 100
},
"client_reference": {
"type": [
"string",
"null"
]
},
"created_at": {
"type": "string",
"format": "date-time"
},
"completed_at": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"result_url": {
"type": [
"string",
"null"
],
"format": "uri-reference"
},
"error": {
"oneOf": [
{
"$ref": "#/components/schemas/Error"
},
{
"type": "null"
}
]
},
"request_id": {
"type": "string"
}
}
},
"StatementResult": {
"type": "object",
"required": [
"id",
"status",
"page_scope",
"statement",
"validation",
"delivery",
"request_id"
],
"properties": {
"id": {
"type": "string"
},
"status": {
"$ref": "#/components/schemas/JobStatus"
},
"page_scope": {
"type": "object",
"required": [
"source_document_total_pages",
"requested_pages",
"delivery_pages",
"document_completeness"
],
"properties": {
"source_document_total_pages": {
"type": "integer",
"minimum": 1
},
"requested_pages": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1
}
},
"delivery_pages": {
"type": "array",
"items": {
"type": "integer",
"minimum": 1
}
},
"document_completeness": {
"type": "string",
"enum": [
"full_document",
"user_selected_subset",
"pipeline_incomplete"
]
}
}
},
"statement": {
"type": "object",
"required": [
"currency",
"period",
"transactions"
],
"properties": {
"institution_name": {
"type": [
"string",
"null"
]
},
"account_type": {
"type": "string",
"enum": [
"checking",
"savings",
"credit_card",
"loan",
"unknown"
]
},
"currency": {
"type": "string",
"minLength": 3,
"maxLength": 3
},
"period": {
"type": "object",
"properties": {
"start_date": {
"type": [
"string",
"null"
],
"format": "date"
},
"end_date": {
"type": [
"string",
"null"
],
"format": "date"
},
"opening_balance": {
"type": [
"number",
"null"
]
},
"closing_balance": {
"type": [
"number",
"null"
]
}
}
},
"transactions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Transaction"
}
}
}
},
"validation": {
"type": "object",
"additionalProperties": true,
"description": "Named validation checks and warning identifiers used by the BASE delivery contract."
},
"delivery": {
"type": "object",
"required": [
"automatic_complete",
"allowed_exports",
"blocking_reasons",
"review_reasons"
],
"properties": {
"automatic_complete": {
"type": "boolean"
},
"allowed_exports": {
"type": "array",
"items": {
"type": "string",
"enum": [
"csv",
"excel",
"qbo",
"ofx"
]
}
},
"blocking_reasons": {
"type": "array",
"items": {
"type": "string"
}
},
"review_reasons": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"request_id": {
"type": "string"
}
}
},
"Transaction": {
"type": "object",
"required": [
"id",
"date",
"description",
"debit",
"credit",
"balance",
"currency",
"source"
],
"properties": {
"id": {
"type": "string"
},
"date": {
"type": "string",
"format": "date"
},
"description": {
"type": "string"
},
"debit": {
"type": [
"number",
"null"
]
},
"credit": {
"type": [
"number",
"null"
]
},
"balance": {
"type": [
"number",
"null"
]
},
"currency": {
"type": "string"
},
"reference": {
"type": [
"string",
"null"
]
},
"source": {
"type": "object",
"required": [
"page_number",
"line_ids"
],
"properties": {
"page_number": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"line_ids": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"Export": {
"type": "object",
"required": [
"id",
"format",
"download_url",
"expires_at"
],
"properties": {
"id": {
"type": "string"
},
"format": {
"type": "string",
"enum": [
"csv",
"excel",
"qbo",
"ofx"
]
},
"download_url": {
"type": "string",
"format": "uri"
},
"expires_at": {
"type": "string",
"format": "date-time"
}
}
},
"Usage": {
"type": "object",
"required": [
"period_start",
"period_end",
"included_pages",
"used_pages",
"remaining_pages"
],
"properties": {
"period_start": {
"type": "string",
"format": "date-time"
},
"period_end": {
"type": "string",
"format": "date-time"
},
"included_pages": {
"type": "integer",
"minimum": 0
},
"used_pages": {
"type": "integer",
"minimum": 0
},
"remaining_pages": {
"type": "integer",
"minimum": 0
},
"overage_pages": {
"type": "integer",
"minimum": 0
}
}
},
"WebhookEvent": {
"type": "object",
"required": [
"id",
"type",
"created_at",
"data"
],
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"job.complete",
"job.partial",
"job.needs_review",
"job.failed",
"job.cancelled"
]
},
"created_at": {
"type": "string",
"format": "date-time"
},
"data": {
"type": "object",
"required": [
"job"
],
"properties": {
"job": {
"$ref": "#/components/schemas/Job"
}
}
}
}
},
"Error": {
"type": "object",
"required": [
"type",
"code",
"message",
"retryable",
"request_id"
],
"properties": {
"type": {
"type": "string",
"const": "api_error"
},
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"retryable": {
"type": "boolean"
},
"request_id": {
"type": "string"
},
"details": {
"type": "object",
"additionalProperties": true
}
}
}
},
"responses": {
"BadRequest": {
"description": "The request could not be validated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"Unauthorized": {
"description": "The API key is missing, invalid, or revoked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"AllowanceRequired": {
"description": "The workspace does not have sufficient page allowance",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"NotFound": {
"description": "The requested resource was not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"Conflict": {
"description": "The request conflicts with the current resource state",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"ResultNotReady": {
"description": "The job has not reached a deliverable terminal state",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"ExportNotAllowed": {
"description": "The validation or page-scope decision does not allow this export",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"PayloadTooLarge": {
"description": "The PDF exceeds the pilot file or page limit",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"RateLimited": {
"description": "The workspace exceeded its current request limit",
"headers": {
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"x-bankscanpro-status": "private_pilot_preview",
"x-bankscanpro-generated": "2026-07-16"
}Changelog
Contract history
0.9.0-preview
Initial private-pilot preview covering jobs, canonical results, fail-closed delivery states, exports, usage, deletion, errors, and signed terminal webhooks.