On this page

Start
  • Overview
  • Get access
  • Base URL & versioning
  • Authentication
  • Live vs test
Platform
  • Idempotency
  • Rate limits
  • Errors
  • Pagination
Endpoints
  • Ping
  • Create a deal
  • Request body
  • Lendflow-shaped JSON
  • List deals
  • Get a deal
  • Upload documents
Ops
  • Webhooks
  • IP allowlists
  • Scopes
  • Security
  • Code examples
  • Changelog
  • Support

Documentation

Partner API

Referral partners send funding deals to Loanable over HTTPS with an API key — the same idea as posting a file to a lender API. Version 1.0. Access is issued by Loanable on request, not self-serve.

Open Partner Portal OpenAPI 3.0 spec Request API access

Docs updated: August 26, 2026 · API version 1.0.0

This is a server-to-server API. Never put a live or test key in a browser, mobile app, Postman public workspace, or git. Loanable stores only a hash of the secret. If a key leaks, revoke it in the Partner Portal and ask us to issue a new one.

Overview

The Partner API lets an approved referral partner create a funding application in Loanable, attach supporting documents, and read status for files credited to their partner code. It is the inbound equivalent of Loanable posting a deal to a lender API.

Typical integration:

  1. Loanable enables API access on your partner account and issues a test key.
  2. You call GET /ping, then POST /deals with sandbox data.
  3. You attach bank statements / IDs with POST /deals/{id}/documents.
  4. Optionally you register an HTTPS webhook to receive deal.created, deal.updated, and deal.document_added.
  5. When the mapping is correct, Loanable issues a live key. Live posts require applicant consents.

Machine-readable spec: GET /api/v1/partner/openapi.json (no auth). Human docs stay on this page.

Get access

API access is not on by default. A signed-in partner cannot mint keys. Email apply@loanableusa.com or message Loanable from the Partner Portal and ask for API access.

  1. We enable the API on your specific partner account.
  2. We issue a test key first. The secret is shown once — save it in your secret manager.
  3. Optionally we lock the key to your office or server IPs.
  4. When you are ready for production, we issue a live key.

Keys look like lnb_test_… or lnb_live_…. After access is enabled, signed-in partners can revoke a leaked key from Resources → Partner API. They cannot create a replacement — Loanable issues the next secret.

Maximum 5 active keys per environment (test and live). Revoke one before asking for another.

Base URL & versioning

https://www.loanableusa.com/api/v1/partner

All paths in this guide are relative to that base. The current version is v1. Breaking changes ship under a new prefix (/api/v2/partner), not by silently changing v1. Additive fields on responses are not breaking.

TLS 1.2+ is required. HTTP is not accepted. There is no sandbox hostname — environment is selected by which key you send.

Unauthenticated index:

GET https://www.loanableusa.com/api/v1/partner

{
  "name": "Loanable Partner API",
  "version": "1.0.0",
  "docs": "https://www.loanableusa.com/partner-api",
  "openapi": "https://www.loanableusa.com/api/v1/partner/openapi.json"
}

Authentication

Every authenticated request needs the secret in one of these headers:

Authorization: Bearer lnb_live_YOUR_SECRET
Content-Type: application/json

or

X-Api-Key: lnb_live_YOUR_SECRET
Content-Type: application/json

Do not send both. Query-string keys are not supported and never will be — they leak in logs and Referer headers.

Missing, unknown, or revoked keys return 401. A well-formed key that cannot be used (API not enabled, IP not on the allowlist, partner inactive) returns 403 so you can tell “wrong secret” from “right secret, blocked.”

Live vs test

Test keys (lnb_test_)

Create sandbox deals (source: partner_api_sandbox). They never go to lenders and are not counted as referrals. Consents are optional so you can wire the integration first. List/get only return sandbox deals.

Live keys (lnb_live_)

Create real files, email the applicant, credit your partner code, and can fire webhooks. consents.credit_auth and consents.terms are required. List/get never return sandbox deals.

Live keys can list every non-sandbox application credited to your referral code — including deals submitted through the Partner Portal or website with your code — not only source=partner_api. Filter with ?source=partner_api if you only want API posts.

