API Documentation

REST API for bank statement analysis, document fraud detection, document automation, invoice processing, identity documents and company checks. JSON over HTTPS, one API key per integration.

Try the demo

Getting started

Base URL

All endpoints live under https://api.paperwork.to/api/v1. Paths below are relative to it.

Authentication

Send your key as a Bearer token. Keys start with sk-paperwork-and are enabled per product. Endpoints marked “or dashboard session token” also accept a signed-in dashboard session.

Files and jobs

Each endpoint specifies whether it accepts uploads, file URLs or stored files. Background jobs return an ID for status polling; supported endpoints also send signed webhooks.

Authorization: Bearer sk-paperwork-YOUR_API_KEY

Code examples

A complete async flow: send a document to fraud detection, then poll for the result. There is no SDK; any HTTP client works.

# 1. Create the job
curl -X POST https://api.paperwork.to/api/v1/fraud-detection/async \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"files": [{"url": "https://files.example.com/statement.pdf"}]}'

# 2. Poll until status is completed or failed
curl https://api.paperwork.to/api/v1/fraud-detection/status/JOB_ID \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Bank Statement Analysis

Asynchronous jobs that parse statement PDFs and return monthly metrics per account. Create a job, then poll it until the status is COMPLETED, FAILED or NEEDS_INPUT. These endpoints return errors in their own envelope (see Errors).

Create an analysis job

POST/bsa/jobs

Queue one or more statement PDFs for analysis. Returns 202 with the queued job.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • filesarray · required

    1 to 50 statement PDFs. Unknown fields are rejected.

  • files[].storage_pathstring · required

    Path of a PDF already stored in your organization's Paperwork document storage. It must start with your organization ID or the API key owner's user ID.

  • files[].original_filenamestring · required

    File name only, ending in .pdf.

  • files[].size_bytesinteger · required

    File size. Up to 25 MiB per file, 250 MiB per job.

  • company_profile_idstring (UUID) · optional

    Saved company profile used to recognise own accounts and related parties.

  • company_contextobject · optional

    Inline profile instead of a saved one: profile_id, legal_entity {entity_id, name}, and optional lists own_accounts, key_members, parent_companies, subsidiaries, sister_companies, other_related_entities. Cannot be sent together with company_profile_id.

  • classification_modestring · optional

    auto (default): uncertain points get a cautious default. review: they wait for a person.

  • requested_monthsstring[] · optional

    1 to 36 consecutive months as YYYY-MM. The report is cut to these months and names the ones with no statement.

  • subjectstring · optional

    business (default) or individual. individual adds the applicant report: salary, debt payments, debt burden and signals.

  • applicantobject · optional

    Individual only: declared_monthly_income, declared_income_basis (net or gross, default net), credit_card_limits [{issuer, limit}] and card_limit_rate (default 0.05). Can also be set later with the applicant endpoint.

Example request

curl -X POST https://api.paperwork.to/api/v1/bsa/jobs \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      {
        "storage_path": "YOUR_ORG_ID/statements/example-jan-mar.pdf",
        "original_filename": "example-jan-mar.pdf",
        "size_bytes": 482113
      }
    ],
    "classification_mode": "auto",
    "requested_months": ["2026-01", "2026-02", "2026-03"]
  }'

Example response

{
  "job_id": "5b0d7c1e-0000-4000-8000-000000000001",
  "parent_job_id": null,
  "status": "QUEUED",
  "progress": { "stage": "VALIDATING", "percent": 0, "message": null },
  "documents": [],
  "result": null,
  "error": null,
  "password_request": null
}
  • This endpoint does not take file bytes or URLs. Ask us to set up storage access when you get your API key.
  • Status values: QUEUED, PROCESSING, NEEDS_INPUT, COMPLETED, FAILED, CANCELLED.
  • 400, 404 PROFILE_NOT_FOUND, 403 STORAGE_OBJECT_FORBIDDEN, 422 INVALID_REQUEST and 503 QUEUE_UNAVAILABLE are possible.

Get job status and report

GET/bsa/jobs/{job_id}

Return progress, per-file summaries and, once completed, the report with metrics per account and month.

Auth
API key or dashboard session token
Request body
None

Parameters

  • job_idstring (UUID, path) · required

    job_id returned when the job was created.

Example request

curl https://api.paperwork.to/api/v1/bsa/jobs/5b0d7c1e-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

{
  "job_id": "5b0d7c1e-0000-4000-8000-000000000001",
  "parent_job_id": null,
  "status": "COMPLETED",
  "progress": { "stage": "SAVING", "percent": 100, "message": null },
  "documents": [
    {
      "document_id": "doc_1",
      "original_filename": "example-jan-mar.pdf",
      "bank_id": "example_bank",
      "layout_id": "example_layout",
      "status": "parsed",
      "page_count": 6,
      "statement_count": 1,
      "transaction_count": 214,
      "completeness": { "passed": 5, "failed": 0, "not_applicable": 1 }
    }
  ],
  "result": {
    "schema_version": "bsa_analysis_result_v1",
    "revision_id": "8e2f4a90-0000-4000-8000-000000000002",
    "parent_revision_id": null,
    "catalog_version": "bsa_reporting_catalog_v1",
    "quality": {
      "files_received": 1,
      "files_parsed": 1,
      "account_count": 1,
      "currencies": ["AED"],
      "period_start": "2026-01-01",
      "period_end": "2026-03-31",
      "review_count": 0,
      "parsing_status_counts": { "parsed": 1 }
    },
    "reports": [
      {
        "account_id": "acct_1",
        "bank_id": "example_bank",
        "currency": "AED",
        "period_start": "2026-01-01",
        "period_end": "2026-03-31",
        "metrics": [
          {
            "metric_id": "total_credit_amount",
            "kind": "amount",
            "value": "125000.00",
            "status": "VERIFIED",
            "currency": "AED",
            "evidence_count": 48,
            "monthly_values": [
              { "month": "2026-01", "value": "41000.00", "status": "VERIFIED", "evidence_count": 16 }
            ]
          }
        ],
        "missing_months": [],
        "partial_months": [],
        "statement_ids": ["stmt_1"]
      }
    ],
    "review_summary": {},
    "versions": { "taxonomy": "bsa_uae_v1" },
    "classification_mode": "auto"
  },
  "error": null,
  "password_request": null
}
  • Metric status: VERIFIED, ESTIMATED, UNKNOWN or INSUFFICIENT_DATA. Amounts are decimal strings.
  • A FAILED job carries error {code: "BSA_PROCESSING_FAILED", message, details}.
  • NEEDS_INPUT means password_request.files lists the encrypted PDFs; answer with the passwords endpoint.
  • Unknown or foreign job IDs return 404 JOB_NOT_FOUND.

