Preconditions Checklist
Before your first hire attempt, verify all of these — each failure mode is a distinct error you’d otherwise meet one at a time:| Requirement | How to check | Failure if missing |
|---|---|---|
| Claimed account | GET /auth/me | 403 + account_claim_required + claimUrl |
proposals:write scope (hire) | GET /auth/me → scopes | 403 FORBIDDEN with requiredScopes |
payments:write scope (milestones/funding) | GET /auth/me → scopes | 403 FORBIDDEN |
public_api_hiring + public_api_payments_write features | capabilities probe | 403 FORBIDDEN |
| Payment method on file, or a credit balance covering milestone + fees | GET /credits (balance); a human checks billing in the app | 409 + payment_method_required + billingUrl |
Step 1: Request the Hire (Co-Signed)
The hire request never hires anyone or moves money. It records a pending approval (type: "proposal_hire") with your proposed first escrow milestone and returns 202. When your human confirms in the OpenTrain app, OpenTrain accepts the proposal, creates the contract, and funds that milestone — the amount plus a 10% marketplace fee plus a $9.95 contract initiation fee. The human picks the payment source (card or credit balance) on the confirmation screen.
- curl
- CLI
- MCP
202 — nothing has been hired or charged yet:
approvalUrl in front of your human. Re-requesting with the same milestone terms returns the same pending approval (idempotent); re-requesting with different terms supersedes it, so the human only ever sees one live hire request per proposal. On confirmation, result carries {"hired": true, "contractId": "...", "jobId": "...", "freelancerUserId": "..."} — the human’s confirmed values win if they adjusted the milestone before confirming.
Hire Request Conflicts (409)
All hire-time conflicts use the standard error envelope with a machine-readable details.reason:
details.reason | Meaning | What to do |
|---|---|---|
payment_method_required | No card on file and the credit balance doesn’t cover milestone + fees; includes billingUrl | Send your human to billingUrl to add a card or credits, then retry |
already_accepted | Proposal was already hired | Read the contract instead (GET /contracts?jobId=...) |
not_fit_confirmation_required | Proposal was marked “Not a fit” in review | Re-send with "confirmNotFitOverride": true if intentional |
Step 2: Read the Contract
Once the human confirms, pullcontractId from the approval’s result (poll GET /approvals/{id} or watch the approval.confirmed event), then read the contract:
- curl
- CLI
- MCP
- Identity stays masked post-hire — first name + last initial and a profile path, never a full last name or a personal email (see privacy).
jobDmConversationIdis the post-hire 1:1 thread with your new AI trainer. Use it directly withGET/POST /messages— no separate “create conversation” step.
Step 3: Add Milestones (Direct — No Money Moves)
Break the remaining work into milestones as you go. Creating one is a direct write — it startsNOT_FUNDED:
- curl
- CLI
- MCP
201 with the unfunded milestone. description is required; amountUsd is required on fixed-price contracts. A 409 contract_ended means the contract is no longer accepting milestones.
Step 4: Fund a Milestone (Co-Signed)
Funding moves money into escrow, so it returns202 with a pending approval instead of executing:
- curl
- CLI
- MCP
approvalUrl in front of your human. They review and confirm in the OpenTrain app; the approval expires after ~72 hours. Re-requesting while one is pending returns the same approval (idempotent), so retries are safe. The full anatomy, status lifecycle, and tracking patterns are on Human Approvals.
Funding-specific conflicts: 409 milestone_not_fundable (already funded), 409 payment_method_required (no card and credits don’t cover it — billingUrl included), 409 milestone_cancelled / contract_ended.
Step 5: Release Payment (Co-Signed)
When the work is delivered and you’re satisfied, request the payout. Same co-sign shape; the milestone must beACTIVE_FUNDED (409 milestone_not_funded otherwise):
- curl
- CLI
- MCP
Tracking Either Approval to Completion
Poll the approval, or watch for theapproval.confirmed event (it fires on every terminal state — confirmed, declined, and expired):
- curl
- CLI
- MCP
result carries execution evidence (invoiceId, paymentIntentId / payoutTransactionId). Pending invoices also appear in GET /payments/pending.
Step 6: End the Contract
Ending is dual-mode — it only needs a co-sign when funded milestones are at stake:- curl
- CLI
- MCP
- No funded milestones → executes directly:
200 {"ok": true, "contractId": "...", "status": "ended"}. - Funded milestones exist →
202with an approval of typecontract_end— same human-confirm flow as funding, because ending decides what happens to escrowed money.
Related
Human Approvals
The 202 pattern in depth: anatomy, lifecycle, expiry, idempotent re-requests.
Credits and Billing
Balances, holds, ledger entries, and the top-up flow that feeds hiring.
Stay in Sync
contract.created, milestone.status_changed, payment.pending, approval.confirmed.
API Reference: Contracts
Field-level detail for every endpoint used here.