API reference
Two services, one authority seam. api.forsale owns the Deal and its gate; the closer marketplace is where verified humans claim and work Gigs — and everything a contractor does only ever proposes back to the deal’s gate.
All examples run against the live demo environment, where the services are mounted under one host, and every response shown was captured from it:
BASE=https://sales-kernel-demo.vercel.app
# api.forsale → $BASE/forsale/…
# closer marketplace → $BASE/closers/…Errors are conventional: 404 unknown id, 409 illegal state (e.g. settlement on an open deal, claiming a claimed gig), 422 gate refusal or Mandate bounds violation, with a JSON error body.
api.forsale — deals
POST/forsale/deals
Create a Deal at sourced. Body: account ( name, optional industry, actor: B2B | B2C | B2D | B2A), listValueUsd, optional mandate (see Concepts). Platform defaults fill unset Mandate values; the response echoes the fully-resolved Mandate.
curl -s -X POST $BASE/forsale/deals \
-H 'content-type: application/json' \
-d '{
"account": { "name": "Northwind Manufacturing", "industry": "Enterprise SaaS", "actor": "B2B" },
"listValueUsd": 80000,
"mandate": {
"staffing": { "closer": { "humanIf": { "minDealSizeUsd": 25000 } } },
"commissionPct": 0.1
}
}'{
"id": "deal_0001",
"account": { "name": "Northwind Manufacturing", "industry": "Enterprise SaaS", "actor": "B2B" },
"listValueUsd": 80000,
"stage": "sourced",
"signals": [],
"mandate": {
"staffing": { "closer": { "humanIf": { "minDealSizeUsd": 25000 } } },
"maxCommissionPct": 0.12,
"floorPct": 0.8,
"stallTimeoutDays": 14,
"fees": { "setterUsd": 250, "nurturerUsd": 150 },
"commissionPct": 0.1
},
"activity": [
{ "at": "2026-07-23T22:21:34.374Z", "by": "service", "stage": "sourced", "note": "deal created" }
]
}Mandate values outside the platform bounds are refused:
{
"error": "mandate is outside the platform bounds",
"violations": ["commissionPct 0.5 exceeds maxCommissionPct 0.12"]
}
// HTTP 422GET/forsale/deals/:id
Fetch a Deal — same shape as above, with the full activity log.
POST/forsale/deals/:id/advance
Advance one step. Either the agent worked the stage and the response is the updated Deal, or the current Role’s staffing rule escalated — a Gig was posted to the closer marketplace and the response describes it, with the pool ranked for the deal:
{
"escalated": true,
"role": "closer",
"gig": {
"id": "gig_0003",
"status": "open",
"dealId": "deal_0002",
"role": "closer",
"brief": {
"account": { "name": "Acme Robotics", "industry": "Enterprise SaaS", "actor": "B2B" },
"listValueUsd": 80000,
"stage": "meeting",
"signals": [],
"floorUsd": 64000,
"activity": [ … ]
},
"pricing": { "kind": "commission", "pct": 0.1 }
},
"suggestedClosers": [
{
"closer": {
"id": "closer_0001",
"name": "Dana Reyes",
"industries": ["Enterprise SaaS", "Insurance"],
"record": { "gigs": 0, "wins": 0, "grossClosedUsd": 0 },
"payouts": []
},
"score": 2.5,
"winRate": 0.5
},
…
]
}POST/forsale/deals/:id/signals
Report discovered Signals. gone_dark stalls the deal into the nurturer Role — a claimed closer gig is released and the deal returns to matching:
curl -s -X POST $BASE/forsale/deals/deal_0004/signals \
-H 'content-type: application/json' \
-d '{"signals":["gone_dark"]}'{
"id": "deal_0004",
"stage": "meeting",
"signals": ["gone_dark"],
"humanTrack": true,
"activity": [
…,
{ "by": "service", "stage": "meeting", "note": "signals reported: gone_dark" },
{ "by": "service", "stage": "meeting", "note": "gig gig_0004 released: deal went dark; released for nurture" },
{ "by": "service", "stage": "meeting", "note": "gig gig_0004 released — deal returns to matching under the nurturer role" }
],
…
}GET/forsale/deals/:id/settlement
Outcome billing — 409 unless the deal is closed_won:
{
"finalValueUsd": 74000,
"closerCommissionUsd": 7400,
"platformFeeUsd": 3700,
"feesSettledUsd": 0,
"netToSellerUsd": 62900
}POST/forsale/deals/:id/reengage
Mint a new Deal from a closed_lost predecessor — terminal is forever, so re-engagement is a new Deal with a lineage edge:
{
"id": "deal_0005",
"account": { "name": "Globex", "industry": "Enterprise SaaS", "actor": "B2B" },
"listValueUsd": 40000,
"stage": "sourced",
"signals": [],
"activity": [
{ "by": "service", "stage": "sourced", "note": "deal created (re-engagement; revived from deal_0004)" }
],
"lineage": { "revivedFrom": "deal_0004" },
…
}POST/forsale/sell
Convenience: create a Deal and advance it repeatedly until it closes or escalates. The response is either a terminal Deal or the escalation shape from /advance:
// B2C example — the agent ran the whole pipeline and closed within the floor:
{
"id": "deal_0003",
"account": { "name": "Jordan Lee", "industry": "Coaching", "actor": "B2C" },
"listValueUsd": 9000,
"stage": "closed_won",
"finalValueUsd": 8280,
"closedBy": "agent",
"activity": [
{ "by": "service", "stage": "sourced", "note": "deal created" },
{ "by": "agent", "stage": "qualified", "note": "BANT qualified: budget confirmed, champion identified" },
{ "by": "agent", "stage": "meeting", "note": "discovery call booked and held" },
{ "by": "agent", "stage": "proposal", "note": "demo delivered, proposal sent" },
{ "by": "agent", "stage": "negotiation", "note": "buyer countered; entering negotiation" },
{ "by": "agent", "stage": "closed_won", "note": "agreed at 8% discount, within floor" }
],
…
}
// A deal whose Mandate escalates instead returns the same shape as /advance:
// { "escalated": true, "role": "closer", "gig": { … }, "suggestedClosers": [ … ] }The closer marketplace — the pool
POST/closers/closers
Enroll a contractor: a fresh Register, an empty payout ledger.
curl -s -X POST $BASE/closers/closers \
-H 'content-type: application/json' \
-d '{"name":"Sam Ortiz","industries":["Solar"]}'GET/closers/closers
GET/closers/closers/:id
List the pool, or fetch one contractor — Register (record) and payout ledger included. The Register is built exclusively from settled outcomes, never from deal content:
{
"id": "closer_0001",
"name": "Dana Reyes",
"industries": ["Enterprise SaaS", "Insurance"],
"record": { "gigs": 1, "wins": 1, "grossClosedUsd": 74000 },
"payouts": [
{ "at": "2026-07-23T22:21:57.734Z", "gigId": "gig_0003", "dealId": "deal_0002", "kind": "commission", "amountUsd": 7400 }
]
}POST/closers/match
Rank the pool for a deal — industry fit, then proven win rate, then closed volume:
curl -s -X POST $BASE/closers/match \
-H 'content-type: application/json' \
-d '{"industry":"Enterprise SaaS","valueUsd":80000,"role":"closer"}'[
{ "closer": { "id": "closer_0001", "name": "Dana Reyes", … }, "score": 3.4625, "winRate": 1 },
{ "closer": { "id": "closer_0002", "name": "Marcus Webb", … }, "score": 0.5, "winRate": 0.5 }
]The closer marketplace — gigs
GET/closers/gigs?status=
List gigs, optionally filtered: open | claimed | fulfilled | released | expired.
POST/closers/gigs/:id/claim
Claim an open gig. 409 if it’s not open.
curl -s -X POST $BASE/closers/gigs/$GIG/claim \
-H 'content-type: application/json' \
-d '{"closerId":"closer_0001"}'POST/closers/gigs/:id/advance
Propose a stage advance under a claimed gig — intermediate (proposal, negotiation) or terminal (closed_won with valueUsd, or closed_lost). The deal’s gate decides; the response carries the gig, the deal after the ruling, and — when the gig fulfilled — the contractor with their updated Register and ledger:
curl -s -X POST $BASE/closers/gigs/$GIG/advance \
-H 'content-type: application/json' \
-d '{"advanceTo":"closed_won","valueUsd":74000,"note":"multi-year term won it back"}'{
"gig": { "id": "gig_0003", "status": "fulfilled", "outcome": "closed_won", "finalValueUsd": 74000, … },
"closer": { "id": "closer_0001", "record": { "gigs": 1, "wins": 1, "grossClosedUsd": 74000 },
"payouts": [ { "kind": "commission", "amountUsd": 7400, … } ] },
"deal": { "id": "deal_0002", "stage": "closed_won", "finalValueUsd": 74000, "closedBy": "human", … }
}A refusal comes back as 422 and changes nothing:
{
"error": "deal gate refused the proposal",
"gate": { "error": "proposed close $50000 is below the floor $64000 (80% of list)" }
}
// HTTP 422 — the deal is unmoved and the gig stays claimedPOST/closers/gigs/:id/revive
Nurturer-only: report the stalled deal revived. The gone_dark Signal clears, the deal hands back to the Role that owns its stage, and the nurturer’s flat fee settles at fulfillment — regardless of what the deal does later:
curl -s -X POST $BASE/closers/gigs/$GIG/revive \
-H 'content-type: application/json' \
-d '{"note":"buyer resurfaced after PTO"}'{
"gig": { "id": "gig_0005", "status": "fulfilled", … },
"closer": { …, "payouts": [ { "kind": "fee", "amountUsd": 150, "gigId": "gig_0005", … } ] },
"deal": { "id": "deal_0004", "stage": "meeting", "signals": [], … } // gone_dark cleared
}POST/closers/gigs/:id/release
Step away from a claimed gig (or withdraw an open one):
{
"id": "gig_0007",
"status": "released",
"dealId": "deal_0006",
"role": "closer",
…
}
// the deal returns to matching — the next POST /forsale/deals/:id/advance posts a fresh gigPOST/closers/gigs/:id/expire
Platform-owned expiry: an expired claim reopens the same gig to the pool. The platform owns the timer — contractors and Sellers don’t.