DEVELOPER DOCUMENTATION
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.
Step 1: List Proposals for Your Job
Section titled “Step 1: List Proposals for Your Job”Requires proposals:read (in the pre-claim scope set).
curl
curl -sS "https://app.opentrain.ai/api/public/v1/jobs/<JOB_ID>/proposals?status=UNREVIEWED&limit=25" \ -H "Authorization: Bearer $OT_API_TOKEN" | jq .CLI
opentrain proposals list --job-id "<JOB_ID>" --status UNREVIEWED --jsonMCP
Call opentrain_list_proposals:
{ "jobId": "<JOB_ID>", "status": "UNREVIEWED" }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
Section titled “Step 2: Read a Proposal in Detail”curl
curl -sS https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID> \ -H "Authorization: Bearer $OT_API_TOKEN" | jq .CLI
opentrain proposals get --proposal-id "<PROPOSAL_ID>" --jsonMCP
Call opentrain_get_proposal with { "proposalId": "<PROPOSAL_ID>" }.
{ "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
Section titled “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
curl -sS https://app.opentrain.ai/api/public/v1/proposals/<PROPOSAL_ID>/interview \ -H "Authorization: Bearer $OT_API_TOKEN" | jq .CLI
opentrain proposals get --proposal-id "<PROPOSAL_ID>" --interview --jsonMCP
Call opentrain_get_proposal with:
{ "proposalId": "<PROPOSAL_ID>", "includeInterview": true }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
Section titled “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
curl -sS https://app.opentrain.ai/api/public/v1/freelancers/<FREELANCER_ID_OR_SLUG> \ -H "Authorization: Bearer $OT_API_TOKEN" | jq .CLI
opentrain freelancers get --id "<FREELANCER_ID_OR_SLUG>" --jsonMCP
Call opentrain_get_freelancer_profile with { "idOrSlug": "alex-r" }.
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)
Section titled “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
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 .CLI
opentrain messages start-proposal-thread --proposal-id "<PROPOSAL_ID>" --jsonopentrain messages send --conversation-id "<CONVERSATION_ID>" \ --content "Thanks for your proposal! Have you worked with dashcam footage at night before?" \ --jsonMCP
Call opentrain_start_proposal_conversation with { "proposalId": "<PROPOSAL_ID>" }, then opentrain_send_message:
{ "conversationId": "<CONVERSATION_ID>", "content": "Thanks for your proposal! Have you worked with dashcam footage at night before?"}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.
A Practical Ranking Loop
Section titled “A Practical Ranking Loop”1. proposals list (status=UNREVIEWED) → collect bid, interviewScore, matchScore2. 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 + profile4. Ask each finalist 1–2 job-specific questions in the proposal thread5. Present the shortlist (with evidence) to your human, or proceed to hireWhen 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.
Related
Section titled “Related”Turn the winning proposal into a contract with an escrowed first milestone.
Exactly what’s masked pre-hire and what unlocks after.
React to proposal.received and message.received events instead of polling each job.
Field-level detail for the proposal endpoints.