Skip to content
Lender OS
Talk to the team

API overview

A small API with strong guarantees

Two resources — consents and data sessions — eight webhook events, one error envelope. Everything below is normative in the OpenAPI reference, which is generated from the same spec that drives the platform.

Authentication & environments

Every request carries Authorization: Bearer <secret key>. Keys are per tenant and per environment, and are server-side only — they must never reach a browser, mobile app or borrower device. Two keys can be active at once, so rotation is zero-downtime: create the new key, deploy, revoke the old.

Environment Base URL Key prefix livemode
Sandbox https://api.sandbox.lenderos.in/v1 los_sk_test_ false
Production https://api.lenderos.in/v1 los_sk_live_ true

Sandbox and production are fully separate deployments — separate keys, connector credentials and data plane. A key presented to the wrong environment fails with ENVIRONMENT_MISMATCH; keys never cross environments. The sandbox serves mock FI data and needs no AA contract, so integration starts before commercial paperwork finishes — promotion to production is a credential change, never a code change. Optional per-tenant hardening includes a source-IP allowlist and mutual TLS on the single endpoint where financial data crosses.

Consent lifecycle

A consent is created with canonical parameters — purpose, FI types, mode, validity window, data range, fetch frequency, data-life — either inline or via a saved template that expands to exactly the same document. Lender OS validates the terms against Fair Use bounds, selects a rail from your contracted pool, and registers the consent. Routing is decided at creation and is final for that consent; if no rail can express the terms, the call fails with a typed routing error listing which rails were excluded and why.

The borrower approves on their AA’s own journey via a short-lived URL you mint at redirect time. The redirect back to your return_url is advisory — it drives UX and funnel metrics, never state. Authoritative transitions come only from verified rail notifications, authoritative polls, and the poll-confirmed expiry clock.

PENDING
Created and registered with the selected rail; awaiting the borrower.
ACTIVE
Borrower approved and the rail confirmed. Data sessions may be opened.
PAUSED
Suspended by the borrower at the AA. Resumes as ACTIVE; fetches are refused meanwhile.
REJECTED
Terminal. Borrower declined, or the journey/rail failed — with a machine-readable reason.
REVOKED
Terminal. Withdrawn by the borrower at the AA, or by you via the API.
EXPIRED
Terminal. The consent’s validity window ended, confirmed against the rail.
FAILED
Terminal. Registration with the rail failed before the borrower ever saw a journey.

Data sessions

A data session is one acquisition attempt under an active consent. The requested window and the consent’s fetch frequency are enforced locally, before the rail sees the request — an out-of-bounds fetch fails immediately with a typed error instead of a downstream rejection. Scheduling stays yours: the orchestrator enforces that you may fetch, and you decide when.

On rails that push the first data drop automatically after activation, the session appears with origin: VENDOR_INITIATED so the fetch is visible, metered and evidenced like any other — and a matching request from you reuses it rather than paying for a duplicate fetch.

REQUESTED
Fetch validated against the consent’s terms and dispatched to the rail.
READY / FETCHED / DECRYPTED
The rail-side pipeline. On push-delivery rails these are recorded with coincident timestamps and fast_forwarded: true.
DELIVERED
Payload retrievable via the pull API. session.data_ready fires with the data URL.
PURGED
Data-life elapsed; data destroyed and attested. The API returns the attestation, not a 404.
FAILED
Terminal, only when every account failed. Any successful account makes it a delivery instead.

Financial information crosses exactly one endpoint: GET /data-sessions/{id}/data. It returns a normalized envelope wrapping the raw, untransformed ReBIT FI JSON per account — Lender OS normalizes the wrapper, never the data, so your parsers read ReBIT schemas rather than proprietary inventions. Partial results are results: accounts that failed carry typed failure reasons alongside the accounts that delivered.

Webhooks & signatures

Every lifecycle transition is delivered as an HTTPS POST to your endpoint. Events carry references and the full resource — never financial information, which keeps borrower data off retry queues and out of your webhook logs. Delivery is at-least-once with a jittered 24-hour backoff schedule; deduplicate on the event id and order by the gapless per-entity sequence.

verifying LenderOS-Signature
# LenderOS-Signature header on every webhook delivery
LenderOS-Signature: t=1752311642,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

# Verify: HMAC-SHA256 over "<t>.<raw body bytes>"
expected = HMAC_SHA256(endpoint_secret, t + "." + raw_body)
# 1. constant-time compare against each v1 value (several during rotation)
# 2. reject if |now - t| > 300 seconds (replay protection)
# 3. deduplicate on the event id, order by per-entity sequence
consent.activated
Borrower approved and the rail confirmed; the consent is usable. Also signals a resume from PAUSED.
consent.rejected
Terminal decline or journey failure, with reasons and serviceability.
consent.revoked
Revoked by the borrower at the AA, or by you. Flag the credit file.
consent.paused
Borrower paused data acquisition at the AA. Not terminal.
consent.expired
The consent’s validity window ended (rail-confirmed).
session.data_ready
FI is retrievable at the referenced data URL. Completeness may be PARTIAL.
session.data_delivered
You pulled every available account — the custody-handoff record.
session.failed
Every account in the session terminally failed, with a typed reason.

Custody: who decrypts, who holds, who purges

AA payloads are end-to-end encrypted from the FIP. Every consent and session reports custody.decryption_performed_by:

LENDEROS

The key exchange and decryption happened inside your tenant’s isolated key boundary at Lender OS. Keys are per tenant; no cross-tenant material exists.

RAIL

The contracted rail delivered already-decrypted data — a property of the rail your consent was routed to, reported as-is.

Retention is governed by the consent’s data-life: every session carries a purge_due_at, purge is executed and attested rather than merely scheduled, and after purge the data endpoint returns the attestation — evidence outlives the data. Each fetch also records a payload hash and, on your first successful retrieval, a custody-handoff event.

One error envelope, no upstream leakage

Every non-2xx response is the same envelope: a broad type you can switch on, a stable machine code, a retryable flag, a request_id for support, and structured details. Upstream AA error codes are mapped into this taxonomy inside each connector and never surface — the only upstream handle you ever see is an opaque incident reference that Lender OS support can resolve.

a consent-enforcement error
{
  "error": {
    "type": "consent_enforcement_error",
    "code": "FETCH_FREQUENCY_EXCEEDED",
    "message": "This consent permits 2 fetches per MONTH; 2 have been used.",
    "retryable": true,
    "request_id": "req_01J8ZJ",
    "details": {
      "frequency": { "unit": "MONTH", "value": 2 },
      "used_in_window": 2,
      "window_resets_at": "2026-08-01T00:00:00Z"
    }
  }
}

Mutating calls are idempotent: Idempotency-Key is required on consent and data-session creation, replays return the original response, and conflicting reuse is a typed 409. Versioning is additive within /v1; clients must tolerate unknown enum members and response fields, and breaking changes ship as a new major version with at least 12 months of overlap.

Ready to go deeper?

The full interactive reference — every endpoint, schema, webhook payload and error code — is rendered live from the OpenAPI spec.