Skip to main content

Before You Start

You will need:
  • Sandbox API credentials: A client_id and client_secret provided by your Lead Technical Account Manager during kickoff.
  • curl: Installed on macOS by default; curl --version should print something. Any HTTP client works.
  • A Funding FBO provisioned for your program: Lead opens this during onboarding. The Funding API moves money into your Funding FBO when the Funding object posts.
  • Optional: jq— for cleaner JSON output. Pipe responses through | jq . if you have it installed.
Sandbox OnlyEvery example uses api.sandbox.lead.bank. The same calls work against api.lead.bank in production once your program is approved for go-live, but you should never run this guide against production credentials.
If you have not already done Make Your First Payment API Call, start there — it covers authentication, idempotency, and the Entity API in more depth.

Steps

25 minutes We’ll walk through originating a simple $5,000 personal term loan to an individual borrower, repaid monthly over 24 months.
Same OAuth2 client credentials flow as the Payment Quickstart. Exchange your client_id and client_secret for a short-lived access token and pass it as a Bearer header on every subsequent request.
curl -X POST https://api.sandbox.lead.bank/v1/oauth/token \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "scope": "entity/read_write application/read_write account/read_write subledger_balance/read_write funding/read_write"
  }'
Save the access_token value as a shell variable:
export LEAD_TOKEN="eyJhbGciOiJSUzI1NiIs..."
ScopesLending workflows need broader scopes than payments. The scope list above covers everything in this guide.
Every loan starts with an Entity for the borrower. We’ll create an Individual with placeholder KYC and OFAC results so the sandbox accepts it.
curl -X POST https://api.sandbox.lead.bank/v0/entities \
  -H "Authorization: Bearer $LEAD_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-loan-entity-001' \
  -d '{
    "type": "individual",
    "client_customer_id": "qs-borrower-001",
    "intended_roles": ["account_holder"],
    "risk_score": "low",
    "individual_details": {
      "first_name": "Ada",
      "last_name": "Lovelace",
      "date_of_birth": "1990-12-10",
      "identification_details": {
        "type": "us_entity",
        "tax_identification": { "type": "ssn", "value": "123456789" }
      },
      "contact_methods": [
        { "type": "email", "email": "ada@example.com" }
      ],
      "address_details": {
        "physical_address": {
          "line_1": "123 Analytical Engine Way",
          "city": "Brooklyn",
          "state": "NY",
          "postal_code": "11201",
          "country": "US"
        }
      }
    },
    "kyc_details": { "result": "passed", "screened_at": "2026-01-15T10:00:00Z" },
    "ofac_details": { "result": "passed", "screened_at": "2026-01-15T10:00:00Z" }
  }'
Save the returned id (starts with entity_):
export LEAD_ENTITY_ID="entity_abc123..."
The Application is your record of the underwriting decision. Lead relies on this data to fulfill critical compliance requirements — including Fair Lending audits and adverse action tracking under Regulation B (ECOA) and Regulation V (FCRA), Military Lending Act (MLA) status verification, and BSA/AML recordkeeping. Applications are always created in a terminal status approved, declined, or canceled). Lead does not track in-flight applications; you finalize the underwriting decision on your side, then create the API object when you have a definitive result.For our approved $5,000 loan:
curl -X POST https://api.sandbox.lead.bank/v0/applications \
  -H "Authorization: Bearer $LEAD_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-loan-app-001' \
  -d '{
    "client_application_id": "QS-LOAN-001",
    "status": "approved",
    "entities": {
      "account_holders": ["'$LEAD_ENTITY_ID'"]
    },
    "details": {
      "product_name": "Personal Term Loan",
      "credit": {
        "is_secured": false,
        "is_mla": false,
        "currency": "USD",
        "underwriting_grade": "A",
        "limit": 500000,
        "max_limit": 500000,
        "report": {
          "score": 760,
          "pulled_at": "2026-04-01T09:30:00Z",
          "source": "experian"
        }
      }
    },
    "decision": {
      "decided_at": "2026-04-01T10:00:00Z",
      "reason": "Approved based on credit profile and debt-to-income ratio."
    },
    "documents": [
      {
        "document_id": "credit-pull-consent-ada-20260401",
        "type": "credit_pull_consent",
        "consented_at": "2026-04-01T09:25:00Z"
      }
    ]
  }'