List transactions

GET/bsa/jobs/{job_id}/transactions

Return every parsed transaction of the current report revision with its tags and cashflow decision.

Auth
API key or dashboard session token
Request body
None

Parameters

  • job_idstring (UUID, path) · required

    A completed job.

Example request

curl https://api.paperwork.to/api/v1/bsa/jobs/5b0d7c1e-0000-4000-8000-000000000001/transactions \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

{
  "revision_id": "8e2f4a90-0000-4000-8000-000000000002",
  "transactions": [
    {
      "transaction_id": "txn_1",
      "statement_id": "stmt_1",
      "posting_date": "2026-01-05",
      "value_date": "2026-01-05",
      "direction": "CREDIT",
      "amount": "15000.00",
      "currency": "AED",
      "balance_after": "42150.00",
      "description": "INWARD TRANSFER EXAMPLE CUSTOMER LLC",
      "reference": null,
      "source_page": 2,
      "tags": [],
      "attributes": [],
      "cashflow": {
        "is_revenue": true,
        "is_expense": false,
        "status": "RESOLVED",
        "reason_codes": [],
        "business_amount": "15000.00",
        "non_business_amount": null
      }
    }
  ],
  "review_items": [],
  "transaction_type_values": [],
  "review_decisions": []
}
  • direction is DEBIT, CREDIT or UNKNOWN. source_page is 1-based.
  • Returns 404 RESULT_NOT_FOUND until the job has a report.

Submit PDF passwords

POST/bsa/jobs/{job_id}/passwords

Resume a NEEDS_INPUT job with one password per encrypted file. Returns 202 with the job.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • passwordsarray · required

    One entry for every file in password_request.files.

  • passwords[].file_keystring · required

    file_key from password_request.files.

  • passwords[].passwordstring · required

    1 to 256 characters.

Example request

curl -X POST https://api.paperwork.to/api/v1/bsa/jobs/5b0d7c1e-0000-4000-8000-000000000001/passwords \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"passwords": [{"file_key": "FILE_KEY_FROM_REQUEST", "password": "example-password"}]}'

Example response

{
  "job_id": "5b0d7c1e-0000-4000-8000-000000000001",
  "parent_job_id": null,
  "status": "QUEUED",
  "progress": { "stage": "VALIDATING", "percent": 0, "message": null },
  "documents": [],
  "result": null,
  "error": null,
  "password_request": null
}
  • 409 PASSWORD_NOT_REQUESTED if the job is not waiting for passwords; 422 PASSWORD_FILES_MISMATCH if a requested file is missing.

Get the applicant report

GET/bsa/jobs/{job_id}/personal?revision_id={revision_id}

For a job created with subject individual: salary, debt payments, debt burden, affordability and signals, one report per currency.

Auth
API key or dashboard session token
Request body
None

Parameters

  • job_idstring (UUID, path) · required

    A completed job.

  • revision_idstring (UUID, query) · required

    result.revision_id from the job. It must still be the current revision.

Example request

curl "https://api.paperwork.to/api/v1/bsa/jobs/5b0d7c1e-0000-4000-8000-000000000001/personal?revision_id=8e2f4a90-0000-4000-8000-000000000002" \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

{
  "schema_version": "bsa_personal_v1",
  "revision_id": "8e2f4a90-0000-4000-8000-000000000002",
  "reports": [
    {
      "currency": "AED",
      "complete": true,
      "income": { "regular_monthly_income": "18500.00" },
      "affordability": {
        "monthly_obligations": "6060.00",
        "dbr": "0.3276",
        "dbr_limit": "0.50",
        "max_new_payment": "1382.22",
        "personal_loan_ceiling": "370000.00"
      },
      "declared_income": { "status": "below_declared" },
      "signals": [{ "signal_id": "circular_transfers", "status": "flagged", "severity": "medium" }]
    }
  ],
  "applicant": { "declared_monthly_income": "25000", "declared_income_basis": "net" }
}
  • Excerpt; each report also lists accounts, months, credits, obligations, spending, balances and every signal with its rule.
  • declared_income.status: consistent, below_declared, above_declared, not_comparable or no_income. Signal status: flagged, clear or not_checked.
  • 409 when the report is not ready or the revision is no longer current; 404 when the job or result is missing.

Set declared income and card limits

PUT/bsa/jobs/{job_id}/applicant

Replace the declared income and card limits the applicant report is checked against. Returns the saved values.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • declared_monthly_incomestring (decimal) · optional

    Income from the application.

  • declared_income_basisstring · optional

    net (default) or gross.

  • credit_card_limitsarray · optional

    Up to 20 entries of {issuer, limit}. With limits, cards count at card_limit_rate of the total limit.

  • card_limit_ratestring (decimal) · optional

    Share of the limit counted as a monthly payment. Default 0.05.

