# Kita API > AI Underwriter, Kita Capture, and Kita Risk Score. Source: /docs ## AI Underwriter Send borrower documents from your LOS or core banking system and get fraud-checked credit signals and cited credit memos. Kita recommends; your team decides. [AI Underwriter docs](/docs/underwriter) · `https://underwriter.kita.ai/api/v1` ## Kita Capture Turn bank statements, payslips, IDs, and tax filings into validated, fraud-checked JSON for PH, MX, and ID. Send a file, URL, or base64; poll or use webhooks. [Capture docs](/docs/capture) · `https://api.kita.ai` ## Kita Risk Score Turn borrower documents into up to ten risk scores, customized for each client. [Risk Score docs](/docs/risk-score) --- # API reference > Endpoints for document processing and optional workflows. Source: /docs/capture/api-reference Base URL: `https://api.kita.ai/api/v1`. Authenticate with a Bearer API key. | Resource | Use it to | | --- | --- | | [Documents](#documents) | Upload, poll, retrieve, list, delete, or merge documents. | | [Batch](#batch) | Submit separate documents and retrieve batch results. | | [Transactions](#transactions) | Replace transactions, revert edits, or rerun validation. | | [Verification](#verification) | Compare completed documents or inspect authenticity. | | [Exports](#exports) | Download Excel output. | | [Webhooks](/docs/capture/webhooks) | Register signed callbacks. | | [Folders & custom schemas](/docs/capture/folders-schemas) | Group documents and configure extraction fields. | Use [Results](/docs/capture/schemas) for response fields and [Integration guide](/docs/capture/integration) for permissions and retries. {/* Generated reference sections. Edit the OpenAPI spec, then run npm run generate:api. */} ## Documents ### List documents List documents in your organization, newest first. Requires document:read. ### Upload and process a document Upload a file, base64, or URL for processing (supports `webhook_url`). Provide one of `file`, `file_base64`, or `file_url`. Requires `document:write`. The `document_id` (an integer) in the response identifies the document in the path of every other document endpoint. ### Merge several files into one document Merge multiple files into one document for processing. Send a JSON body with an array of 2–50 files to merge and process as one document. ### Check processing status Poll using document_id, not the queue job_id. Stop on completed or failed. A completed job includes processing output in result; retrieve the document for normalized output. ### Retrieve a document result Read document metadata and normalized output in document.result after completion. Requires document:read. ### Delete a document Soft-delete a document. Requires document:delete. ## Transactions ### Edit transactions Replace the transaction array on a processed document. A full replacement, not a patch. Available for `bank_statement` and `passbook`. Requires `document:write`. ### Revert to the original extraction Discard all edits and restore the original extracted transactions, resetting metrics and validation to their original state. ### Re-run balance validation Re-run validation and metrics after edits. ## Batch ### Submit a batch Create batch: pass `documents` array with `file_url` + `document_type`. Process up to 100 documents per request (paid plans). Poll `GET /batch/{batch_id}` for status. ### Submit a batch as file uploads Batch upload multiple files as multipart form-data. ### Check batch status Poll status, `progress_percent`, and per-document state. ### Get batch results Get full extracted data for all documents. ## Verification ### Cross-document verification Cross-document verification across 2–50 processed documents. Returns per-document authenticity summaries, a cross-document consistency score (0–100), and field-level corroboration. ### Single-document authenticity Returns the full `fraud_detection` block for one document, with no cross-document analysis. ## Exports ### Download a single document as Excel Download a multi-sheet Excel workbook built from the document's type-specific schema (`audited_financial_statement`, credit report, SLIK). For `audited_financial_statement`, the export is a 17-sheet workbook (company info, statements, notes, ratios, risk flags, and more). A document type with no export schema returns `400 INVALID_EXPORT_TYPE`. A document that hasn't finished processing returns `DOCUMENT_NOT_PROCESSED`. ### Download an organization-specific workbook Download the workbook defined by your organization configuration. Requires document:read, processed data, and custom exports enabled for the organization. --- # Document types > Supported values for document_type. Source: /docs/capture/document-types Send `document_type` with each upload. Names are normalized to lowercase with underscores; `afs` and `gis` are aliases. Enabled types vary by organization. An accepted type does not guarantee every field is extractable. ## Common types | Type | Use | | --- | --- | | `bank_statement` | Transactions, balances, metrics, and review signals. | | `payslip` | Pay, earnings, deductions, and employer details. | | `government_id` | Identity fields. | | `audited_financial_statement` | Financial statements and related fields. | | `general_information_sheet` | Company and ownership information. | | `credit_report` | Bureau report information. | | `bill` | Billing details and amounts. | | `sales_invoice` | Seller, buyer, items, and totals. | | `receipt` | Purchase and payment details. | | `other_document` | General extraction when no specific type fits. | ## Other built-in types | Type | Country / group | Use | | --- | --- | --- | | `certificate_of_employment` | General | Employment confirmation and employer details. | | `credit_card_statement` | General | Card balances, charges, and payments. | | `loan_statement` | General | Loan balances and repayment activity. | | `passbook` | General | Bank passbook entries and balances. | | `remittance_slip` | General | Money transfer and remittance details. | | `tin_id` | Philippines | Taxpayer identification card. | | `bill_of_lading` | General | Shipping and cargo details. | | `bir_2303` | Philippines | BIR certificate of registration. | | `business_permit` | General | Business operating permit. | | `business_registration_dti` | Philippines | DTI business registration. | | `business_registration_sec` | Philippines | SEC business registration. | | `certificate_of_registration` | General | Registration certificate details. | | `mayors_permit` | Philippines | Mayor’s business permit. | | `purchase_order` | General | Ordered goods, quantities, and amounts. | | `secretarys_certificate` | General | Corporate secretary’s certification. | | `certificate_of_incorporation` | General | Company incorporation details. | | `bir_2307` | Philippines | BIR certificate of creditable tax withheld. | | `income_tax_return` | General | Reported income and tax information. | | `insurance_policy` | General | Insurance coverage and policy details. | | `land_title` | General | Property title and ownership details. | | `loan_agreement` | General | Loan terms and parties. | | `proof_of_billing` | General | Billing record used as address evidence. | | `vehicle_registration` | General | Vehicle registration details. | | `mobile_banking_screenshot` | General | Banking information captured from a mobile app. | | `combined_document` | General | A file containing multiple documents. | | `general_document` | General | General document extraction. | | `slik` | Indonesia | SLIK credit report. | | `ine` | Mexico | INE voter identification card. | | `acta_constitutiva` | Mexico | Company incorporation document. | | `constancia_situacion_fiscal` | Mexico | Tax status certificate. | | `actas_de_asamblea` | Mexico | Company meeting minutes. | | `poder_notarial` | Mexico | Notarial power of attorney. | | `estados_financieros` | Mexico | Financial statements. | | `debtor_statement_letter` | Indonesia | Debtor statement letter (Surat Pernyataan Debitur). | | `akta_pendirian` | Indonesia | Deed of establishment. | | `akta_perubahan` | Indonesia | Deed of amendment. | | `sk_kemenkumham` | Indonesia | Ministry of Law decree. | | `nib` | Indonesia | Business registration number (Nomor Induk Berusaha). | | `ktp` | Indonesia | Identity card (Kartu Tanda Penduduk). | | `npwp` | Indonesia | Taxpayer number (Nomor Pokok Wajib Pajak). | | `ktp_npwp` | Indonesia | Combined identity card and taxpayer information. | | `ar_aging` | General | Accounts receivable aging report. | | `cash_flow_projection` | General | Projected cash inflows and outflows. | | `client_service_contract` | General | Client service agreement. | | `business_financials` | General | Business financial records. | Registered [custom schema](/docs/capture/folders-schemas) types are also supported where enabled. See [Results](/docs/capture/schemas) for response examples and [supported input formats](/docs/capture/integration#input-methods-and-limits). --- # Folders & Schemas > Group documents into folders and define custom extraction schemas. Source: /docs/capture/folders-schemas **Folders** group documents (e.g. by applicant). **Schemas** define custom extraction shapes that override the default output. ## Folders | Method | Path | Description | | --- | --- | --- | | `POST` | `/api/v1/folders` | Create a folder | | `GET` | `/api/v1/folders` | List folders | | `GET` | `/api/v1/folders/{id}/documents` | List documents in a folder | ## Custom Extraction Schemas Custom Extraction Schemas require an **Enterprise**, **Unlimited**, or **Custom** plan. Maximum 20 schemas per organization. | Method | Path | Description | | --- | --- | --- | | `GET` | `/api/v1/schemas` | List custom schemas | | `GET` | `/api/v1/schemas/{id}` | Get a single schema | | `POST` | `/api/v1/schemas` | Create a custom schema | | `PUT` | `/api/v1/schemas/{id}` | Update a schema | | `DELETE` | `/api/v1/schemas/{id}` | Delete a schema | ## Authentication and identifiers Use `Authorization: Bearer $KITA_API_KEY`. Reads require `document:read`; folder creation and schema mutations require `document:write`. Use the `id` from create/list responses. Folder IDs are UUIDs, not numeric document IDs. Validate the format client-side: malformed folder IDs currently return `500`; an unknown, well-formed UUID returns `404`. ## Create and use a folder ```bash curl -X POST "https://api.kita.ai/api/v1/folders" \ -H "Authorization: Bearer $KITA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Loan 44821","source":"los","source_id":"44821"}' ``` `name` is required (1–100 characters). Optional: `color`, `source`, `source_id`, `source_url`. With both `source` and `source_id`, repeated creation returns the existing folder and updates its name and any supplied color/source URL. A new folder returns `201` with `{ "folder": { "id": "…" }, "created": true }`; an existing one returns `200` with `created: false` (folder fields abbreviated). `GET /api/v1/folders` returns `{ "folders": [...] }` and accepts a `source` filter. `GET /api/v1/folders/{id}/documents` returns `{ "documents": [...] }` with `id`, `fileName`, `documentType`, `status`, `uploadedAt`, and `completedAt` per file. ## Define a custom extraction schema ```bash curl -X POST "https://api.kita.ai/api/v1/schemas" \ -H "Authorization: Bearer $KITA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "document_type": "loan_summary", "display_name": "Loan summary", "schema_definition": { "sections": { "summary": { "type": "object", "fields": { "borrower_name": { "type": "string" } } } } } }' ``` Required: `document_type` (must match `^[a-z][a-z0-9_]{2,99}$`), non-empty `display_name`, and `schema_definition`. Optional: `description`, `category`. `schema_definition.sections` is an object of named sections. Each section has type `object` or `array`; define object fields in `fields`, or array item fields in `item_schema`. Field types are `string`, `number`, `integer`, `currency`, `date`, `boolean`, `array`, or `object`. A slug matching a built-in document type creates an additive override. Creation returns `201` with `{ "schema": { ... } }`. List returns `{ "schemas": [...] }`; get and update return `{ "schema": { ... } }`. Use the schema ID for get/update/delete and `document_type` when uploading documents. `PUT /api/v1/schemas/{id}` accepts any of `display_name`, `description`, `category`, and `schema_definition`; omitted fields are preserved. A supplied schema replaces the definition and increments its version. The type slug is not editable. `DELETE` deactivates the schema and returns `200` with `success: true` and a `deleted` object containing `id` and `document_type`. Invalid definitions return `400 INVALID_SCHEMA`; an active duplicate type returns `409 DUPLICATE_SCHEMA`. Missing/inactive schemas return `404 SCHEMA_NOT_FOUND`. An ineligible plan returns `403`; reaching 20 active schemas returns `400 SCHEMA_LIMIT_REACHED`. --- # Quickstart > Upload a document, wait for processing, and retrieve structured data. Source: /docs/capture ## 1. Create an API key Create a key in the [Kita dashboard](https://portal.kita.ai) with `document:write` and `document:read` scopes. Keep it on your backend. ```bash export KITA_API_KEY="kita_prod_REPLACE_ME" export KITA_CAPTURE_BASE="https://api.kita.ai/api/v1" ``` ## 2. Upload a document Replace the file path and choose a [document type](/docs/capture/document-types) enabled for your organization. ```bash curl --fail-with-body "$KITA_CAPTURE_BASE/documents" \ -H "Authorization: Bearer $KITA_API_KEY" \ -F 'file=@./statement.pdf' \ -F 'document_type=bank_statement' ``` The response is `202 Accepted`. Save `document_id`: ```json { "success": true, "document_id": 42, "status": "pending", "status_url": "/api/v1/documents/jobs/42" } ``` Response abbreviated. Poll with `document_id`, not the queue `job_id`. ## 3. Wait for completion ```bash export KITA_DOCUMENT_ID="42" # Replace with your document_id curl --fail-with-body "$KITA_CAPTURE_BASE/documents/jobs/$KITA_DOCUMENT_ID" \ -H "Authorization: Bearer $KITA_API_KEY" ``` Poll every few seconds with backoff and a client-side timeout. Continue while `status` is `pending` or `processing`. Stop on `completed` or `failed`; a failed job includes an `error` and can still return HTTP `200`. ## 4. Retrieve the result ```bash curl --fail-with-body "$KITA_CAPTURE_BASE/documents/$KITA_DOCUMENT_ID" \ -H "Authorization: Bearer $KITA_API_KEY" \ -o result.json ``` The response contains `document.status` and, after completion, `document.result`. For bank statements, transactions and metrics are in `document.result.extracted_data`. See [Results](/docs/capture/schemas). --- # Integration guide > Input methods, permissions, processing, and error handling. Source: /docs/capture/integration ## Authentication Authenticate with a Bearer key. Scopes: | Scope | Use | | --- | --- | | `document:read` | Retrieve documents, results, exports, and verification results. | | `document:write` | Upload, merge, batch-process, or edit documents. | | `document:delete` | Delete documents. | | `webhook:manage` | Register and manage webhook endpoints. | ## Input methods and limits `POST /documents` accepts one of: | Input | Request | | --- | --- | | Local file | Multipart `file` plus `document_type`. | | Download URL | JSON `file_url` plus `document_type`. The URL must be reachable by Kita (e.g. an unexpired presigned URL). | | Base64 | JSON `file_base64`, `filename`, and `document_type`. | **Limit: 100 MB per file.** Formats: PDF, PNG, JPG/JPEG, TIFF/TIF, BMP, TXT, CSV, XLSX, XLS. Send `password` for encrypted PDFs. Use original files where possible. ## Processing and retries Poll by `document_id` until `completed` or `failed`, with backoff and a timeout. A client timeout does not cancel processing. | Endpoint | Status field | Completed result | | --- | --- | --- | | `/documents/jobs/{document_id}` | `status` | `result` (processing output). | | `/documents/{document_id}` | `document.status` | `document.result` (normalized result). | If an upload response is lost, check the document list before retrying. Reuploads can create new records even when a completed extraction is reused (see `document.processing`). ## Webhooks Pass `webhook_url` with an upload, or [register a signed webhook](/docs/capture/webhooks). Deduplicate repeated notifications, fetch results with your API key, and keep polling as a fallback. ## Batch or merge - **Batch:** process up to 100 separate documents. Each has its own result. Requires a paid plan. - **Merge:** combine 2–50 PDF/image inputs into one document and one result. See [API reference](/docs/capture/api-reference) for requests and responses. ## Errors Errors generally return `error` and `message`. `request_id` and `details` are not always present. | Status | What to do | | --- | --- | | `400` | Correct the input, document type, or identifier. | | `401` | Check the API key and Bearer header. | | `402` / `403` | Check credits, plan, scopes, and enabled document types. | | `404` | Check the ID and organization. | | `413` / `415` | Reduce the file size or use a supported format. | | `429` | Back off; honor `Retry-After` when present. | | `500` / `502` / `503` | Retry reads with backoff. Check for a created document before retrying uploads. | A job can fail after `202`; check `status` even when polling returns `200`. For support, send the document ID and any request ID. --- # Integrity checks > The eight standard integrity checks and how to read them. Source: /docs/capture/integrity-checks `fraud_detection.integrity_checks` contains these eight keys. Which checks run, and their evidence, depend on the document type. | Check | Description | |-----|---------------| | `file_structure` | Common document fraud assessment: file structure. | | `timestamps` | Common document fraud assessment: timestamp consistency. | | `editing_software` | Common document fraud assessment: editing-software indicators. | | `layout_analysis` | Common document fraud assessment: layout consistency. | | `font_consistency` | Common document fraud assessment: font consistency. | | `template_consistency` | Bank-statement analysis: template consistency. | | `visual_quality` | Bank-statement analysis: visual quality. | | `data_quality` | Bank-statement data-quality analysis; a check result is emitted when data-quality signals are produced. | Read each check's `status` and `description`. `pass`, `warn` and `fail` are returned results. `not_run` means no result for that check, including when it does not apply (e.g. `data_quality` with no signals). `not_run` is not a pass and is excluded from pass, warning and failure counts. A key's presence does not prove its check ran. --- # SDKs & tools > Optional Python, Postman, and coding-agent setup. Source: /docs/capture/python-sdk ## Python ```bash pip install kita export KITA_API_KEY="kita_prod_REPLACE_ME" ``` ```python from kita import KitaClient, KitaError client = KitaClient() try: result = client.process("statement.pdf", "bank_statement", timeout=600) print(result.document_id, result.status) result.save_json("result.json") except KitaError as error: print(error) ``` `process()` polls until done by default. To return immediately, pass `wait=False`, save `result.document_id`, and later call `client.get_result(document_id)`. Passing `webhook_url` does not disable waiting. Use `process_url()` or `process_base64()` for other inputs. Read `result.extracted_data`, or serialize with `result.to_dict()`. The SDK base URL is `https://api.kita.ai`, without `/api/v1`. ## Postman Set Bearer auth and import the curl commands from the [Quickstart](/docs/capture). ## MCP For coding agents, configure `kita-docs-mcp` with `KITA_API_KEY` in your MCP client: ```json { "mcpServers": { "kita-docs": { "command": "npx", "args": ["-y", "kita-docs-mcp"], "env": { "KITA_API_KEY": "kita_prod_REPLACE_ME" } } } } ``` Keep this configuration private. Reference tools work without a key; execution tools use it to access documents. --- # Transaction categories > Category values for transactions on bank and e-wallet statements. Source: /docs/capture/transaction-categories Categories apply to bank statements and similar transaction documents, such as e-wallet statements. They are derived from the transaction description, so any institution is supported. Coverage depends on each row's layout, language and narrative. `category_confidence` is `h` (clear match), `m` (reasonable match) or `l` (uncertain). | Category | Description | |----------|---------| | `income` | Salary, payroll, business receipts and employer deposits. | | `p2p` | Transfers between people or accounts, including instant-payment and e-wallet transfers. | | `p2m` | Merchant payments by card or e-wallet. | | `atm` | ATM withdrawals, deposits and cash advances. | | `cheque` | Cheque deposits, withdrawals and clearing. | | `loan` | Loan disbursements, repayments, credit-card payments and instalments. | | `utilities` | Electricity, water, telco, internet and subscriptions. | | `food` | Restaurants, food delivery, groceries and supermarkets. | | `retail` | Shopping, e-commerce and department stores. | | `transportation` | Ride-hailing, taxis, fuel, tolls and airlines. | | `medical` | Hospitals, pharmacies and clinics. | | `financial` | Investments, insurance, interest and dividends. | | `overdraft` | Bank fees, commissions, penalties and returned items. | | `gambling` | Casinos, betting and lottery. | | `legal` | Legal and notary services. | | `recreation` | Entertainment, hobbies, travel and gyms. | | `ignore` | Non-transaction rows such as opening balances and subtotals; excluded from category metrics. | | `unknown` | Insufficient narrative to assign a category, such as a generic memo code or reference number alone. | Generic `CM`/`DM` memo codes indicate direction, not purpose; these, reference numbers and name-only descriptions may stay `unknown`, so some passbooks have a high unknown share. `unknown` is not a fraud finding; do not force it into an unsupported category. --- # Webhooks > Register an endpoint and verify signed notifications. Source: /docs/capture/webhooks ## Register an endpoint Use a key with `webhook:manage`. Save the returned `webhook.id` and `webhook.secret_key` securely. ```bash curl --fail-with-body "$KITA_CAPTURE_BASE/webhooks" \ -H "Authorization: Bearer $KITA_API_KEY" \ -H 'Content-Type: application/json' \ -d '{"url":"https://your-app.example/webhooks/kita","events":["document.completed","document.failed"]}' ``` Test delivery with `POST /webhooks/{id}/test`. Inspect attempts with `GET /webhooks/{id}/deliveries`. ## Verify signatures Registered deliveries include `X-Kita-Signature: t=TIMESTAMP,v1=HEX_DIGEST`. The digest is **HMAC-SHA256 of `TIMESTAMP.RAW_BODY`**, using the webhook secret. Verify the raw bytes before parsing (do not reserialize the JSON), compare digests in constant time, and reject timestamps outside a five-minute tolerance. Record `X-Kita-Delivery-Id` to avoid processing a retry twice. Acknowledge promptly, then fetch the document with your API key. Delivery order is not guaranteed; check the document's current status. ## Per-upload callbacks You can also pass `webhook_url` on an upload; it still returns `202` immediately. Do not assume the registered webhook secret applies to per-upload callbacks. Use the callback to trigger an authenticated result lookup, with polling as a fallback. ## Management | Operation | Purpose | | --- | --- | | `GET /webhooks` | List registered endpoints. | | `GET /webhooks/{id}` | Inspect an endpoint. | | `PATCH /webhooks/{id}` | Change URL, events, or active state. | | `DELETE /webhooks/{id}` | Remove an endpoint. | | `POST /webhooks/{id}/rotate-secret` | Rotate its signing secret. | --- # Overview > Turn borrower documents into up to ten risk scores for your lending workflow. Source: /docs/risk-score Kita reads borrower documents (proof of income, proof of address, any image or file) and turns income, employment, banking, and document-quality signals into risk scores called Kita Financial Signals (KFS). Use them alongside your existing credit assessment. ## A simple way to share income Applicants share proof of income inside your app and review it before sending. **File formats:** `.pdf`, `.jpg`, `.png`, `.heic` Your score set, calibration, thresholds, and delivery format are agreed during onboarding. ## Example request Call from your backend and keep the key there. `KITA_BASE_URL` is `https://riskscore.kita.ai/` in production (`kita_prod_` keys) or `https://sandbox-api.kita.ai/` in sandbox (`kita_sbx_` keys). Sync requests time out after 120 seconds. Node.js needs version 20 or later. The full API reference (async jobs, batches, signed uploads, errors, limits) is at [riskscore.kita.ai/docs](https://riskscore.kita.ai/docs) (sign-in required). ## Example response Scores are under `application.scores`, each with `key`, `name`, `score`, and `percentile`. A score can be absent or `null` depending on your model and the documents; `address_verification_index` is always `null` for now. Check `n_scored` and the per-file outcomes in `documents` before using the result. ```json title="Response (abridged)" { "application_id": "app_example_001", "n_documents": 2, "n_scored": 2, "application": { "model_version": "kfs-29k-20260622", "schema_version": 4, "n_documents": 2, "scores": { "bank_cash_flow_stability_index": { "key": "bank_cash_flow_stability_index", "name": "Bank Cash-Flow & Stability Index", "score": 0.7937, "percentile": 85.0 }, "address_verification_index": null } }, "documents": [ { "document_name": "bank-statement.pdf", "status": "ok" }, { "document_name": "payslip.pdf", "status": "ok" } ] } ``` --- # The 10 scores > Each score is 0 to 1. Higher is always more favorable, including for distress and file checks. Five scores run on any document. The others are returned only when the matching document is sent, or when the feature is configured for your market. Source: /docs/risk-score/scores --- # Running a backtest > See how the scores relate to repayment in your own portfolio before you integrate. Source: /docs/risk-score/usage 1. **Share historical data.** Past borrower documents, each matched to a loan reference and repayment outcome. Include your current risk score to measure lift. 2. **Kita scores every application** and sends you the results file. 3. **Review the report:** repayment by score band and lift over your current model. Documents added +7.0 Gini in [microloan underwriting](https://www.kita.ai/blog/documents-alternative-data-microlending). --- # API reference > Request and response details for the core Underwriter integration. Source: /docs/underwriter/api-reference Base URL: `https://underwriter.kita.ai/api/v1`. Send your API key with every request. | Resource | Operations | | --- | --- | | [Applications](#applications) | Intake, create, list, retrieve, update, delete. | | [Documents](#documents) | Upload files, check extraction, retrieve results, delete. | | [Credit](#credit) | Retrieve the current assessment and credit signals. | | [Memo](#memo) | Start generation, check progress, read sections. | | [Conversation](#conversation) | Read borrower messages and sync new messages. | | [Exports](#exports) | Download the extraction workbook. | See the [Quickstart](/docs/underwriter) for a working example and the [Integration guide](/docs/underwriter/integration) for permissions, retries, and processing states. {/* Generated reference sections. Edit the OpenAPI spec, then run npm run generate:api. */} ## Applications ### Single-call application intake Create an application with optional files. Send multipart form data with an `application` JSON string and `file`/`files` parts, or JSON without files. Keep the total request below 4.5 MB. Reusing `external_ref` within your organization returns the existing application (200, `idempotent: true`) without changes; attached files are skipped. Every application endpoint identifies the application in its path by the UUID `id` or the human `app_id` (e.g. `APP-1042`) from the response. Scope: `write` ### List applications List applications in your org, most-recently-active first. Scope: `read` ### Create an application Create an application without files. Accepts the same metadata as intake, including `external_ref` for deduplication. Add files with the upload endpoint. Scope: `write` ### Get an application Fetch one application by UUID or by human `app_id` (e.g. `APP-1042`). Includes a live `document_count`. Scope: `read` ### Update an application Update mutable fields. All fields optional; omit to leave unchanged. Scope: `write` ### Archive an application Reversibly archive an application. Its child records and stored documents are retained. `deleted: true` is kept for compatibility; `archived: true` describes the operation. This endpoint does not permanently erase data. Scope: `write` ## Documents ### List documents List an application's documents. Add `?include=download_url` to attach a signed download URL (1-hour expiry) to each. Scope: `read` ### Upload documents Upload one or more files to an existing application as `multipart/form-data`. Accepts a single `file` field or repeated `file`/`files` fields (a whole package in one call). A single-file upload returns one document object; multiple files return an array under `data`. Poll the document(s) for progress. Keep the request below 4.5 MB. After a partial failure, list documents before retrying; retries can create duplicate files. Scope: `write` ### Get a document Fetch a single document including the full extraction result. Poll this endpoint for progress: `status` transitions `awaiting → processing → verified` / `low_confidence`. Scope: `read` ### Delete a document Delete a document (row + storage file) and recompute file completeness. Returns `{ "data": { "deleted": true } }`. Scope: `write` ### Read a document transcript Reconstruct existing layout text; this does not run OCR. Defaults to plain text. JSON includes per-page text, counts, and has_text. Documents without layout text return has_text: false in JSON; TXT returns a no-text message. Scope: `read` ### Read all document transcripts Read existing text for every document. JSON is the default and includes has_text per file; TXT groups text under document headers. Documents without layout text remain in the inventory. Scope: `read` ## Credit ### Get the credit picture Read the current assessment: scorecard, credit policy, financial spread, adjustments, and routing recommendation. Unavailable computed objects are null; adjustments is an array. Results update as documents process. Scope: `read` ### Read decision-engine inputs Versioned evidence for a lender-owned decision engine. Inspect assessment.complete and missing_required_metrics before using the snapshot. Completeness requires configured metrics, provider operations, and a versioned calculation timestamp; it is not loan approval. Scope: `read` ### Diagnose the scorecard Read product configuration, inputs, computed metrics, rendered scorecard, and diagnosis. diagnosis.blocked_at identifies the first missing or invalid layer; this read does not recalculate the assessment. Scope: `read` ### Read historical precedents Read the advisory precedent table. Disabled, not_run, no_matches, and unavailable states return 200 with an empty precedents array. Historical comparisons do not make a lending decision. Scope: `read` ## Memo ### Read the memo Read the memo sections in order. Bodies preserve their JSON structure; citation markers and citation_map are removed recursively. Scope: `read` ### Generate the credit memo Start memo generation. Returns 202 with `data.status` of `accepted` or `already_running`. Poll `/applications/{id}/memo/status` and check for failure before reading the memo. Sample applications cannot generate memos. Scope: `write` ### Poll memo freshness Freshness + synthesis progress. Poll after `POST …/memo` until `synthesis_in_progress` is `false`. Check `last_run_status` for failure; `is_stale` indicates whether the memo needs refreshing. Scope: `read` ## Conversation ### Read borrower conversation The borrower-facing thread, ordered by `display_order`. Pass `?after={display_order}` to fetch only newer messages for incremental sync. Scope: `read` ## Exports ### Download the extraction workbook Download the extraction workbook (a Summary sheet plus one sheet per file area) as `.xlsx`. Returns `404` if no document has extraction data yet. Scope: `read` ### Export the memo or financial spread Download the current memo PDF (default) or financial-spread XLSX. This does not start synthesis. Generate and poll the memo first for populated narrative. XLSX returns 404 when no spread exists; it differs from the raw extraction workbook at /export. Historical precedents can be included in PDF only. Scope: `read` --- # Quickstart > Upload borrower documents, read the credit assessment, and generate a memo. Source: /docs/underwriter Kita extracts and analyzes documents. Your lending team makes the credit decision. ## 1. Set your API key Open [AI Underwriter → Settings → API keys](https://underwriter.kita.ai/settings/api-keys) and create a key with `read` and `write` permissions. Keep it server-side, never in browser or mobile code. ```bash export KITA_UNDERWRITER_API_KEY="kita_uw_REPLACE_ME" export KITA_UNDERWRITER_BASE="https://underwriter.kita.ai/api/v1" ``` ## 2. Create an application and upload a file Set `loan_type` to your configured loan product. Keep the request under 4.5 MB; upload larger packages one file at a time. ```bash curl --fail-with-body "$KITA_UNDERWRITER_BASE/intake" \ -H "Authorization: ApiKey $KITA_UNDERWRITER_API_KEY" \ -F 'application={"business_name":"Example Bakery","borrower_email":"borrower@example.com","loan_type":"SBA 7(a)","loan_amount":250000,"external_ref":"LOS-44821","send_outreach":false}' \ -F 'file=@./tax-return.pdf' ``` A new application returns `201` with the application in `data` and uploaded files in `data.documents`. Save `data.id`: ```bash export KITA_APPLICATION_ID="REPLACE_WITH_DATA_ID" ``` Reusing `external_ref` returns the existing application (`200`) and skips the files. Add files later with the [upload endpoint](/docs/underwriter/api-reference#upload-documents). ## 3. Check documents and read the assessment ```bash curl --fail-with-body "$KITA_UNDERWRITER_BASE/applications/$KITA_APPLICATION_ID/documents" \ -H "Authorization: ApiKey $KITA_UNDERWRITER_API_KEY" curl --fail-with-body "$KITA_UNDERWRITER_BASE/applications/$KITA_APPLICATION_ID/credit" \ -H "Authorization: ApiKey $KITA_UNDERWRITER_API_KEY" ``` Documents process asynchronously: poll while files are `awaiting` or `processing`; `low_confidence` needs review. Credit results update as documents process and are `null` when unavailable. See [polling and statuses](/docs/underwriter/integration#processing-and-polling). ## 4. Generate and retrieve a memo ```bash curl --fail-with-body -X POST "$KITA_UNDERWRITER_BASE/applications/$KITA_APPLICATION_ID/memo" \ -H "Authorization: ApiKey $KITA_UNDERWRITER_API_KEY" curl --fail-with-body "$KITA_UNDERWRITER_BASE/applications/$KITA_APPLICATION_ID/memo/status" \ -H "Authorization: ApiKey $KITA_UNDERWRITER_API_KEY" ``` Generation returns `202`. Poll status until `synthesis_in_progress` is `false`, then check `last_run_status`; a failed run is not a completed memo. Then read the sections: ```bash curl --fail-with-body "$KITA_UNDERWRITER_BASE/applications/$KITA_APPLICATION_ID/memo" \ -H "Authorization: ApiKey $KITA_UNDERWRITER_API_KEY" ``` Each `data.sections[].body` keeps its JSON structure (object, string, array, or null) with citation markers removed. For a rendered document, use the [memo PDF export](/docs/underwriter/api-reference#export-the-memo-or-financial-spread). --- # Integration guide > Permissions, processing states, retries, and errors for a reliable integration. Source: /docs/underwriter/integration ## Authentication Send `Authorization: ApiKey YOUR_KEY` on every request (`Bearer YOUR_KEY` also works). Keys are organization-scoped and shown once at creation. | Permission | Use | | --- | --- | | `read` | Retrieve applications, documents, credit results, memos, conversations, and exports. | | `write` | Create, update, or delete records; upload files; generate memos. | The quickstart needs both. Records in other organizations return `404`. ## Processing and polling Uploads return before extraction finishes. Poll `GET /applications/{id}/documents` or `GET /applications/{id}/documents/{docId}`, backing off from a few seconds, with a client timeout. | Document status | Client action | | --- | --- | | `awaiting` | Queued; poll again. | | `processing` | Extraction is running; poll again. | | `verified` | Extraction passed verification; read the results. | | `low_confidence` | Inspect `flag_message` and extraction results; arrange review. | | `missing`, `required` | A requested document has not been supplied; these are not processing jobs. | The credit picture updates as documents are accepted; it doesn't wait for a complete file. Missing results are `null`, not zero. **Memo generation:** `POST /applications/{id}/memo` returns `202` with `data.status` of `accepted` or `already_running`. Poll `/memo/status`: - `synthesis_in_progress: true`: continue polling. - `last_run_status: failed`: stop and inspect `last_run_failure_code` and `last_run_failure_stage`. - A completed run with `synthesis_in_progress: false`: read `/memo`. - `is_stale: true`: the memo is out of date with current evidence. It does not mean a job is running. ## Retries and duplicate applications Supply a stable `external_ref` (your loan ID, up to 200 characters) to `POST /intake` or `POST /applications`. Repeating it within the same organization returns the existing application, unchanged, with `200` and `idempotent: true`; repeated intake also skips attached files. Use `PATCH /applications/{id}` for changes and `POST /applications/{id}/documents` for more files. Upload retries can create duplicates: after a partial failure, list the documents and retry only missing files. Do not rely on `Idempotency-Key` response replay; deduplicate applications with `external_ref`. ## Required application fields `POST /applications` and `POST /intake` require `business_name`, `borrower_email`, `loan_type` (your organization's configured loan product), and a `loan_amount` greater than zero. Product-specific answers go in `intake_data`. ## Archiving applications `DELETE /applications/{id}` reversibly archives the application; documents, stored files, and child records are retained. The response includes `archived: true` and archive metadata (`deleted: true` remains for compatibility). There is no v1 restore or permanent-erasure endpoint; restore through the authorized dashboard archive workflow. ## Upload limits Keep the whole multipart request, including metadata, under 4.5 MB: upload one file per request and shrink files that exceed it. A batch can store some files before failing, and accepted files keep processing, so list documents before retrying. A failed intake may already have created the application: retry with its `external_ref` to recover it, then upload only missing files. ## Exports and transcripts | Endpoint suffix (after `/applications/{id}`) | Result | | --- | --- | | `/export?format=xlsx` | Extracted fields; `404` until extraction data exists. | | `/memo/export?format=pdf` | Current rendered memo; generate a memo first for populated narrative. | | `/memo/export?format=xlsx` | Financial spread; `404` until spread data exists. | | `/documents/{docId}/transcript?format=json` | One document’s layout text, grouped by page. | | `/transcripts?format=json` | All document transcripts; entries may have `has_text: false`. | All are read-only. Transcripts return existing layout text and do not start OCR. Memo section bodies are structured JSON (citation markers removed); do not assume every body is a string. For lender-owned decisioning, use `/decision-input` and check `assessment.complete` and `missing_required_metrics`; an incomplete result is still usable evidence. `/scorecard` explains missing configuration or inputs; `/historical-precedents` returns advisory comparisons when enabled. ## Errors API errors return a `message`: ```json { "message": "This API key lacks the 'write' scope required for this endpoint." } ``` | HTTP status | What to do | | --- | --- | | `400` | Correct the input using the error message. | | `401` | Check the key and Authorization header. | | `403` | Check permissions; sample applications also prohibit memo generation. | | `404` | Check the identifier, organization, or whether export data exists. | | `413` | Reduce the upload size. Hosting-layer errors may not use the JSON envelope. | | `500` / `502` / `503` | Retry reads with backoff. Before retrying a write, check whether it took effect. | ## Application statuses These describe the lending file, not an extraction job: `submitted`, `awaiting_docs`, `underwriting`, `question_raised`, `memo_drafted`, `approved`, and `declined`. Approval and decline are lender decisions. ## Responses and identifiers Successful JSON responses use a `data` envelope; application lists add `pagination`, and exports return files. Application endpoints accept the UUID or `app_id` (e.g. `APP-1042`). Save the returned identifier rather than constructing one. --- # MCP setup > Optional: connect a coding agent to the Underwriter API. Source: /docs/underwriter/mcp-server Create an API key with the permissions your agent needs. Configure `kita-docs-mcp` in a trusted local environment; tools can access borrower data and change applications. `KITA_UNDERWRITER_API_BASE` optionally overrides the default `https://underwriter.kita.ai/api/v1`. Start by asking your agent to list applications. Tools also cover intake, documents, credit results, memos, conversations, and exports. Delete tools require `confirm: true`. See the [Integration guide](/docs/underwriter/integration) for permissions and processing behavior. --- # Indonesia (ID) > Region-specific document types for the Indonesian market. Source: /docs/capture/schemas/id --- # Results > Read extracted fields, transaction metrics, and integrity signals. Source: /docs/capture/schemas ## Response structure After processing completes, `GET /documents/{document_id}` returns the normalized output in `document.result`. Its shape depends on the [document type](/docs/capture/document-types). The polling endpoint instead returns processing output in top-level `result`. For bank statements, `metadata` holds the account and statement period, `extracted_data` holds transactions and metrics, and fraud/integrity fields (when present) support review. ## Interpreting signals Missing or unreadable fields may be null or absent. A fraud signal needs review; it is not a lending decision. An integrity check with `not_run` is not a pass. See [integrity checks](/docs/capture/integrity-checks) and [transaction categories](/docs/capture/transaction-categories). To compare already-processed documents, use [cross-document verification](/docs/capture/api-reference#verification). ## Bank statements ## Other document types --- # Mexico (MX) > Region-specific document types for the Mexican market. Source: /docs/capture/schemas/mx --- # Philippines (PH) > Region-specific document types for the Philippine market. Source: /docs/capture/schemas/ph --- # SDK quickstart > Collect borrower documents with a server-created session and an in-app upload flow. Source: /docs/risk-score/sdk Use your Risk Score API key (`kita_prod_` or `kita_sbx_`) from the [dashboard](https://riskscore.kita.ai), on your backend only. Your app gets a short-lived session token. The SDK uses the same base URLs and keys as the Scoring API: `https://riskscore.kita.ai` with `kita_prod_` keys, and `https://sandbox-api.kita.ai` with `kita_sbx_` keys. On the dashboard’s [SDK](https://riskscore.kita.ai/sdk) page, add your app’s allowed origins for each environment and upload your logo. Borrowers see your organization’s name and logo; there is nothing to deploy on Kita’s side. Only photo and document uploads are available on iOS and Android. ## 1. Create a session Call from your server with your applicant or loan ID as `reference_id`. ```bash curl --fail-with-body -X POST "$KITA_BASE_URL/v1/risk/sessions" \ -H "X-API-Key: $KITA_API_KEY" \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: session:LOAN-2026-001' \ -d '{"reference_id":"LOAN-2026-001","origin":"https://app.example.com","location":"disabled"}' ``` Pass only `session_id` and `client_token` to your app. Tokens last 30 minutes. `origin` must be one of your allowed origins. A native app without a website uses an HTTPS origin you own (for example `https://app.example.com`) as its app origin; nothing has to be served there. ## 2. Open inside your app Present `KitaRiskViewController` on [iOS](/docs/risk-score/sdk/server#ios). On [Android](/docs/risk-score/sdk/server#android), load the flow in your WebView with the `capture` module. A [web embed](/docs/risk-score/sdk/server#web-sdk) is also available. Borrowers take a photo or choose PDFs and images of statements, payslips, invoices or payment records, review them, and send. Password and retry screens appear only when needed. ## 3. Retrieve the outcome `onSubmitted` means uploading finished; processing may still be running. Poll from your server or use a [signed webhook](/docs/risk-score/sdk/server#webhooks). ```bash curl --fail-with-body "$KITA_BASE_URL/v1/risk/sessions/$SESSION_ID" \ -H "X-API-Key: $KITA_API_KEY" ``` On `completed`, `partial`, or `failed`, fetch `/v1/risk/sessions/{session_id}/results` and check per-file outcomes. Data-collection sessions return results without scores. See the [upload walkthrough](/docs/risk-score#a-simple-way-to-share-income) and the [SDK reference](/docs/risk-score/sdk/server). --- # SDK reference > In-app iOS and Android hosts, web embedding, server endpoints, and troubleshooting. Source: /docs/risk-score/sdk/server ## Web SDK Load the script from `sdk_url`, then call `KitaRisk.create(options).open()`. For CSP, allow your Kita host in `script-src`, `style-src`, and `frame-src`. | Option | Type | Required | Description | | --- | --- | --- | --- | | `sessionId` | string | yes | `session_id` from [create session](/docs/risk-score/sdk/server#create-a-session). | | `clientToken` | string | yes | `client_token` from the same response. Expires after 30 minutes. | | `container` | element or selector | no | Render inline inside this element. Omit for a modal dialog. | | `connectUrl` | string | no | `connect_url` from the session response. Needed only when the script is served from a different host than the flow. | | `onSubmitted` | function | no | `({ sessionId, status: 'accepted' })`. The borrower sent their files. | | `onExit` | function | no | `({ sessionId })`. Closed before finishing; uploads are kept until the session expires. | | `onError` | function | no | `({ code, sessionId })`. See [errors](/docs/risk-score/sdk/server#errors); most are recoverable in the flow. | | `onEvent` | function | no | `({ name, sessionId })`: `consent_recorded`, `documents_added`, `documents_submitted`. | Methods: `open()`, `back()`, `exit()`, and `destroy()` (call on unmount). An inline `container` needs at least 360 × 640 CSS pixels. To resume, create a new handle with the same session ID and a new client token. Verify outcomes from your server, not browser callbacks. ## Server API Send `X-API-Key` with your Risk Score key to the same base URL as the Scoring API: `https://riskscore.kita.ai` for `kita_prod_` keys, `https://sandbox-api.kita.ai` for `kita_sbx_` keys. Your organization path (`//v1/risk/...`) works too. Manage keys on the dashboard's [API keys](https://riskscore.kita.ai/keys) page, and allowed origins and your logo on its [SDK](https://riskscore.kita.ai/sdk) page, which also lists recent runs. Never send an API key to a browser or mobile app. | Method | Path | Result | | --- | --- | --- | | `POST` | `/v1/risk/sessions` | Create a session; returns `201` with identifiers, token, expiry, SDK URL, and connect URL. | | `POST` | `/v1/risk/sessions/{id}/client-token` | Issue a new 30-minute token; the previous token stops working. | | `GET` | `/v1/risk/sessions/{id}` | Read status and per-file outcomes. | | `GET` | `/v1/risk/sessions?offset=0` | List sessions, 40 per page, newest first. | | `GET` | `/v1/risk/sessions/{id}/results` | Read results; returns `409 results_not_ready` before a terminal state. | | `DELETE` | `/v1/risk/sessions/{id}` | Delete session, files, and results; returns `204`. | ### Create a session | Field | Type | Required | Description | | --- | --- | --- | --- | | `reference_id` | string, 1–128 | yes | Your applicant or loan ID. | | `origin` | string | yes | The exact origin that embeds the SDK, saved as an allowed origin on the dashboard's SDK page. For iOS and Android, the app origin you pass to the SDK: an HTTPS origin you own; nothing has to be served there. | | `display_name` | string, 1–60 | no | Overrides the name borrowers see for this session. Default: your organization's name, shown beside your logo. | | `country` | `MX` or `PH` | no | Default `MX`. Must be a country enabled for you. | | `location` | `optional` or `disabled` | no | Legacy; use `disabled`. | Send an `Idempotency-Key` header (up to 128 characters) per applicant. Repeating the same key and body returns the same session with a new client token; a different body returns `409 idempotency_conflict`. Returns `201` with `session_id`, `client_token`, `expires_at`, `sdk_url`, `connect_url` and `environment`. ### Session status `collecting` → `queued` → `processing` → `completed`, `partial`, or `failed`. Check each document's `status` and `reason`. Scores are included only with live scoring. ### Python helper [Download kita_risk.py](/downloads/kita_risk.py) (standard library only). ```python import os from kita_risk import KitaRisk kita = KitaRisk(api_key=os.environ["KITA_API_KEY"], base_url=os.environ["KITA_BASE_URL"]) session = kita.create_session(reference_id="LOAN-2026-001", origin="https://app.example.com") status = kita.status(session["session_id"]) # When the borrower returns: token = kita.client_token(session["session_id"]) ``` ## Webhooks Kita turns on webhooks for your organization on request: send the HTTPS URL and we will share the signing secret. Events carry `id`, `session_id`, and a type: `risk.session.completed`, `risk.session.partial`, or `risk.session.failed`. They contain no scores; fetch results. Verify `Kita-Signature: t=TIMESTAMP,v1=DIGEST`, where the digest is HMAC-SHA256 of `TIMESTAMP.RAW_BODY` with your webhook secret (or use `KitaRisk.verify_webhook(secret, signature_header, raw_body)`). Reject timestamps older than five minutes and deduplicate event IDs. Return `2xx` after storing; failures retry up to six times. ## iOS Add the supplied `KitaRisk` Swift package and present `KitaRiskViewController` (iOS 15+). It runs in-app with the photo, Files and camera pickers. ```swift import KitaRisk let session = try KitaSession( id: response.sessionID, clientToken: response.clientToken, sdkOrigin: URL(string: "https://riskscore.kita.ai")!, // sandbox: https://sandbox-api.kita.ai appOrigin: URL(string: "https://app.example.com")! ) let controller = KitaRiskViewController(session: session) { event in switch event { case .submitted, .exited: host.dismiss(animated: true) case .failed: // Offer retry with a fresh token from your backend. break } } host.present(controller, animated: true) ``` Add `NSCameraUsageDescription` to Info.plist. Both origins must be HTTPS, and `appOrigin` must match the session and an allowed origin. The callback confirms receipt only; fetch results on your server. ## Android Android has no drop-in screen yet: host the flow in your own `WebView` with the supplied `capture` Gradle module (Android 8+, Java 17, compile SDK 36). 1. Enable JavaScript and DOM storage, and allow navigation only to the SDK origin and your app origin. 2. Attach the bridge, then load a small host page under your app origin that loads `{sdkOrigin}/sdk/v1/kita-risk.js` and `kita-risk.css` and calls `KitaRisk.create({ sessionId, clientToken, baseUrl: sdkOrigin, container: '#sdk' }).open()`. 3. Handle `WebChromeClient.onShowFileChooser` for photos and documents, and receive `onSubmitted` / `onExit` / `onError` through a message listener scoped to your app origin (`WebViewCompat.addWebMessageListener`). ```java String sdkOrigin = "https://riskscore.kita.ai"; // sandbox: https://sandbox-api.kita.ai CaptureConfig config = new CaptureConfig(sdkOrigin, sessionId, clientToken, 702); KitaCapture capture = new KitaCapture(activity, webView, config); webView.loadDataWithBaseURL(appOrigin + "/", hostHtml, "text/html", "utf-8", null); // Never put the server API key in the app. ``` Forward `onActivityResult` and `onRequestPermissionsResult` to the bridge, and call `capture.close()` when the host is destroyed. Call `capture.setPhotoCaptureAvailable(true)` only if your file chooser supports the camera. The SDK repository's Android sample is a complete host to start from. ## Testing uploads Use the SDK repository's iOS and Android samples and Maestro journeys; see `docs/SDK_MOBILE_TESTING.md`. ## Errors Errors contain `error.code` and `error.request_id`; include the request ID when contacting Kita. | Status | Code | Fix | | --- | --- | --- | | 401 | `invalid_api_key` | Missing, truncated, rotated, or revoked key. A key used on the other environment's base URL gets `wrong_environment`, which names the right URL. | | 403 | `sdk_not_enabled` | Your organization has no allowed origins for this environment yet. Add them on the dashboard's SDK page. | | 403 | `origin_not_allowed` | `origin` must exactly match one of your allowed origins: scheme, host and port, no path. | | 403 | `profile_not_enabled` | This country is not enabled for your organization. | | 404 | `session_not_found` | Wrong ID, another organization's session, or expired. | | 409 | `idempotency_conflict` | Same `Idempotency-Key`, different body. Use one key per applicant. | | 409 | `results_not_ready` | Wait for the webhook or for a terminal `status`. | | 413 | `request_too_large` | Over the size limit. | | 422 | `invalid_request`, `market_profile_not_validated` | A field failed validation (`error.fields` names it), or `country` does not match your profile. Checked before the key. | | 429 | `rate_limited` | Wait for `Retry-After` seconds. | | 503 | `sdk_settings_unavailable` | Your SDK settings could not be read. Retry after `Retry-After` seconds. | ### Upload errors | Code | Cause | What happens | | --- | --- | --- | | `encrypted_pdf` | The PDF needs a document password. | The flow asks for it. | | `invalid_pdf_password` | Wrong document password. | The borrower can retry. | | `unsupported_format` | Not a PDF or supported image. | The borrower picks another file. | | `invalid_image`, `corrupt_pdf`, `empty_document` | The file cannot be opened. | The borrower picks another file. | | `document_too_large`, `too_many_pages`, `oversized_image` | Over a [limit](/docs/risk-score/sdk/server#limits). | The borrower picks a smaller copy. | | `multiple_image_frames` | An animated or multi-frame image. | Export pages as a PDF or separate photos. | | `document_limit_reached` | 12 files already uploaded. | Remove one first. | | `connection_error`, `rate_limited` | Network trouble or throttling. | The flow offers Retry. | | `client_token_expired`, `invalid_client_token`, `missing_client_token` | The 30-minute token ran out, was replaced, or was not sent. | Get a [new client token](/docs/risk-score/sdk/server#server-api) and reopen. | | `session_locked` | The files were already shared. | Nothing to do; the session shows its receipt. | Finished sessions may report per-file `model_rejected`, `blank_document`, `extraction_error`, or `processing_timeout`. These are analysis outcomes; the borrower still sees a receipt. ## Limits | | | | --- | --- | | Write requests | 120 per minute per organization, in bursts of up to 30. `429` carries `Retry-After`. | | File size | 100 MB per file uploaded through the SDK. The Scoring API accepts up to 30 MB per document. | | Files per session | 12 | | PDF pages | 150 per file | | Client token lifetime | 30 minutes | | Session retention | 24 hours unless agreed otherwise |