> ## 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 Job Proposals

> List proposals submitted to a job you own, with bids, AI-interview scores, and candidate summaries.

Lists the proposals on one of your jobs, newest first — each with the candidate's bid, AI-interview score, and a privacy-safe candidate summary. This is the entry point of the [candidate evaluation flow](/docs/developers/guides/evaluate-candidates): list here, then drill into [`GET /proposals/{id}`](/docs/developers/api-reference/proposals/get) and the [interview transcript](/docs/developers/api-reference/proposals/interview).

Candidate identity is masked pre-hire: first name + last initial, no contact details. Personal emails are never exposed at any stage — see [privacy](/docs/developers/concepts/privacy-and-work-email).

**Requirements:** `proposals:read` scope. The job must be yours (`403` otherwise). Works pre-claim.

## Request

<ParamField path="id" type="string" required>
  Your job's ID.
</ParamField>

<ParamField query="status" type="string">
  Filter by proposal status: `UNREVIEWED`, `SHORTLISTED`, `MAYBE`, `HIRED`, `DECLINED`, `NOT_A_FIT`, or `RESUME_SENT`. Case-insensitive; spaces and hyphens are normalized to underscores. Invalid values return `400` with `details.supportedStatuses`.
</ParamField>

<ParamField query="limit" type="number" default="25">
  Page size, max 100. Below 1 → `400`.
</ParamField>

<ParamField query="cursor" type="string">
  Pagination cursor (a proposal ID) from a previous response's `nextCursor`.
</ParamField>

## Response

<ResponseField name="proposals" type="object[]">
  Proposals ordered by creation date, newest first.

  <Expandable title="proposal entry">
    <ResponseField name="id" type="string">
      Proposal ID.
    </ResponseField>

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

    <ResponseField name="jobTitle" type="string">
      Job title (denormalized for convenience).
    </ResponseField>

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

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

    <ResponseField name="status" type="object">
      `{raw, label}` — machine value (e.g. `UNREVIEWED`) plus display label.
    </ResponseField>

    <ResponseField name="bid" type="object">
      `{amountUsd, unit, labelerHourlyRateUsd}` — the candidate's asking rate and its unit.
    </ResponseField>

    <ResponseField name="candidate" type="object">
      Privacy-safe candidate summary: `{id, profileSlug, displayName, firstName, lastNameInitial, profileTitle, profilePhotoUrl, countryCode, country, talentType, highestEarningsUsd, reviewCount}`. Use `id` or `profileSlug` with [`GET /freelancers/{idOrSlug}`](/docs/developers/api-reference/freelancers/get) for the full profile.
    </ResponseField>

    <ResponseField name="metrics" type="object">
      `{interviewScore, matchScore}` — AI-interview score and job-match score, `null` when not available.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="nextCursor" type="string | null">
  Pass back as `cursor` for the next page; `null` at the end.
</ResponseField>

## Errors

| Status | `code`         | Meaning                                                               |
| ------ | -------------- | --------------------------------------------------------------------- |
| `400`  | `BAD_REQUEST`  | Invalid `status` (`details.supportedStatuses`) or `limit` below 1     |
| `401`  | `UNAUTHORIZED` | Missing or invalid token                                              |
| `403`  | `FORBIDDEN`    | Missing `proposals:read` scope, or the job belongs to another account |
| `404`  | `NOT_FOUND`    | No such job                                                           |

<RequestExample>
  ```bash curl theme={null}
  curl -sS "https://app.opentrain.ai/api/public/v1/jobs/<JOB_ID>/proposals?status=UNREVIEWED&limit=25" \
    -H "Authorization: Bearer $OT_API_TOKEN"
  ```

  ```bash CLI theme={null}
  opentrain proposals list --job-id <JOB_ID> --status UNREVIEWED --limit 25 --json
  ```

  ```json MCP: opentrain_list_proposals theme={null}
  {
    "jobId": "<JOB_ID>",
    "status": "UNREVIEWED",
    "limit": 25
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "proposals": [
      {
        "id": "<PROPOSAL_ID>",
        "jobId": "<JOB_ID>",
        "jobTitle": "Spanish Sentiment Labeling — Social Media Posts",
        "createdAt": "2026-06-11T15:40:00.000Z",
        "updatedAt": "2026-06-11T15:40:00.000Z",
        "status": {
          "raw": "UNREVIEWED",
          "label": "Unreviewed"
        },
        "bid": {
          "amountUsd": 0.06,
          "unit": "PER_LABEL",
          "labelerHourlyRateUsd": null
        },
        "candidate": {
          "id": "<FREELANCER_ID>",
          "profileSlug": "maria-g",
          "displayName": "Maria G.",
          "firstName": "Maria",
          "lastNameInitial": "G",
          "profileTitle": "Text Annotation Specialist",
          "profilePhotoUrl": "https://app.opentrain.ai/<PHOTO_PATH>",
          "countryCode": "ES",
          "country": "Spain",
          "talentType": "FREELANCER",
          "highestEarningsUsd": 2400,
          "reviewCount": 7
        },
        "metrics": {
          "interviewScore": 86,
          "matchScore": 0.91
        }
      }
    ],
    "nextCursor": null
  }
  ```
</ResponseExample>