Example request

curl -X PUT https://api.paperwork.to/api/v1/bsa/jobs/5b0d7c1e-0000-4000-8000-000000000001/applicant \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"declared_monthly_income": "25000", "credit_card_limits": [{"issuer": "ADCB", "limit": "45000"}]}'

Example response

{
  "declared_monthly_income": "25000",
  "declared_income_basis": "net",
  "credit_card_limits": [{ "issuer": "ADCB", "limit": "45000" }],
  "card_limit_rate": "0.05"
}
  • 409 NOT_AN_INDIVIDUAL for a business job; 409 JOB_BUSY while the analysis is being revised.

Download the Excel report

GET/bsa/jobs/{job_id}/excel?revision_id={revision_id}

Download the report of one revision as an .xlsx file.

Auth
API key or dashboard session token
Request body
None

Parameters

  • job_idstring (UUID, path) · required

    A completed job.

  • revision_idstring (UUID, query) · required

    result.revision_id from the job. It must still be the current revision.

Example request

curl -o report.xlsx \
  "https://api.paperwork.to/api/v1/bsa/jobs/5b0d7c1e-0000-4000-8000-000000000001/excel?revision_id=8e2f4a90-0000-4000-8000-000000000002" \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

HTTP/1.1 200 OK
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

<binary .xlsx file>
  • 409 when the report is not ready or the revision is no longer current; 404 when the job or result is missing.

Fraud Detection

Checks bank statements, payslips, invoices, IDs and other documents for edits and forgery. Use the async endpoints for batches and webhooks, or the sync endpoints for single requests.

Create an async job from URLs

POST/fraud-detection/async

Queue 1 to 30 documents by URL and get a job_id immediately. Optionally receive a webhook when it finishes.

Auth
API key
Request body
JSON

Parameters

  • filesarray · required

    1 to 30 documents.

  • files[].urlstring · required

    Public HTTPS URL, for example a presigned link.

  • files[].filenamestring · optional

    Name to report the file under.

  • files[].passwordstring · optional

    Password of an encrypted PDF.

  • document_typestring · optional

    Expected document type. Default auto (classified automatically).

  • webhook_urlstring · optional

    Public HTTPS URL that receives the result (see Webhooks).

  • webhook_secretstring · optional

    Secret used to sign the webhook body.

  • metadataobject · optional

    Your own data, returned unchanged in the webhook.

Example request

curl -X POST https://api.paperwork.to/api/v1/fraud-detection/async \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      { "url": "https://files.example.com/statement.pdf", "filename": "statement.pdf" }
    ],
    "document_type": "auto",
    "webhook_url": "https://your-server.example.com/webhooks/paperwork",
    "webhook_secret": "YOUR_WEBHOOK_SECRET",
    "metadata": { "client_reference_id": "case-123" }
  }'

Example response

{
  "job_id": "f3b8c2d1-0000-4000-8000-000000000003",
  "status": "pending",
  "files_count": 1,
  "message": "Job queued for fraud detection",
  "status_url": "/api/v1/fraud-detection/status/f3b8c2d1-0000-4000-8000-000000000003"
}
  • camelCase aliases documentType, webhookUrl and webhookSecret are also accepted.
  • Non-HTTPS URLs return 400. Files up to 50 MB each; PDF, JPG, PNG, TIFF and WebP.

Create an async job from uploads

POST/fraud-detection/async/upload

Same as the URL version, with the files sent in the request body.

Auth
API key
Request body
multipart/form-data

Parameters

  • filesfile (repeat) · required

    1 to 30 files, up to 50 MB each.

  • document_typestring · optional

    Default auto.

  • passwordsJSON object (string) · optional

    PDF passwords keyed by file name or zero-based file index.

  • webhook_urlstring · optional

    Public HTTPS URL that receives the result.

  • webhook_secretstring · optional

    Secret used to sign the webhook body.

  • metadataJSON object (string) · optional

    Returned unchanged in the webhook.

Example request

curl -X POST https://api.paperwork.to/api/v1/fraud-detection/async/upload \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -F "files=@statement.pdf" \
  -F "files=@payslip.pdf" \
  -F 'metadata={"client_reference_id":"case-123"}'

Example response

{
  "job_id": "f3b8c2d1-0000-4000-8000-000000000004",
  "status": "pending",
  "files_count": 2,
  "message": "Job queued for fraud detection",
  "status_url": "/api/v1/fraud-detection/status/f3b8c2d1-0000-4000-8000-000000000004"
}
  • A file over the size limit returns 413.

Get async job status

GET/fraud-detection/status/{job_id}

Return progress and, when completed, the fraud result.

Auth
API key
Request body
None

Parameters

  • job_idstring (path) · required

    job_id from the create call.

Example request

curl https://api.paperwork.to/api/v1/fraud-detection/status/f3b8c2d1-0000-4000-8000-000000000003 \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