Idempotency

Retries happen. Use both of these:

  • Idempotency-Key header (max 128 characters). Loanable stores the SHA-256 of the raw JSON for 24 hours. The same key + same body replays the original status and JSON. The same key + a different body returns 409 idempotency_mismatch.
  • external_id in the body (max 80 characters). Unique per partner and environment. A later create with the same id returns 200 with replayed: true instead of inserting a second file.

Generate a new Idempotency-Key per logical submit (UUID is fine). Do not reuse keys across different applicants.

Rate limits

BucketLimitKeyed by
Well-formed keys120 requests / 15 minutesConnecting server IP
Missing or garbage keys40 requests / 15 minutesConnecting server IP
POST /deals40 creates / hourAPI key
JSON body256 KBper request

Successful-looking keys are not starved by scanners hitting 401. GET /openapi.json and the unauthenticated index are not counted.

When you hit a limit you receive 429 rate_limited plus standard RateLimit-* headers (limit, remaining, reset). Include request_id if you write in to support.

Errors

All errors are JSON:

{
  "error": "Validation failed",
  "code": "validation_error",
  "request_id": "a1b2c3d4-…",
  "fields": [
    { "field": "owner.email", "message": "A valid owner email is required" }
  ]
}

fields is only present on validation failures. Always log request_id.

HTTPcodeWhen
401missing_keyNo Authorization / X-Api-Key
401invalid_keySecret does not match a stored hash
401revoked_keyKey was revoked
403api_disabledPartner does not have API access enabled
403ip_not_allowedConnecting IP is not on the key allowlist
403partner_inactivePartner account is not active
403partner_pendingPartner application is still under review
403insufficient_scopeKey is missing the required scope
409idempotency_mismatchIdempotency-Key reused with a different body
413payload_too_largeJSON over 256 KB
415unsupported_media_typePOST without application/json
422validation_errorRequired fields missing or invalid
429rate_limitedToo many requests
404not_foundUnknown path, or deal is not yours
400no_files / file_*Upload rejected (see documents)
500create_failed etc.Unexpected server error — retry with backoff + Idempotency-Key

A deal that is not credited to your partner code looks like not_found. We do not confirm that the id exists for someone else.

Pagination

GET /deals returns the newest deals first.

QueryDefaultMax
limit50100
offset010000
status—exact match, 50 chars
sourceall (live)partner_api or portal

The response includes count, limit, offset, and has_more. When has_more is true, request the next page with offset += limit.

Ping

GET /ping

Confirms the key, environment, and partner code. No scope required. Use this as a health check from your CRM.

curl https://www.loanableusa.com/api/v1/partner/ping \
  -H "Authorization: Bearer lnb_test_YOUR_SECRET"
{
  "ok": true,
  "environment": "test",
  "partner_code": "CP1234",
  "company": "Northstar Capital",
  "request_id": "…"
}

Create a deal

POST /deals scope deals:write

Creates an application credited to your partner code. Returns 201 on insert, 200 when external_id already exists.

curl -X POST https://www.loanableusa.com/api/v1/partner/deals \
  -H "Authorization: Bearer lnb_live_YOUR_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f3a1c02-88d1-4b0e-9c1a-2e7b4d6a1100" \
  -d '{
    "external_id": "crm-88421",
    "product_type": "working_capital",
    "business": {
      "name": "Acme Services",
      "legal_name": "Acme Services LLC",
      "entity_type": "LLC",
      "industry": "Retail",
      "years_in_business": "4",
      "ein": "12-3456789",
      "address": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701",
      "monthly_revenue": 45000
    },
    "owner": {
      "first_name": "Jane",
      "last_name": "Owner",
      "email": "jane@acme.com",
      "phone": "5125550100",
      "ssn": "123-45-6789",
      "dob": "1982-04-12",
      "ownership_percent": 100,
      "credit_score": "680"
    },
    "funding": {
      "amount": 75000,
      "purpose": "working capital",
      "urgency": "immediately"
    },
    "consents": { "credit_auth": true, "terms": true },
    "notes": "Existing customer for 3 years"
  }'

