Skip to content

Latest commit

 

History

History
160 lines (136 loc) · 6.89 KB

File metadata and controls

160 lines (136 loc) · 6.89 KB

Backend API — Frontend Handoff Contract

Base URL (local): http://localhost:8787

Auth (Hexclave)

Every /api/* route requires a verified Hexclave user. On the frontend:

// 1. Wrap your app (see Hexclave React/Next SDK)
//    <HexclaveProvider app={hexclaveClientApp}> ... </HexclaveProvider>

// 2. On every request, attach the Hexclave auth header:
const authHeader = await hexclaveClientApp.getAuthorizationHeader();
const res = await fetch(`${BASE}/api/requirements`, {
  method: "POST",
  headers: { "Content-Type": "application/json", Authorization: authHeader },
  body: JSON.stringify({ query: "..." }),
});

Dev mode: with DEV_AUTH_BYPASS=1 (or no Hexclave keys), the backend runs in dev-bypass — auth is skipped and every call uses a fake dev-user. GET /health reports auth: "enforced" | "dev-bypass ...". Build the frontend against this now; set DEV_AUTH_BYPASS=0 to enforce real Hexclave auth (the frontend must then send a valid Hexclave access token).

Backend is FastAPI (Python). Run it with: uvicorn app.main:app --reload --port 8787. Shared models live in app/models.py — mirror them on the frontend for type safety. Field names are snake_case.


The procurement pipeline (9 steps)

# Endpoint Purpose
1 POST /api/requirements Extract structured requirements + negotiation bounds
2 POST /api/requirements/:id/search Find + rank vendor products (Channel3)
2b POST /api/requirements/:id/refine Merge an NL adjustment into the requirement + re-search
5 GET /api/products/:productId/reviews Review digest — Dean's piece
3+4 POST /api/negotiate Open + run negotiation — Edward's piece
GET /api/negotiate/:id Poll negotiation state / transcript
POST /api/negotiate/:id/check-price Price-target tick (call on a schedule)
6 POST /api/negotiate/:id/approve Approve a settled deal
7 POST /api/negotiate/:id/purchase Get Channel3 buy link + record purchase
8 GET /api/inventory List purchases (inventory)
9 PATCH /api/purchases/:id/delivery Update delivery status
$ GET /api/billing Credit balance + owned products (Hexclave)
$ POST /api/billing/checkout Stripe checkout URL to buy credits
📒 GET /api/ledger Every payment event the agent executed + feedback
📒 GET /api/graph Payment knowledge graph (nodes + edges)

Endpoint details

1. POST /api/requirements

// body
{ "query": "gaming laptop under $1200, RTX 4060, 16GB RAM",
  "initial_offer": 950,   // optional
  "max_limit": 1200,      // optional — hard ceiling
  "currency": "USD" }    // optional
// 200
{ "requirement": { "id": "req_...", "item": "...", "specs": {...},
                   "initial_offer": 950, "max_limit": 1200, "currency": "USD" } }

Prices are never invented. max_limit/initial_offer are set only when the user states them (in the query or the body); otherwise both are 0. 0 means "no ceiling" — /search then applies no price filter and ranks on fit alone. (Negotiation needs a real max_limit; with 0 it has no guardrail — the current product flow is search → refine → buy link, no negotiation step.)

2. POST /api/requirements/:id/search

// 200 — sorted by fit_score desc
{ "candidates": [ { "product_id": "...", "title": "...", "price": 1099,
                    "rating": 4.5, "fit_score": 0.92, "rationale": "..." } ] }

2b. POST /api/requirements/:id/refine

// body — a natural-language adjustment to the SAME requirement (id = the loop context)
{ "message": "actually under $180 and over-ear only" }
// 200 — requirement updated in place, search re-run with the merged criteria
{ "requirement": { "id": "req_...", "item": "...", "specs": { "style": "over-ear" },
                   "initial_offer": 160, "max_limit": 180, "currency": "USD" },
  "candidates": [ /* same shape as /search, re-ranked */ ] }

5. GET /api/products/:productId/reviews (Dean)

{ "digest": { "product_id": "...", "rating": 4.5, "review_count": 320,
              "pros": [...], "cons": [...], "verdict": "...",
              "warranty": "...", "financing_notes": "...", "sources": [...] } }

3+4. POST /api/negotiate (Edward) ⚠️ consumes 1 credit

// body
{ "requirement_id": "req_...", "product_id": "...",
  "mode": "both",                 // "simulated" | "price_target" | "both"
  "canonical_product_id": "..." }   // optional — enables real price tracking
// 200
{ "session": { "id": "neg_...", "status": "awaiting_approval",
               "agreed_price": 1049, "rounds": [ {role, offer, message, ts} ] } }
// 402 if out of credits
{ "detail": "insufficient_credits" }

status flow: active → awaiting_approval → approved → purchased (or walked_away if the seller won't meet the ceiling).

6/7. approve → purchase

POST /api/negotiate/:id/approve   // 200 { session: { status: "approved" } }
POST /api/negotiate/:id/purchase  // 200 { purchase: {...}, buyUrl: "https://..." }

buyUrl is the Channel3 monetized checkout link — send the user there to pay the merchant.

8/9. inventory + delivery

GET   /api/inventory                    // { items: PurchaseRecord[] }
PATCH /api/purchases/:id/delivery        // body { status: "shipped"|"in_transit"|"delivered" }

Billing (Hexclave Payments)

GET  /api/billing                        // { credits, creditCostPerNegotiation, meteringEnabled }
POST /api/billing/checkout               // body { product_id?, return_url? } -> { checkoutUrl }

Payment ledger + knowledge graph

Every payment the agent makes (credit charges, checkouts, merchant purchases) is appended to a durable session log (data/payment-log.jsonl) and projected into a graph. purchase events carry feedback: agreed_price, list_price, savings_vs_list, negotiation_rounds.

GET /api/ledger   // { events: PaymentEvent[] }
GET /api/graph    // { nodes: GraphNode[], edges: GraphEdge[] }
// graph edges: CHARGED, NEGOTIATED, RESULTED_IN, PAID_FOR, SUPPLIED_BY
// e.g.  (User) -PAID_FOR-> (Product) -SUPPLIED_BY-> (Merchant)
//       (User) -NEGOTIATED-> (NegotiationSession) -RESULTED_IN-> (Product)

Suggested frontend flow

  1. Intake form → POST /api/requirements
  2. Show ranked candidates ← POST /api/requirements/:id/search
  3. Per candidate, lazy-load reviews ← GET /api/products/:id/reviews
  4. User picks one → POST /api/negotiate → stream/poll GET /api/negotiate/:id and render the rounds[] transcript live
  5. On awaiting_approval, show agreed price → Approve button → Purchase button
  6. Open buyUrl, then show it in the Inventory view (GET /api/inventory)
  7. Header widget: credit balance from GET /api/billing; "Buy credits" → POST /api/billing/checkout → redirect to checkoutUrl