# 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 |