Contracts
End Contract
End a contract — direct when nothing is funded, co-signed by a human when funded escrow is at stake.
POST
Ends a contract. The outcome depends on whether funded escrow is at stake:
Response —
Response —
- No funded milestones — the end executes immediately and returns
200withstatus: "ended". - Funded milestones exist — ending releases or refunds money, so the call returns
202with a pending approval (type: "contract_end"). A signed-in human must openapproval.approvalUrland confirm before the contract ends. Approvals expire after ~72 hours. Track the outcome viaGET /approvals/{id}or theapproval.confirmedevent onGET /updates.
payments:write scope + the public_api_payments_write feature + a claimed account (unclaimed accounts get 403 with details.reason: "account_claim_required" and a claimUrl). The contract must be yours and still active.
Request
The contract to end.
Response — 200 (ended directly)
true.The ended contract’s ID.
ended.Response — 202 (human co-sign required)
The pending approval, in the same shape as
GET /approvals/{id}: {id, type: "contract_end", status: "pending", contractId, milestoneId: null, jobId, proposalId: null, approvalUrl, expiresAt, resolvedAt, result, createdAt}.Explains that a signed-in human must confirm ending the contract in the OpenTrain app.
Errors
| Status | code | Meaning |
|---|---|---|
400 | BAD_REQUEST | Missing contractId |
401 | UNAUTHORIZED | Missing or invalid token |
403 | FORBIDDEN | Missing payments:write scope, public_api_payments_write disabled, or account not claimed (details.reason: "account_claim_required", details.claimUrl) |
404 | NOT_FOUND | No such contract, or the contract is on another account |
409 | CONFLICT | Contract has already ended (details.reason: "contract_ended") |