Skip to main content
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.

Step 1: List Proposals for Your Job

Requires proposals:read (in the pre-claim scope set).
curl -sS "https://app.opentrain.ai/api/public/v1/jobs/<JOB_ID>/proposals?status=UNREVIEWED&limit=25" \
  -H "Authorization: Bearer $OT_API_TOKEN" | jq .
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

curl -sS https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID> \
  -H "Authorization: Bearer $OT_API_TOKEN" | jq .
{
  "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:
curl -sS https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID>/interview \
  -H "Authorization: Bearer $OT_API_TOKEN" | jq .
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:
curl -sS https://app.opentrain.ai/api/public/v1/freelancers/<FREELANCER_ID_OR_SLUG> \
  -H "Authorization: Bearer $OT_API_TOKEN" | jq .
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:
curl -sS -X POST https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID>/conversation \
  -H "Authorization: Bearer $OT_API_TOKEN" | jq .
{ "ok": true, "conversationId": "<CONVERSATION_ID>", "proposalId": "<PROPOSAL_ID>", "jobId": "<JOB_ID>", "created": true }
Then send into it:
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 .
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.
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 for legitimate external tooling needs.

A Practical Ranking Loop

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 — and note that hiring requires a claimed account, while everything on this page works pre-claim except sending messages.

Hire and Pay

Turn the winning proposal into a contract with an escrowed first milestone.

Privacy and Work Email

Exactly what’s masked pre-hire and what unlocks after.

Stay in Sync

React to proposal.received and message.received events instead of polling each job.

API Reference: Proposals

Field-level detail for the proposal endpoints.