Kita Underwriter
Push borrower documents from your core banking system or LOS, read fraud-checked and fully-traceable credit signals, and generate cited credit memos, all over a REST API. Kita recommends; your team decides. The API never approves a loan.
https://underwriter.kita.ai/api/v1AuthApiKey kita_uw_...Scopesread | writeOverview
The Underwriter API exposes the same three layers as the Kita dashboard: Capture (document extraction), the credit picture (deterministic metrics + policy routing), and the AI Underwriter (cited credit memo). A typical integration does three things:
- Write: push a borrower application and its documents in one call (
POST /intake). - Read: poll the deterministic credit picture as extraction tightens (
GET …/credit). - Generate: trigger and pull back the cited credit memo (
POST …/memo→GET …/memo).
Processing is continuous: every accepted document immediately updates the assessment. There is no “100% complete” gate before underwriting starts.
All endpoints are served under a single versioned prefix. Responses are JSON with a { "data": ... } envelope (binary for /export). The version is pinned in the path (/v1); breaking changes ship under a new version.
Base URL
https://underwriter.kita.ai/api/v1MCP Server
kita-docs-mcp exposes the Underwriter API to Claude Code, Claude Desktop, and Codex. It ships the searchable API reference plus tool calls for the full CDFI workflow: intake borrower files, upload documents, read the credit picture, generate memos, sync borrower conversations, and export workbooks or PDFs.
Claude Code
claude mcp add kita-docs --env KITA_UNDERWRITER_API_KEY=kita_uw_xxxxxxxxxxxx -- npx -y kita-docs-mcpenvironment
| Field | Type | Description |
|---|---|---|
KITA_UNDERWRITER_API_KEYrequired | secret | Underwriter API key beginning with kita_uw_. Required for every underwriter_* tool. |
KITA_UNDERWRITER_API_BASE | url | Optional. Defaults to https://underwriter.kita.ai/api/v1. Include /api/v1 when overriding. |
KITA_API_KEY | secret | Optional. Enables the same MCP server’s Kita Capture tools. |
Use server-side keys only
KITA_UNDERWRITER_API_KEY out of browser code and public repos.Authentication
Authenticate every request with an organization API key. Mint keys in Settings → API keys (admins only). A key looks like kita_uw_… and is shown exactly once at creation. Kita stores only a SHA-256 hash, so it cannot be recovered. If you lose it, revoke and mint a new one.
Pass the key in the Authorization header in either form:
Authorization header
Authorization: ApiKey kita_uw_xxxxxxxxxxxx
# or
Authorization: Bearer kita_uw_xxxxxxxxxxxxKeep keys server-side
Scopes & permissions
Each key carries one or both scopes, chosen at creation. Scope is enforced per endpoint: a key missing the required scope gets 403.
| Field | Type | Description |
|---|---|---|
read | scope | Every GET: list/read applications, credit picture, memo & memo status, documents, conversation, export. |
write | scope | Every mutation: intake, create application, upload documents, generate memo, PATCH/DELETE. |
Least privilege
Errors
Errors use a consistent envelope and standard HTTP status codes:
Error envelope
{ "message": "This API key lacks the 'write' scope required for this endpoint." }Status codes
| Field | Type | Description |
|---|---|---|
200 / 201 | ok | Success. 201 on resource creation. |
400 | client | Malformed body, missing required field, or bad parameter. |
401 | auth | Missing, malformed, unknown, or revoked API key. |
403 | auth | Valid key, but it lacks the scope this endpoint requires. |
404 | client | Application or document not found in your org (cross-org access is indistinguishable from not-found). |
500 / 502 | server | Server-side failure. 502 from /intake means the application was created but a document upload failed. |
There are currently no hard rate limits. Be a good client: run document pushes at a reasonable concurrency and poll status endpoints on a backoff (every few seconds, not in a tight loop). Heavy programmatic load should be coordinated with your Kita contact.
Quickstart
Push a borrower and a tax return in one call, then read the credit picture. Replace the key and file path:
1. Intake an application with a document
curl -X POST https://underwriter.kita.ai/api/v1/intake \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx" \
-F 'application={
"business_name": "Rivera Family Bakery LLC",
"borrower_email": "maria@riverabakery.com",
"loan_type": "SBA 7(a)",
"loan_amount": 250000,
"external_ref": "LOS-44821"
};type=application/json' \
-F "file=@./2023_form_1120s.pdf"Response · 201 Created
{
"data": {
"id": "b1e7c9a4-...-...",
"app_id": "APP-1042",
"external_ref": "LOS-44821",
"business_name": "Rivera Family Bakery LLC",
"borrower_email": "maria@riverabakery.com",
"loan_type": "SBA 7(a)",
"loan_amount": 250000,
"status": "submitted",
"file_completeness": 0,
"documents": [
{ "id": "9f2...", "doc_name": "2023_form_1120s.pdf", "status": "awaiting" }
]
}
}2. Read the credit picture (poll as docs process)
curl https://underwriter.kita.ai/api/v1/applications/APP-1042/credit \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"End-to-end workflow
The canonical loop a lender integration runs:
- Intake.
POST /intakewith the borrower, loan terms, and document files. Passexternal_ref(your LOS/loan id) so retries are idempotent. - Wait for extraction. Each document moves
awaiting → processing → verified(orlow_confidence). PollGET …/documentsor a singleGET …/documents/{docId}. - Read the assessment.
GET …/creditreturns the spread (adjusted EBITDA/DSCR/ratios with per-metric provenance), the normalization adjustments, and the policy decision. It tightens as more documents land. - Generate the memo.
POST …/memosynthesizes the cited credit memo, then pollGET …/memo/statusuntil synthesis settles and read it withGET …/memo. - Sync back. Pull
…/conversationfor the borrower thread and…/exportfor the extraction workbook into your system of record.
Human-in-the-loop by design
Idempotency
POST /intake is idempotent on external_ref. Send your own unique loan/record id; if the same external_ref is pushed again, Kita returns the existing application with 200 (instead of 201) and "idempotent": true, and does not re-upload documents. This makes safe retries trivial after a network blip.
Without external_ref
POST /intake and POST /applications create a brand new application on every call. Always set external_ref for automated pushes.Async & polling
Document processing and memo synthesis run after the HTTP response returns, so these endpoints are async:
- Documents: after
POST …/documentsor/intake, poll a document untilstatusleavesawaiting/processing. - Memo: after
POST …/memo, pollGET …/memo/statusuntilsynthesis_in_progressisfalseandis_staleisfalse.
Use an exponential backoff starting around 2–3 seconds. There are no webhooks yet; polling is the supported pattern.
/intakewriteSingle-call application intake for a core banking system or LOS. Create the borrower + loan and push every document file in one request; Kita stores each file and kicks off extraction immediately. The file appears in the dashboard right away.
Body: two shapes
multipart/form-data: an application part holding the JSON metadata below, plus repeated file/files parts. Or application/json: the metadata object alone (no documents).
application metadata
| Field | Type | Description |
|---|---|---|
business_namerequired | string | Borrower business / legal name. |
borrower_emailrequired | string | Borrower contact email. |
borrower_phone | string | Borrower contact phone. |
loan_type | string | e.g. "SBA 7(a)", "Term loan". |
loan_amount | number | Requested amount in dollars. |
application_context | string | Loan purpose / free-text context for the file. |
external_ref | string | Your unique loan/record id. Enables idempotent retries. |
send_outreach | boolean | Default false. If true, Kita emails the borrower for missing docs. |
Request
curl -X POST https://underwriter.kita.ai/api/v1/intake \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx" \
-F 'application={"business_name":"Rivera Family Bakery LLC","borrower_email":"maria@riverabakery.com","loan_type":"SBA 7(a)","loan_amount":250000,"external_ref":"LOS-44821"};type=application/json' \
-F "file=@./2023_form_1120s.pdf" \
-F "file=@./jan_bank_statement.pdf"Response · 201 (or 200 on idempotent retry)
{
"data": {
"id": "b1e7c9a4-...",
"app_id": "APP-1042",
"external_ref": "LOS-44821",
"business_name": "Rivera Family Bakery LLC",
"borrower_name": null,
"borrower_email": "maria@riverabakery.com",
"borrower_phone": null,
"loan_type": "SBA 7(a)",
"loan_amount": 250000,
"status": "submitted",
"file_completeness": 0,
"application_context": null,
"submitted_at": "2026-06-17T18:04:11.000Z",
"last_activity_at": "2026-06-17T18:04:11.000Z",
"documents": [
{ "id": "9f2...", "doc_name": "2023_form_1120s.pdf", "doc_type": null, "classified_type": null, "status": "awaiting", "flag_message": null, "source": "API upload", "storage_path": "org/app/2023_form_1120s.pdf" }
]
}
}/applicationswriteCreate an application without files. Same metadata as intake, minus external_ref (use /intake when you need idempotency). Add documents afterwards with the upload endpoint.
body (application/json)
| Field | Type | Description |
|---|---|---|
business_namerequired | string | Borrower business / legal name. |
borrower_emailrequired | string | Borrower contact email. |
borrower_phone | string | Borrower contact phone. |
loan_type | string | Loan product. |
loan_amount | number | Requested amount. |
application_context | string | Loan purpose / context. |
send_outreach | boolean | Default false. |
Request
curl -X POST https://underwriter.kita.ai/api/v1/applications \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"business_name": "Rivera Family Bakery LLC",
"borrower_email": "maria@riverabakery.com",
"loan_type": "SBA 7(a)",
"loan_amount": 250000
}'/applicationsreadList applications in your org, most-recently-active first.
query parameters
| Field | Type | Description |
|---|---|---|
status | string | Filter to one status value (see Status reference). |
limit | number | Default 50, clamped to 1–200. |
offset | number | Default 0. Use with limit to page. |
Request
curl "https://underwriter.kita.ai/api/v1/applications?status=underwriting&limit=25" \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"Response · 200
{
"data": [ { "id": "...", "app_id": "APP-1042", "status": "underwriting", "...": "..." } ],
"pagination": { "total": 137, "limit": 25, "offset": 0 }
}/applications/{id}readFetch one application by UUID or by human app_id (e.g. APP-1042). Includes a live document_count.
Request
curl https://underwriter.kita.ai/api/v1/applications/APP-1042 \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"/applications/{id}writeUpdate mutable fields. All fields optional; omit to leave unchanged.
body (application/json)
| Field | Type | Description |
|---|---|---|
business_name | string | Non-empty. Applies only if the app has a linked borrower. |
loan_type | string | Non-empty. |
loan_amount | number | Must be > 0. |
application_context | string | null | Loan purpose / context. |
status | string | One of the 7 status values (see Status reference). |
Request
curl -X PATCH https://underwriter.kita.ai/api/v1/applications/APP-1042 \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "status": "approved" }'/applications/{id}writePermanently delete an application, its documents (files + storage), and any orphaned borrower record. Returns { "data": { "deleted": true } }.
Request
curl -X DELETE https://underwriter.kita.ai/api/v1/applications/APP-1042 \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"/applications/{id}/documentswriteUpload 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). Each file’s pipeline (prescreen → classify → extract) runs after the response.
Request
curl -X POST https://underwriter.kita.ai/api/v1/applications/APP-1042/documents \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx" \
-F "file=@./941_q1.pdf" \
-F "file=@./balance_sheet.pdf"Single vs. multiple
data. Poll the document(s) for pipeline progress./applications/{id}/documentsreadList an application’s documents. Add ?include=download_url to attach a signed download URL (1-hour expiry) to each.
Request
curl "https://underwriter.kita.ai/api/v1/applications/APP-1042/documents?include=download_url" \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"Response · 200
{
"data": [
{
"id": "9f2...",
"doc_name": "2023_form_1120s.pdf",
"doc_type": "Tax Return",
"classified_type": "form_1120s",
"status": "verified",
"flag_message": null,
"source": "API upload",
"storage_path": "org/app/2023_form_1120s.pdf",
"download_url": "https://...signed...&expires=3600"
}
]
}/applications/{id}/documents/{docId}readFetch a single document including the full extraction result. Poll this endpoint for pipeline progress: status transitions awaiting → processing → verified / low_confidence.
additional fields (vs. list)
| Field | Type | Description |
|---|---|---|
kita_raw | object | null | Full grounded extraction result (shape varies by document type). |
recommendations | array | null | System/AI recommendations for the document. |
inconsistencies | array | null | Detected inconsistencies / fraud signals. |
Request
curl https://underwriter.kita.ai/api/v1/applications/APP-1042/documents/9f2... \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"/applications/{id}/documents/{docId}writeDelete a document (row + storage file) and recompute file completeness. Returns { "data": { "deleted": true } }.
Request
curl -X DELETE https://underwriter.kita.ai/api/v1/applications/APP-1042/documents/9f2... \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"/applications/{id}/creditreadThe deterministic credit picture: the numbers the dashboard shows, minus the memo narrative. Three layers, each null until computed (the assessment is continuous):
| Field | Type | Description |
|---|---|---|
spread | object | null | Adjusted EBITDA/net income, normalized ratios (DSCR, margins, debt-to-worth, current ratio, reserve months, LTV), the DSCR sensitivity table, and per-metric provenance (formula, version, inputs, threshold). No LLM math. |
adjustments | array | Normalization line items applied to this borrower's own reported numbers (owner-comp add-backs, non-recurring items), each tagged ai or lo. |
decision | object | null | Policy engine routing recommendation, whether it auto-routed, and every rule's pass/fail with observed vs. expected. |
Response · 200 (abridged)
{
"data": {
"application_id": "b1e7c9a4-...",
"app_id": "APP-1042",
"spread": {
"adjusted_ebitda": 412000,
"adjusted_net_income": 188500,
"ratios": { "dscr": 1.42, "global_dscr": 1.31, "debt_to_worth": 2.1, "current_ratio": 1.6, "ltv": 0.74 },
"sensitivity": [ /* DSCR at revenue stress levels */ ],
"metric_results": [ /* per-metric provenance */ ],
"engine_version": "biz-v3"
},
"adjustments": [ { "line_item": "owner_comp_addback", "label": "Owner compensation to market", "amount": 60000, "source": "ai" } ],
"decision": {
"routing": "human_review",
"auto_routed": false,
"rules": [ { "label": "Min DSCR 1.25x", "outcome": "pass", "actual": 1.42, "expected": 1.25 } ]
}
}
}/applications/{id}/memowriteSynthesize the credit memo: promote verified extractions into the memo tables, refresh deterministic metrics + policy routing (best-effort), then run synthesis inline. Poll …/memo/status afterwards, then read …/memo.
Request
curl -X POST https://underwriter.kita.ai/api/v1/applications/APP-1042/memo \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"/applications/{id}/memoreadRead the memo sections in order. Bodies are plain text with internal [Cn] citation markers stripped.
Response · 200
{
"data": {
"last_synthesis_at": "2026-06-17T18:22:09.000Z",
"sections": [
{ "section_number": 1, "title": "Deal Summary", "body": "Rivera Family Bakery...", "locked_by_lo": false, "updated_at": "2026-06-17T18:22:09.000Z" }
]
}
}/applications/{id}/memo/statusreadFreshness + synthesis progress. Poll after POST …/memo until synthesis_in_progress is false.
| Field | Type | Description |
|---|---|---|
synthesis_in_progress | boolean | A synthesis run is currently underway. |
is_stale | boolean | Memo is out of date vs. current docs/messages. |
stale_reasons | string[] | Why it's stale. |
new_doc_count | number | Documents added since last synthesis. |
new_message_count | number | Messages added since last synthesis. |
last_run_status | string | null | completed | failed. |
last_synthesis_at | string | null | Timestamp of last successful synthesis. |
/applications/{id}/conversationreadThe borrower-facing thread, ordered by display_order. Pass ?after={display_order} to fetch only newer messages for incremental sync.
Request
curl "https://underwriter.kita.ai/api/v1/applications/APP-1042/conversation?after=12" \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"/applications/{id}/exportreadDownload the extraction workbook (a Summary sheet plus one sheet per file area) as .xlsx. Returns 404 if no document has extraction data yet.
Request
curl https://underwriter.kita.ai/api/v1/applications/APP-1042/export \
-H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx" \
-o "rivera-extractions.xlsx"Status reference
Application status: the lifecycle value on every application:
| Field | Type | Description |
|---|---|---|
submitted | status | Created; intake received. |
awaiting_docs | status | Waiting on borrower documents. |
underwriting | status | Assessment in progress as docs process. |
question_raised | status | An open question / escalation on the file. |
memo_drafted | status | A credit memo has been synthesized. |
approved | status | Marked approved by the lender. |
declined | status | Marked declined by the lender. |
Document status: the extraction state of each file:
| Field | Type | Description |
|---|---|---|
awaiting | status | Queued; pipeline not started. |
processing | status | Prescreen / parse / extraction running. |
verified | status | Extracted and grounded with high confidence. |
low_confidence | status | Extracted but flagged for review. |
missing | status | A required document not yet provided. |
Need help?
