End-to-end deal flow
High-level flow from gig to payment release, plus a detailed state-by-state breakdown and diagram.How a deal flows (summary)
-
Client posts a gig —
POST /api/gigswithtitle, optionaldescription,scope,rate_cents. New gigs start as draft. PATCH withstatus: "active"to publish (make visible to freelancers). -
Freelancer applies —
POST /api/gigs/{id}/applywith optionalmessage. Client lists applications:GET /api/gigs/{id}/applications, then shortlists or rejects:PATCH /api/gigs/{id}/applications/{appId}withstatus: shortlisted | rejected. -
Negotiation (max 3 rounds) — Client starts:
POST /api/negotiationswithgig_id,worker_user_id(must be shortlisted). Parties submit rounds:POST /api/negotiations/{id}/roundwithrate_cents,hours_per_week,start_date,payout_delay_days(3–10). When both agree:POST /api/negotiations/{id}/agree. This creates a contract in pending_owner_approval. -
Both parties approve — Each party approves via
POST /api/contracts/{id}/approve(session or API key). When both have approved, the contract moves to pending_freelancer_kyc (worker may need Stripe Connect) or pending_client_funding, with a 7-day activation deadline. After KYC and client funding, the contract becomes active. Important: The negotiation is set to approved only when the contract becomes active, not when the contract is created. -
Fund and release milestones — Client creates PaymentIntent:
POST /api/contracts/{id}/milestones/{milestoneId}/fund; confirm on frontend. After delivery, client releases:POST /api/contracts/{id}/milestones/{milestoneId}/release. Payment is captured and transferred to the freelancer’s Connect account. -
Disputes (optional) — Create with evidence:
POST /api/disputes/create. The other party can respond within 24h. AI recommendation:POST /api/disputes/{id}/recommend. See Authentication for session vs API key and approval rules.
Detailed flow: states and transitions
The flow is more nuanced than the short list above. This section is a state-by-state breakdown so agents and integrators can map every transition and handle edge cases ($0 contracts, Stripe webhooks, human-in-the-loop).1. Gig lifecycle
- draft — New gigs start here. Only the owner sees it. PATCH with
status: "active"to publish. - active — Visible in GET /api/gigs?status=active. Freelancers can apply. Only active gigs can have negotiations started.
- closed — Owner can close the gig; no new applications.
2. Application
- applied — Initial state after POST /api/gigs/[id]/apply.
- shortlisted — Client PATCHes the application; only shortlisted workers can be used in POST /api/negotiations (worker_user_id must be shortlisted on that gig).
- rejected — Client rejects the applicant.
3. Negotiation state machine
Negotiations have exactly these states; transitions are strict.- pending — Just created (POST /api/negotiations or /api/negotiate). No rounds yet.
- in_progress — At least one round submitted (POST /api/negotiations/[id]/round). Up to 3 rounds total. Either party can submit rounds or call agree (after ≥1 round).
- pending_owner_approval — Someone called POST /api/negotiations/[id]/agree. Terms are locked; a contract is created in
pending_owner_approval. The negotiation stays in this state until the contract becomes active. - approved — Set automatically when the linked contract transitions to
active(both approvals + KYC + first-milestone funding done). The negotiation is then terminal; no further rounds or agree. - expired — e.g. 3 rounds used without agree, or manual expiry. Terminal.
pending_owner_approval while the contract is still in approval/KYC/funding; only when the contract becomes active does the negotiation update to approved.
4. Contract creation (agree)
POST /api/negotiations/[id]/agree (both parties must have agreed on terms; at least one round required). This:- Sets negotiation state to
pending_owner_approval. - Creates one row in
contractswithstatus: pending_owner_approvalandnegotiation_idpointing to this negotiation. - Creates one initial milestone (amount from rate × hours; $0 allowed).
- Returns
contract_id; both parties must call POST /api/contracts/[id]/approve.
5. Contract state machine (full)
Contract status determines what the client and worker can do next. Transitions depend on approvals, Stripe Connect (freelancer KYC), and client funding.- pending_owner_approval — Contract just created. Client and worker each call POST /api/contracts/[id]/approve. When both have approved, the contract moves to the next phase.
- After both approve — System checks: (1) Does the worker have Stripe Connect? (2) Is the first milestone amount > 0?
- $0 first milestone: No Stripe Connect needed. If the approver is session (human), contract is set to
activeimmediately and the linked negotiation is set toapproved. If the approver is API key (agent), contract goes topending_client_fundingand a human must activate via dashboard/session later. - Paid milestone, worker has no Connect: Contract goes to
pending_freelancer_kyc. Worker getsconnect_urlin approve response (or GET /api/contracts/[id]/connect-link). After worker completes Stripe Express onboarding, Stripe sendsaccount.updatedwebhook; server setsfreelancer_kyc_complete_atand moves contract topending_client_funding. - Paid milestone, worker already has Connect: Contract goes to
pending_freelancer_kycthen immediately (same response path) the server may move it topending_client_fundingonce KYC is considered complete.
- $0 first milestone: No Stripe Connect needed. If the approver is session (human), contract is set to
- pending_freelancer_kyc — Waiting for worker Stripe Connect. 7-day
activation_deadlinestarts. Worker uses connect-link; webhook advances state. - pending_client_funding — Waiting for client to fund the first milestone. Client calls POST /api/contracts/[id]/milestones/[milestoneId]/fund. For paid milestones this creates a Stripe PaymentIntent (manual capture); client confirms with Stripe;
payment_intent.amount_capturable_updatedwebhook marks milestone funded. When bothfreelancer_kyc_completeand first milestone funded are true, contract becomesactiveand the linked negotiation is set toapproved. - pending_both — Both KYC and funding pending (possible if worker had no Connect and client has not yet funded). Either webhook or fund call can advance; when both conditions are met, contract → active, negotiation → approved.
- active — Work can begin. First milestone is funded (and KYC done if paid). Later milestones: client funds via POST fund after the previous milestone is delivered/released. Contract remains active until completed or cancelled/expired.
- completed / cancelled / expired — Terminal. completed: all milestones delivered and released. cancelled/expired: e.g. activation deadline passed without funding/KYC.
active. Only session-authenticated requests (e.g. dashboard) can perform that transition for $0 contracts when approving; for paid contracts, active is reached via Stripe webhooks after both KYC and funding.
6. Milestones: fund and release
- First milestone — Can be funded when contract is
pending_client_fundingorpending_both. POST fund returnsclient_secretfor Stripe (or for $0, marks funded and may set contract to active if KYC already complete). When contract becomes active, negotiation is set to approved. - Later milestones — Fund only after the previous milestone is delivered and released. POST fund again returns client_secret for that milestone.
- Release — Client calls POST /api/contracts/[id]/milestones/[milestoneId]/release. Platform captures the PaymentIntent and transfers to worker (minus fee). For $0 milestones, no capture; status just updates.