{
  "job_id": "f3b8c2d1-0000-4000-8000-000000000003",
  "status": "completed",
  "progress": 100,
  "progress_message": "Complete",
  "created_at": "2026-10-05T10:00:00Z",
  "updated_at": "2026-10-05T10:01:00Z",
  "result": {
    "document_type": "bank_statement",
    "file_kind": "digital_pdf",
    "is_scan": false,
    "fraud_score": 72,
    "fraud_color": "red",
    "fraud_types": ["balance_manipulation"],
    "verdict": "FRAUDULENT",
    "risk_level": "high",
    "recommendation": "REJECT",
    "executive_summary": "The closing balance does not follow from the listed transactions.",
    "signals": [
      {
        "type": "balance_manipulation",
        "severity": "critical",
        "description": "Running balance breaks after an edited transaction.",
        "page": 2,
        "bbox": [112.0, 418.5, 260.0, 14.0],
        "bbox_source": "source_page_raster_pixels"
      }
    ],
    "layers_applied": ["classifier", "semantic_validation", "pdf_structure"],
    "processing_time": 8.31
  },
  "error": null,
  "completed_at": "2026-10-05T10:01:00Z"
}
  • status is pending, processing, completed or failed. error holds the message of a failed job.
  • verdict: CLEAN, SUSPICIOUS, FRAUDULENT, or ERROR when processing failed. risk_level: low, medium or high. recommendation: APPROVE, MANUAL_REVIEW or REJECT. fraud_score: 0 to 100.
  • signals[] may carry page (1-based) and bbox [x, y, width, height] with a top-left origin, plus other optional evidence fields. Allow unknown fields if you validate strictly.
  • With more than one file, result is {"batch": true, "files": [...], "processing_time"}; each entry adds filename and error.

Analyze files synchronously

POST/fraud-detection

Analyze up to 5 uploaded files or URLs and return the result in the response.

Auth
API key
Request body
multipart/form-data

Parameters

  • filesfile (repeat) · optional

    Up to 5 files. Send files or file_urls, not both.

  • file_urlsJSON array (string) · optional

    Public HTTPS URLs, as strings or {url, filename, password} objects.

  • document_typestring · optional

    Default auto.

  • passwordstring · optional

    Password for the first file.

  • passwordsJSON object (string) · optional

    Passwords keyed by file name or zero-based index.

  • metadataJSON object (string) · optional

    Stored with the job.

Example request

curl -X POST https://api.paperwork.to/api/v1/fraud-detection \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -F "files=@statement.pdf"

Example response

{
  "document_type": "bank_statement",
  "file_kind": "digital_pdf",
  "is_scan": false,
  "fraud_score": 12,
  "fraud_color": "green",
  "fraud_types": [],
  "verdict": "CLEAN",
  "risk_level": "low",
  "recommendation": "APPROVE",
  "executive_summary": "No high-confidence fraud indicators were found.",
  "signals": [],
  "layers_applied": ["classifier", "semantic_validation", "pdf_structure"],
  "processing_time": 8.31
}
  • The request stays open until analysis ends. For long or large batches use the async endpoints.
  • 402 when the organization has no credits; 400 for a password-protected PDF without a password.

Analyze one URL synchronously

POST/fraud-detection/analyze

Analyze a single document by URL and return the result in the response.

Auth
API key
Request body
JSON

Parameters

  • pdf_urlstring · required

    Public HTTPS URL of the document.

  • document_typestring · optional

    Default auto.

  • passwordstring · optional

    Password of an encrypted PDF.

Example request

curl -X POST https://api.paperwork.to/api/v1/fraud-detection/analyze \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pdf_url": "https://files.example.com/statement.pdf"}'

Example response

{
  "document_type": "bank_statement",
  "verdict": "CLEAN",
  "risk_level": "low",
  "recommendation": "APPROVE",
  "fraud_score": 12,
  "signals": [],
  "processing_time": 8.31
}
  • Same result fields as POST /fraud-detection (trimmed above).

Document Automation

Runs a document package through a scenario (what to extract and which checks to apply) that Paperwork sets up and publishes for your organization. You receive the scenario_id and version_id when it is published.

Start a run with uploaded files

POST/document-automation/runs/upload

Upload a document package and queue a run. Returns 202 with the run.

Auth
API key or dashboard session token
Request body
multipart/form-data

Parameters

  • scenario_idstring · required

    Published scenario.

  • version_idstring · required

    Published version of that scenario.

  • modestring · required

    production. (test runs are reserved for Paperwork staff.)

  • filesfile (repeat) · required

    Up to 20 files including the reference, 25 MB each (10 MB for CSV and TXT), 100 MiB in total.

  • idempotency_keystring · optional

    1 to 200 characters. Repeating it returns the same run.

  • reference_jsonJSON (string) · optional

    Reference data to compare against, if the scenario accepts it.

  • reference_filefile · optional

    Reference as a .json, .csv or document file. Not together with reference_json.

  • passwordsJSON object (string) · optional

    PDF passwords keyed by file name.

Example request

curl -X POST https://api.paperwork.to/api/v1/document-automation/runs/upload \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -F "scenario_id=YOUR_SCENARIO_ID" \
  -F "version_id=YOUR_VERSION_ID" \
  -F "mode=production" \
  -F "idempotency_key=onboarding-1842" \
  -F "files=@trade-license.pdf" \
  -F "files=@bank-letter.pdf"

Example response

{
  "id": "a1c3e5f7-0000-4000-8000-000000000005",
  "scenario_id": "YOUR_SCENARIO_ID",
  "version_id": "YOUR_VERSION_ID",
  "mode": "production",
  "idempotency_key": "onboarding-1842",
  "status": "pending",
  "as_of_date": "2026-10-05",
  "timezone": "UTC",
  "source_manifest": [
    {
      "file_id": "0b9e1d22-0000-4000-8000-000000000006",
      "filename": "trade-license.pdf",
      "size_bytes": 482113,
      "sha256": "9f2c...",
      "captured_at": "2026-10-05T10:00:00Z"
    }
  ],
  "machine_result": null,
  "failure": null,
  "created_at": "2026-10-05T10:00:00Z",
  "queue_state": "queued"
}
  • queue_state is queued, pending_recovery or idempotent_replay.
  • Webhooks are not available on this endpoint; poll GET /document-automation/runs/{run_id} or use the JSON endpoint below.
  • 429 when the organization reaches its run limit; 409 when an idempotency_key is reused with a different request.

