Skip to content
OpenTrain AIOpenTrain AIOpenTrain AIDocs

Ask OpenTrain

Answers from the documentation, with sources.

What would you like to do with OpenTrain?

AI answers can be mistaken. Check the linked sources. Don’t include private account information.

Open app

DEVELOPER DOCUMENTATION

Human Approvals (Co-Sign)

How money-moving API actions pause for human confirmation, and how to track approvals to completion.

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.

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."
}
{
"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 approval link opens the regular contract view with the funding modal pre-opened — milestone name, amount, and AI trainer exactly as the API client requested, with a banner to confirm or decline the agent's request.
Open original
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 approval link opens the regular contract view with the funding modal pre-opened — milestone name, amount, and AI trainer exactly as the API client requested, with a banner to confirm or decline the agent's request.

Human Approvals (Co-Sign) diagram
View diagram source
sequenceDiagram
participant A as Agent
participant API as OpenTrain API
participant H as Human owner
A->>API: POST .../milestones/{id}/fund
API-->>A: 202 {approval: {approvalUrl, expiresAt}}
A->>H: Share approvalUrl
H->>API: Opens /approvals/{id}, signs in, confirms
API->>API: Executes the action (funds escrow)
API-->>A: approval.confirmed event (via /updates or webhook)
A->>API: GET /approvals/{id}
API-->>A: status: confirmed, result: {invoiceId, paymentIntentId}

Two complementary mechanisms:

Poll the approval directly:

Terminal window
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": "..."
}
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 }
  • 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.

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.