Partner API
Send us an opportunity. We'll handle intake, dedupe, and conversion.
One canonical pipeline behind three transports, so a partner with a full engineering team and a partner with none both land in the same review queue with the same field set. This page documents what exists today -- if something below isn't listed, it isn't built yet.
Getting access
Credentials are issued by BayLineup staff today -- apply here and we'll set up whichever transport fits your integration. There is no self-service signup yet.
Transports
Pick whichever matches your engineering resources. All three create the same kind of record and land in the same staff review queue.
| Transport | When to use it | Auth |
|---|---|---|
| REST API | You have an API/integration team and want programmatic control (idempotency, immediate response). | Scoped API key (Authorization: Bearer) |
| Signed webhook | You already run your own event delivery and just need a URL + secret to push to. | HMAC-SHA256 request signature |
| Secure Partner Form | You have no API or engineering resources at all -- a no-login web form your staff fill in by hand. | Possessing the form's link (high-entropy token) |
REST API
POST /api/partners/v1/opportunities
Authenticate with Authorization: Bearer <api key>. Your key needs the partner_intake:write scope. Pass an Idempotency-Key header on every request -- retrying the same key returns the original result (200, deduped: true) instead of creating a duplicate opportunity.
POST /api/partners/v1/opportunities
Authorization: Bearer <api key>
Idempotency-Key: <your own UUID for this submission>
Content-Type: application/json
{
"externalId": "your-system's-id-for-this-lead",
"customerName": "Jamie Rivera",
"customerPhone": "5551234567",
"vehicleMake": "Honda",
"vehicleModel": "Civic",
"vehicleYear": 2019,
"complaint": "Brakes squealing",
"requestedTiming": "This week"
}Responses:
201 Created -- new opportunity
{ "opportunity": { "id": "...", "orgId": "...", "partnerKey": "...", "status": "received", ... }, "deduped": false }
200 OK -- same Idempotency-Key seen before
{ "opportunity": { ... }, "deduped": true }
400 Bad Request -- validation failed (e.g. no customer info at all)
401 Unauthorized -- missing/invalid key
403 Forbidden -- key lacks partner_intake:write
429 Too Many Requests -- rate limit or concurrency limit (see below)Sandbox & testing
GET /api/partners/sandbox/fixtures
Ask BayLineup for a sandbox key (prefixed blp_test_ instead of blp_live_) to exercise the real intake/dedupe/validation pipeline with test data. Sandbox activity never appears in real ops metrics and can never create a real customer record -- a converted sandbox opportunity is simulated end-to-end, including a real signed outbound event to any subscription you have configured, without ever touching production data.
A live fixture library of ready-to-send request bodies -- valid, malformed, duplicate/idempotency-retry, and empty-submission cases -- is served at the endpoint above, so your own test suite can pull them directly instead of copy-pasting from this page.
Signed webhook (inbound)
POST /api/partners/webhooks/{orgId}/{partnerKey} -- the URL and shared secret are issued when your webhook is set up.
Sign the raw request body with your secret and send the result as X-BayLineup-Partner-Signature:
signature = hex(HMAC-SHA256(secret, rawRequestBody))
Same JSON body shape as the REST API above. Send a stable X-BayLineup-Delivery-Id header (your own delivery/event ID) on every attempt -- retried deliveries with the same ID are acknowledged as duplicates without reprocessing, so retrying on a timeout is always safe. A request with a bad or missing signature is recorded and rejected with 401; an unconfigured URL returns 404 rather than confirming whether a partner key exists.
Outbound events
Subscribe to be notified when an opportunity you sent us is decided. Deliveries are signed the same way as the inbound webhook (HMAC-SHA256 over the raw body, same header), with automatic retry and an idempotency key per delivery.
| Event type | Fires when |
|---|---|
opportunity.converted | Staff accepted your opportunity and it became a customer/vehicle record. |
opportunity.declined | Staff declined your opportunity, with a reason. |
opportunity.duplicate | Staff marked your opportunity a duplicate of an existing one. |
Field reference
Every field is optional individually, but a submission needs at least one piece of real customer or vehicle content -- an entirely empty payload is rejected.
| Field | Type | Notes |
|---|---|---|
externalId | string | Your own ID for this lead -- resubmitting the same externalId updates rather than duplicates it. |
referralId | string | Your campaign/referral tracking ID, if any. |
customerName | string | |
customerPhone | string | Used for duplicate/existing-customer matching. |
customerEmail | string | |
vehicleYear | number | |
vehicleMake / vehicleModel / vehicleVin | string | |
complaint | string | What the customer reported. |
requestedTiming | string | Free text -- "today", "this week", etc. |
quoteReference | object | Your own quote/estimate metadata, passed through as-is. |
notes | string | |
attachments | string[] | URLs only -- no file upload endpoint. |
consentMetadata | object | Record of what the customer consented to share, if applicable. |
Rate limits & errors
| Transport | Sustained | Concurrent in-flight |
|---|---|---|
| REST API / Secure Partner Form | 120 requests/minute | 10 |
| Signed webhook | 300 requests/minute | 20 |
A 429 response includes Retry-After. These are engineering defaults we can raise for a high-volume partner after a load/security review -- not a hard commercial cap.