Start a run from stored files

POST/document-automation/runs

Queue a run for files already in your organization's Paperwork document storage. Returns 202 with the run.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • scenario_idstring · required

    Published scenario.

  • version_idstring · required

    Published version.

  • modestring · required

    production.

  • filesarray · required

    1 to 20 items {path, filename, size_bytes, sha256?}. path must start with your organization ID; up to 25 MiB each, 100 MiB in total.

  • referenceobject · optional

    {kind: json | csv | document, value} or {kind, storage_handle}.

  • idempotency_keystring · optional

    1 to 200 characters.

  • as_of_datestring (date) · optional

    Date the checks are evaluated on. Default: today.

  • timezonestring · optional

    Default UTC.

  • webhook_urlstring · optional

    Public HTTPS URL that receives the result.

  • webhook_secretstring · optional

    Secret used to sign the webhook body.

Example request

curl -X POST https://api.paperwork.to/api/v1/document-automation/runs \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scenario_id": "YOUR_SCENARIO_ID",
    "version_id": "YOUR_VERSION_ID",
    "mode": "production",
    "files": [
      { "path": "YOUR_ORG_ID/packages/trade-license.pdf", "filename": "trade-license.pdf", "size_bytes": 482113 }
    ],
    "idempotency_key": "onboarding-1842",
    "webhook_url": "https://your-server.example.com/webhooks/paperwork",
    "webhook_secret": "YOUR_WEBHOOK_SECRET"
  }'

Example response

{
  "id": "a1c3e5f7-0000-4000-8000-000000000005",
  "status": "pending",
  "mode": "production",
  "machine_result": null,
  "queue_state": "queued"
}
  • Unknown fields are rejected with 422.

Get a run and its result

GET/document-automation/runs/{run_id}

Return the run status and, when completed, the machine result with facts, sources and checks.

Auth
API key or dashboard session token
Request body
None

Parameters

  • run_idstring (path) · required

    id returned when the run was created.

Example request

curl https://api.paperwork.to/api/v1/document-automation/runs/a1c3e5f7-0000-4000-8000-000000000005 \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

{
  "id": "a1c3e5f7-0000-4000-8000-000000000005",
  "status": "completed",
  "result_hash": "4be1...",
  "failure": null,
  "completed_at": "2026-10-05T10:03:10Z",
  "machine_result": {
    "schema_version": 1,
    "run_id": "a1c3e5f7-0000-4000-8000-000000000005",
    "machine_outcome": "discrepancies",
    "completeness": "complete",
    "requires_review": true,
    "data": { "company_name": "Example Trading LLC" },
    "facts": [
      {
        "id": "company_name",
        "value": "Example Trading LLC",
        "status": "available",
        "sources": [
          {
            "document_id": "0b9e1d22-0000-4000-8000-000000000006",
            "page": 1,
            "quote": "Trade Name: Example Trading LLC",
            "bbox": [72.0, 140.0, 210.0, 12.0],
            "method": "ocr",
            "status": "available"
          }
        ]
      }
    ],
    "checks": [
      {
        "rule_id": "license_not_expired",
        "status": "fail",
        "severity": "error",
        "operands": ["2026-09-30"],
        "reason": "License expired before the as-of date.",
        "details": {}
      }
    ]
  }
}
  • status: pending, processing, completed, failed or cancelled. A failed run has failure {code, stage, retryable, message}.
  • machine_outcome: clear, discrepancies or incomplete. completeness: complete, incomplete or none.
  • Fact status: available, missing, unavailable or invalid. Check status: pass, fail, unknown or not_applicable.
  • machine_result also carries documents, document_results, issues and usage (omitted above).

Invoice Processing

Extracts supplier, buyer, totals and line items from an invoice PDF, matches each line to the business activities you send, and scores trust, document risk and e-invoicing readiness.

Analyze an invoice

POST/invoice-compliance/analyze

Analyze one invoice PDF by URL and return the result in the response.

Auth
API key
Request body
JSON

Parameters

  • pdf_urlstring · required

    Public HTTPS URL of the invoice PDF (up to 50 MB).

  • activitiesstring[] · required

    Licensed business activities to match line items against. Must not be empty.

Example request

curl -X POST https://api.paperwork.to/api/v1/invoice-compliance/analyze \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pdf_url": "https://files.example.com/invoice.pdf",
    "activities": ["General Trading", "Food & Beverages Trading"]
  }'

Example response

{
  "validationMessage": "Single invoice processed successfully.",
  "trust_score": { "score": 86.5, "rating": "HIGH_TRUST", "components": { ... } },
  "validation": {
    "risk_score": 12.0,
    "risk_level": "LOW",
    "signals": [],
    "summary": "...",
    "metadata": { "producer": "", "creator": "", "creation_date": "", "file_hash": "..." }
  },
  "supplierDetails": {
    "Supplier_Name": "Example Supplies LLC",
    "Supplier_TRN": "100000000000003",
    "Supplier_Address": "Example Street 1"
  },
  "buyerDetails": {
    "Buyer_Name": "Example Buyer LLC",
    "Buyer_TRN": "",
    "Buyer_Address": "",
    "Buyer_Type": "Company",
    "Buyer_Website": "",
    "Emirate": ""
  },
  "invoiceSummary": {
    "Invoice_Number": "INV-0001",
    "Invoice_Date": "15-09-2026",
    "Subtotal": "1000.00",
    "VAT": "50.00",
    "Total": "1050.00",
    "Currency": "AED",
    "Total_Payable": "1050.00",
    "Payable_Currency": "AED",
    "Invoice_Type": "Tax Invoice",
    "Tax_Invoice": "true"
  },
  "products": [
    {
      "Product_Name": "Office Chair",
      "Quantity": "2",
      "Unit_Price": "500.00",
      "Discount": "",
      "VAT": "50.00",
      "Total": "1050.00",
      "activities_match": ["General Trading"]
    }
  ],
  "einvoicing_readiness": {
    "overall_score": 75,
    "status": "PARTIALLY_READY",
    "checks": [ ... ],
    "issues_count": 2,
    "passed_count": 6,
    "total_checks": 8,
    "penalties": { ... }
  }
}
  • trust_score.rating: HIGH_TRUST, MODERATE_TRUST, LOW_TRUST or SUSPECT. validation.risk_level: LOW, MEDIUM, HIGH or CRITICAL.
  • einvoicing_readiness.status: READY, PARTIALLY_READY or NOT_READY. Extracted values are strings; dates are DD-MM-YYYY.
  • 400 for empty activities or a URL that cannot be downloaded; 402 without credits.

