{"paths":{"/documents":{"post":{"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"A unique key to ensure the request is processed at most once. Must be unique per request — use a UUID. Keys expire after 24 hours. If a duplicate key is detected, the original response is replayed with `X-Idempotency-Replayed: true`.","schema":{"type":"string"}},{"name":"X-Filename","in":"header","required":false,"description":"Original filename for the uploaded PDF. Used for display purposes only. Defaults to 'document.pdf' if not provided.","schema":{"type":"string"}},{"name":"X-Webhook-URL","in":"header","required":false,"description":"Webhook URL for processing completion notifications. Overrides the default webhook URL configured on the API key.","schema":{"type":"string","format":"uri"}},{"name":"X-Password","in":"header","required":false,"description":"Decryption password for encrypted PDFs. When provided, the PDF is decrypted and processed in a single request — no need for the two-step unlock flow. The password is used in memory only and never stored or logged.","schema":{"type":"string","maxLength":1000}},{"name":"retry","in":"query","required":false,"description":"Set to 'true' to reprocess a previously failed upload of the same file. Without this, re-uploading a failed file returns an already_processing error.","schema":{"type":"string","enum":["true"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"upload_id":{"type":"string","description":"An upload_id from POST /v1/upload-tokens, after PUTting the file to its upload_url. Converts the previously-uploaded file by reference instead of sending PDF bytes in this request."},"webhook_url":{"type":"string","format":"uri","description":"Webhook URL for processing completion notifications. Overrides the default webhook URL configured on the API key."}},"required":["upload_id"]}},"application/pdf":{"schema":{"type":"string","format":"binary","description":"The raw PDF bank statement bytes. Set Content-Type to application/pdf."}}}},"operationId":"uploadDocument","summary":"Upload a bank statement PDF","description":"Upload a PDF bank statement for conversion. The file is processed asynchronously. Poll the returned `id` via GET /v1/documents/{id} to check progress. Rate limit: 10 requests per minute. Alternatively, POST a JSON body `{ \"upload_id\": \"...\" }` referencing a token minted via POST /v1/upload-tokens, to convert a previously-PUT file by reference.","tags":["Documents"],"responses":{"202":{"description":"Upload accepted. Processing has started, or the PDF requires a password.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-API-Version":{"$ref":"#/components/headers/X-API-Version"},"X-Idempotency-Replayed":{"$ref":"#/components/headers/X-Idempotency-Replayed"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatementUploadResponse"},"examples":{"processing":{"summary":"Upload accepted — processing started","value":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"processing","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","statements":[],"links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851"}}},"encrypted_pdf":{"summary":"Password-protected PDF — requires unlock","value":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"password_required","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","statements":[],"links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851","unlock":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/unlock"}}}}}}},"400":{"description":"Invalid request (e.g., missing file, wrong content type, invalid file).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing_file":{"summary":"No file uploaded","value":{"error":{"type":"invalid_request_error","code":"missing_file","message":"No file provided. Send a PDF as multipart/form-data with field name \"file\".","request_id":"req_7yNm3kPqW2a4bZ1c"}}},"invalid_file_type":{"summary":"Unsupported file type","value":{"error":{"type":"invalid_request_error","code":"invalid_file_type","message":"Invalid file type. Only PDF files are supported.","request_id":"req_2bFh6jLnR0c4dE8g"}}},"invalid_pdf":{"summary":"Corrupted or unreadable PDF","value":{"error":{"type":"invalid_request_error","code":"invalid_pdf","message":"This file could not be read as a valid PDF. It may be corrupted.","request_id":"req_6fJl0nRtV4g8hI2k"}}},"invalid_parameters":{"summary":"Invalid webhook_url or other parameters","value":{"error":{"type":"invalid_request_error","code":"invalid_parameters","message":"Invalid webhook_url: Webhook URL must use HTTPS.","meta":{"fields":[{"field":"webhook_url","message":"Webhook URL must use HTTPS."}]},"request_id":"req_8hLn2pTvX6i0jK4m"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Quota exceeded (insufficient credits, page limit, or file size).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"insufficient_credits":{"summary":"Not enough credits to process file","value":{"error":{"type":"quota_exceeded","code":"insufficient_credits","message":"Insufficient credits. This file requires 3 credit(s) but you have 1 remaining. Purchase more at https://bankstatemently.com/pricing.","meta":{"credits_required":3,"credits_remaining":1},"request_id":"req_8zAb2cDe4fGh6iJk"}}},"page_limit_exceeded":{"summary":"PDF exceeds plan's page limit","value":{"error":{"type":"quota_exceeded","code":"page_limit_exceeded","message":"Page limit exceeded. This file has 25 pages but your plan allows 10. Upgrade at https://bankstatemently.com/pricing.","meta":{"page_count":25,"page_limit":10},"request_id":"req_0bDf4hJlN8c2eG6i"}}},"file_too_large":{"summary":"File exceeds plan's size limit","value":{"error":{"type":"quota_exceeded","code":"file_too_large","message":"File too large. Your plan allows files up to 10 MB. Upgrade at https://bankstatemently.com/pricing.","meta":{"max_size_mb":10},"request_id":"req_2dFh6jLnR0e4gI8k"}}}}}}},"409":{"description":"Document already exists or is being processed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"already_processing","message":"This document is already being processed. Poll GET /v1/documents/{id} for status updates.","request_id":"req_4fHj8lNpT2g6iK0m"}}}}},"422":{"description":"Wrong password. Only returned when X-Password header is provided for an encrypted PDF.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"wrong_password","message":"The password provided is incorrect. Please try again.","request_id":"req_3eGi7kMoS1f5hJ9l"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}}},"get":{"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of documents to return.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","required":false,"description":"Number of documents to skip for pagination.","schema":{"type":"integer","minimum":0,"default":0}},{"name":"status","in":"query","required":false,"description":"Filter documents by processing status.","schema":{"$ref":"#/components/schemas/DocumentStatus"}}],"operationId":"listDocuments","summary":"List recent documents","description":"Retrieve a paginated list of your uploaded documents, ordered by creation date descending. Rate limit: 120 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Paginated list of documents.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-API-Version":{"$ref":"#/components/headers/X-API-Version"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatementListResponse"},"example":{"data":[{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"completed","contentHash":"a1b2c3d4e5f6","filename":"statement-jan-2026.pdf","pageCount":3,"source":"api","createdAt":"2026-02-27T10:30:00.000Z","updatedAt":"2026-02-27T10:31:15.000Z","exports":{"json":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/json","csv":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/csv","xlsx":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/xlsx","qbo":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/qbo","xero":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/xero"}},{"id":"e3a1b2c3-7d54-4b01-90e6-f801849g1962","status":"processing","contentHash":"b2c3d4e5f6g7","filename":"statement-feb-2026.pdf","pageCount":null,"source":"api","createdAt":"2026-02-27T11:00:00.000Z","updatedAt":"2026-02-27T11:00:00.000Z"}],"pagination":{"total":42,"limit":20,"offset":0,"hasMore":true}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/upload-tokens":{"post":{"operationId":"mintUploadToken","summary":"Mint a single-use upload token","description":"Mints a single-use, expiring upload token for hosts with no direct byte-upload equivalent (e.g. an agent sandbox with outbound-only network access). PUT the PDF to the returned upload_url, then POST /v1/documents with `{ \"upload_id\": \"...\" }` to convert it by reference. Tokens expire after ~2 hours and are single-use — PUTting a second file overwrites the first, but converting consumes the token. No credit is deducted on mint; credits are charged only at conversion. Rate limit: 20 requests per minute.","tags":["Documents"],"responses":{"201":{"description":"Upload token minted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"type":"object","required":["upload_id","upload_url","expires_at","max_bytes"],"properties":{"upload_id":{"type":"string","description":"Pass this to POST /v1/documents as { \"upload_id\": ... } to convert the uploaded file."},"upload_url":{"type":"string","format":"uri","description":"PUT the raw PDF bytes here, with Content-Type: application/pdf."},"expires_at":{"type":"string","format":"date-time","description":"When this token expires (~2 hours from mint)."},"max_bytes":{"type":"integer","description":"Maximum PDF size in bytes this token accepts, derived from the caller's plan."}}},"example":{"upload_id":"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e","upload_url":"https://api.bankstatemently.com/uploads/7f3a...c9","expires_at":"2026-08-10T14:00:00.000Z","max_bytes":52428800}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}":{"get":{"parameters":[{"name":"id","in":"path","required":true,"description":"The document UUID returned from the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"operationId":"getDocument","summary":"Get document processing status","description":"Poll the status of an uploaded document. When status is `completed`, the response includes statement metadata and export URLs. Rate limit: 120 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Document status and metadata.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-API-Version":{"$ref":"#/components/headers/X-API-Version"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatementDetailResponse"},"examples":{"processing":{"summary":"Document still processing","value":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"processing","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","updatedAt":"2026-02-27T10:30:00.000Z","statements":[],"links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851"}}},"completed":{"summary":"Document completed with statements","value":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"completed","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","updatedAt":"2026-02-27T10:31:15.000Z","document":{"bank":"DBS Bank","country":"SG","currency":"SGD","documentType":"bank-statement","pageCount":3,"transactionCount":78,"accountCount":1},"statements":[{"index":0,"statementPeriod":{"from":"2026-01-01","to":"2026-01-31"},"transactionCount":47},{"index":1,"statementPeriod":{"from":"2026-02-01","to":"2026-02-28"},"transactionCount":31}],"exports":{"json":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/json","csv":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/csv","xlsx":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/xlsx","qbo":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/qbo","xero":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/xero"},"links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851","data":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/data"}}},"password_required":{"summary":"Document requires a password","value":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"password_required","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","updatedAt":"2026-02-27T10:30:00.000Z","statements":[],"links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851","unlock":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/unlock"}}},"failed":{"summary":"Document processing failed","value":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"failed","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","updatedAt":"2026-02-27T10:30:45.000Z","statements":[],"error":"Unable to extract data from PDF. The file may be scanned or password-protected.","links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Document not found or does not belong to the authenticated user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_found","message":"Document not found.","request_id":"req_9xQr5tVwY3b6dE2f"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/data":{"get":{"parameters":[{"name":"id","in":"path","required":true,"description":"The document UUID.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"raw","in":"query","required":false,"description":"Set to `true` to include `originalData` (raw pre-normalization cell values) on each transaction. When enabled, max limit is reduced to 500.","schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"description":"Maximum number of transactions per page.","schema":{"type":"integer","minimum":1,"maximum":2000,"default":500}},{"name":"offset","in":"query","required":false,"description":"Number of transactions to skip for pagination.","schema":{"type":"integer","minimum":0,"default":0}}],"operationId":"getDocumentData","summary":"Get structured document data","description":"Retrieve structured document data including accounts and transactions. Only available when document status is `completed`. Transactions are paginated. Rate limit: 60 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Structured document data. Primary shape: document.statements[].products[].accounts[].transactions[]. Legacy flat accounts[] and transactions[] retained for backward compatibility.","content":{"application/json":{"example":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","updatedAt":"2026-02-27T10:31:15.000Z","processedAt":"2026-02-27T10:31:15.000Z","document":{"documentType":"bank-statement","bank":"DBS Bank","bankId":"dbs-bank-ltd","country":"SG","currency":"SGD","languages":["en"],"pageCount":3,"statementDate":"2026-01-31","statementPeriod":{"from":"2026-01-01","to":"2026-01-31"},"statements":[{"date":"2026-01-31","period":{"from":"2026-01-01","to":"2026-01-31"},"products":[{"productId":null,"productName":null,"currency":"SGD","declared":[{"payload":{"kind":"balance:closing","amount":4250.75,"direction":"credit","currency":"SGD"},"ownership":{"level":"product"}}],"categories":[],"accounts":[{"accountId":0,"accountNumber":"XXX-X-XX1234","accountHolderName":"John Doe","currency":"SGD","declared":[{"payload":{"kind":"balance:opening","amount":5000,"direction":"credit","currency":"SGD"},"ownership":{"level":"account"}},{"payload":{"kind":"balance:closing","amount":4250.75,"direction":"credit","currency":"SGD"},"ownership":{"level":"account"}},{"payload":{"kind":"total","amount":1200,"currency":"SGD"},"ownership":{"level":"account"},"slice":{"direction":"credit"}},{"payload":{"kind":"total","amount":1949.25,"currency":"SGD"},"ownership":{"level":"account"},"slice":{"direction":"debit"}}],"categories":[],"transactions":[{"sequence":0,"date":"2026-01-02","description":"SALARY CREDIT","amount":1200,"direction":"credit","currency":"SGD","balance":6200,"accountId":0,"accountNumber":"XXX-X-XX1234"}]}]}]}]},"accounts":[{"accountId":0,"accountNumber":"XXX-X-XX1234","accountHolderName":"John Doe","currency":"SGD","totals":{"openingBalance":5000,"closingBalance":4250.75,"totalCredits":1200,"totalDebits":1949.25},"declared":[{"payload":{"kind":"balance:opening","amount":5000,"direction":"credit","currency":"SGD"},"ownership":{"level":"account"}},{"payload":{"kind":"balance:closing","amount":4250.75,"direction":"credit","currency":"SGD"},"ownership":{"level":"account"}}],"categories":[]}],"transactions":[{"sequence":0,"date":"2026-01-02","description":"SALARY CREDIT","amount":1200,"direction":"credit","currency":"SGD","balance":6200,"accountId":0,"accountNumber":"XXX-X-XX1234"}],"pagination":{"total":47,"limit":500,"offset":0,"hasMore":false},"exports":{"json":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/json","csv":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/csv","xlsx":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/xlsx","qbo":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/qbo","xero":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851/export/xero"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Document not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_found","message":"Document not found.","request_id":"req_9xQr5tVwY3b6dE2f"}}}}},"409":{"description":"Document processing not yet completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_ready","message":"Document is not ready (status: processing). Poll GET /v1/documents/{id} until status is \"completed\".","meta":{"status":"processing"},"request_id":"req_3cGi7jKlN1b5eF2g"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/export/{format}":{"get":{"parameters":[{"name":"id","in":"path","required":true,"description":"The document UUID.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"format","in":"path","required":true,"description":"The export format.","schema":{"$ref":"#/components/schemas/ExportFormat"}},{"name":"dataMode","in":"query","required":false,"description":"The data mode to export. `original` returns raw extracted data; `enhanced` (default) includes AI-enriched fields; `normalized` applies standardized formatting.","schema":{"$ref":"#/components/schemas/ExportDataMode"}},{"name":"dateFormat","in":"query","required":false,"description":"Date format string for Xero CSV exports (e.g., `DD/MM/YYYY`). Only applicable when format is `xero`.","schema":{"type":"string","enum":["DD/MM/YYYY","MM/DD/YYYY","YYYY/MM/DD"]}}],"operationId":"exportDocument","summary":"Download converted document","description":"Download the processed document in the specified format. The document must have status `completed`. Rate limit: 30 requests per minute.","tags":["Exports"],"responses":{"200":{"description":"The exported file. Content-Type varies by format.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-API-Version":{"$ref":"#/components/headers/X-API-Version"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Content-Disposition":{"description":"Attachment filename for the exported file.","schema":{"type":"string"},"example":"attachment; filename=\"statement-jan-2026.csv\""}},"content":{"application/json":{"schema":{"type":"object","description":"Structured JSON export of the document."},"example":{"document":{"bank":"DBS Bank","country":"SG","currency":"SGD"},"statements":[{"period":{"from":"2026-01-01","to":"2026-01-31"},"transactions":[{"date":"2026-01-05","description":"GIRO PAYMENT","amount":-150,"balance":4850}]}]}},"text/csv":{"schema":{"type":"string","description":"CSV export of transactions."}},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"schema":{"type":"string","format":"binary","description":"Excel XLSX export."}},"application/x-ofx":{"schema":{"type":"string","description":"QBO (QuickBooks Online) OFX export."}}}},"400":{"description":"Invalid request parameters (e.g., invalid dataMode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"invalid_export_data_mode","message":"Invalid dataMode. Use \"original\", \"enhanced\", or \"normalized\".","request_id":"req_4kLm8nOpR1c5fG3h"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Document or completed processing run not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_found","message":"Document not found.","request_id":"req_2bHj6kMnQ0a4cD9e"}}}}},"409":{"description":"Document processing not yet completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_ready","message":"Document is not ready for export (status: processing). Poll GET /v1/documents/{id} until status is \"completed\".","meta":{"status":"processing"},"request_id":"req_5mNo9pQrS2d6gH4i"}}}}},"422":{"description":"Export generation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"export_failed","message":"Failed to generate CSV export: no transactions found.","request_id":"req_5eIk9lNoS2d7gH4i"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/unlock":{"post":{"parameters":[{"name":"id","in":"path","required":true,"description":"The document UUID returned from the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string","minLength":1,"maxLength":1000,"description":"The password to unlock the PDF. Used in memory only for decryption and never stored."}},"required":["password"]},"example":{"password":"my-pdf-password"}}}},"operationId":"unlockDocument","summary":"Unlock a password-protected document","description":"Submit a password to unlock and process a password-protected PDF. The document must have status `password_required`. On success, the document transitions to `processing` status. Rate limit: 20 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Password accepted. Document is now processing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-API-Version":{"$ref":"#/components/headers/X-API-Version"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatementUploadResponse"},"example":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","status":"processing","contentHash":"a1b2c3d4e5f6","createdAt":"2026-02-27T10:30:00.000Z","statements":[],"links":{"self":"https://api.bankstatemently.com/v1/documents/d290f1ee-6c54-4b01-90e6-d701748f0851"}}}}},"400":{"description":"Invalid request (e.g., document is not in `password_required` status).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"invalid_status","message":"Document status is \"processing\", not \"password_required\".","request_id":"req_9aXc3eGi5kMo7qSu"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Document not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_found","message":"Document not found.","request_id":"req_1bYd4fHj6lNp8rTv"}}}}},"422":{"description":"Wrong password.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"wrong_password","message":"The password provided is incorrect. Please try again.","meta":{"attempts_remaining":8},"request_id":"req_2cZe5gIk7mOq9sUw"}}}}},"429":{"description":"Rate limit exceeded or maximum unlock attempts reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"rate_limited":{"summary":"Too many requests","value":{"error":{"type":"rate_limit_error","code":"rate_limit_exceeded","message":"Rate limit exceeded. Try again later.","request_id":"req_3dAf6hJl8nPr0tVx"}}},"attempts_exceeded":{"summary":"Maximum unlock attempts exceeded","value":{"error":{"type":"rate_limit_error","code":"unlock_attempts_exceeded","message":"Maximum unlock attempts (10) exceeded. Please re-upload the file to try again.","request_id":"req_4eBg7iKm9oQs1uWy"}}}}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/categorize":{"post":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document UUID returned by the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"operationId":"categorizeDocument","summary":"Categorize transactions","description":"Categorize a document's transactions using AI. Returns cached categories if already generated (no credit charge). Otherwise deducts 1 credit and runs LLM-based categorization. Rate limit: 20 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Categories returned (cached or freshly generated).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Document UUID."},"contentHash":{"type":"string","description":"SHA-256 content hash of the original file."},"categories":{"type":"object","properties":{"metadata":{"type":"object","properties":{"categorizedAt":{"type":"string","format":"date-time","description":"When categorization was performed."},"model":{"type":"string","description":"AI model used for categorization."},"totalTransactions":{"type":"integer","description":"Number of transactions categorized."}}},"mappings":{"type":"object","additionalProperties":{"type":"string"},"description":"Map of transaction sequence number to category ID.","example":{"1":"groceries_personal","2":"meals_entertainment","3":"transfers","4":"utilities_telecom"}}}}}},"example":{"id":"d290f1ee-6c54-4b01-90e6-d701748f0851","contentHash":"a1b2c3d4e5f6...","categories":{"metadata":{"categorizedAt":"2026-03-04T12:00:00.000Z","model":"claude-haiku-4-5-20251001","totalTransactions":42},"mappings":{"1":"groceries_personal","2":"meals_entertainment","3":"transfers","4":"utilities_telecom","5":"supplies_equipment"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"quota_exceeded","code":"insufficient_credits","message":"Insufficient credits. Categorization requires 1 credit.","meta":{"credits_required":1,"credits_remaining":0},"request_id":"req_8zOn4lQrX3b5cA2d"}}}}},"404":{"description":"Document not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_found","message":"Document not found.","request_id":"req_9aPo5mRsY4c6dB3e"}}}}},"409":{"description":"Document not ready for categorization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_ready","message":"Document is not ready for categorization (status: processing). Poll GET /v1/documents/{id} until status is \"completed\".","meta":{"status":"processing"},"request_id":"req_0bQp6nStZ5d7eC4f"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/credits":{"get":{"operationId":"getCredits","summary":"Get credit balance","description":"Returns the current credit balance for the authenticated user. Recommended: call this before large uploads to ensure sufficient credits. 1 credit = 1 page. Rate limit: 120 requests per minute.","tags":["Credits"],"responses":{"200":{"description":"Current credit balance.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Cache-Control":{"description":"Caching directive. Response is cached for 5 seconds.","schema":{"type":"string"},"example":"private, max-age=5"}},"content":{"application/json":{"schema":{"type":"object","required":["balance","limit","planId","creditsExpireAt"],"properties":{"balance":{"type":"integer","description":"Current remaining credits. 1 credit = 1 page.","example":47},"limit":{"type":["integer","null"],"description":"Plan allowance. 10 for free, plan-specific for paid. null when the plan has no monthly allowance (e.g. enterprise).","example":100},"planId":{"type":"string","description":"Current plan identifier.","enum":["free","pro","business"],"example":"pro"},"creditsExpireAt":{"type":["string","null"],"format":"date-time","description":"When current plan credits reset, or null for plans without expiry.","example":"2026-04-01T00:00:00.000Z"}}},"example":{"balance":47,"limit":100,"planId":"pro","creditsExpireAt":"2026-04-01T00:00:00.000Z"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/product-rating":{"put":{"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5},"feedback":{"type":"string","maxLength":1000}},"required":["rating"]},"example":{"rating":5}}}},"operationId":"putProductRating","summary":"Submit or update your rating of Bankstatemently","description":"Submits (or updates) the caller's own 1-5 rating of the product as a whole, not of one document. One rating is stored per user; a later call replaces the rating, and replaces the stored feedback only when the call carries `feedback`. Permission to quote is asked on our own page only and is not accepted here. Rate limit: 10 requests per minute.","tags":["Feedback"],"responses":{"200":{"description":"Rating stored.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5}}}}},"example":{"data":{"rating":5}}}}},"400":{"description":"Invalid rating.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"invalid_parameters","message":"Invalid parameters: rating: Number must be less than or equal to 5.","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/benchmark/evaluate":{"post":{"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contentHash":{"type":"string","pattern":"^[a-f0-9]+$","minLength":64,"maxLength":64,"description":"SHA-256 hex digest of the benchmark PDF file.","example":"ba92a4b40c7a259f1e3d4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d"},"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Submission-internal handle, referenced by transactions[].accountId."},"accountNumber":{"type":"string","description":"Verbatim printed account number — never normalized by the submitter."},"name":{"type":"string","description":"Verbatim printed account label."},"currency":{"type":"string","description":"ISO 4217 currency code."}},"required":["id"],"additionalProperties":false},"description":"Optional account roster for multi-account statements. Each transaction references one via accountId."},"transactions":{"type":"array","minItems":1,"maxItems":2000,"items":{"$ref":"#/components/schemas/BenchmarkTransaction"},"description":"Parsed transactions from the benchmark statement (max 2000)."}},"required":["contentHash","transactions"]},"example":{"contentHash":"ba92a4b40c7a259f1e3d4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d","transactions":[{"date":"2025-06-02","description":"NTUC FAIRPRICE","amount":12.2,"direction":"debit","balance":15438.55,"originalData":{"Date":"02/06/2025","Description":"NTUC FAIRPRICE","Withdrawal (-)":"12.20","Balance":"15,438.55"}}]}}}},"operationId":"evaluateBenchmark","summary":"Evaluate parsed transactions against benchmark ground truth","description":"Submit parsed transactions from a [Bank Statement Parsing Benchmark](https://github.com/bankstatemently/bank-statement-parsing-benchmark) PDF for scoring. The `contentHash` is the SHA-256 hex digest of the PDF you parsed — this ties the evaluation to the exact file. Free to use, no credits consumed. Rate limit: 50 requests per hour.","tags":["Benchmark"],"responses":{"200":{"description":"Evaluation results with both normalized and parsed scores.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BenchmarkEvaluationResult"},"example":{"contentHash":"ba92a4b40c7a259f1e3d4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d","id":"bsb-001","datasetVersion":"2.0","difficulty":"basic","challenges":["credit-debit-columns","balance-validation","multi-line-descriptions"],"normalizedScore":{"overall":0.94,"extraction":0.97,"integrity":0.97,"fields":{"date":0.98,"description":0.91,"amount":0.95},"alignment":{"matched":11,"missing":1,"extra":0,"total":12}},"parsedScore":{"overall":0.95,"extraction":0.98,"integrity":0.97,"fields":{"Date":1,"Description":0.95,"Withdrawal (-)":1,"Deposit (+)":1,"Balance":0.94},"alignment":{"matched":11,"missing":1,"extra":0,"total":12}}}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/rating":{"get":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document UUID returned by the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"operationId":"getDocumentRating","summary":"Read your own rating for a document","description":"Returns the caller's own stored rating for this document, or `data: null` if the caller has not rated it yet. Rate limit: 10 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"The caller's own rating, or null.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"oneOf":[{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5},"hasFeedback":{"type":"boolean","description":"Whether structured or free-text feedback is stored for this rating."},"hasUseCase":{"type":"boolean","description":"Whether use-case tags are stored for this rating."}}},{"type":"null"}],"description":"null when the caller has not rated this document."}}},"example":{"data":{"rating":4,"hasFeedback":false,"hasUseCase":true}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Document not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_found","message":"Document not found.","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"put":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document UUID returned by the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5,"description":"1-5 star rating for this conversion."}},"required":["rating"]},"example":{"rating":4}}}},"operationId":"putDocumentRating","summary":"Submit or update your rating for a document","description":"Submits (or updates) the caller's own 1-5 rating for a completed document. Re-rating overwrites your stored rating and keeps any feedback already stored for it. Requires the caller's own document to be in `completed` status. Rate limit: 10 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Rating stored.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5}}}}},"example":{"data":{"rating":4}}}}},"400":{"description":"Invalid rating parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"invalid_parameters","message":"Invalid parameters: rating: Number must be less than or equal to 5.","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"The caller's own document is not found or not yet `completed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"document_not_ready","message":"Document not found or not yet processed.","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/rating/feedback":{"patch":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document UUID returned by the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","items":{"type":"string","enum":["missing_transactions","incorrect_amounts","export_issue","wrong_dates","formatting_issue","other"]}},"feedback":{"type":"string","maxLength":1000}},"description":"At least one of `categories` or `feedback` is required."},"example":{"categories":["missing_transactions"],"feedback":"Missed two rows near the end of the statement."}}}},"operationId":"patchDocumentRatingFeedback","summary":"Submit structured feedback on your rating","description":"Adds feedback (categories and/or free text) to the caller's own existing rating, at any rating. Rate limit: 10 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Feedback stored.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"categories":{"type":"array","items":{"type":"string"}},"feedback":{"type":["string","null"]}}}}},"example":{"data":{"categories":["missing_transactions"],"feedback":"Missed two rows near the end of the statement."}}}}},"400":{"description":"At least one of categories or feedback is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"invalid_parameters","message":"Invalid parameters: At least one category or feedback text is required.","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"The caller has no rating of their own for this document yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"rating_not_found","message":"Rating not found.","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/documents/{id}/rating/use-case":{"patch":{"parameters":[{"name":"id","in":"path","required":true,"description":"Document UUID returned by the upload endpoint.","schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"useCase":{"type":"array","minItems":1,"items":{"type":"string","enum":["bookkeeping","tax_preparation","financial_analysis","audit_compliance","reconciliation","personal_finance","data_migration","other"]}},"useCaseOther":{"type":"string","maxLength":500}},"required":["useCase"]},"example":{"useCase":["bookkeeping"]}}}},"operationId":"patchDocumentRatingUseCase","summary":"Submit use-case tags for your rating","description":"Adds or updates use-case tags on the caller's own existing rating. A silent no-op (still 200) if the caller has not rated this document yet — use-case is only captured once a rating exists. Rate limit: 10 requests per minute.","tags":["Documents"],"responses":{"200":{"description":"Use-case tags stored (or silently no-op'd if the caller has not rated this document yet).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"useCase":{"type":"array","items":{"type":"string"}},"useCaseOther":{"type":["string","null"]}}}}},"example":{"data":{"useCase":["bookkeeping"],"useCaseOther":null}}}}},"400":{"description":"Invalid use-case parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"invalid_parameters","message":"Invalid parameters: useCase: Array must contain at least 1 element(s).","request_id":"req_5fCh8jLn0pRt2vXz"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/tools/{name}":{"post":{"parameters":[{"name":"name","in":"path","required":true,"description":"Tool name, exactly as served by the hosted MCP server's `tools/list` (e.g. `convert_statement`, `get_credits`, `list_transactions`).","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"The named tool's own arguments, exactly as documented by `tools/list`'s per-tool `inputSchema`. No fixed shape is declared here — every tool has a different one.","additionalProperties":true}}}},"operationId":"callTool","summary":"Call a hosted tool by name","description":"Dispatches a `tools/call` against the SAME tool registry the hosted MCP server (`/mcp`) and the self-contained stdio server (`@bankstatemently/mcp`) serve — the generic surface a local/offline MCP client uses to reach every hosted tool without a bespoke per-tool REST endpoint. Returns the tool's result verbatim: `content`, `structuredContent`, `isError`, `_meta`. HTTP 200 whether the tool itself succeeded or failed — MCP tool failures are results, never a distinct HTTP status. Rate limit: 60 requests per minute.","tags":["Tools"],"responses":{"200":{"description":"The tool's CallToolResult, unwrapped by no one.","content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"array","description":"MCP content blocks — text/structured content the tool produced."},"structuredContent":{"type":"object","description":"The tool's structured (JSON) result, when it declares an outputSchema."},"isError":{"type":"boolean","description":"True when the tool itself failed (auth required, invalid arguments, downstream error) — still HTTP 200."},"_meta":{"type":"object","description":"Protocol metadata, e.g. the auth-error `mcp/www_authenticate` hint."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"openapi":"3.1.0","info":{"title":"Bankstatemently API","version":"1.0.0","description":"[Bankstatemently - PDF to CSV Bank Statement Converter](https://bankstatemently.com)\n\nConvert bank statement PDFs into structured data. Upload a PDF, poll for completion, then export as JSON, CSV, XLSX, QBO, or Xero-compatible formats.\n\nFull docs: [bankstatemently.com/developers/api](https://bankstatemently.com/developers/api)"},"servers":[{"url":"https://api.bankstatemently.com/v1","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication. Obtain one from the Bankstatemently dashboard."}},"headers":{"X-Request-Id":{"description":"Unique identifier for this request, useful for debugging and support.","schema":{"type":"string","pattern":"^req_[a-f0-9]{16}$"},"example":"req_7yNm3kPqW2a4bZ1c"},"X-API-Version":{"description":"The API version used to process this request. Always `1` for v1 endpoints.","schema":{"type":"string","enum":["1"]},"example":"1"},"X-Idempotency-Replayed":{"description":"Present and set to `true` when the response was served from the idempotency cache rather than reprocessed.","schema":{"type":"string","enum":["true"]}},"Retry-After":{"description":"Number of seconds to wait before retrying a rate-limited request.","schema":{"type":"integer"},"example":12},"RateLimit-Limit":{"description":"Maximum number of requests allowed in the current rate limit window.","schema":{"type":"integer"},"example":120},"RateLimit-Remaining":{"description":"Number of requests remaining in the current rate limit window.","schema":{"type":"integer"},"example":117},"RateLimit-Reset":{"description":"Unix timestamp (seconds) when the current rate limit window resets.","schema":{"type":"integer"},"example":1740653520}},"schemas":{"DocumentStatus":{"type":"string","enum":["processing","completed","failed","password_required"],"description":"The processing status of a document."},"ExportFormat":{"type":"string","enum":["json","csv","xlsx","qbo","xero"],"description":"Supported export formats."},"ExportDataMode":{"type":"string","enum":["original","enhanced","normalized"],"default":"enhanced","description":"The data mode to export. `original` returns raw extracted data; `enhanced` (default) includes AI-enriched fields; `normalized` applies standardized formatting."},"ApiErrorType":{"type":"string","enum":["authentication_error","invalid_request_error","quota_exceeded","rate_limit_error","api_error"],"description":"Broad error category (5 values, frozen). Determines the developer's retry/handling strategy."},"ApiErrorCode":{"type":"string","enum":["invalid_api_key","key_revoked","key_expired","invalid_export_token","missing_file","invalid_file_type","encrypted_pdf","invalid_pdf","invalid_parameters","invalid_export_data_mode","endpoint_not_found","document_not_found","document_not_ready","already_processing","export_failed","insufficient_credits","page_limit_exceeded","file_too_large","rate_limit_exceeded","internal_error","service_unavailable"],"description":"Specific error code (append-only, never rename or repurpose). Use this for granular programmatic handling."},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message","request_id"],"properties":{"type":{"$ref":"#/components/schemas/ApiErrorType"},"code":{"$ref":"#/components/schemas/ApiErrorCode"},"message":{"type":"string","description":"A human-readable description of the error with actionable context. Do not parse this — use `code` for programmatic handling."},"meta":{"type":"object","description":"Optional structured data for programmatic use. Shape varies by `code`. Only present when actionable data exists.","additionalProperties":true},"request_id":{"type":"string","description":"The unique request identifier. Include this when contacting support.","pattern":"^req_[a-f0-9]{16}$"}}}}},"ExportUrls":{"type":"object","description":"URLs to download the document in each export format.","properties":{"json":{"type":"string","format":"uri"},"csv":{"type":"string","format":"uri"},"xlsx":{"type":"string","format":"uri"},"qbo":{"type":"string","format":"uri"},"xero":{"type":"string","format":"uri"}},"required":["json","csv","xlsx","qbo","xero"]},"StatementPeriod":{"type":"object","description":"The date range covered by a single statement within the document.","required":["from","to"],"properties":{"from":{"type":"string","format":"date","description":"Start date of the statement period (ISO 8601)."},"to":{"type":"string","format":"date","description":"End date of the statement period (ISO 8601)."}}},"StatementSummary":{"type":"object","description":"Summary of a single bank statement found within the uploaded document.","required":["index"],"properties":{"index":{"type":"integer","minimum":0,"description":"Zero-based index of this statement within the document."},"statementPeriod":{"oneOf":[{"$ref":"#/components/schemas/StatementPeriod"},{"type":"null"}],"description":"The date range of the statement, or null if not detected."},"transactionCount":{"type":["integer","null"],"minimum":0,"description":"Number of transactions in this statement, or null if not yet counted."}}},"DocumentMetadata":{"type":"object","description":"Metadata about the uploaded document, populated once processing completes.","properties":{"bank":{"type":["string","null"],"description":"Detected bank or financial institution name."},"country":{"type":["string","null"],"description":"Detected country code (ISO 3166-1 alpha-2)."},"currency":{"type":["string","null"],"description":"Detected currency code (ISO 4217)."},"documentType":{"type":["string","null"],"description":"Detected document type (e.g., `bank-statement`, `credit-card-statement`)."},"pageCount":{"type":["integer","null"],"minimum":1,"description":"Number of pages in the uploaded PDF."},"transactionCount":{"type":"integer","minimum":0,"description":"Total number of transactions extracted from the document."},"accountCount":{"type":"integer","minimum":0,"description":"Number of bank accounts found in the document."}}},"StatementUploadResponse":{"type":"object","description":"Response returned when a PDF is successfully accepted for processing.","required":["id","status","contentHash","createdAt","statements","links"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique document identifier. Use this to poll status and request exports."},"status":{"$ref":"#/components/schemas/DocumentStatus"},"contentHash":{"type":"string","description":"Content-addressable hash of the uploaded file."},"createdAt":{"type":"string","format":"date-time","description":"Timestamp when the document was created."},"statements":{"type":"array","items":{"$ref":"#/components/schemas/StatementSummary"},"description":"Always an empty array on upload; populated once processing completes."},"links":{"type":"object","required":["self"],"properties":{"self":{"type":"string","format":"uri","description":"URL to poll for processing status."},"unlock":{"type":"string","format":"uri","description":"URL to submit a password for unlocking. Only present when status is `password_required`."}}}}},"StatementDetailResponse":{"type":"object","description":"Full document status including statement metadata and export URLs when completed.","required":["id","status","contentHash","createdAt","updatedAt","statements","links"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique document identifier."},"status":{"$ref":"#/components/schemas/DocumentStatus"},"contentHash":{"type":"string","description":"Content-addressable hash of the uploaded file."},"createdAt":{"type":"string","format":"date-time","description":"Timestamp when the document was created."},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of the last status update."},"document":{"$ref":"#/components/schemas/DocumentMetadata","description":"Document-level metadata. Present only when status is `completed`."},"statements":{"type":"array","items":{"$ref":"#/components/schemas/StatementSummary"},"description":"Individual bank statements found in the document. Empty until processing completes."},"exports":{"$ref":"#/components/schemas/ExportUrls","description":"Export download URLs. Present only when status is `completed`."},"error":{"type":"string","description":"Human-readable error message. Present only when status is `failed`."},"links":{"type":"object","required":["self"],"properties":{"self":{"type":"string","format":"uri","description":"URL for this document resource."},"data":{"type":"string","format":"uri","description":"URL for structured document data. Present only when status is `completed`."},"unlock":{"type":"string","format":"uri","description":"URL to submit a password for unlocking. Present only when status is `password_required`."}}}}},"StatementListItem":{"type":"object","description":"A document summary as returned in the list endpoint.","required":["id","status","contentHash","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique document identifier."},"status":{"$ref":"#/components/schemas/DocumentStatus"},"contentHash":{"type":"string","description":"Content-addressable hash of the uploaded file."},"filename":{"type":["string","null"],"description":"Original filename of the uploaded PDF."},"pageCount":{"type":["integer","null"],"minimum":1,"description":"Number of pages in the uploaded PDF."},"source":{"type":["string","null"],"description":"How the document was uploaded (e.g., `api`, `web`)."},"createdAt":{"type":"string","format":"date-time","description":"Timestamp when the document was created."},"updatedAt":{"type":"string","format":"date-time","description":"Timestamp of the last status update."},"exports":{"$ref":"#/components/schemas/ExportUrls","description":"Export download URLs. Present only when status is `completed`."}}},"Pagination":{"type":"object","description":"Pagination metadata for list responses.","required":["total","limit","offset","hasMore"],"properties":{"total":{"type":"integer","minimum":0,"description":"Total number of documents matching the query."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum number of documents returned in this page."},"offset":{"type":"integer","minimum":0,"description":"Number of documents skipped."},"hasMore":{"type":"boolean","description":"Whether there are more results beyond this page."}}},"StatementListResponse":{"type":"object","description":"Paginated list of documents.","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/StatementListItem"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"BenchmarkTransaction":{"type":"object","required":["date","description","amount","originalData"],"properties":{"date":{"type":"string","description":"ISO 8601 date (YYYY-MM-DD).","example":"2025-06-02"},"description":{"type":"string","description":"Transaction description.","example":"NTUC FAIRPRICE"},"amount":{"type":"number","description":"Transaction amount. Positive absolute value, or negative to indicate debit. When direction is also provided, positive amounts are treated as absolute values.","example":12.2},"direction":{"type":"string","enum":["credit","debit"],"description":"Explicit direction. If omitted, inferred from amount sign (negative = debit, positive/zero = credit). Negative amount with direction \"credit\" is rejected as contradictory."},"balance":{"type":"number","description":"Running balance after this transaction.","example":15438.55},"currency":{"type":"string","description":"ISO 4217 currency code. Defaults to statement currency.","example":"SGD"},"originalData":{"type":"object","additionalProperties":{"type":"string"},"description":"Raw column values as they appear in the PDF.","example":{"Date":"02/06/2025","Description":"NTUC FAIRPRICE","Withdrawal (-)":"12.20","Balance":"15,438.55"}}}},"BenchmarkScoreDetail":{"type":"object","properties":{"overall":{"type":"number","description":"Headline score: extraction × integrity (0-1).","example":0.94},"extraction":{"type":"number","description":"Weighted accuracy across all fields (0-1).","example":0.97},"integrity":{"type":"number","description":"Structural correctness: validation checks, row coverage, direction accuracy (0-1).","example":0.97},"fields":{"type":"object","additionalProperties":{"type":"number"},"description":"Per-field accuracy scores. Normalized uses canonical names (date, description, amount). Parsed uses original column headers from the PDF.","example":{"date":0.98,"description":0.91,"amount":0.95}},"alignment":{"type":"object","properties":{"matched":{"type":"integer","description":"Transactions matched between submission and ground truth."},"missing":{"type":"integer","description":"Transactions in ground truth but not in submission."},"extra":{"type":"integer","description":"Transactions in submission but not in ground truth."},"total":{"type":"integer","description":"Total ground truth transactions."}}}}},"BenchmarkEvaluationResult":{"type":"object","required":["contentHash","id","datasetVersion","difficulty","challenges","normalizedScore","parsedScore"],"properties":{"contentHash":{"type":"string","description":"Echo of the submitted content hash."},"id":{"type":"string","description":"Statement ID (e.g. bsb-001).","example":"bsb-001"},"datasetVersion":{"type":"string","description":"Ground truth dataset version.","example":"2.0"},"difficulty":{"type":"string","enum":["basic","intermediate","advanced"],"description":"Statement difficulty tier."},"challenges":{"type":"array","items":{"type":"string"},"description":"Parsing challenges present in the statement."},"normalizedScore":{"$ref":"#/components/schemas/BenchmarkScoreDetail","description":"Scores against canonical values (ISO dates, numeric amounts)."},"parsedScore":{"$ref":"#/components/schemas/BenchmarkScoreDetail","description":"Scores against raw PDF cell values."}}}},"responses":{"Unauthorized":{"description":"Authentication failed, API key revoked, or API key expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missing_key":{"summary":"No API key provided","value":{"error":{"type":"authentication_error","code":"invalid_api_key","message":"Invalid API key provided.","request_id":"req_3cGi7jKlN1b5eF2g"}}},"revoked_key":{"summary":"API key has been revoked","value":{"error":{"type":"authentication_error","code":"key_revoked","message":"This API key has been revoked. Generate a new key at the Developer Portal.","request_id":"req_8wTs2uXvA4f9hJ6k"}}},"expired_key":{"summary":"API key has expired","value":{"error":{"type":"authentication_error","code":"key_expired","message":"This API key has expired. Generate a new key at the Developer Portal.","request_id":"req_1aEh5iLmP0c4dC8f"}}},"invalid_export_token":{"summary":"Export download token is invalid, expired, or doesn't match this document (export endpoint only)","value":{"error":{"type":"authentication_error","code":"invalid_export_token","message":"This download link has expired or is invalid. Ask your AI assistant for a fresh link, or sign in with an API key.","request_id":"req_6nQr0sUwB3e8iK5j"}}}}}}},"RateLimited":{"description":"Too many requests. Wait the number of seconds indicated by the `Retry-After` header.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-API-Version":{"$ref":"#/components/headers/X-API-Version"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"rate_limit_error","code":"rate_limit_exceeded","message":"Rate limit exceeded. Retry after 12 seconds.","meta":{"retry_after":12},"request_id":"req_6dFj8kMnR2a3bE5g"}}}}},"InternalError":{"description":"An unexpected server error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"api_error","code":"internal_error","message":"An unexpected error occurred. If this persists, contact support with the request_id.","request_id":"req_0zAg4hIlO9b2cD7e"}}}}},"ServiceUnavailable":{"description":"The service is temporarily unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"api_error","code":"service_unavailable","message":"Document processing is temporarily unavailable. Please try again later.","request_id":"req_1aEh5iLmP0c4dC8f"}}}}},"NotFound":{"description":"The requested endpoint does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"type":"invalid_request_error","code":"endpoint_not_found","message":"The requested endpoint does not exist.","request_id":"req_3cGi7jKlN1b5eF2g"}}}}}}},"tags":[{"name":"Documents","description":"Upload, poll, list, and retrieve structured data from documents."},{"name":"Exports","description":"Download converted documents in various formats."},{"name":"Credits","description":"Check your credit balance and plan information."},{"name":"Feedback","description":"Rate Bankstatemently."},{"name":"Benchmark","description":"Evaluate parsed transactions against the Bankstatemently open benchmark for bank statement parsing."},{"name":"Tools","description":"Call any hosted MCP tool by name — the generic surface behind the self-contained stdio server and every other non-`/mcp` client."}]}