Skip to main content
Agents operate; humans authorize spend. Any API action that would move money does not execute immediately — it returns 202 Accepted with a pending approval, and a signed-in human confirms it in the OpenTrain app before anything happens. Money never moves from an API call alone.

Which Actions Are Co-Signed

ActionEndpointApproval type
Hire from a proposal (creates contract + escrow)POST /proposals/{proposalId}/hireproposal_hire
Fund a milestone (escrow)POST /milestones/{milestoneId}/fundmilestone_fund
Approve a milestone (release payout)POST /milestones/{milestoneId}/approvemilestone_approve
End a contract that has funded milestonesPOST /contracts/{id}/endcontract_end
Non-money writes — creating an unfunded milestone, or ending a contract with no funded milestones — execute directly with a 200. Contract end is the dual-mode case:
// No funded milestones → direct
{ "ok": true, "contractId": "...", "status": "ended" }
// Funded milestones exist → co-signed (202)
{
  "approval": { "type": "contract_end", "...": "..." },
  "message": "This contract has funded milestones, so a signed-in human must confirm ending it in the OpenTrain app."
}

Anatomy of the 202 Response

{
  "approval": {
    "id": "<APPROVAL_ID>",
    "type": "milestone_fund",
    "status": "pending",
    "contractId": "...",
    "milestoneId": "...",
    "jobId": "...",
    "proposalId": null,
    "approvalUrl": "https://app.opentrain.ai/approvals/<APPROVAL_ID>",
    "expiresAt": "...",
    "resolvedAt": null,
    "result": null,
    "createdAt": "..."
  },
  "message": "A human must confirm this request before any money moves. Share the approvalUrl."
}
Your job after a 202: surface the approvalUrl to your human — in chat, a notification, wherever they’ll see it. They open it, review the amount and context, and confirm or decline. Opening /approvals/{approvalId} for a pending milestone_fund drops the human into the normal contract view with the funding modal already open and pre-filled — the same interface they use for any other milestone, plus a banner noting the agent requested it:
OpenTrain employer dashboard with the Fund and Activate Milestone modal open over the contract view. A banner reads Your agent requested this action, confirm below or decline, with a Decline button. The modal shows the AI trainer Marcus O., the milestone name Production batch 2, a due date, a description, and a button reading Deposit 450 dollars and Activate Milestone.

The Flow

Tracking an Approval

Two complementary mechanisms: Poll the approval directly:
curl -s https://app.opentrain.ai/api/public/v1/approvals/<APPROVAL_ID> \
  -H "Authorization: Bearer $OT_API_TOKEN"
Watch for the approval.confirmed event in /updates or a webhook — it fires when the approval resolves (whether confirmed, declined, or expired):
{
  "approvalId": "...",
  "approvalType": "MILESTONE_FUND",
  "status": "confirmed",
  "contractId": "...",
  "milestoneId": "...",
  "jobId": "..."
}

Status Lifecycle

statusMeaning
pendingWaiting for the human
confirmedHuman approved — the action has executed; see result
declinedHuman rejected it — the action did not execute
expiredNobody acted within the window — the action did not execute
On confirmation, result carries the execution evidence:
Typeresult
proposal_hire{ "hired": true, "contractId": "...", "jobId": "...", "freelancerUserId": "..." }
milestone_fund{ "invoiceId": "...", "paymentIntentId": "..." }
milestone_approve{ "invoiceId": "...", "payoutTransactionId": "..." }
contract_end{ "contractEnded": true }

Rules That Matter in Practice

  • Approvals expire after ~72 hours. An expired approval never executes; create a new request if the action is still wanted.
  • A decline is final for that approval. It resolves to declined, emits approval.confirmed with status: "declined", and nothing executes. Talk to your human before re-requesting.
  • Re-requesting is safe and idempotent. Posting the same fund/approve/end/hire request while an approval is pending returns the same approval — no duplicates pile up for the human, and no money moves until a confirm. A hire re-requested with different milestone terms supersedes the old approval so only one live hire request exists per proposal.
  • approval.confirmed fires for every terminal state — check the status field in the payload; the event name does not mean “approved”.
  • Co-sign applies even with credits on balance. A funded credit balance changes how a hire or milestone is funded, not whether a human confirms it. The human picks the payment source — card or credit balance — on the confirmation screen.

Why It Works This Way

Unattended agents shouldn’t be able to spend their owner’s money on their own — a bug, a prompt injection, or a misread requirement could be expensive. The co-sign pattern keeps the agent in the driver’s seat for everything operational (drafting, publishing, evaluating candidates, preparing the hire) while reserving the irreversible steps — hiring a person and money leaving the account — for an authenticated human with full context on one screen.

Hire and Pay

The full hire → milestone → fund → approve walkthrough.

Credits and Billing

How hires are funded: balances, holds, and top-ups.