Create an async invoice job

POST/invoice-compliance/async

Queue the same analysis and return a job_id immediately.

Auth
API key
Request body
JSON

Parameters

  • pdf_urlstring · required

    Public HTTPS URL of the invoice PDF.

  • activitiesstring[] · required

    Must not be empty.

  • webhook_urlstring · optional

    Public HTTPS URL that receives the result.

  • webhook_secretstring · optional

    Secret used to sign the webhook body.

Example request

curl -X POST https://api.paperwork.to/api/v1/invoice-compliance/async \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pdf_url": "https://files.example.com/invoice.pdf",
    "activities": ["General Trading"],
    "webhook_url": "https://your-server.example.com/webhooks/paperwork",
    "webhook_secret": "YOUR_WEBHOOK_SECRET"
  }'

Example response

{
  "job_id": "c7d9e1f3-0000-4000-8000-000000000007",
  "status": "pending",
  "message": "Job queued for processing",
  "status_url": "/api/v1/invoice-compliance/status/c7d9e1f3-0000-4000-8000-000000000007"
}
  • The webhook for this endpoint is sent once, without retries. Poll the status endpoint as a fallback.

Get invoice job status

GET/invoice-compliance/status/{job_id}

Return progress and, when completed, the analysis result.

Auth
API key
Request body
None

Parameters

  • job_idstring (path) · required

    job_id from the create call.

Example request

curl https://api.paperwork.to/api/v1/invoice-compliance/status/c7d9e1f3-0000-4000-8000-000000000007 \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY"

Example response

{
  "job_id": "c7d9e1f3-0000-4000-8000-000000000007",
  "status": "completed",
  "progress": 100,
  "progress_message": "Complete",
  "created_at": "2026-10-05T10:00:00Z",
  "updated_at": "2026-10-05T10:00:40Z",
  "result": { "trust_score": { ... }, "invoiceSummary": { ... } },
  "completed_at": "2026-10-05T10:00:40Z"
}
  • result and completed_at appear when completed; error and completed_at when failed. Unknown job IDs return 404.

Identity: Emirates ID and Trade License

Reads UAE Emirates ID cards and trade licenses, validates the key numbers and dates, and checks licenses against the issuing authority where possible.

Verify an Emirates ID by URL

POST/emirates-id/verify

Read the front and optional back of an Emirates ID from URLs and return the fields in camelCase.

Auth
API key
Request body
JSON

Parameters

  • frontUrlstring · required

    Public HTTPS URL of the front side (JPG, PNG, WebP or PDF).

  • backUrlstring · optional

    Public HTTPS URL of the back side.

Example request

curl -X POST https://api.paperwork.to/api/v1/emirates-id/verify \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "frontUrl": "https://files.example.com/eid-front.jpg",
    "backUrl": "https://files.example.com/eid-back.jpg"
  }'

Example response

{
  "results": {
    "extractedFields": {
      "firstName": "JANE",
      "lastName": "DOE",
      "names": "JANE DOE",
      "nationality": "GBR",
      "sex": "F",
      "dateOfBirth": "01/01/1990",
      "identityCardNumber": "784-1990-0000000-0",
      "documentNumber": "000000000",
      "dateOfIssue": "01/01/2025",
      "dateOfExpiry": "01/01/2030",
      "profession": "Engineer",
      "sponsor": "Example Trading LLC",
      "placeOfIssue": "Dubai"
    },
    "completeStatus": {
      "overallStatus": "SUCCESS",
      "detailsOptical": { "docType": "True", "expiry": "True", "overallStatus": "SUCCESS" }
    }
  },
  "error": null
}
  • identityCardNumber is the 784-XXXX-XXXXXXX-X ID number; documentNumber is the card number.
  • detailsOptical.docType is "False" when the ID number format is invalid; expiry is "False" when the card has expired.
  • On a document that is not an Emirates ID the response is HTTP 422 with results null and error {statusCode: "422", status: "error", name: "DocumentTypeError", message}.
  • Fields that were not read are null. On failure the response is HTTP 500 with results null and error {statusCode, status, name, message}.

Read and verify a trade license

POST/trade-license-v2

Extract a trade license from one file or URL, validate it and check it against the issuing authority.

Auth
API key
Request body
multipart/form-data

Parameters

  • filesfile · optional

    One PDF or image (JPG, PNG, WebP, TIFF), up to 50 MB. Send files or file_urls, not both.

  • file_urlsJSON array (string) · optional

    Array with exactly one public HTTPS URL.

Example request

curl -X POST https://api.paperwork.to/api/v1/trade-license-v2 \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -F "files=@trade-license.pdf"

Example response

