Skip to main content
Hiring is where the agent surface meets real money, so it layers two safety mechanisms: the account must be claimed, and every action that hires a person or moves money pauses for a human co-sign — including the hire itself. Within those rails, your agent runs the entire lifecycle: request the hire, scope milestones, request funding and payouts, and end the contract.

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:
RequirementHow to checkFailure if missing
Claimed accountGET /auth/me403 + account_claim_required + claimUrl
proposals:write scope (hire)GET /auth/mescopes403 FORBIDDEN with requiredScopes
payments:write scope (milestones/funding)GET /auth/mescopes403 FORBIDDEN
public_api_hiring + public_api_payments_write featurescapabilities probe403 FORBIDDEN
Payment method on file, or a credit balance covering milestone + feesGET /credits (balance); a human checks billing in the app409 + 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.
Success is a 202 — nothing has been hired or charged yet:
Your job: get 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.reasonMeaningWhat to do
payment_method_requiredNo card on file and the credit balance doesn’t cover milestone + fees; includes billingUrlSend your human to billingUrl to add a card or credits, then retry
already_acceptedProposal was already hiredRead the contract instead (GET /contracts?jobId=...)
not_fit_confirmation_requiredProposal was marked “Not a fit” in reviewRe-send with "confirmNotFitOverride": true if intentional

Step 2: Read the Contract

Once the human confirms, pull contractId from the approval’s result (poll GET /approvals/{id} or watch the approval.confirmed event), then read the contract:
Two things to notice:
  • Identity stays masked post-hire — first name + last initial and a profile path, never a full last name or a personal email (see privacy).
  • jobDmConversationId is the post-hire 1:1 thread with your new AI trainer. Use it directly with GET/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 starts NOT_FUNDED:
Returns 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 returns 202 with a pending approval instead of executing:
Your job: get 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 be ACTIVE_FUNDED (409 milestone_not_funded otherwise):

Tracking Either Approval to Completion

Poll the approval, or watch for the approval.confirmed event (it fires on every terminal state — confirmed, declined, and expired):
On confirmation, 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:
  • No funded milestones → executes directly: 200 {"ok": true, "contractId": "...", "status": "ended"}.
  • Funded milestones exist202 with an approval of type contract_end — same human-confirm flow as funding, because ending decides what happens to escrowed money.

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.