Proposals
Hire From Proposal
Request a hire — records a pending approval a human must co-sign in the OpenTrain app before the contract is created and any money moves.
POST
Requests a hire of the candidate behind one of your proposals. This call never hires anyone or moves money. It records a pending approval (
type: "proposal_hire") and returns 202; a signed-in human must open approval.approvalUrl and confirm in the OpenTrain app. Approvals expire after ~72 hours.
When the human confirms, OpenTrain accepts the proposal, creates the contract, creates the first escrow milestone, and funds it — the milestone amount plus a 10% marketplace fee plus a $9.95 contract initiation fee (see credits & billing). The human chooses the payment source (card or credit balance) at confirm time. Funds are held, not paid out; payout happens when you approve the milestone later.
Re-requesting with the same milestone terms while a pending approval exists returns the same approval (idempotent). Re-requesting with different terms supersedes the old approval, so the human only ever sees one live hire request per proposal. Learn the outcome by polling GET /approvals/{id} or watching for the approval.confirmed event on GET /updates; once confirmed, the contract appears in GET /contracts with the post-hire job conversation. The AI trainer’s identity stays masked — first name + last initial, never an email (see privacy).
Requirements: proposals:write scope + the public_api_hiring feature + a claimed account (unclaimed accounts get 403 with details.reason: "account_claim_required" and a claimUrl) + a payment method on file or a credit balance covering the full charge (409 with details.reason: "payment_method_required" otherwise). The proposal must be on a job you own.
Request
The proposal to hire from.
The first escrow milestone for the new contract. At least one of
name or description must be non-empty.Set
true to confirm hiring a proposal you previously marked Not a fit, after receiving a 409 with details.reason: "not_fit_confirmation_required".Response
Returns202 — the request is recorded; nothing has been hired or charged yet.
The pending approval, in the same shape as
GET /approvals/{id}: {id, type: "proposal_hire", status: "pending", contractId: null, milestoneId: null, jobId, proposalId, approvalUrl, expiresAt, resolvedAt, result, createdAt}. contractId stays null until the human confirms and the contract is created.Explains that a signed-in human must confirm the approval before the freelancer is hired and any money moves.
Errors
| Status | code | Meaning |
|---|---|---|
400 | BAD_REQUEST | Empty body, invalid JSON, or invalid milestone (details.issues carries zod details — e.g. missing amount, or neither name nor description provided) |
401 | UNAUTHORIZED | Missing or invalid token |
403 | FORBIDDEN | Missing proposals:write scope, public_api_hiring disabled, account not claimed (details.reason: "account_claim_required", details.claimUrl), or the proposal is on another account’s job |
404 | NOT_FOUND | No such proposal |
409 | CONFLICT | Hire request blocked — see the details.reason catalog below |
409 reason catalog
details.reason | Meaning | Extra details fields |
|---|---|---|
payment_method_required | No payment method on file and the credit balance doesn’t cover the full charge; a human must add a card or credits in the OpenTrain app | billingUrl |
already_accepted | The proposal was already hired | — |
not_fit_confirmation_required | Proposal is marked Not a fit; retry with confirmNotFitOverride: true | — |