Save the returned id (starts with application_):
export LEAD_APPLICATION_ID="application_xyz789..."
DocumentsDocument IDs reference filenames in your /documents SFTP directory. For this quickstart you can use any string — sandbox does not verify the file exists. In production, the file must be delivered to SFTP before referencing it.
The Account is the legal account the borrower holds with Lead. For credit products, the Account is required and must reference an application_id. Capabilities tell Lead what type(s) of Balances the Account will hold — credit_with_underwriting for products requiring a credit Balance with an underwriting component (e.g., a loan with an application) and credit_without_underwriting for products requiring a credit Balance but do not have an underwriting component (e.g., courtesy overdraft).
curl -X POST https://api.sandbox.lead.bank/v0/accounts \
  -H "Authorization: Bearer $LEAD_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-loan-account-001' \
  -d '{
    "application_id": "'$LEAD_APPLICATION_ID'",
    "capabilities": ["credit_with_underwriting"],
    "entities": {
      "account_holders": ["'$LEAD_ENTITY_ID'"]
    },
    "details": {
      "product_name": "Personal Term Loan",
      "credit": {
        "is_secured": false,
        "is_mla": false,
        "underwriting_grade": "A",
        "currency": "USD",
        "available_credit": 500000,
        "limit": 500000,
        "max_limit": 500000
      }
    },
    "documents": [
      {
        "document_id": "loan-agreement-ada-20260401",
        "type": "loan_agreement",
        "displayed_at": "2026-04-01T10:05:00Z"
      }
    ]
  }'
Save the returned id (starts with account_):
export LEAD_ACCOUNT_ID="account_def456..."
This is where the loan actually comes into existence. The Subledger Balance defines all the terms — amount, maturity, repayment schedule, interest — and is what Lead’s ledger tracks throughout the life of the loan. When you create it, every outstanding amount (principal, interest, fees) is zero because no draw has happened yet.For a term loan, use structure: non_revolving — there’s a fixed loan amount and repayments do not restore available credit. Use structure: revolving instead if you’re opening a line of credit or credit card where the borrower can re-draw after repayment.
curl -X POST https://api.sandbox.lead.bank/v0/subledger_balances \
  -H "Authorization: Bearer $LEAD_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-loan-balance-001' \
  -d '{
    "account_id": "'$LEAD_ACCOUNT_ID'",
    "client_subledger_balance_name": "Personal Term Loan - Ada Lovelace",
    "currency_code": "USD",
    "type": "credit",
    "details": {
      "structure": "non_revolving",
      "is_interest_based": true,
      "interest_rate": "9.990000",
      "effective_apr": "10.250",
      "autopay_method": "ach",
      "expected_maturity_date": "2028-04-01",
      "max_funding_amount": 500000,
      "expected_finance_charge_amount": 53400,
      "term": { "value": 24, "unit": "months" },
      "repayment_details": [
        {
          "expected_payment_frequency": "monthly",
          "expected_number_of_payments": 24,
          "expected_first_payment_date": "2026-05-01",
          "expected_first_payment_amount": 23058,
          "expected_last_payment_date": "2028-04-01",
          "expected_last_payment_amount": 23058,
          "expected_payment_amount": 23058
        }
      ]
    }
  }'
Save the returned id (starts with subledger_balance_):
export LEAD_BALANCE_ID="subledger_balance_ghi789..."
Why Balance and Funding are DecoupledYou define loan terms once (Balance) but can draw funds against them many times (Funding). For a term loan this usually means one draw at origination, but for a line of credit you’ll create multiple Funding objects against the same Balance throughout its life.
The Funding object instructs Lead to move principal from a Lead GL into your Funding FBO. Once posted, you can disburse those funds externally via ACH, wire, or instant payment — but that’s a separate step on a payment rail.For this term loan, the borrower is taking the full $5,000 as a single draw. There is no merchant origination fee (this isn’t a BNPL product) and nothing is withheld for later disbursement.
curl -X POST https://api.sandbox.lead.bank/v0/fundings \
  -H "Authorization: Bearer $LEAD_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: quickstart-loan-funding-001' \
  -d '{
    "subledger_balance_id": "'$LEAD_BALANCE_ID'",
    "currency_code": "USD",
    "principal": {
      "external_disbursement_amount": 500000,
      "withheld_amount": 0,
      "merchant_origination_fee_amount": 0
    },
    "metadata": {
      "client_draw_id": "QS-DRAW-001"
    }
  }'
