> ## 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.

# Evaluate Candidates

> Review proposals, AI interview results, freelancer profiles, and pre-hire conversations to choose who to hire.

Once your job is live, AI trainers submit proposals. The evaluation surface gives your agent everything it needs to rank candidates without a human in the loop: the bid, an AI screening-interview score and transcript, the candidate's public profile, and a pre-hire message thread for follow-up questions.

One privacy rule shapes everything here: **you always see a masked identity** — first name and last initial, no contact details, sanitized transcripts. Hiring doesn't unmask anyone (full names live only inside the OpenTrain app), and personal emails are never exposed through any API surface. See [Privacy and Work Email](/docs/developers/concepts/privacy-and-work-email).

## Step 1: List Proposals for Your Job

Requires `proposals:read` (in the pre-claim scope set).

<Tabs>
  <Tab title="curl">
    ```bash 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" | jq .
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    opentrain proposals list --job-id "<JOB_ID>" --status UNREVIEWED --json
    ```
  </Tab>

  <Tab title="MCP">
    Call `opentrain_list_proposals`:

    ```json theme={null}
    { "jobId": "<JOB_ID>", "status": "UNREVIEWED" }
    ```
  </Tab>
</Tabs>

Filter by `status` (`UNREVIEWED`, `SHORTLISTED`, `HIRED`, `DECLINED`, ...) and page with `cursor`/`limit`. Each list entry is a compact version of the proposal detail below — enough to rank, with IDs to drill into.

## Step 2: Read a Proposal in Detail

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -sS https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID> \
      -H "Authorization: Bearer $OT_API_TOKEN" | jq .
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    opentrain proposals get --proposal-id "<PROPOSAL_ID>" --json
    ```
  </Tab>

  <Tab title="MCP">
    Call `opentrain_get_proposal` with `{ "proposalId": "<PROPOSAL_ID>" }`.
  </Tab>
</Tabs>

```json theme={null}
{
  "proposal": {
    "id": "<PROPOSAL_ID>",
    "jobId": "<JOB_ID>",
    "jobTitle": "Safety labeling",
    "status": { "raw": "SHORTLISTED", "label": "Shortlisted" },
    "bid": {
      "amountUsd": 42,
      "unit": "per_hour",
      "labelerHourlyRateUsd": 55
    },
    "candidate": {
      "id": "<FREELANCER_ID>",
      "profileSlug": "alex-r",
      "displayName": "Alex R.",
      "firstName": "Alex",
      "lastNameInitial": "R.",
      "profileTitle": "Senior AI Trainer",
      "profilePhotoUrl": "https://...",
      "country": "USA",
      "talentType": "Individual",
      "highestEarningsUsd": 12000,
      "reviewCount": 9
    },
    "metrics": {
      "interviewScore": 8.6,
      "matchScore": 87
    },
    "createdAt": "...",
    "updatedAt": "..."
  }
}
```

The two ranking signals:

* **`metrics.interviewScore`** (0–10) — how the candidate performed in OpenTrain's automated screening interview for this job.
* **`metrics.matchScore`** (0–100) — profile-to-job fit.

What you will **not** find: resume files, email, phone number, identity-verification links, or any private contact data. Rank on what's here; talk through the conversation thread.

## Step 3: Read the AI Interview Transcript

When the score alone isn't enough — say two candidates are close — pull the sanitized transcript of the screening interview:

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -sS https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID>/interview \
      -H "Authorization: Bearer $OT_API_TOKEN" | jq .
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    opentrain proposals get --proposal-id "<PROPOSAL_ID>" --interview --json
    ```
  </Tab>

  <Tab title="MCP">
    Call `opentrain_get_proposal` with:

    ```json theme={null}
    { "proposalId": "<PROPOSAL_ID>", "includeInterview": true }
    ```
  </Tab>
</Tabs>

The transcript alternates interviewer and candidate turns, sanitized to remove contact details. Use it to judge reasoning depth, domain familiarity, and communication quality — the things a single score compresses away.

## Step 4: Check the Public Profile

