> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# List Contracts

> List your contracts (hired AI trainers) with milestones, optionally filtered by job or status.

Lists the contracts on your account — one per hired AI trainer per job — newest first, each with its milestone timeline. Contracts are created by [hiring from a proposal](/docs/developers/api-reference/proposals/hire). The AI trainer's identity stays masked: first name + last initial, country, and profile path (never a full last name or an email — see [privacy](/docs/developers/concepts/privacy-and-work-email)).

Refunded and cancelled milestones are excluded from the milestone timeline, mirroring the in-app contract view. For the post-hire conversation ID, read a single contract with [`GET /contracts/{id}`](/docs/developers/api-reference/contracts/get).

**Requirements:** `payments:read` scope. Works pre-claim.

## Request

<ParamField query="jobId" type="string">
  Only return contracts on this job. Must be a job you own or can access (`403` otherwise; `404` if the job doesn't exist).
</ParamField>

<ParamField query="status" type="string">
  Filter by contract status: `active` or `ended`. Omit to list both.
</ParamField>

## Response

<ResponseField name="contracts" type="object[]">
  <Expandable title="contract entry">
    <ResponseField name="id" type="string">
      Contract ID — use with [`GET /contracts/{id}`](/docs/developers/api-reference/contracts/get), [`POST /contracts/{id}/milestones`](/docs/developers/api-reference/contracts/create-milestone), and [`POST /contracts/{id}/end`](/docs/developers/api-reference/contracts/end).
    </ResponseField>

    <ResponseField name="status" type="string">
      `active` or `ended`.
    </ResponseField>

    <ResponseField name="title" type="string | null">
      Contract display title.
    </ResponseField>

    <ResponseField name="jobId" type="string | null">
      The job this contract belongs to.
    </ResponseField>

    <ResponseField name="proposalId" type="string | null">
      The proposal the hire came from.
    </ResponseField>

    <ResponseField name="paymentType" type="string | null">
      Payment model: `FIXED_PRICE`, `PAY_PER_HOUR`, or `PAY_PER_LABEL`.
    </ResponseField>

    <ResponseField name="rateUsd" type="number | null">
      Contract rate in USD (per hour / per label for per-unit contracts; total for fixed-price).
    </ResponseField>

    <ResponseField name="estimatedTotalUsd" type="number | null">
      Estimated total contract value in USD.
    </ResponseField>

    <ResponseField name="estimatedVolume" type="number | null">
      Estimated unit volume for per-unit contracts.
    </ResponseField>

    <ResponseField name="hasActiveMilestone" type="boolean">
      `true` when the contract currently has a funded, in-progress milestone.
    </ResponseField>

    <ResponseField name="startDate" type="string | null">
      ISO contract start timestamp.
    </ResponseField>

    <ResponseField name="endDate" type="string | null">
      ISO contract end timestamp, `null` while active.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO creation timestamp.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO last-change timestamp.
    </ResponseField>

    <ResponseField name="freelancer" type="object">
      The hired AI trainer: `{userId, displayName, country, profilePath}`. `displayName` is masked to first name + last initial (`"Maria G."`); full last names and emails are never included.
    </ResponseField>

    <ResponseField name="milestones" type="object[]">
      Milestone timeline, ordered by milestone number.

      <Expandable title="milestone entry">
        <ResponseField name="id" type="string">
          Milestone ID — use with [`POST /milestones/{id}/fund`](/docs/developers/api-reference/milestones/fund) and [`POST /milestones/{id}/approve`](/docs/developers/api-reference/milestones/approve).
        </ResponseField>

        <ResponseField name="name" type="string | null">
          Short milestone name.
        </ResponseField>

        <ResponseField name="description" type="string | null">
          Work to deliver.
        </ResponseField>

        <ResponseField name="status" type="string | null">
          `NOT_FUNDED`, `ACTIVE_FUNDED`, or `COMPLETED`.
        </ResponseField>

        <ResponseField name="amountUsd" type="number | null">
          Milestone amount in USD.
        </ResponseField>

        <ResponseField name="volume" type="number | null">
          Unit volume for per-unit milestones.
        </ResponseField>

        <ResponseField name="milestoneNumber" type="number | null">
          Position in the contract's milestone sequence.
        </ResponseField>

        <ResponseField name="dueDate" type="string | null">
          ISO due date.
        </ResponseField>

        <ResponseField name="pendingApproval" type="boolean">
          `true` when the AI trainer has submitted work and the milestone awaits your approval.
        </ResponseField>

        <ResponseField name="needsReview" type="boolean">
          `true` when the milestone is flagged for review.
        </ResponseField>

        <ResponseField name="invoiceId" type="string | null">
          Linked invoice once funded/paid.
        </ResponseField>

        <ResponseField name="createdAt" type="string">
          ISO creation timestamp.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Errors

| Status | `code`         | Meaning                                                              |
| ------ | -------------- | -------------------------------------------------------------------- |
| `400`  | `BAD_REQUEST`  | `status` is not `active` or `ended` (`details: {field: "status"}`)   |
| `401`  | `UNAUTHORIZED` | Missing or invalid token                                             |
| `403`  | `FORBIDDEN`    | Missing `payments:read` scope, or `jobId` belongs to another account |
| `404`  | `NOT_FOUND`    | `jobId` does not exist                                               |

<RequestExample>
  ```bash curl theme={null}
  curl -sS "https://app.opentrain.ai/api/public/v1/contracts?status=active" \
    -H "Authorization: Bearer $OT_API_TOKEN"
  ```

  ```bash CLI theme={null}
  opentrain contracts list --status active --json
  ```

  ```json MCP: opentrain_list_contracts theme={null}
  {
    "status": "active"
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "contracts": [
      {
        "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"
          }
        ]
      }
    ]
  }
  ```
</ResponseExample>