A successful create returns the public deal object (no SSN/EIN), plus application_id and tracking_code. Save id — you need it to attach documents and to poll status.

Live posts send applicant personal information, including SSN and EIN when you provide them. Those values are encrypted at rest and are never returned on GET, list, or webhooks. Use of the API is covered by your Referral Partner Agreement, the Data Processing Addendum, and our Privacy Policy and Terms of Service.

Request body reference

Three shapes are accepted. Unknown keys are dropped (they are not stored). SSN and EIN never go into unstructured additional_data.

1. Nested Loanable JSON (preferred)

FieldRequiredNotes
external_idRecommendedYour CRM id, max 80 chars, unique per partner + environment
product_typeNoSee product types below. Default general
notesNoMax 5,000 characters
business.nameYesDBA / operating name
business.legal_nameNo
business.entity_typeNoe.g. LLC, Corp, Sole Prop
business.industryNo
business.years_in_businessNoString is fine ("4")
business.einNo9 digits; encrypted; never returned
business.addressNoString or { line1, city, state, zip }
business.city / state / zipNoState max 50 chars
business.monthly_revenueNoNumber or "45,000"
owner.first_name / last_nameYes
owner.emailYesMust look like an email. Live deals email the applicant a signup link
owner.phoneYesAt least 10 digits
owner.ssnNo9 digits when provided; encrypted; never returned
owner.dobNoYYYY-MM-DD. Invalid dates are dropped (the deal still creates)
owner.ownership_percentNoDefaults to 100
owner.credit_scoreNoString, max 20
funding.amountYesPositive number. Personal-loan products are capped at $100,000
funding.purposeNoComma-separated values are split into a list
funding.urgencyNoe.g. immediately
consents.credit_authLive yesApplicant authorized a credit pull. This is not SMS consent.
consents.termsLive yesApplicant agreed to Loanable terms
consents.smsNoSet true only if the applicant checked a dedicated SMS opt-in (frequency, HELP, STOP disclosed; not required to apply). Loanable will not text them otherwise. consents.transactional is an alias.

Product types

Aliases collapse to these keys: general, working_capital, mca, term_loan, line_of_credit, equipment, sba, personal, personal_loan, commercial_re, real_estate, ironfund_re, bridge, fix_and_flip.

Examples: "Working Capital" → working_capital, "SBA 7(a)" → sba, "personal loan" → personal_loan.

2. Flat portal-style fields

If you already post businessName, firstName, lastName, email / clientEmail, phone / clientPhone, fundingAmount, ssn, ein, we map those the same way as nested JSON.

Lendflow-shaped JSON

If the body has personal[], business.business_name / business_legal_name, or funding.funding_amount_requested, we treat it as a Lendflow-style workflow payload and map:

InboundLoanable
business.business_dba / business_namebusiness.name
business.business_legal_namebusiness.legal_name
business.business_einbusiness.ein
business.business_addresses[0]address / city / state / zip
personal[0].personal_first_name etc.owner.*
personal[0].personal_ssn_itin.ssnowner.ssn
funding.funding_amount_requestedfunding.amount
external_id or client_tracking_tokenexternal_id

You still add consents for live posts. A Lendflow body without consents succeeds on a test key and returns 422 on a live key.

List deals

GET /deals scope deals:read
curl "https://www.loanableusa.com/api/v1/partner/deals?limit=50&offset=0&source=partner_api" \
  -H "Authorization: Bearer lnb_live_YOUR_SECRET"
{
  "deals": [ { "id": "APP-…", "status": "pending", "external_id": "crm-88421", "sandbox": false } ],
  "count": 1,
  "limit": 50,
  "offset": 0,
  "has_more": false
}

List rows include contact fields (email/phone) for deals credited to you. They do not include SSN, EIN, offers, or file bytes.

Get a deal

GET /deals/{id} scope deals:read

Returns the public deal plus:

  • offers — anonymized (lender brand names are not exposed through this API)
  • documents — id, file name, type, mime, size, created_at. No bytes, no storage URLs

Common status values include pending, in_review, submitted, funded, and declined. Treat status as an opaque string and display it; new values can appear without a version bump.

Upload documents

POST /deals/{id}/documents scope documents:write

