curl -sS https://app.opentrain.ai/api/public/v1/contracts/<CONTRACT_ID> \
-H "Authorization: Bearer $OT_API_TOKEN"
opentrain contracts get --contract-id <CONTRACT_ID> --json
{
"contractId": "<CONTRACT_ID>"
}
{
"contract": {
"id": "<CONTRACT_ID>",
"status": "active",
"title": "Spanish Sentiment Labeling Contract",
"jobId": "<JOB_ID>",
"proposalId": "<PROPOSAL_ID>",
"paymentType": "FIXED_PRICE",
"rateUsd": 300,
"estimatedTotalUsd": 300,
"estimatedVolume": null,
"hasActiveMilestone": true,
"startDate": "2026-06-12T09:30:00.000Z",
"endDate": null,
"createdAt": "2026-06-12T09:30:00.000Z",
"updatedAt": "2026-06-12T09:30:00.000Z",
"freelancer": {
"userId": "<FREELANCER_ID>",
"displayName": "Maria G.",
"country": "Spain",
"profilePath": "/profile/maria-g"
},
"milestones": [
{
"id": "<MILESTONE_ID>",
"name": "First labeling batch",
"description": "Label the first 5,000 posts per the guidelines",
"status": "ACTIVE_FUNDED",
"amountUsd": 300,
"volume": null,
"milestoneNumber": 1,
"dueDate": "2026-07-01T00:00:00.000Z",
"pendingApproval": false,
"needsReview": false,
"invoiceId": "<INVOICE_ID>",
"createdAt": "2026-06-12T09:30:00.000Z"
}
],
"jobDmConversationId": "<CONVERSATION_ID>",
"budget": {
"contractId": "<CONTRACT_ID>",
"paymentType": "FIXED_PRICE",
"state": "OK",
"fundedVolume": 0,
"fundedAmountUsd": 300,
"consumed": { "seconds": 0, "hours": 0, "labels": 0, "tasks": 0 },
"consumedVolume": 0,
"remainingVolume": 0,
"consumedFraction": 0,
"activeMilestone": {
"id": "<MILESTONE_ID>",
"name": "First labeling batch",
"amountUsd": 300,
"volume": null,
"status": "ACTIVE_FUNDED"
},
"lastUsageAt": null
}
}
}
Contracts
Get Contract
Read one contract in detail: status, milestone timeline, the hired AI trainer, budget state, and the post-hire conversation ID.
GET
/
api
/
public
/
v1
/
contracts
/
{contractId}
curl -sS https://app.opentrain.ai/api/public/v1/contracts/<CONTRACT_ID> \
-H "Authorization: Bearer $OT_API_TOKEN"
opentrain contracts get --contract-id <CONTRACT_ID> --json
{
"contractId": "<CONTRACT_ID>"
}
{
"contract": {
"id": "<CONTRACT_ID>",
"status": "active",
"title": "Spanish Sentiment Labeling Contract",
"jobId": "<JOB_ID>",
"proposalId": "<PROPOSAL_ID>",
"paymentType": "FIXED_PRICE",
"rateUsd": 300,
"estimatedTotalUsd": 300,
"estimatedVolume": null,
"hasActiveMilestone": true,
"startDate": "2026-06-12T09:30:00.000Z",
"endDate": null,
"createdAt": "2026-06-12T09:30:00.000Z",
"updatedAt": "2026-06-12T09:30:00.000Z",
"freelancer": {
"userId": "<FREELANCER_ID>",
"displayName": "Maria G.",
"country": "Spain",
"profilePath": "/profile/maria-g"
},
"milestones": [
{
"id": "<MILESTONE_ID>",
"name": "First labeling batch",
"description": "Label the first 5,000 posts per the guidelines",
"status": "ACTIVE_FUNDED",
"amountUsd": 300,
"volume": null,
"milestoneNumber": 1,
"dueDate": "2026-07-01T00:00:00.000Z",
"pendingApproval": false,
"needsReview": false,
"invoiceId": "<INVOICE_ID>",
"createdAt": "2026-06-12T09:30:00.000Z"
}
],
"jobDmConversationId": "<CONVERSATION_ID>",
"budget": {
"contractId": "<CONTRACT_ID>",
"paymentType": "FIXED_PRICE",
"state": "OK",
"fundedVolume": 0,
"fundedAmountUsd": 300,
"consumed": { "seconds": 0, "hours": 0, "labels": 0, "tasks": 0 },
"consumedVolume": 0,
"remainingVolume": 0,
"consumedFraction": 0,
"activeMilestone": {
"id": "<MILESTONE_ID>",
"name": "First labeling batch",
"amountUsd": 300,
"volume": null,
"status": "ACTIVE_FUNDED"
},
"lastUsageAt": null
}
}
}
Reads one contract in full, in the same shape as the entries on
GET /contracts plus two detail-only fields: jobDmConversationId — the post-hire 1:1 thread with the hired AI trainer, usable with GET /messages and POST /messages — and budget, the funded-vs-consumed budget snapshot.
Unknown contract IDs and contracts on other accounts both return 404, so contract IDs cannot be probed.
Requirements: payments:read scope. Works pre-claim.
Request
string
required
The contract to read.
Response
object
Hide contract
Hide contract
string
Contract ID.
string
active or ended.string | null
Contract display title.
string | null
The job this contract belongs to.
string | null
The proposal the hire came from.
string | null
FIXED_PRICE, PAY_PER_HOUR, or PAY_PER_LABEL.number | null
Contract rate in USD.
number | null
Estimated total contract value in USD.
number | null
Estimated unit volume for per-unit contracts.
boolean
true when a funded milestone is in progress.string | null
ISO contract start timestamp.
string | null
ISO contract end timestamp,
null while active.string
ISO creation timestamp.
string
ISO last-change timestamp.
object
The hired AI trainer:
{userId, displayName, country, profilePath}. displayName is masked to first name + last initial ("Maria G.") — full last names and emails never appear (see privacy).object[]
Milestone timeline — same entry shape as on
GET /contracts: {id, name, description, status, amountUsd, volume, milestoneNumber, dueDate, pendingApproval, needsReview, invoiceId, createdAt}.string | null
The post-hire 1:1 conversation with the hired AI trainer. Pass to
GET /messages to read it or POST /messages to message them.object | null
Funded milestones vs work consumed:
{contractId, paymentType, state, fundedVolume, fundedAmountUsd, consumed: {seconds, hours, labels, tasks}, consumedVolume, remainingVolume, consumedFraction, activeMilestone, lastUsageAt}. state is OK, LOW (≥ 80% of funded volume consumed), or DEPLETED (≥ 100%); volume is hours for PAY_PER_HOUR, labels for PAY_PER_LABEL; FIXED_PRICE contracts track progress only and always report OK. State changes emit the contract.budget_state_changed event — when it lands on LOW or DEPLETED, the usual move is to propose funding the next milestone. null when the budget cannot be computed. Only this detail endpoint returns it, not the list.Errors
| Status | code | Meaning |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid token |
403 | FORBIDDEN | Missing payments:read scope |
404 | NOT_FOUND | No such contract, or the contract is on another account (details: {resource: "contracts", contractId}) |
curl -sS https://app.opentrain.ai/api/public/v1/contracts/<CONTRACT_ID> \
-H "Authorization: Bearer $OT_API_TOKEN"
opentrain contracts get --contract-id <CONTRACT_ID> --json
{
"contractId": "<CONTRACT_ID>"
}
{
"contract": {
"id": "<CONTRACT_ID>",
"status": "active",
"title": "Spanish Sentiment Labeling Contract",
"jobId": "<JOB_ID>",
"proposalId": "<PROPOSAL_ID>",
"paymentType": "FIXED_PRICE",
"rateUsd": 300,
"estimatedTotalUsd": 300,
"estimatedVolume": null,
"hasActiveMilestone": true,
"startDate": "2026-06-12T09:30:00.000Z",
"endDate": null,
"createdAt": "2026-06-12T09:30:00.000Z",
"updatedAt": "2026-06-12T09:30:00.000Z",
"freelancer": {
"userId": "<FREELANCER_ID>",
"displayName": "Maria G.",
"country": "Spain",
"profilePath": "/profile/maria-g"
},
"milestones": [
{
"id": "<MILESTONE_ID>",
"name": "First labeling batch",
"description": "Label the first 5,000 posts per the guidelines",
"status": "ACTIVE_FUNDED",
"amountUsd": 300,
"volume": null,
"milestoneNumber": 1,
"dueDate": "2026-07-01T00:00:00.000Z",
"pendingApproval": false,
"needsReview": false,
"invoiceId": "<INVOICE_ID>",
"createdAt": "2026-06-12T09:30:00.000Z"
}
],
"jobDmConversationId": "<CONVERSATION_ID>",
"budget": {
"contractId": "<CONTRACT_ID>",
"paymentType": "FIXED_PRICE",
"state": "OK",
"fundedVolume": 0,
"fundedAmountUsd": 300,
"consumed": { "seconds": 0, "hours": 0, "labels": 0, "tasks": 0 },
"consumedVolume": 0,
"remainingVolume": 0,
"consumedFraction": 0,
"activeMilestone": {
"id": "<MILESTONE_ID>",
"name": "First labeling batch",
"amountUsd": 300,
"volume": null,
"status": "ACTIVE_FUNDED"
},
"lastUsageAt": null
}
}
}
⌘I