§6 · REST reference

The API, endpoint by endpoint.

Thirteen endpoints, three auth mechanisms, one SSE stream. This page is a reference — not a tutorial. If you want the walkthrough, start with the protocol or the demo.

For: anyone writing a client.

Base URL

http://localhost:3000 in development. Production base URLs are whatever your deployment sets. The API is currently un-versioned; a /v1/ prefix is on the roadmap but not yet in the code.

Authentication

Three headers, three actors. The registry decides which one to validate from the route, not from the caller — there is no negotiation.

ActorHeaderFormatExample
Buyerx-user-idany stringuser-1
Vendorx-api-keypak_<uuid>pak_a1b2c3d4…
Tier-2 accessx-session-tokenst_<uuid>st_abc123def4…

Endpoints

User

POST
/api/intents

Publish a new intent.

auth: x-user-id
Request body
{
  "category": "plumbing_fixtures",
  "description": "Pink undermount kitchen sink, porcelain, 30\"",
  "attributes": { "material": "porcelain", "color": "pink", "size_inches": 30 },
  "constraints": [{ "type": "material_exclude", "value": "stainless_steel" }],
  "budget": { "min": 200, "max": 400, "currency": "USD" },
  "urgency": "ready_to_buy",
  "location": { "metro": "NYC", "zip": "10013" },
  "tags": ["kitchen", "renovation"],
  "autoApproveBelow": 300
}
Response
{ "id": "int_7f...", "status": "active", "createdAt": "2025-11-22T18:04:11Z" }
Error responses
  • ·401 missing x-user-id
  • ·422 schema validation failed
GET
/api/intents

List the caller's intents.

auth: x-user-id
Response
{ "intents": [{ "id": "int_7f...", "status": "active", "offerCount": 4 }] }
GET
/api/intents/:id

Get an intent with its offer list.

auth: x-user-id
Response
{ "intent": { ... }, "offers": [...] }
Error responses
  • ·404 not owned by caller
PATCH
/api/intents/:id

Update an intent (cancel, extend, revise).

auth: x-user-id
Request body
{ "status": "cancelled" }

Vendor

POST
/api/vendors

Register a vendor. Returns the API key ONCE.

auth:
Request body
{ "name": "Premium Sink Co", "email": "ops@premiumsink.co", "categories": ["plumbing_fixtures"] }
Response
{ "vendor": { "id": "ven_...", "rating": null }, "apiKey": "pak_a1b2c3..." }
GET
/api/vendors/me

Get the authenticated vendor's profile.

auth: x-api-key

Offer

POST
/api/offers

Submit an offer. Server scores it; may auto-accept (returns session token) or auto-reject (422).

auth: x-api-key
Request body
{
  "intentId": "int_7f...",
  "price": 280,
  "currency": "USD",
  "deliveryDays": 2,
  "inStock": true,
  "attributes": { "material": "porcelain", "color": "pink", "size_inches": 30 },
  "description": "Pink undermount porcelain sink, 30 inches.",
  "matchPercentage": 95
}
Response
{ "offer": { "id": "off_...", "status": "accepted", "score": 82 }, "sessionToken": "st_abc123..." }
Error responses
  • ·401 missing x-api-key
  • ·403 vendor not registered for this category
  • ·422 auto-rejected: reason
POST
/api/offers/:id/accept

Buyer accepts an offer. Issues a session token to the vendor.

auth: x-user-id
Response
{ "offer": { "status": "accepted" }, "sessionToken": "st_..." }
POST
/api/offers/:id/reject

Buyer rejects an offer.

auth: x-user-id
POST
/api/offers/:id/counter

Buyer counters an offer with a message.

auth: x-user-id
Request body
{ "counterMessage": "Can you do 260 for same-day?" }

Registry

GET
/api/registry/intents

Browse active intents. Returns Tier 1 data only. Filter by category, metro, urgency, budget.

auth:
Response
{ "intents": [{ "id": "int_...", "category": "...", "budget": {...}, "urgency": "...", "metro": "...", "tags": [...] }] }
GET
/api/registry/intents/:id/details

Tier 2 data for a specific intent. Requires a matching session token.

auth: x-session-token
Error responses
  • ·401 missing token
  • ·403 token expired or mismatched

Events

GET
/api/events

Server-Sent Events stream. Emits intent_created, intent_updated, offer_created, offer_status_changed.

auth:

Info

GET
/api/health

Liveness probe.

auth:
Response
{ "status": "ok" }

SSE stream format

GET /api/events returns a text/event-stream response. Each event is a JSON payload with a fixed shape. The client reconnects automatically on drop — the EventSource API handles it.

SSE frames
# Example stream (whitespace inserted for readability)

event: intent_created
data: {"id":"int_7f3a1b2c","category":"plumbing_fixtures","urgency":"ready_to_buy"}

event: offer_created
data: {"id":"off_9a2b4c8d","intentId":"int_7f3a1b2c","score":82,"status":"accepted"}

event: offer_status_changed
data: {"id":"off_3c5d7e9f","status":"rejected"}

cURL cookbook

Four copy-paste scripts for the whole happy path.

create-intent.sh
curl -X POST http://localhost:3000/api/intents \
  -H "content-type: application/json" \
  -H "x-user-id: user-1" \
  -d '{
    "category": "plumbing_fixtures",
    "description": "Pink undermount kitchen sink, porcelain, 30\"",
    "attributes": { "material": "porcelain", "color": "pink", "size_inches": 30 },
    "budget": { "min": 200, "max": 400, "currency": "USD" },
    "urgency": "ready_to_buy",
    "location": { "metro": "NYC", "zip": "10013" },
    "autoApproveBelow": 300
  }'
Route handlers