Multipart only. Repeat the files field (or documents) once per file. Max 10 files, 10 MB each.

curl -X POST https://www.loanableusa.com/api/v1/partner/deals/APP-…/documents \
  -H "Authorization: Bearer lnb_live_YOUR_SECRET" \
  -F "files=@bank-statement.pdf" \
  -F "files=@id.jpg"

We inspect magic bytes, not just the extension. Allowed: PDF, JPEG, PNG, TIFF, DOC, DOCX, XLS, XLSX, CSV, TXT.

Rejected with 400:

codeMeaning
no_filesNeither files nor documents was attached
empty_fileZero-byte upload
file_too_largeOver 10 MB
file_type_rejectedExtension not on the allowlist
extension_mismatchName says PDF, contents are not
executable_or_markupPE/ELF, HTML, SVG, script
archive_or_unknown_zipZip that is not a valid DOCX/XLSX
unrecognized_typeContents not an allowed document

You cannot download stored file bytes through the Partner API.

Webhooks

Optional. Set an HTTPS URL in the Partner Portal (or ask Loanable to set it). We POST signed JSON when a deal you own is created, its status changes, or documents are added.

EventWhen
deal.createdAfter a successful API create
deal.updatedWhen the application status (or related fields) change in Loanable
deal.document_addedAfter documents are stored on a deal you own

Headers

Content-Type: application/json
User-Agent: Loanable-PartnerWebhook/1.0
X-Loanable-Event: deal.updated
X-Loanable-Timestamp: 1710000000
X-Loanable-Signature: t=1710000000,v1=hex_hmac_sha256

Body

{
  "id": "evt_1710000000_dealupdated",
  "type": "deal.updated",
  "created": 1710000000,
  "data": {
    "deal": {
      "id": "APP-…",
      "tracking_code": "TC-…",
      "status": "funded",
      "business_name": "Acme Services LLC",
      "owner_name": "Jane Owner",
      "email": "jane@acme.com",
      "phone": "5125550100",
      "funding_amount": 75000,
      "external_id": "crm-88421",
      "sandbox": false
    }
  }
}

Verify the signature

Compute HMAC-SHA256(webhook_secret, timestamp + "." + raw_body) using the raw bytes of the JSON (do not re-serialize). Compare the hex digest to v1 with a constant-time equals. Reject the request if:

  • the header is missing or malformed
  • the timestamp is more than 5 minutes off your clock (replay window)
  • the hex does not match

Node.js:

const crypto = require('crypto');

function verifyLoanableWebhook(secret, signatureHeader, rawBody, now = Math.floor(Date.now() / 1000)) {
  const t = /(?:^|,)\s*t=(\d+)/.exec(signatureHeader);
  const v1 = /(?:^|,)\s*v1=([a-f0-9]+)/i.exec(signatureHeader);
  if (!t || !v1) return false;
  if (Math.abs(now - Number(t[1])) > 300) return false;
  const expected = crypto.createHmac('sha256', secret)
    .update(t[1] + '.' + rawBody, 'utf8')
    .digest('hex');
  const a = Buffer.from(expected, 'utf8');
  const b = Buffer.from(v1[1].toLowerCase(), 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python:

import hmac, hashlib, time, re

def verify_loanable_webhook(secret, header, raw_body, now=None):
    now = int(time.time() if now is None else now)
    t = re.search(r'(?:^|,)\s*t=(\d+)', header)
    v1 = re.search(r'(?:^|,)\s*v1=([a-fA-F0-9]+)', header)
    if not t or not v1:
        return False
    if abs(now - int(t.group(1))) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t.group(1)}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1.group(1).lower())

Delivery rules

  • Respond with 2xx within 8 seconds. We do not follow redirects.
  • Timeouts, 429, and 5xx are retried once after about 1.5 seconds (at-least-once). Your handler must be idempotent on data.deal.id + type.
  • 4xx (except 429) are not retried.
  • Webhook URLs must be HTTPS, must not include username/password, and must resolve only to public IPs. Localhost, RFC1918, link-local, .internal, and cloud metadata hosts are blocked (SSRF).
  • Webhooks stop if Loanable turns API access off for the partner.
  • The signing secret is stored encrypted. It is separate from the API key.

