docs reference

Examples

Shortlist, rerun, enroll, and reply triage.

Four recipes. The shell blocks are the ones the test suite runs against a real server. They default to the public hosts; set RECRUITMCP_BASE and RECRUITMCP_MCP to talk to a local server.

Shortlist for a job spec

Resolve the place, plan from the job text, and stop at the estimate. Nothing is spent.

#!/usr/bin/env bash
set -euo pipefail
DOMAIN="${RECRUITMCP_DOMAIN:-recruitmcp.com}"
BASE="${RECRUITMCP_BASE:-https://api.${DOMAIN}}"
MCP="${RECRUITMCP_MCP:-https://mcp.${DOMAIN}/mcp}"
WS=$(curl -fsS -X POST "$BASE/v1/workspaces" \
  -H 'content-type: application/json' \
  -d '{"name":"Northwind Search","email":"ada@northwind.example"}')
KEY=$(node -e 'const j=JSON.parse(process.argv[1]); if(!String(j.api_key||"").startsWith("rmcp_live_")) process.exit(1); process.stdout.write(j.api_key)' "$WS")

PLACES=$(curl -fsS "$BASE/v1/lookups/locations?q=Gent,%20Belgium&prefer_area=true" \
  -H "authorization: Bearer $KEY")
node -e 'const j=JSON.parse(process.argv[1]); if(!/^[0-9]+$/.test(j.data?.[0]?.geo_id||"")) { console.error(j); process.exit(1); }' "$PLACES"

PLAN=$(curl -fsS -X POST "$BASE/v1/search-plans" \
  -H "authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d '{"job_description":"Senior backend engineer in Gent, Belgium. Go, 5+ years.","target":40}')
node -e 'const j=JSON.parse(process.argv[1]); if(j.estimate?.requires_confirm!==true||typeof j.estimate.credits_min!=="number"||!j.tiers?.[0]?.id||!j.tiers[0].label||!j.summary){console.error(j); process.exit(1)}' "$PLAN"

PID=$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).id)' "$PLAN")
DRY=$(curl -fsS -X POST "$BASE/v1/runs?dry_run=true" \
  -H "authorization: Bearer $KEY" \
  -H 'content-type: application/json' \
  -d "$(node -e 'process.stdout.write(JSON.stringify({plan_id:process.argv[1]}))' "$PID")")
node -e 'const j=JSON.parse(process.argv[1]); if(j.estimate?.requires_confirm!==true||j.run){console.error(j); process.exit(1)}' "$DRY"
echo "$PLAN"

Ask Claude: "Start a search for this role. Show me the summary, credits_max, and three facts you would read for each candidate. Do not score them."

Re-run without paying twice

The same Idempotency-Key and body returns the original 202 and the same run. The balance does not move a second time. Checkpoints cover any later run over the same query_key.

#!/usr/bin/env bash
set -euo pipefail
DOMAIN="${RECRUITMCP_DOMAIN:-recruitmcp.com}"
BASE="${RECRUITMCP_BASE:-https://api.${DOMAIN}}"
MCP="${RECRUITMCP_MCP:-https://mcp.${DOMAIN}/mcp}"
WS=$(curl -fsS -X POST "$BASE/v1/workspaces" -H 'content-type: application/json' -d '{"name":"Northwind Search","email":"ada@northwind.example"}')
KEY=$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).api_key)' "$WS")
BODY='{"job_description":"Senior backend engineer in Gent, Belgium. Go, 5+ years.","target":40,"budget":{"max_credits":10}}'
ONE=$(curl -fsS -X POST "$BASE/v1/runs" -H "authorization: Bearer $KEY" -H 'content-type: application/json' -H 'idempotency-key: rerun-gent' -d "$BODY")
BAL1=$(curl -fsS "$BASE/v1/workspace" -H "authorization: Bearer $KEY")
TWO=$(curl -fsS -X POST "$BASE/v1/runs" -H "authorization: Bearer $KEY" -H 'content-type: application/json' -H 'idempotency-key: rerun-gent' -d "$BODY")
BAL2=$(curl -fsS "$BASE/v1/workspace" -H "authorization: Bearer $KEY")
node -e 'const [one,two,a,b]=process.argv.slice(1).map((v)=>JSON.parse(v)); if(!one.run?.id||one.run.id!==two.run?.id) process.exit(1); if(a.balance_credits!==b.balance_credits) process.exit(2);' "$ONE" "$TWO" "$BAL1" "$BAL2"

Enroll with approval

Needs RECRUITMCP_KEY and the id of a connected outreach account in ACCOUNT_ID. The script saves a sequence with approve_every_message, previews one fictional person, enrolls them, pauses, reads today's limits, and shows that raising the daily invite cap above 20 needs risk_acknowledged.