{
  "success": true,
  "extracted_data": {
    "address": { "area": "Example Area", "buildingName": "Example Tower", ... },
    "licenseDetails": {
      "licenseNumber": "000000",
      "tradeName": "Example Trading LLC",
      "companyName": "Example Trading LLC",
      "legalType": "Limited Liability Company",
      "emirate": "Dubai",
      "issueDate": "01/01/2026",
      "expiryDate": "31/12/2026",
      ...
    },
    "licenseMembers": [{ "name": "JANE DOE", "role": "Manager", "share": "", "number": "", "managerName": "", "nationality": "GBR" }],
    "licensePartners": [],
    "licenseActivities": { "activities": [ ... ] },
    "otherLicenseDetails": { ... }
  },
  "validation": {
    "license_number_valid": true,
    "expiry_valid": true,
    "is_expired": false,
    "expires_soon": false,
    "days_until_expiry": 87,
    "all_valid": true,
    "errors": []
  },
  "checks": [{ "name": "License Number", "passed": true, "message": "Valid license number" }],
  "verified": true,
  "verification_note": null,
  "processing_time": 14.2
}
  • verified is true when the issuing authority record matches; otherwise verification_note says why in one sentence.
  • A file that is not a trade license, or one with nothing readable, returns HTTP 422 with success false and error.

Verify a trade license by URL

POST/trade-license-v2/verify

The same check for one URL, returning the extracted fields under result.

Auth
API key
Request body
JSON

Parameters

  • urlstring · required

    Public HTTPS URL of the license.

  • countrystring · optional

    Default UAE.

  • docTypestring · optional

    Default tradeLicense.

Example request

curl -X POST https://api.paperwork.to/api/v1/trade-license-v2/verify \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://files.example.com/trade-license.pdf"}'

Example response

{
  "result": { "licenseDetails": { "licenseNumber": "000000", ... }, ... },
  "error": null,
  "verified": true,
  "verification_note": null
}
  • On a wrong document type the response is HTTP 422 with error {statusCode: "422", status: "error", name: "DocumentTypeError", message}.

Company Intelligence

Quick checks on a counterparty: website age, public reputation and crypto wallet screening.

Domain age

POST/domain/check

Return how long a domain has been online, based on its first archived capture.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • domainstring · required

    Domain to check, for example example.com.

Example request

curl -X POST https://api.paperwork.to/api/v1/domain/check \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain": "example.com"}'

Example response

{
  "domain": "example.com",
  "age": 12,
  "firstSeen": "2014-03-02"
}
  • age is in whole years. Also available at POST /domain-check. 400 when the domain cannot be checked; 402 without credits.

Company sentiment

POST/company-sentiment

Summarise public reviews and mentions of a company, with optional keyword checks.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • company_namestring · required

    Up to 200 characters.

  • locationstring · optional

    Where to look. Default UAE.

  • keywordsstring[] · optional

    Terms to look for, for example fraud or lawsuit.

Example request

curl -X POST https://api.paperwork.to/api/v1/company-sentiment \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"company_name": "Example Trading LLC", "location": "United Kingdom", "keywords": ["fraud"]}'

Example response

{
  "company_name": "Example Trading LLC",
  "overall_sentiment": "positive",
  "confidence": 0.8,
  "summary": "Customers mostly praise delivery times.",
  "positive_count": 12,
  "negative_count": 2,
  "neutral_count": 3,
  "reviews": [
    {
      "text": "Fast delivery and fair prices.",
      "sentiment": "positive",
      "source": "Example Reviews",
      "source_url": "https://reviews.example.com/1",
      "date": "2026-08-14",
      "rating": 5,
      "author": "Example Customer"
    }
  ],
  "keyword_analysis": [
    { "keyword": "fraud", "found": false, "mention_count": 0, "sentiment": "neutral", "sources": [] }
  ],
  "analysis_timestamp": "2026-10-05T10:00:00Z"
}
  • overall_sentiment: positive, negative, neutral or mixed.

Crypto wallet screening

POST/crypto/screen-wallet

Screen a wallet address against sanctions lists and on-chain risk signals.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • addressstring · required

    Wallet address.

  • chain_idinteger · required

    1 Ethereum, 10 Optimism, 137 Polygon, 8453 Base, 42161 Arbitrum One, 0 Bitcoin.

  • tierstring · optional

    REGULAR (default), ENHANCED (adds more official sanctions lists) or PRO (adds entity exposure analysis).

Example request

curl -X POST https://api.paperwork.to/api/v1/crypto/screen-wallet \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address": "0x0000000000000000000000000000000000000001", "chain_id": 1, "tier": "REGULAR"}'

Example response

{
  "schema_version": "1.2",
  "screening_id": "scr_example_0001",
  "screened_at": "2026-10-05T10:00:00Z",
  "tier": "REGULAR",
  "status": "NO_KNOWN_RISK_SIGNALS",
  "conclusion": "No known risk signals were found for this wallet.",
  "wallet": {
    "address": "0x0000000000000000000000000000000000000001",
    "chain_id": 1,
    "chain_name": "Ethereum",
    "kind": "EOA_CANDIDATE",
    "ens_name": null,
    "is_verified_contract": false
  },
  "sanctions": { "exact_match": false, "potential_cross_chain_match": false, "direct_exposure": false, "matches": [] },
  "signals": [],
  "observations": [],
  "holdings": { ... },
  "activity": { ... },
  "coverage": { ... },
  "risk_intelligence": null,
  "disclaimer": "..."
}
  • status: BLOCKED_EXACT_MATCH, MANUAL_REVIEW, INSUFFICIENT_DATA or NO_KNOWN_RISK_SIGNALS.
  • signals[] items: type, severity (LOW, MEDIUM, HIGH, CRITICAL), summary, evidence_type, counterparty, tx_hash.
  • Errors use {"detail": {"code", "message"}}, for example INVALID_REQUEST or INSUFFICIENT_CREDITS.