The response returns an id that starts with funding_ and an initial status of processing. Save it:
export LEAD_FUNDING_ID="funding_jkl012..."
Principal ComponentsFor a simple personal term loan, the entire principal goes to external_disbursement_amount. The other two fields cover other use cases: withheld_amount is for funds you’ll disburse later and merchant_origination_fee_amount is for merchant fees (typically in BNPL loans).
Funding objects move asynchronously from processing to posted (or rejected). Poll the Funding endpoint — or listen for the funding.posted webhook event — to confirm.
curl https://api.sandbox.lead.bank/v0/fundings/$LEAD_FUNDING_ID \
  -H "Authorization: Bearer $LEAD_TOKEN"
When status flips to posted, the $5,000 is sitting in your Funding FBO and ready to be disbursed externally.
SandboxFunding does not auto-advance in sandbox. Use POST /v1/simulations/advance_funding to move the Funding from processing → posted (or processing → rejected) so you can test your webhook handlers and downstream disbursement logic.

What’s Next

  • Disburse Funds Externally Now that $5,000 is in your Funding FBO, send it to the borrower via ACH, wire, or instant payment.
  • Webhooks Listen for funding.posted and funding.rejected instead of polling. Configure your endpoint per Webhooks & Events.
  • Service the Loan Report ongoing activity — interest accrual, principal repayments, fees — against the Subledger Balance via the Balance and Transaction files. See Balance Reconciliation.
  • Multi-Draw Use Cases For multi-draw term loans or lines of credit, create additional Funding objects against the same Subledger Balance over its life. The sum of all principal.* amounts across posted and processing Fundings cannot exceed the Balance’s max_funding_amount (for non_revolving) or the available credit (for revolving).
  • Closing the Loan Once the balance is paid off and the loan is in a terminal status, update the Subledger Balance to closed via the Balance file. Closure requires all outstanding amounts to be zero.

Troubleshooting

Error

Potential Fix

401 Unauthorized
  • Token expired (24h default). Run Step 1 again.
  • Authorization header malformed. Should be exactly Authorization: Bearer <token> — note the space and capitalization.
422 Unprocessable Entity on Create Application
  • details.decision.adverse_action_notice missing for a declined status. Required when the decision is declined.
  • credit.limit exceeds credit.max_limit. The assigned limit cannot be higher than what underwriting approved.
  • client_application_id starts with the reserved prefix application_. Pick a different prefix on your side.
422 Unprocessable Entity on Create Entity
  • application_id missing. Required for credit accounts. The Account must reference the Application that authorized the credit.
  • details.credit.available_credit greater than details.credit.limit. Available credit cannot exceed the current limit.
  • Missing authorized_signers when account holders are commercial. Required for business or sole prop account holders.
422 Unprocessable Entity on Create Subledger Balance
  • type is anything other than credit. Lending workflows require credit-type balances; deposit is not valid here.
  • details.structure missing or invalid. Must be non_revolving for term loans or revolving for lines of credit and credit cards.
  • details.expected_maturity_date, details.max_funding_amount, details.term, or details.expected_finance_charge_amount missing for a non_revolving structure. All four are required.
  • details.is_interest_based missing. Required for credit-type Subledger Balances.
422 Unprocessable Entity on Create Funding
  • Sum of external_disbursement_amount + withheld_amount is 0. At least one of the two must be greater than 0.
  • merchant_origination_fee_amount is greater than or equal to external_disbursement_amount + withheld_amount. The merchant fee must be less than total principal.
  • Total funding across all posted and processing Fundings against this Subledger Balance exceeds max_funding_amount. Only relevant for non_revolving structures.
409 Conflict
  • You reused an Idempotency-Key with a different request body.
  • Either send the same body (and you’ll get the original response back), or pick a new key.