Partner integration

Partner API reference

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.

Getting access

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.

Authentication

An API key has two parts, separated by a dot:

pk_1a2b3c4d.<secret>
└─ public id ─┘ └ secret ┘
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.

Conventions

Application lifecycle

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

Endpoints

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).

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" }
FieldTypeReq.Description
requirementstring (enum)requiredOne of the required data item keys.
sourcestringoptionalWhere the data came from (e.g. plaid); recorded for provenance. Defaults to unknown.

Application fields

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.

FieldTypeReq.Description & handling
legal_namestringrequiredRegistered legal entity name.
business_namestringoptionalDoing-business-as / storefront name, if different from the legal name.
entity_typestringrequiredLegal form, e.g. llc, corp, sole_prop, partnership.
einstringrequiredFederal Employer Identification Number, NN-NNNNNNN.
monthly_revenueintegerrequiredSelf-reported average monthly revenue in whole USD. Verified against connector data before pricing (Principle 1).
addressstringoptionalBusiness mailing address.
first_namestringrequiredOwner first name. Contact-PII: access-logged, stays queryable.
last_namestringrequiredOwner last name. Contact-PII.
emailstringrequiredOwner email. Contact-PII; also used for the resume link.
mobilestringrequiredOwner mobile phone (E.164 preferred). Contact-PII.
ssnstringrequiredWrite-only, vaulted. Sealed into the PII vault on write and never echoed back — only ssn_last4 returns.
dobstring (date)requiredWrite-only, vaulted. Encrypted at rest; never returned.
bank_accountstringoptionalWrite-only, vaulted. Usually supplied via the bank connector, not typed. Only bank_account_last4 returns.
routing_numberstringoptionalWrite-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.

Required data items

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.

KeySupplied byDescription
merchant_profileMerchant (fields)Legal entity, ownership and EIN, entered on the application.
bank_settlementConnectorBank cash-flow used for lender-debit classification.
marketplace_revenueConnectorMarketplace settlement / completed-revenue history.

The application object

Every endpoint returns the same application object:

FieldTypeDescription
idstring32-char hex application id.
statusstringdraft, awaiting_data, ready_for_decision, or a decided state.
readyForDecisionbooleantrue once every required item is present.
requiredstring[]The full required data-item set for this application.
missingRequirementsstring[]The subset still outstanding — empty when ready.
providedobjectCaptured required items keyed by requirement, each with its source and timestamp.
fieldsobjectThe merchant-entered fields (snapshot-safe values only; masked *_last4 for vaulted fields).
decisionobject · nullThe recorded underwriting decision once one exists; null before then. Sourced only from the authoritative ledger (Principle 4).
attributionobject · nullThe captured marketing origin (lead source), or null if none was signalled.
startedAtstring (RFC 3339)When the application was created.
updatedAtstring (RFC 3339)When it was last modified.

Errors

Non-2xx responses carry { "error": "<code>", "message": "…" }. The codes you may see:

HTTPerrorMeaning
404application_not_foundNo application with that id.
409application_lockedThe application is decision-ready and can no longer be edited.
409invalid_transitionThe action isn't allowed from the current status (e.g. re-submitting).
422unknown_requirementThe requirement key isn't recognised; the response lists accepted keys.

Hosted landing pages

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).

Support

Questions about your integration, a lost key, or your agreement? Email partners@shorthillsfinance.com and we'll route you to your onboarding contact.

Apply to become a partner