Skip to main content
POST
/
api
/
public
/
v1
/
contracts
/
{contractId}
/
milestones
curl -sS -X POST https://app.opentrain.ai/api/public/v1/contracts/<CONTRACT_ID>/milestones \
  -H "Authorization: Bearer $OT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Second labeling batch",
    "description": "Label the next 5,000 posts per the updated guidelines",
    "amountUsd": 300,
    "dueDate": "2026-07-15"
  }'
{
  "milestone": {
    "id": "<MILESTONE_ID>",
    "name": "Second labeling batch",
    "description": "Label the next 5,000 posts per the updated guidelines",
    "status": "NOT_FUNDED",
    "amountUsd": 300,
    "volume": null,
    "milestoneNumber": 2,
    "dueDate": "2026-07-15T00:00:00.000Z",
    "pendingApproval": false,
    "needsReview": false,
    "invoiceId": null,
    "createdAt": "2026-06-12T10:00:00.000Z"
  }
}
Adds a new milestone to an active contract. The milestone is created unfunded (NOT_FUNDED) — no money moves at creation. To put money behind it, request funding afterwards; a signed-in human must confirm that step (see human approvals). The amount must fit the contract’s payment model: fixed-price contracts require amountUsd; hourly and per-label contracts require volume, and when both amountUsd and volume are given the amount must equal the contract rate × volume. Requirements: 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

contractId
string
required
The contract to add the milestone to.
description
string
required
Description of the work to deliver. Must be non-empty.
name
string
Optional short milestone name.
amountUsd
number
Milestone amount in USD. Must be positive. Required for fixed-price contracts.
volume
number
Unit volume (e.g. label count or hours). Must be positive. Required for hourly and per-label contracts.
dueDate
string
Optional due date (ISO 8601).

Response

Returns 201 with the created milestone.
milestone
object

Errors

StatuscodeMeaning
400BAD_REQUESTInvalid JSON, or field errors (details.field names the offender): description missing/empty, amountUsd required for fixed-price contracts, volume required for hourly and per-label contracts, or the amount does not match the contract rate and volume
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENMissing payments:write scope, public_api_payments_write disabled, or account not claimed (details.reason: "account_claim_required", details.claimUrl)
404NOT_FOUNDNo such contract, or the contract is on another account
409CONFLICTdetails.reason: "contract_ended" (contract already ended) or "contract_rate_missing" (contract has no rate to validate against)
curl -sS -X POST https://app.opentrain.ai/api/public/v1/contracts/<CONTRACT_ID>/milestones \
  -H "Authorization: Bearer $OT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Second labeling batch",
    "description": "Label the next 5,000 posts per the updated guidelines",
    "amountUsd": 300,
    "dueDate": "2026-07-15"
  }'
{
  "milestone": {
    "id": "<MILESTONE_ID>",
    "name": "Second labeling batch",
    "description": "Label the next 5,000 posts per the updated guidelines",
    "status": "NOT_FUNDED",
    "amountUsd": 300,
    "volume": null,
    "milestoneNumber": 2,
    "dueDate": "2026-07-15T00:00:00.000Z",
    "pendingApproval": false,
    "needsReview": false,
    "invoiceId": null,
    "createdAt": "2026-06-12T10:00:00.000Z"
  }
}