DocsTwo products, one reference: Kita Capture and Kita Underwriter.Open the portal
Underwriter API · v1

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.

Base URLhttps://underwriter.kita.ai/api/v1AuthApiKey kita_uw_...Scopesread | write
End-to-end examples on GitHub

Overview

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:

  1. Write: push a borrower application and its documents in one call (POST /intake).
  2. Read: poll the deterministic credit picture as extraction tightens (GET …/credit).
  3. Generate: trigger and pull back the cited credit memo (POST …/memoGET …/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/v1

MCP 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-mcp

environment

FieldTypeDescription
KITA_UNDERWRITER_API_KEYrequiredsecretUnderwriter API key beginning with kita_uw_. Required for every underwriter_* tool.
KITA_UNDERWRITER_API_BASEurlOptional. Defaults to https://underwriter.kita.ai/api/v1. Include /api/v1 when overriding.
KITA_API_KEYsecretOptional. Enables the same MCP server’s Kita Capture tools.

Use server-side keys only

MCP execution tools can read and write borrower data. Register the server only in trusted developer environments and keep 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_xxxxxxxxxxxx

Keep keys server-side

A key carries full read/write access to your organization’s borrower data. Never embed it in a browser, mobile app, or public repository. Store it in your secrets manager and call Kita from your backend.

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.

FieldTypeDescription
readscopeEvery GET: list/read applications, credit picture, memo & memo status, documents, conversation, export.
writescopeEvery mutation: intake, create application, upload documents, generate memo, PATCH/DELETE.

Least privilege

Give a reporting/BI integration a read-only key and a core-banking sync a write (or read & write) key. You can revoke either independently.

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

FieldTypeDescription
200 / 201okSuccess. 201 on resource creation.
400clientMalformed body, missing required field, or bad parameter.
401authMissing, malformed, unknown, or revoked API key.
403authValid key, but it lacks the scope this endpoint requires.
404clientApplication or document not found in your org (cross-org access is indistinguishable from not-found).
500 / 502serverServer-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:

  1. Intake. POST /intake with the borrower, loan terms, and document files. Pass external_ref (your LOS/loan id) so retries are idempotent.
  2. Wait for extraction. Each document moves awaiting → processing → verified (or low_confidence). Poll GET …/documents or a single GET …/documents/{docId}.
  3. Read the assessment. GET …/credit returns the spread (adjusted EBITDA/DSCR/ratios with per-metric provenance), the normalization adjustments, and the policy decision. It tightens as more documents land.
  4. Generate the memo. POST …/memo synthesizes the cited credit memo, then poll GET …/memo/status until synthesis settles and read it with GET …/memo.
  5. Sync back. Pull …/conversation for the borrower thread and …/export for the extraction workbook into your system of record.

Human-in-the-loop by design

Routing defaults to human review. The API surfaces the policy engine’s recommendation and every rule’s pass/fail, but the credit officer makes the final call.

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 …/documents or /intake, poll a document until status leaves awaiting/processing.
  • Memo: after POST …/memo, poll GET …/memo/status until synthesis_in_progress is false and is_stale is false.

Use an exponential backoff starting around 2–3 seconds. There are no webhooks yet; polling is the supported pattern.

POST/intakewrite

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

FieldTypeDescription
business_namerequiredstringBorrower business / legal name.
borrower_emailrequiredstringBorrower contact email.
borrower_phonestringBorrower contact phone.
loan_typestringe.g. "SBA 7(a)", "Term loan".
loan_amountnumberRequested amount in dollars.
application_contextstringLoan purpose / free-text context for the file.
external_refstringYour unique loan/record id. Enables idempotent retries.
send_outreachbooleanDefault 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" }
    ]
  }
}
POST/applicationswrite

Create 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)

FieldTypeDescription
business_namerequiredstringBorrower business / legal name.
borrower_emailrequiredstringBorrower contact email.
borrower_phonestringBorrower contact phone.
loan_typestringLoan product.
loan_amountnumberRequested amount.
application_contextstringLoan purpose / context.
send_outreachbooleanDefault 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
  }'
GET/applicationsread

List applications in your org, most-recently-active first.

query parameters

FieldTypeDescription
statusstringFilter to one status value (see Status reference).
limitnumberDefault 50, clamped to 1–200.
offsetnumberDefault 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 }
}
GET/applications/{id}read

Fetch 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"
PATCH/applications/{id}write

Update mutable fields. All fields optional; omit to leave unchanged.

body (application/json)

FieldTypeDescription
business_namestringNon-empty. Applies only if the app has a linked borrower.
loan_typestringNon-empty.
loan_amountnumberMust be > 0.
application_contextstring | nullLoan purpose / context.
statusstringOne 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" }'
DELETE/applications/{id}write

Permanently 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"
POST/applications/{id}/documentswrite

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). 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

A single-file upload returns one document object; multiple files return an array under data. Poll the document(s) for pipeline progress.
GET/applications/{id}/documentsread

List 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"
    }
  ]
}
GET/applications/{id}/documents/{docId}read

Fetch 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)

FieldTypeDescription
kita_rawobject | nullFull grounded extraction result (shape varies by document type).
recommendationsarray | nullSystem/AI recommendations for the document.
inconsistenciesarray | nullDetected inconsistencies / fraud signals.

Request

curl https://underwriter.kita.ai/api/v1/applications/APP-1042/documents/9f2... \
  -H "Authorization: ApiKey kita_uw_xxxxxxxxxxxx"
DELETE/applications/{id}/documents/{docId}write

Delete 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"
GET/applications/{id}/creditread

The deterministic credit picture: the numbers the dashboard shows, minus the memo narrative. Three layers, each null until computed (the assessment is continuous):

FieldTypeDescription
spreadobject | nullAdjusted 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.
adjustmentsarrayNormalization line items applied to this borrower's own reported numbers (owner-comp add-backs, non-recurring items), each tagged ai or lo.
decisionobject | nullPolicy 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 } ]
    }
  }
}
POST/applications/{id}/memowrite

Synthesize 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"
GET/applications/{id}/memoread

Read 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" }
    ]
  }
}
GET/applications/{id}/memo/statusread

Freshness + synthesis progress. Poll after POST …/memo until synthesis_in_progress is false.

FieldTypeDescription
synthesis_in_progressbooleanA synthesis run is currently underway.
is_stalebooleanMemo is out of date vs. current docs/messages.
stale_reasonsstring[]Why it's stale.
new_doc_countnumberDocuments added since last synthesis.
new_message_countnumberMessages added since last synthesis.
last_run_statusstring | nullcompleted | failed.
last_synthesis_atstring | nullTimestamp of last successful synthesis.
GET/applications/{id}/conversationread

The 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"
GET/applications/{id}/exportread

Download 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:

FieldTypeDescription
submittedstatusCreated; intake received.
awaiting_docsstatusWaiting on borrower documents.
underwritingstatusAssessment in progress as docs process.
question_raisedstatusAn open question / escalation on the file.
memo_draftedstatusA credit memo has been synthesized.
approvedstatusMarked approved by the lender.
declinedstatusMarked declined by the lender.

Document status: the extraction state of each file:

FieldTypeDescription
awaitingstatusQueued; pipeline not started.
processingstatusPrescreen / parse / extraction running.
verifiedstatusExtracted and grounded with high confidence.
low_confidencestatusExtracted but flagged for review.
missingstatusA required document not yet provided.

Need help?

Reach out to your Kita contact for higher-volume access, custom deployments (VPC / on-prem), or provider allowlisting.