Partner API
Send deals from your CRM, follow every client file, upload documents, and hear the moment anything changes. An owner or admin of your firm creates API keys and webhooks in the partner portal under Connect your CRM.
Authentication
Send your key as a bearer token on every request. Keys start with bsk_live_, are shown once when created, and can be revoked at any time. A key stops working if your firm’s partnership is paused.
curl https://portal.baystreetlending.com/api/v1/me -H "Authorization: Bearer bsk_live_…"Limits: 1,200 requests per key per hour; 30 new deals per firm per hour. Errors are JSON: { "error": { "code": "…", "message": "…" } } with status 400, 401, 404, 413, 415, 422, 429 or 5xx.
Endpoints
POST /deals — open a client and their document checklist
curl https://portal.baystreetlending.com/api/v1/deals \
-H "Authorization: Bearer $BSL_KEY" -H "Content-Type: application/json" \
-d '{
"business": { "legal_name": "Blue Sky Paving LLC", "dba": "Blue Sky Paving", "years_in_business": 6 },
"owner": { "first_name": "Dana", "last_name": "Rivers", "email": "dana@example.com", "phone": "555-010-0199" },
"request": { "product": "equipment", "amount": 185000,
"equipment_category": "construction", "condition": "used", "seller": "dealer" }
}'product is equipment or working_capital. For equipment, equipment_category is one of titled_vehicle, construction, medical, restaurant_food, manufacturing, technology_soft, agriculture or other; condition is new or used; seller is dealer or private_party. Optional existing_balances is free text listing what the business already owes ("OnDeck 40k / Fox 25k, $1,200 a week"); send it when you know it, so we can look at a refinance alongside another position. The checklist depends on the category, the condition and the amount; the response lists every item. Returns 201 with the file, or 202 with "status": "under_review" and a submission_id when the business is already known to Bay Street Lending — our intake team decides it within one business day. Subscribe a webhook to submission.decided to hear the outcome. The API never emails your client; invite them from the portal if you want them to upload documents themselves.
GET /files and GET /files/:id — your clients’ files
{
"data": {
"id": "5f0c…", "business": "Blue Sky Paving", "product": "equipment", "amount": 185000, "status": "collecting",
"items": [
{ "id": "9a1e…", "from": "checklist", "document_type": "bank_statements_3mo",
"label": "Last 3 months of business bank statements", "required": true, "status": "open",
"files_attached": 0, "requested_at": "2026-09-30T14:02:11Z" },
{ "id": "c7d2…", "from": "lender", "document_type": "voided_check",
"label": "Voided business check", "required": true, "status": "open", "files_attached": 0 }
]
}
}File status is collecting or complete. Item status is open, uploaded, accepted, rejected (with a reason), waived or withdrawn. Items with "from": "lender" are follow-up requests from the funding side.
POST /files/:id/items/:itemId/documents — upload a document
curl https://portal.baystreetlending.com/api/v1/files/$FILE/items/$ITEM/documents -H "Authorization: Bearer $BSL_KEY" -F "file=@statements.pdf"PDF, JPG, PNG, CSV or XLSX, up to 4 MB each. The file’s contents must match its type.
GET /submissions and GET /submissions/:id — deals Bay Street reviewed
Status under_review, opened (with the file_id) or declined (with an outcome and any note from Bay Street), for the last 90 days.
GET /referrals — every business your firm referred
Stage, dates and — once funded — the funded date and amount. Refreshed from our CRM every hour.
Webhooks
Add an https endpoint in the portal and choose the events it receives:
file.created— a client file is opened (with its checklist)file.item_requested— the funding side asked for more documentsfile.completed— every required item is inreferral.stage_changed— a referral moved stagereferral.funded— a referral funded (date and amount)submission.decided— Bay Street opened or declined a deal it was reviewing
POST https://your-endpoint.example.com/bay-street
BayStreet-Event: file.item_requested
BayStreet-Delivery: dlv_1842
BayStreet-Signature: t=1790776800,v1=5d41402abc4b2a76b9719d911017c592…
{ "id": "evt_1203", "occurred_at": "2026-09-30T15:04:05Z",
"data": { "type": "file.item_requested",
"file": { "id": "5f0c…", "business": "Blue Sky Paving" },
"items": [ { "document_type": "voided_check", "label": "Voided business check" } ] } }Answer with any 2xx within 10 seconds. Anything else is retried after 1, 5 and 30 minutes, then 2, 6 and 24 hours. After 20 failures in a row the webhook is switched off, and the portal shows why. Use id to ignore a repeat. Each webhook has a Send test button that posts a signed ping.
Verifying signatures
v1 is the hex HMAC-SHA256 of "<t>.<raw request body>" using your webhook’s signing secret (whsec_…, shown once when the webhook is created). Reject the request if it doesn’t match, or if t is more than 5 minutes old.
// Node
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
const want = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
return v1?.length === want.length && timingSafeEqual(Buffer.from(v1), Buffer.from(want))
}# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
want = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, parts.get("v1", ""))CRM recipes
HubSpot
- Send deals: in a deal-based workflow, add a Send a webhook action (Operations Hub) or a custom-code action:
POST https://portal.baystreetlending.com/api/v1/dealswith the bearer key stored as a secret. - Receive updates: point a Bay Street webhook at a Zapier or Make catch hook (below) and map
file.item_requestedto a HubSpot task andreferral.fundedto the deal stage.
Salesforce
- Send deals: create a Named Credential for
https://portal.baystreetlending.comwith a custom headerAuthorization: Bearer …, then a record-triggered Flow on Opportunity with an HTTP Callout action toPOST /api/v1/deals. - Receive updates: an Apex REST class (or Zapier/Make) that verifies the signature and updates the Opportunity from
referral.stage_changedandreferral.funded.
Zapier / Make / any CRM
- Receive: a Webhooks by Zapier → Catch Raw Hook trigger (or a Make custom webhook); paste its URL into a Bay Street webhook. Keep that URL private: catch hooks can’t check signatures.
- Send: a Webhooks by Zapier → Custom Request action,
POST https://portal.baystreetlending.com/api/v1/deals, headerAuthorization: Bearer …, JSON body as above.
Email submissions
No CRM? Email the deal from your partner login’s address to the submission address shown on the portal’s Submit a deal page, with labelled lines at the top and documents attached:
Business: Blue Sky Paving LLC
Owner: Dana Rivers
Owner email: dana@example.com
Phone: 555-010-0199
Request: Equipment, $185,000
Equipment: used excavator from a dealer
Years in business: 6We reply with the file’s link, what we received and what’s still needed. Reply to that email (keeping the [BSL-…] code in the subject) to add documents. Mail must come from an active partner login and pass your domain’s DMARC or DKIM check; anything else isn’t processed. Your client is never emailed from an email submission.