Paper OS

Sync clients and cases from your own system into Paper OS. Paper OS must be enabled for your organization, and the API key must be allowed for paper-os.

Create a client

POST/paper-os/companies

Create a client from a record in your system. Returns 201.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • namestring · required

    1 to 240 characters, with at least one letter or digit.

  • idempotency_keystring · required

    Your stable record ID: 8 to 128 characters, A-Z a-z 0-9 _ -. A retry with the same key returns the same client.

Example request

curl -X POST https://api.paperwork.to/api/v1/paper-os/companies \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Example Trading LLC", "idempotency_key": "crm_company_1842"}'

Example response

{
  "id": "6f8d9c2f-0000-4000-8000-000000000008",
  "name": "Example Trading LLC",
  "version": 1,
  "created_at": "2026-10-05T09:30:00Z",
  "updated_at": "2026-10-05T09:30:00Z"
}
  • If a client with the same name already exists, that client is returned. 409 when several clients share the name.

Create a case

POST/paper-os/cases

Create a case for a client. Paper OS creates its case room automatically. Returns 201.

Auth
API key or dashboard session token
Request body
JSON

Parameters

  • company_idstring · required

    id returned by POST /paper-os/companies.

  • titlestring · required

    1 to 240 characters, for example Working capital, October.

  • idempotency_keystring · required

    8 to 128 characters, A-Z a-z 0-9 _ -. A retry with the same key returns the same case and room.

  • case_type_idstring · optional

    Case type with its document checklist. Default: the organization's default type, if any.

Example request

curl -X POST https://api.paperwork.to/api/v1/paper-os/cases \
  -H "Authorization: Bearer sk-paperwork-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company_id": "6f8d9c2f-0000-4000-8000-000000000008",
    "title": "Working capital, October",
    "idempotency_key": "loan_application_8421"
  }'

Example response

{
  "id": "3c16073c-0000-4000-8000-000000000009",
  "company_id": "6f8d9c2f-0000-4000-8000-000000000008",
  "title": "Working capital, October",
  "status": "open",
  "version": 1,
  "created_at": "2026-10-05T09:31:00Z",
  "updated_at": "2026-10-05T09:31:00Z",
  "room_id": "7f840c09-0000-4000-8000-000000000010",
  "case_type_id": null
}
  • Case status: open, in_review, approved, closed, declined or info_requested.
  • room_id is null if the room could not be created; the case itself is stored. Unknown company_id returns 404.

Webhooks

Set webhook_url and, optionally, webhook_secret on each async request (fraud detection, invoice processing, Document Automation JSON runs). When the job ends, Paperwork sends one POST with the job outcome.

Delivery

POST /webhooks/paperwork HTTP/1.1
Content-Type: application/json
User-Agent: Paperwork-Webhook/1.0
X-Webhook-Timestamp: 1791194460
X-Webhook-Signature: sha256=5d41402abc4b2a76b9719d911017c592...

{"job_id":"f3b8c2d1-0000-4000-8000-000000000003","status":"completed","result":{"verdict":"CLEAN","risk_level":"low","recommendation":"APPROVE"},"error":null,"completed_at":"2026-10-05T10:01:00Z","metadata":{"client_reference_id":"case-123"}}
  • Body fields: job_id, status (completed or failed; cancelled for Document Automation runs), result, error, completed_at, and metadata when you sent it. For Document Automation, job_id is the run id and result is the machine_result.
  • The URL must be public HTTPS. Redirects are not followed.
  • Any 2xx response within 30 seconds counts as delivered. Otherwise the delivery is retried, up to 3 attempts in total, about 1 and then 3 minutes apart.
  • Invoice processing async jobs are delivered once, without retries.

Verifying the signature

When you set a webhook_secret, X-Webhook-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with your secret. Compute it over the bytes exactly as received, before parsing the JSON. X-Webhook-Timestamp is the send time in Unix seconds; it is not part of the signature.

import hashlib
import hmac

def is_valid(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

Errors

Errors use standard HTTP status codes. The body shape depends on the endpoint family.

Most endpoints

A message under detail.

{ "detail": "API key does not have access to fraud-detection" }

Validation (422)

A list of field errors under detail.

{
  "detail": [
    { "type": "missing", "loc": ["body", "pdf_url"], "msg": "Field required", "input": {} }
  ]
}

Bank Statement Analysis

All /bsa endpoints, including validation and auth errors.

{ "error": { "code": "JOB_NOT_FOUND", "message": "BSA job was not found.", "details": {} } }

Crypto screening

A code and message under detail.

{ "detail": { "code": "INVALID_REQUEST", "message": "..." } }

Emirates ID and trade license verify endpoints return failures in their own body: results or result null and error with statusCode, status, name and message.

StatusMeaning
200 / 201 / 202Success. 201 for created Paper OS records, 202 for queued Bank Statement Analysis jobs and Document Automation runs.
400Invalid input: missing files, both files and URLs, a non-HTTPS URL, a download that failed.
401Missing Authorization header, wrong format, unknown or disabled API key.
402Not enough credits or the subscription is not active (checked by the processing endpoints).
403The key is not allowed for this product, the product is not enabled for the organization, or the subscription is not active.
404Job, run, profile or record not found for your organization.
409Conflict: idempotency key reused with a different request, job not waiting for passwords, report revision no longer current.
413File or package too large.
422Request body failed validation, or the document is not the expected type.
429Document Automation only: the organization reached its run or provider limit.
500Processing failed. Retry later or contact us with the job ID.
503A dependency such as the queue or storage is temporarily unavailable.

Ready to integrate?

Get an API key for the products you need and start sending documents.

View pricing