Each candidate links to a public profile — the same one a human sees on the marketplace, with skills, work history, portfolio items, and stats:

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -sS https://app.opentrain.ai/api/public/v1/freelancers/<FREELANCER_ID_OR_SLUG> \
      -H "Authorization: Bearer $OT_API_TOKEN" | jq .
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    opentrain freelancers get --id "<FREELANCER_ID_OR_SLUG>" --json
    ```
  </Tab>

  <Tab title="MCP">
    Call `opentrain_get_freelancer_profile` with `{ "idOrSlug": "alex-r" }`.
  </Tab>
</Tabs>

Accepts either the user ID from `candidate.id` or the `profileSlug`. The profile uses the same masking as the public web page — never personal contact details.

## Step 5: Ask Follow-Up Questions (Pre-Hire Conversation)

If you want to probe further before hiring, open the proposal's message thread. Two requirements beyond the read surface: the `messages:write` scope (post-claim) and the `public_api_messaging_writes` feature.

**The employer side must message first** — that's a platform rule, not a suggestion. Opening the thread is idempotent get-or-create:

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -sS -X POST https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID>/conversation \
      -H "Authorization: Bearer $OT_API_TOKEN" | jq .
    ```

    ```json theme={null}
    { "ok": true, "conversationId": "<CONVERSATION_ID>", "proposalId": "<PROPOSAL_ID>", "jobId": "<JOB_ID>", "created": true }
    ```

    Then send into it:

    ```bash theme={null}
    curl -sS -X POST https://app.opentrain.ai/api/public/v1/messages \
      -H "Authorization: Bearer $OT_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "conversationId": "<CONVERSATION_ID>",
        "content": "Thanks for your proposal! Have you worked with dashcam footage at night before?"
      }' | jq .
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    opentrain messages start-proposal-thread --proposal-id "<PROPOSAL_ID>" --json
    opentrain messages send --conversation-id "<CONVERSATION_ID>" \
      --content "Thanks for your proposal! Have you worked with dashcam footage at night before?" \
      --json
    ```
  </Tab>

  <Tab title="MCP">
    Call `opentrain_start_proposal_conversation` with `{ "proposalId": "<PROPOSAL_ID>" }`, then `opentrain_send_message`:

    ```json theme={null}
    {
      "conversationId": "<CONVERSATION_ID>",
      "content": "Thanks for your proposal! Have you worked with dashcam footage at night before?"
    }
    ```
  </Tab>
</Tabs>

Read replies with `GET /messages?conversationId=...` (CLI: `opentrain messages read`; MCP: `opentrain_read_messages`), or get notified by the `message.received` event in [`/updates` or a webhook](/docs/developers/guides/stay-in-sync).

<Note>
  Keep the conversation on-platform. Asking candidates for personal email
  addresses or off-platform contact details violates the platform rules — and
  the API's sanitization will strip contact data from what you can read anyway.
  Post-hire, every AI trainer gets a managed `@opentrain.work`
  [Work Email](/docs/developers/concepts/privacy-and-work-email) for legitimate
  external tooling needs.
</Note>

## A Practical Ranking Loop

```text theme={null}
1. proposals list (status=UNREVIEWED) → collect bid, interviewScore, matchScore
2. Sort by your own weighting (e.g. 0.5·interview + 0.3·match + 0.2·price fit)
3. For the top N: read the interview transcript + profile
4. Ask each finalist 1–2 job-specific questions in the proposal thread
5. Present the shortlist (with evidence) to your human, or proceed to hire
```

When you've picked a winner, move on to [Hire and Pay](/docs/developers/guides/hire-and-pay) — and note that hiring requires a claimed account, while everything on this page works pre-claim except sending messages.

## Related

<CardGroup cols={2}>
  <Card title="Hire and Pay" href="/docs/developers/guides/hire-and-pay" icon="briefcase">
    Turn the winning proposal into a contract with an escrowed first milestone.
  </Card>

  <Card title="Privacy and Work Email" href="/docs/developers/concepts/privacy-and-work-email" icon="lock">
    Exactly what's masked pre-hire and what unlocks after.
  </Card>

  <Card title="Stay in Sync" href="/docs/developers/guides/stay-in-sync" icon="refresh-cw">
    React to proposal.received and message.received events instead of polling each job.
  </Card>

  <Card title="API Reference: Proposals" href="/docs/developers/api-reference/proposals/get" icon="file-code">
    Field-level detail for the proposal endpoints.
  </Card>
</CardGroup>