#!/usr/bin/env bash
set -euo pipefail
DOMAIN="${RECRUITMCP_DOMAIN:-recruitmcp.com}"
BASE="${RECRUITMCP_BASE:-https://api.${DOMAIN}}"
MCP="${RECRUITMCP_MCP:-https://mcp.${DOMAIN}/mcp}"
: "${RECRUITMCP_KEY:?Set RECRUITMCP_KEY to an API key with the outreach scopes.}"
: "${ACCOUNT_ID:?Set ACCOUNT_ID to a connected account (GET /v1/outreach/accounts).}"
KEY="$RECRUITMCP_KEY"
SEQ=$(curl -fsS -X POST "$BASE/v1/outreach/sequences" \
  -H "authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d "$(node -e 'process.stdout.write(JSON.stringify({account_id:process.argv[1],name:"Backend Ghent",approve_every_message:true,steps:[{channel:"linkedin",type:"invite",wait_days:0,note:"Hi {{first_name}}, I work with backend teams in Ghent."},{channel:"linkedin",type:"message",wait_days:2,only_after_accept:true,body:"Hi {{first_name}}, a senior backend role in Ghent. Open to a look?"}]}))' "$ACCOUNT_ID")")
SID=$(node -e 'const j=JSON.parse(process.argv[1]); if(j.approve_every_message!==true) process.exit(1); process.stdout.write(j.id)' "$SEQ")
BODY=$(node -e 'process.stdout.write(JSON.stringify({sequence_id:process.argv[1],candidates:[{profile_url:"https://www.linkedin.com/in/a-veld-example",full_name:"A. Veld",company:"Northwind Labs"}]}))' "$SID")
PREVIEW=$(curl -fsS -X POST "$BASE/v1/outreach/enrollments?dry_run=true" -H "authorization: Bearer $KEY" -H 'content-type: application/json' -d "$BODY")
node -e 'const j=JSON.parse(process.argv[1]); if(j.requires_confirm!==true||j.enrolled!=null){console.error(j); process.exit(1)}' "$PREVIEW"
ENR=$(curl -fsS -X POST "$BASE/v1/outreach/enrollments" -H "authorization: Bearer $KEY" -H 'content-type: application/json' -H 'idempotency-key: enroll-a-veld' -d "$BODY")
EID=$(node -e 'const j=JSON.parse(process.argv[1]); if(j.enrolled!==1){console.error(j); process.exit(1)} process.stdout.write(j.enrollments[0].id)' "$ENR")
PAUSED=$(curl -fsS -X POST "$BASE/v1/outreach/enrollments/$EID/pause" -H "authorization: Bearer $KEY")
node -e 'const j=JSON.parse(process.argv[1]); if(j.status!=="paused"){console.error(j); process.exit(1)}' "$PAUSED"
LIMITS=$(curl -fsS "$BASE/v1/outreach/accounts/$ACCOUNT_ID/limits" -H "authorization: Bearer $KEY")
node -e 'const j=JSON.parse(process.argv[1]); const cap=20; if(j.invites.daily_limit>cap||j.invites.daily_limit<1){console.error(j); process.exit(1)}' "$LIMITS"
RAISE=$(curl -sS -w '\n%{http_code}' -X PATCH "$BASE/v1/outreach/accounts/$ACCOUNT_ID/limits" -H "authorization: Bearer $KEY" -H 'content-type: application/json' -d '{"limits":{"invitations_per_day":21}}')
node -e 'const raw=process.argv[1]; const code=raw.trim().split(/\n/).pop(); if(code!=="400"){console.error(raw); process.exit(1)}' "$RAISE"
ACK=$(curl -sS -w '\n%{http_code}' -X PATCH "$BASE/v1/outreach/accounts/$ACCOUNT_ID/limits" -H "authorization: Bearer $KEY" -H 'content-type: application/json' -d '{"risk_acknowledged":true,"limits":{"invitations_per_day":21}}')
node -e 'const raw=process.argv[1]; const lines=raw.trim().split(/\n/); const code=lines.pop(); const body=JSON.parse(lines.join("\n")); if(code!=="200"||typeof body.risk_warning!=="string"||!/warmup/i.test(body.risk_warning)){console.error(raw); process.exit(1)}' "$ACK"

The person in that script is fictional: A. Veld at Northwind Labs.

Daily reply triage

Over MCP, use the daily_reply_triage prompt. Over REST, GET /v1/outreach/conversations?needs_reply=true. Keep negative replies (already do_not_contact) apart from messages that need an answer, draft replies, and send each one only after the recruiter approves it.

For push instead of a morning poll, subscribe a webhook endpoint to reply.received.

Ask Claude

Morning triage. Who replied, who declined, and which drafts are waiting for me. Do not send anything.

API reference