Partner integration
This reference is for approved Short Hills Finance referral partners integrating with our Applications API. It documents the full application lifecycle — create, save, submit, and supply late data — with the exact request and response fields, which are required, and how they are handled. Partners who prefer a no-code path can instead use a hosted landing page. Your integration type and product scopes are set when your application is approved.
Access is granted only after an application is reviewed and approved — nothing is provisioned while an application is pending (Principle 3). When an API integration is approved, we mint a single API key and show its secret exactly once, in the approval confirmation. Store it somewhere safe at that moment: we keep only a one-way hash of the secret, so it can never be shown or recovered again. If a key is lost or compromised, we revoke it and issue a new one. Your operators can see a key's non-secret public id at any time on your partner record.
An API key has two parts, separated by a dot:
pk_1a2b3c4d.<secret>
└─ public id ─┘ └ secret ┘
pk_…) is non-secret. It identifies the key and is
what we display back to you and to our operators so a key can be recognised without exposing
its secret.
public id + . + secret) as a bearer token on every
request:
Authorization: Bearer pk_1a2b3c4d.<secret>
Keys are validated with a constant-time comparison against the stored hash. Treat a key like a password: never embed it in client-side code or a public repository, and rotate it if it may have leaked. Which products and scopes a key may act on is bound to your agreement; the specific scope binding for your key is confirmed with your integration engineer during onboarding.
https://shorthillsfinance.com. All paths below are relative to it.Content-Type: application/json. Responses are JSON.id is a 32-character hex string minted on create. Use it in the path for every follow-up call.2026-08-27T14:03:11+00:00.monthly_revenue: 42000), no currency symbol or decimals.{ "error": "<code>", "message": "…" }. See Errors.An application is created, saved as fields arrive, then submitted. Underwriting never runs until every required data item is present — if a slow data source is still outstanding the application parks in a saved, waiting state rather than being forced to a decision (Principle 6).
draft ──save fields──▶ draft ──submit──▶ awaiting_data ──provide data──▶ ready_for_decision
│ │
└──────── (all required present) ────┴──▶ decision recorded
POST /applications
Start a new merchant application. Any fields in the body are stored as the first save, so a lead
can be created fully-formed in one call. Returns 201 with the
application object, including what data is still required before a
decision.
Attribution is captured from the query string (write-once, first-touch), never
the body, so a marketing origin can never be confused with an underwriting field. Allow-listed
params: utm_source, utm_medium, utm_campaign,
partner, click_id.
Body — all fields optional here; see the full Application fields table. A minimal example:
POST /applications
Authorization: Bearer pk_1a2b3c4d.<secret>
Content-Type: application/json
{
"legal_name": "Acme Coffee LLC",
"entity_type": "llc",
"ein": "82-1234567",
"monthly_revenue": 42000,
"first_name": "Dana",
"last_name": "Ruiz",
"email": "dana@acmecoffee.example",
"mobile": "+1 555 010 2233"
}
201 Created
{
"id": "9f8c1e77a0b24d3e91c4f5a6b7d8e0f1",
"status": "draft",
"readyForDecision": false,
"required": ["merchant_profile", "bank_settlement", "marketplace_revenue"],
"missingRequirements": ["bank_settlement", "marketplace_revenue"],
"provided": {},
"fields": { "legal_name": "Acme Coffee LLC", "entity_type": "llc", "ein": "82-1234567",
"monthly_revenue": 42000, "first_name": "Dana", "last_name": "Ruiz",
"email": "dana@acmecoffee.example", "mobile": "+1 555 010 2233" },
"decision": null,
"attribution": null,
"startedAt": "2026-08-27T14:03:11+00:00",
"updatedAt": "2026-08-27T14:03:11+00:00"
}
GET /applications/{id}
Fetch the current state of a saved application — its fields, what is still outstanding, and the
decision once one has been recorded. Use it to poll for the outcome after
submit. Returns 200 with the
application object, or 404 if the id is unknown.
PATCH /applications/{id}
Save merchant-entered fields mid-flight. The body is merged into the application — send only the
keys you are adding or changing; earlier fields are preserved. Call it as many times as you like
while the application is still open for editing. Returns 200 with the updated
object. A locked, decision-ready application is refused with 409 so a decision can't
be re-based on changed facts (Principle 4); 404 if unknown.
PATCH /applications/9f8c1e77a0b24d3e91c4f5a6b7d8e0f1
Content-Type: application/json
{ "monthly_revenue": 45000, "address": "12 Bloom St, Newark, NJ 07102" }
POST /applications/{id}/submit
Signal that data entry is finished and ask us to evaluate completeness. If every required item is
present the underwriting engine runs and the decision is recorded; otherwise the application
parks in awaiting_data — a 200 either way, because a submission that
still lacks slow data is a legitimate saved-and-waiting state, not an error (Principle 6).
readyForDecision: true → underwriting ran; read status for the outcome and decision for the recorded result.readyForDecision: false → missingRequirements lists what is still outstanding.409 if the application was already submitted; 404 if unknown. No request body.
POST /applications/{id}/data
Record that a required data item has arrived — typically a slow connector (bank or marketplace)
finishing after submit. If it was the last thing outstanding, the application transitions itself
to ready-for-decision. Returns 200 with the updated object; 422 if the
requirement key is not recognised; 404 if unknown.
POST /applications/9f8c1e77a0b24d3e91c4f5a6b7d8e0f1/data
Content-Type: application/json
{ "requirement": "bank_settlement", "source": "plaid" }
| Field | Type | Req. | Description |
|---|---|---|---|
| requirement | string (enum) | required | One of the required data item keys. |
| source | string | optional | Where the data came from (e.g. plaid); recorded for provenance. Defaults to unknown. |
The fields object holds the merchant-entered data. The table lists every recognised
key, whether it is required to complete a merchant profile, and how
it is handled. Required here means the field is needed before the application can be
underwritten; bank and marketplace facts are satisfied through the
data endpoint by a connector, not typed into fields.
| Field | Type | Req. | Description & handling |
|---|---|---|---|
| legal_name | string | required | Registered legal entity name. |
| business_name | string | optional | Doing-business-as / storefront name, if different from the legal name. |
| entity_type | string | required | Legal form, e.g. llc, corp, sole_prop, partnership. |
| ein | string | required | Federal Employer Identification Number, NN-NNNNNNN. |
| monthly_revenue | integer | required | Self-reported average monthly revenue in whole USD. Verified against connector data before pricing (Principle 1). |
| address | string | optional | Business mailing address. |
| first_name | string | required | Owner first name. Contact-PII: access-logged, stays queryable. |
| last_name | string | required | Owner last name. Contact-PII. |
| string | required | Owner email. Contact-PII; also used for the resume link. | |
| mobile | string | required | Owner mobile phone (E.164 preferred). Contact-PII. |
| ssn | string | required | Write-only, vaulted. Sealed into the PII vault on write and never echoed back — only ssn_last4 returns. |
| dob | string (date) | required | Write-only, vaulted. Encrypted at rest; never returned. |
| bank_account | string | optional | Write-only, vaulted. Usually supplied via the bank connector, not typed. Only bank_account_last4 returns. |
| routing_number | string | optional | Write-only, vaulted. Encrypted at rest; never returned. |
PII handling. Fields marked write-only are governed by our versioned protected-field
policy: ssn, dob, bank_account and routing_number
are sealed into a dedicated vault on write and never appear in any response. For SSN and
bank account we return a masked *_last4 fragment for display; everything else in a
response is the snapshot-safe data you sent. Never rely on reading a secret field back.
A decision cannot be made until all three required items are present. The API surfaces them in
every response as required and missingRequirements. Merchant profile is
satisfied by the fields above; the other two are fetched from connectors and
marked complete via the data endpoint.
| Key | Supplied by | Description |
|---|---|---|
| merchant_profile | Merchant (fields) | Legal entity, ownership and EIN, entered on the application. |
| bank_settlement | Connector | Bank cash-flow used for lender-debit classification. |
| marketplace_revenue | Connector | Marketplace settlement / completed-revenue history. |
Every endpoint returns the same application object:
| Field | Type | Description |
|---|---|---|
| id | string | 32-char hex application id. |
| status | string | draft, awaiting_data, ready_for_decision, or a decided state. |
| readyForDecision | boolean | true once every required item is present. |
| required | string[] | The full required data-item set for this application. |
| missingRequirements | string[] | The subset still outstanding — empty when ready. |
| provided | object | Captured required items keyed by requirement, each with its source and timestamp. |
| fields | object | The merchant-entered fields (snapshot-safe values only; masked *_last4 for vaulted fields). |
| decision | object · null | The recorded underwriting decision once one exists; null before then. Sourced only from the authoritative ledger (Principle 4). |
| attribution | object · null | The captured marketing origin (lead source), or null if none was signalled. |
| startedAt | string (RFC 3339) | When the application was created. |
| updatedAt | string (RFC 3339) | When it was last modified. |
Non-2xx responses carry { "error": "<code>", "message": "…" }. The codes you may see:
| HTTP | error | Meaning |
|---|---|---|
| 404 | application_not_found | No application with that id. |
| 409 | application_locked | The application is decision-ready and can no longer be edited. |
| 409 | invalid_transition | The action isn't allowed from the current status (e.g. re-submitting). |
| 422 | unknown_requirement | The requirement key isn't recognised; the response lists accepted keys. |
If your integration includes a landing page, we host a co-branded page at
/partner/<your-slug> built from one of our templates. A borrower who starts from
your page is attributed to you automatically — you don't need to call the API to get referral
credit for landing-page traffic. Every application still runs through the full underwriting
pipeline on our side; a partner integration never changes an underwriting outcome (Principle 3).
Questions about your integration, a lost key, or your agreement? Email partners@shorthillsfinance.com and we'll route you to your onboarding contact.