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 422

GET/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 claimed

POST/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 gig

POST/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.