Overview
The Case API is the primary integration surface for lending clients running underwriting workflows on KoraConnect. It models each borrower interaction as a case that moves through a configurable workflow of stages, with modules producing the underlying financial signals. Two modules are available today, with more on the way:
| Module | What it produces |
|---|---|
cash_flow_analysis | Structured income, monthly inflow/outflow, and a top-level risk insight from bank transactions. Sourced via automatic bank connection in the hosted flow, or bank statements uploaded directly. |
paystub | Employment and income verification from uploaded paystubs, with built-in document fraud detection and identity matching. |
Every account has a default workflow applied automatically on case creation: you don't pass a workflow_id unless your account has multiple workflows and you need to pick one per case. Which modules run, in which stages, is configured server-side during onboarding.
Base URLs
| Environment | Base URL | IP allowlist |
|---|---|---|
| Sandbox | api.sandbox.koramoney.com | Not required, open from any IP |
| Production | api.koramoney.com | Required, egress IPs whitelisted during onboarding |
How a case flows end to end
Three actors are involved in every case: the borrower (your customer), KoraConnect, and your integration. The production-recommended pattern is borrower-driven:
- Your system calls
POST /api/caseswith borrower info and your ownreference_id. The response carries acase_idand a hosted-flow link (url/short_url). - You forward the link to the borrower by SMS, email or in-app, or call
POST .../send-smsand we text it for you. The link's token expires 30 days after issue. - The borrower opens the link and connects their bank directly: live transaction data, straight from the institution. Statement upload is available as a fallback; either way, data goes directly to KoraConnect and never passes through your servers.
- Analysis runs, typically seconds to about a minute, and the
MODULE_COMPLETEDwebhook delivers theresult_id. - You fetch the result with
GET .../modules/{module_type}/analysis/{result_id}and make your underwriting decision.
Multi-stage workflows repeat the middle of that loop per stage, with STAGE_ADVANCED events in between. You can also drive the flow without the hosted experience by uploading documents via the API and triggering the module run yourself. The quickstart below shows it as path 2b, since it's easier to test without a real borrower.
Quickstart
End to end against sandbox in 5 to 10 minutes. You'll need a sandbox API key from the sandbox dashboard and any HTTP client that can send Bearer auth and multipart uploads.
1. Create a case
Send the borrower's identifying information and your own reference_id so webhook events correlate back to your system.
curl -X POST https://api.sandbox.koramoney.com/api/cases \
-H "Authorization: Bearer $KORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jane",
"last_name": "Doe",
"phone_number": "+15551234567",
"reference_id": "loan_app_42",
"module_data": {
"cash_flow_analysis": { "products": ["DOC_FRAUD"] }
}
}'{
"case_id": "00000000-0000-0000-0000-000000000001",
"url": "https://connect.koramoney.com/verify?token=xxx",
"short_url": "https://koramoney.com/v/xxxxxxxxxxxxxxxx"
}Save case_id and short_url: the borrower link is only returned on creation.
2a. The borrower connects their bank
The production path. The borrower opens the link from step 1 and authenticates with their institution inside the hosted flow. Transaction data flows in live and the analysis runs on its own: no statements to collect, no files passing through your servers, nothing to trigger yourself. Your next touch is the webhook in step 3.
2b. Or upload statements you already collected and run the analysis
The quickstart demo path, no borrower needed: upload the statement PDFs via the API, then trigger the run yourself.
curl -X POST ".../api/cases/$CASE_ID/documents" \ -H "Authorization: Bearer $KORA_API_KEY" \ -F "files=@./statement.pdf" \ -F "module_type=cash_flow_analysis" \ -F "document_category=bank_statement"
curl -X POST ".../api/cases/$CASE_ID/modules/cash_flow_analysis/run" \
-H "Authorization: Bearer $KORA_API_KEY"
# → { "execution_id": "…", "status": "ANALYZING", "message": "Analysis started" }3. Wait for the webhook
Watch for MODULE_COMPLETED with module_type: "CASH_FLOW_ANALYSIS". The payload carries the result_id for the final fetch.
{
"webhook_type": "MODULE",
"event": "MODULE_COMPLETED",
"case_id": "00000000-0000-0000-0000-000000000001",
"module_type": "CASH_FLOW_ANALYSIS",
"instance_id": "00000000-0000-0000-0000-000000000002",
"reference_id": "loan_app_42",
"environment": "production"
}4. Fetch the result
curl ".../api/cases/$CASE_ID/modules/cash_flow_analysis/analysis/$RESULT_ID" \ -H "Authorization: Bearer $KORA_API_KEY"
{
"module_type": "cash_flow_analysis",
"instance_id": "00000000-0000-0000-0000-000000000002",
"result_id": "00000000-0000-0000-0000-000000000003",
"analyzed_at": "2026-04-27T18:00:30.123Z",
"data": { /* full analysis payload, documented in the full reference */ }
}The data field contains the structured analysis: recurring income, monthly inflow/outflow, risk insights, and any flags that triggered. Its exact shape is pinned by your Kora-Api-Version and documented field by field in the full reference.
Authentication
Every request carries a Bearer API key. Requests without a valid key return 401 Unauthorized; a valid key without access to the resource returns 403 Forbidden.
Authorization: Bearer $KORA_API_KEY
Keys live in the KoraConnect dashboard, under your integration settings. Sandbox and production are completely separate accounts with different dashboards, different keys and different data, and the keys are visually identical, so label your environment variables clearly. Each key is bound to a client type: LENDER keys call the Case API, DEALER keys call the Dealer API, never both.
Key rotation runs the new key alongside the old with a cutover window (typically 7 days). Suspected compromise gets immediate revocation. Contact support.
API versioning
Header-based date versioning. Pin your integration to a dated version and that version's request and response shapes keep working, even after breaking changes ship for new clients.
Kora-Api-Version: 2025-09-30
| Version | Status |
|---|---|
2025-09-30 | Current, recommended for new integrations |
2025-07-31 | Supported |
2025-01-31 | Supported, initial stable release |
Omitting the header uses your account's pinned default; best practice is to always send it explicitly. Versions bump only for backward-incompatible changes (renamed or removed fields, type changes, new required fields). New endpoints, optional fields and additive response fields don't bump the version. Deprecations get at least 6 months of notice; no version has been sunset yet.
Webhooks
Webhooks are the production pattern for knowing when work finishes: push, not poll. Configure your endpoint and read your signing secret in the dashboard.
| Family | Events |
|---|---|
CASE | CASE_CREATED, STAGE_ADVANCED, CASE_CANCELLED |
MODULE | MODULE_COMPLETED, MODULE_INSUFFICIENT, MODULE_ERROR, MODULE_DOCUMENT_UPLOADED, MODULE_CONNECTED |
Signature verification
Every delivery carries an X-Signature header: sha256=…, an HMAC-SHA256 over the raw request body keyed with your per-environment signing secret. Compute over the exact bytes received (don't parse-then-restringify) and compare in constant time:
import crypto from "crypto";
function verifyWebhookSignature(rawBody, header, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
if (header.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}Retries, ordering, duplicates
Success is any 2xx in under 10 seconds. Failures retry twice at 10-minute intervals, then the event is dropped, so consume webhooks into a durable queue on your side promptly. Events are not strictly ordered and can occasionally deliver twice, so handlers should be idempotent and read canonical state via GET /api/cases/{case_id} when in doubt.
Errors
Validation failures (400) name the exact field and problem:
{
"message": "Validation failed",
"errors": [
{ "field": "phone_number", "error": "Phone number must be in E.164 format" },
{ "field": "address.postal_code", "error": "Postal code must be exactly 5 digits" }
]
}Domain errors carry a human-readable message only. There is no published error-code enum, so branch on HTTP status, not message text. Validation errors are deterministic; don't retry them.
| Status | Example | Cause |
|---|---|---|
404 | "Case not found" | case_id doesn’t exist on this account |
404 | "Module not configured for this case" | Workflow doesn’t include the module, response lists available_modules |
409 | "Module is already running" | Run called during a transitional state |
Environments
| Sandbox | Production | |
|---|---|---|
| Dashboard | dashboard.sandbox.koramoney.com | dashboard.koramoney.com |
| IP allowlist | No, open from any IP | Yes, required |
| Cost | Free, provisioned on request | Contracted |
| Credentials | Separate account, separate keys | Separate account, separate keys |
Finish your integration in sandbox, then onboard your production egress IPs as the last step before live traffic.
Dealer API
Dealer integrations use a parallel surface under /api/dealer/* with the same conventions: cases, module runs, verification results, and an income verification report PDF (GET /api/dealer/cases/{case_id}/report/pdf) for the deal jacket. Dealer keys and lender keys are separate, so if you need both surfaces you'll receive separate credentials.
The full reference
The complete reference documents every request, response and analysis data attribute, including how the result schema differs across Kora-Api-Version releases. We keep that level of detail off the open web deliberately.
| You are… | How to get in |
|---|---|
| A customer or sandbox user | Sign in to the dashboard, where the full reference is linked next to your API keys and webhook settings. |
| New to KoraConnect | Book a demo and we'll provision a free sandbox with dashboard access, API keys and the full reference included. |
Questions? Reach your account manager or support@koramoney.com.