IP allowlists

Each key can be locked to a list of IPv4 addresses, IPv4 CIDRs (for example 203.0.113.0/24), and exact IPv6 addresses. CIDR matching is IPv4-only today.

The address we check is the TCP peer of the connection to Loanable — not X-Forwarded-For. If you call us through a NAT or reverse proxy, allowlist the egress IP that we actually see. An empty allowlist means any IP may use the key (still over TLS, still hashed).

Mismatch returns 403 ip_not_allowed.

Scopes

Keys issued today include:

ScopeEndpoints
deals:writePOST /deals
deals:readGET /deals, GET /deals/{id}
documents:writePOST /deals/{id}/documents

Missing scope → 403 insufficient_scope. GET /ping does not require a scope. A future key may be issued with a subset; do not assume every key can write.

Security

  • TLS only. API secrets hashed with HMAC-SHA256 and a server pepper (PARTNER_API_PEPPER). A database dump is not enough to impersonate a partner.
  • Webhook secrets encrypted at rest, signed outbound, DNS pinned to public IPs, no redirect following.
  • Optional per-key IP allowlist using the connecting IP.
  • JSON bodies capped at 256 KB before they are fully parsed at the application JSON budget.
  • Unknown JSON keys dropped. XSS sanitization is skipped on this API so business names like Smith & Jones LLC are not entity-encoded into the file.
  • File uploads scanned by content, not name. Archives, HTML, SVG, and executables are rejected.
  • Partners cannot list another partner’s deals. Cross-environment reads are blocked (test ≠ live).
  • Instant revoke in the portal or by Loanable admin. Disabling API access revokes keys and stops webhooks.

More on platform security: loanableusa.com/security.

Code examples

Node.js

const res = await fetch('https://www.loanableusa.com/api/v1/partner/deals', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.LOANABLE_API_KEY,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID()
  },
  body: JSON.stringify(payload)
});
if (!res.ok) {
  const err = await res.json();
  throw new Error(err.code + ' ' + err.request_id + ' ' + err.error);
}
const body = await res.json();
console.log(body.deal.id);

Python

import os, uuid, requests

r = requests.post(
    'https://www.loanableusa.com/api/v1/partner/deals',
    headers={
        'Authorization': 'Bearer ' + os.environ['LOANABLE_API_KEY'],
        'Idempotency-Key': str(uuid.uuid4()),
    },
    json=payload,
    timeout=30,
)
r.raise_for_status()
print(r.json()['deal']['id'])

PHP

$ch = curl_init('https://www.loanableusa.com/api/v1/partner/deals');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . getenv('LOANABLE_API_KEY'),
    'Content-Type: application/json',
    'Idempotency-Key: ' . bin2hex(random_bytes(16)),
  ],
  CURLOPT_POSTFIELDS => json_encode($payload),
  CURLOPT_RETURNTRANSFER => true,
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

There is no official SDK. Import the OpenAPI document into Insomnia, Postman, or your generator of choice. Keep the collection private.

Changelog

DateChange
2026-08-26Expanded public docs and OpenAPI. GET /deals returns offset, has_more, and optional source. Unauthenticated GET /api/v1/partner index. Webhook delivery retries once on timeout/429/5xx. JSON 256 KB cap enforced at parse time.
2026-08v1 launch: hashed keys issued by Loanable, sandbox vs live, file scanning, signed webhooks, IP allowlists.

Support

  • Email apply@loanableusa.com and include request_id, HTTP status, and code.
  • Partner Portal messages (signed-in partners).
  • Legal: DPA · Privacy · Terms · Security

Loanable is a dba of Nextgen Capital Solutions LLC (Nevada). Loanable is not a lender. The Partner API is for approved referral partners only.

Loanable

Making business funding simple, fast, and accessible for entrepreneurs everywhere.

Resources

  • Partner API
  • Referral partners
  • OpenAPI spec

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Notice
  • Data Processing Addendum

© 2026 Loanable. All rights reserved.

Loanable is not a lender. The Partner API is for approved referral partners only.