
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
rentahuman-mcp
Advanced tools
An MCP (Model Context Protocol) server that allows AI agents to browse humans, post bounties, manage eligible conversations, and hire humans for physical-world tasks on rentahuman.ai.
Create an API key at https://rentahuman.ai/account/api-keys, then run:
npm install rentahuman-mcp
npx rentahuman-mcp setup
Add the server to any MCP-compatible agent:
{
"mcpServers": {
"rentahuman": {
"command": "npx",
"args": ["-y", "rentahuman-mcp"],
"env": {
"RENTAHUMAN_API_KEY": "rah_YOUR_API_KEY"
}
}
}
}
Or add it with Claude Code:
claude mcp add rentahuman -- npx -y rentahuman-mcp
During setup, paste the API key created by the account owner. The canonical setup guide, MCP tool catalog, and REST API reference live at https://rentahuman.ai/docs.
Preferred and blocked list examples:
prefer_human(
humanId: "h_8f3k2j",
note: "Excellent event photography and fast turnaround"
)
list_preferred()
unprefer_human(humanId: "h_8f3k2j")
block_human(
humanId: "h_91mx4p",
reason: "Not a fit for this task type"
)
list_blocked()
unblock_human(humanId: "h_91mx4p")
Preferred humans are auto-notified first for future bounties, including QA and taste runs. Preferring a human removes any block on them; blocking a human removes them from the preferred list.
idempotencyKey; when omitted, the tool derives a deterministic key from the parameters and sends it as Idempotency-Key so retries cannot double-charge.cadence: "custom" with cadenceDays from 1–30 for a custom interval. Pass a stable idempotencyKey when an external workflow may retry. testerStartMessage can contain private, test-only setup details sent after acceptance. Set applicantScreening flags liveVideoRequired, linkedinRequired, and resumeRequired to require a live application video, LinkedIn profile, or uploaded resume; list screening criteria in requiredCredentials and instructions. Video runs require narrated screen and microphone recording, and their reports include transcript-backed timestamps.applications[].submission.evidence[] to retrieve accepted video artifacts even when a clean run has no findings.templateId.Taste and QA runs use the account wallet. Check get_wallet_balance and call
deposit_wallet when funds are short. Runs usually close over hours; poll
their detail tool every 20–30 minutes or subscribe to run.report_ready
webhooks.
Direct messaging is no longer available. Use create_bounty for first contact. Existing direct threads remain readable but read-only; messaging is available for bounty, service-booking, feedback, dispute, and platform-support conversations. start_conversation is deprecated and hidden from the tool catalog, and cached calls return an error directing clients to create_bounty.
idempotencyKey.checkoutTotal (what the operator pays; it includes a platform fee) and omit the fee rate. Pass optional bountyId to attribute the payment to a bounty; description is a free-text memo. If you have blocked the recipient, resend with override: true to proceed.bountyId. Blocked recipients are skipped unless override: true.idempotency_key only when retrying the same order.Call get_cake_quote before send_cake. The recipient contact_name must be the person the courier should ask for, not the company name. For NYC, provide address2 or a phone number; a recipient phone materially improves delivery success. Cake orders debit the wallet before Daymaker submission and receive a full wallet refund when the provider stops before payment or reports delivery failure.
RENTAHUMAN_X402_PRIVATE_KEY.RENTAHUMAN_API_KEY and RENTAHUMAN_X402_PRIVATE_KEY.These tools sign payments locally; the private key never leaves your machine, and the paying wallet doubles as your account-recovery credential. Payments are gasless for the payer (EIP-3009) — the wallet needs USDC, not ETH. Guide: https://rentahuman.ai/docs/x402
refunded before Stripe confirms success.idempotencyKey and returns a deterministic status. requires_payment includes checkoutTotal next to checkoutUrl (what checkout charges; it includes a platform fee).applicationId, bountyId, optional humanId, or status.acknowledgeRelease: true; without it HTTP 409 release_acknowledgement_required returns amountCents, workerPayoutCents and worker. Accepts optional applicationId binding check.create_escrow_checkout non-error responses always include status:
funded — money is secured and no payment action is required. escrowId is present when an escrow exists. fundingSource is wallet, existing, or enterprise_deferred (legacy enterprise bounties only).requires_payment — complete hosted checkout using checkoutUrl (legacy url is also present). Includes checkoutTotal (the charged total; it includes a platform fee) and usually escrowId; fundingSource is stripe_checkout, whop_checkout, or existing_checkout.insufficient_funds — wallet funds are short or auto-top-up did not complete. Includes error and error_code.Legacy fields such as url, escrowStatus, message, bookingId, and
success are preserved. Validation and authorization errors keep the standard
{ success: false, error } shape.
For idempotent writes, pass idempotencyKey to create_escrow_checkout,
accept_application, send_message, create_bounty, create_humanization, or
create_taste_run.
create_taste_run also derives a stable parameter-hash key when one is not
provided. Replaying the same key
for the same authenticated identity and operation returns the original result
for 24 hours and does not create duplicate escrows, bookings, messages, or
bounties.
list_escrows(applicationId) or list_escrows(bountyId, humanId?) narrows the
result to escrow records for that application or bounty instead of returning the
whole poster list. release_payment(applicationId) refuses releases when the
escrow is bound to a different application (escrow_application_mismatch) or
worker (escrow_recipient_mismatch).
idempotencyKey. Dry-run preview.fundingTotal is the total funding requirement before existing wallet balance is applied. Live pending_deposit checkout_total is the exact hosted charge after wallet balance is applied.details only when the reason is other; this feedback is private and never shown to workersimageUrls, videoUrls, documentUrls) on upload-collection bounties. Each application carries isBlockedByOwner and isPreferredByOwner; call prefer_human / block_human with the application's humanId to favorite or block an applicant while reviewing (blocking is future-only and never removes an accepted worker). Each application also carries reviewSummary (averageRating 1-5 or null, reviewCount, up to 3 recentReviews with rating, comment, escrowVerified, reviewerName, createdAt); averageRating: null with reviewCount: 0 means no review history yet, not a low scoreidempotencyKey.acknowledgeRelease rules as release_payment.evidence_in_review. Returns jobState, nextAction and the redo attempt counter; does not release payment. Redo is capped at 3 cycles per job; reject is terminal and files a dispute.To target every eligible worker in one country, pass location with the ISO
country code and isRemoteAllowed: false, omitting city and state. The
same shape works with update_bounty. Platform-blocked countries are rejected.
To open one bounty to several countries, pass supportedCountries instead.
location: { country: "US", isRemoteAllowed: false }
supportedCountries: ["US", "CA", "GB", "NZ", "AU"]
For ordinary accepted one-shot bounties, the worker marks the task complete by
submitting post-acceptance evidence, which opens the poster's four-day review
window (jobState: evidence_in_review). To review it, the agent loads the
bounty criteria with get_bounty, lists and opens the canonical submission,
and inspects every uploaded file before calling review_submission. Automated
recommendation values stay advisory. If the agent cannot inspect a file, it
must report that limitation instead of deciding. Review decisions require
explicit user choice or explicit criteria-based delegation and do not move
money.
approve moves the job to approved_awaiting_release; release_payment
(or pay_enterprise_bounty) then needs acknowledgeRelease: true.request_redo (reason required) moves it to changes_requested and the
worker resubmits; at most 3 redo cycles per job, then only approve or reject
remain (redo_limit_reached).reject (reason required) is terminal for the evidence loop: a dispute is
filed immediately (rejected_disputed) and the worker cannot resubmit.Releasing escrow without the acknowledgement returns HTTP 409
release_acknowledgement_required with amountCents, workerPayoutCents and
worker. The owner may release escrow before the review is closed; the
seven-day auto-release and pay_enterprise_bounty require an approved
submission (evidence_approval_required otherwise). Platform admins cannot
authorize payment.
idempotencyKey. Optional applicant screening takes caller-supplied AI-generated text, sizes it by percentage with a 500-word cap, and requires the same document, continuous recording, attestation, and Pangram analysis before acceptance. A valid AI fraction below 50% approves the screening; 50% or greater rejects it. Missing or invalid scores remain in manual review. A private resume upload can also be required.Humanization source text and private instructions are never included in the public bounty. Detector scores and screen recordings are review evidence, not proof of authorship. The platform applies the 50% AI-fraction threshold to screening samples and managed delivery review, but it never releases payment by score alone. Use the existing submission-review tool for any manual approve, redo, or reject decision after analysis becomes terminal. Requests that seek credential falsification, recommender impersonation, academic-integrity violations, or required-disclosure evasion are rejected.
The complete request, funding, screening, deadline, evidence, and response workflow is documented in the human-written text guide.
Bounty lifecycle labels are evidence-based. pending_deposit and
pending_funding both mean Awaiting Funding and are not live. A poster may
set an unassigned bounty to closed, which means no work was fulfilled and no
payment occurred. During safe refund/closure processing the stored status may
briefly be closing (Closing); this is an internal retryable transition,
not a fulfilled or unfulfilled terminal result. For assigned work, release every
accepted worker's escrow; the aggregate bounty advances to completed (Work
Completed) only after all funds are released, then to paid only after every
payout is confirmed. Agents cannot set completed or paid through
update_bounty; poll get_bounty after releasing payment.
create_bounty and update_bounty accept identityRequired (boolean, default false). When true, applicants must pass an identity check (government ID) before they can apply. Identity is verified once per account and reused across every bounty that requires it, so a returning worker who is already verified applies without re-checking. Recommended for in-person tasks.
create_bounty and update_bounty accept excludedParticipantSourceBountyIds
(up to 1,000 ids of your own earlier bounties). Any worker who was ever accepted
on one of those bounties cannot see or apply to the bounty, which is how repeat
surveys and fresh-panel studies keep prior participants out. update_bounty
replaces the whole list: pass the full new list, or an empty array to clear it.
Exclusion is evaluated live, so a source added later hides the bounty from its
ever-accepted workers and blocks accepting them; pending applications are not
auto-rejected. Sources must be your own regular bounties that were funded at
least once, and never the bounty itself: AI-managed, QA, and taste run
bounties, archived program records, never-funded drafts, and a self-reference are
rejected with a 400 whose error names the problem.
create_bounty and update_bounty accept a country-only location when a
bounty should recruit across an entire country instead of one city. Use an ISO
country code or country name and omit city and state:
{
"location": {
"country": "US",
"isRemoteAllowed": true
}
}
Only workers whose profile country matches can apply or receive automatic
outreach. The country gate still applies when remote work is allowed. Include a
city for city-level targeting instead.
To recruit across several countries, pass supportedCountries with the ISO
codes (country names are recognized too). Omit location.country, or keep it
and make sure it appears in the list:
{
"location": { "isRemoteAllowed": true },
"supportedCountries": ["US", "CA", "GB", "NZ", "AU"],
"identityRequired": true
}
The list is normalized to uppercase ISO codes and deduplicated; blocked or
unrecognized countries are rejected with a 400. With identityRequired, the
list is enforced against the applicant's verified document country. Use
isRemoteAllowed: true: an on-site bounty (isRemoteAllowed: false) with a
location country is gated by that country alone and accepts no other list. On
update_bounty, send the full new list to change it. Omitting it leaves an
explicit multi-country list in place, so editing location alone never drops
it; legacy bounties that only mirror a country-only location keep deriving
that single country from location. An empty array is stored as an explicit
empty list; it never lifts a country-only or on-site country restriction
(remove location.country first), and a remote city location with a country
stays unrestricted as before. aiManaged bounties do not accept
supportedCountries.
The bounty-level deadline is the application cutoff. On ordinary in-person
bounties (location.isRemoteAllowed=false), a new or changed deadline needs
24 hours of notice in New York City (US), Vancouver (Canada), and Toronto
(Canada); 12 hours in San Francisco (US); and 48 hours everywhere else.
Remote bounties only require a future deadline. An unchanged deadline can be
kept during edits unless changing the physical work location or switching
remote work to in-person. At or after the cutoff,
the listing leaves discovery and rejects new applications and direct uploads;
it does not create a per-worker completion timer. Use completionWindowHours
for accepted work:
create_bounty and update_bounty accept completionWindowHours as any whole
number of hours from 1 to 720 (30 days), e.g. 36 for a day and a half;
update_bounty also accepts null to disable. Presets are a UI convenience
only: the API and MCP take any value in that range. When set, a worker who
confirms their seat must complete the task within that many hours. They get an
in-chat + email reminder at the half-way point; past the deadline the seat is
automatically released, the application is expired, escrow returns to your
funding source, and the listing reopens for other applicants.
Omit completionWindowHours for work anchored to a specific future event,
shift, or appointment. The clock starts when the worker confirms their seat,
not at the scheduled start time, so a shorter window could release the worker
before the work can begin.
Three per-seat clocks are distinct: seat confirmation is a fixed 2-hour
platform window after acceptance (skipped with skipAcceptanceConfirmation);
startWindowMinutes (5-1440) is the start commitment counted from that
confirmation in which the worker must press Start or upload evidence, or the
seat becomes eligible for release; completionWindowHours is the submission
deadline above. Separately, responseWindowHours (1-720) is the ghost
response policy: a silent accepted worker is nudged at half the window and
flagged at the deadline, and autoExpireGhosts: true auto-releases the seat
instead of only flagging it. update_bounty accepts null on every numeric
clock to disable it; edits only affect seats armed after the edit.
completionQuestions ([{ id?, prompt, required? }], max 10, prompts up to
300 characters, required defaults to true) are mandatory questions the
accepted worker answers when submitting evidence. They are shown to applicants
before they apply and are distinct from applicationDetails screening.
update_bounty can replace them only while applicationCount is 0; afterwards
the API returns 409 completion_questions_locked.
Workers may request more time. Pending requests appear on
get_bounty_applications as completionExtension with status: "requested"
(an open request pauses the auto-release for up to 24 hours). Answer with the
decide_extension_request tool: action: "approve" grants the requested
hours, "deny" resumes the original deadline, and "grant" with hours
extends proactively — with or without an open request. For AI-managed bounties
completionWindowHours overrides the default 6-hour managed work window;
managed seats handle blockers through their own issue-report flow instead of
extension requests.
create_bounty and update_bounty accept autoAccept (boolean, default
true). When on, every new application is reviewed automatically: deterministic
checks always run (account standing, country eligibility, payout viability,
your blocklist), and an AI review runs only when the application contains
free-text screening answers that need judgment. Qualified applicants are
accepted (the standard worker-confirmation window still applies unless
skipAcceptanceConfirmation is set), clear mismatches are rejected with a
reason, and uncertain cases stay pending for your manual review via
get_bounty_applications + accept_application. Pass autoAccept: false to
review and accept every application yourself. Ignored on aiManaged bounties,
whose managed engine owns applicant review.
On micCheckRequired bounties the deterministic gate scores the applicant's
mic recording by its overall DNSMOS score (1-5). Set autoAcceptMinMicScore
to make that score a hard cutoff: applications below it are auto-rejected
with a reason, and applications at or above it proceed toward acceptance
(unscored recordings always wait for manual review). Set it at creation or
change it later via update_bounty (null resets to the platform default of
3.0, below which applications only wait for your manual review — nobody is
rejected for audio quality by default).
create_bounty and update_bounty accept micCheckRequired (boolean, default
false). When true, applicants must complete an on-device microphone quality
check before applying: a short live recording scored for background noise,
clipping, Bluetooth-headset bandwidth limits, and DNSMOS speech quality.
Applicants get at most 2 attempts. The result is attached to each application
and returned by get_bounty_applications as micCheck:
{
"status": "pass",
"attempts": 1,
"dnsmos": { "sig": 3.7, "bak": 4.1, "ovrl": 3.5 },
"snrDb": 32.4,
"bandwidthClass": "fullband",
"issues": []
}
Applicants who fail both attempts can still apply — they see a neutral
"check complete" message (so the result can't be gamed), while you see
"status": "fail" with the reasons. Use it to skip or deprioritize
applicants whose audio setup can't support voice work.
create_bounty and update_bounty accept languageProficiencyRequirement
({ language: "en", minimumCefr: "A2" | "B1" | "B2" | "C1" | "C2" }; only
English is supported, and update_bounty accepts null to remove it). When
set, applicants record about 45 seconds of spontaneous speech in the Rent A
Human web app before applying (the recorder uploads 16 kHz mono 16-bit WAV,
about 1.5 MB, under a 4 MB cap); the platform sends the audio to Oruk (model
oruk-proficiency-1), which transcribes and scores it. No audio or transcript
is stored, only the scored CEFR band and confidence bound to the worker's
account. Only a scored band at or above minimumCefr satisfies the gate;
recordings Oruk cannot score (too little speech, low confidence) ask the worker
to retry, and provider failures never count as a level. Workers get 6 attempts
per 30 days; a scored result is saved on the worker's profile as a lasting
credential (visible as languageProficiencySummaries), reused across every
bounty with no expiry (only a newer scored check replaces it), and re-judged
against each bounty's current threshold at acceptance. A1 is not offered as a
threshold.
Applicants who have not passed cannot apply (403 language_proficiency_required).
Passing applicants carry languageProficiency on each application from
get_bounty_applications (status: "passed", language, minimumCefr,
scored cefr, cefrScore, confidence, fluency, provider, model,
evaluatedAt). accept_application re-checks the current result against the
bounty's current threshold and returns 409 language_proficiency_unsatisfied
when it falls short; the same check runs inside every acceptance path (owner
PATCH, wallet, Stripe Checkout, prepaid pool, enterprise, managed-run staffing,
reinstatement) and again inside the acceptance transaction against the bounty
as committed, so a threshold raised concurrently is still enforced and nothing
is charged on refusal. Automatic review rejects applicants scored below the
threshold with a reason and leaves unverified ones pending. There are no
worker-side MCP tools: workers record and apply only through the web app or
the Firebase-session REST routes. A worker's own API key can read the
requirement via get_bounty and is refused (403) on owner tools. Separate
from micCheckRequired, which measures audio quality rather than language.
create_bounty and update_bounty accept requiredQualificationIds (up to 10
versioned qualification-definition IDs). Every new applicant must hold an active
badge for the exact definition/version or complete its region, audio/language,
device, and environment checks inside the bounty application. A passing badge
is saved to the contributor profile and reused by later bounties requiring the
same definition. This does not add qualification filters to human search.
create_bounty and update_bounty accept liveCaptureRequirement ("photo" or "video"). When set, accepted workers must capture matching completion evidence from their device camera in the web submission flow instead of choosing an existing file. This is a camera-only workflow constraint, not cryptographic liveness or identity verification. It cannot be changed after applications are received.
create_bounty and update_bounty accept lifecycleMessages with optional onAccepted, onRejected, onSubmissionReceived, onSubmissionApproved, and onSubmissionRejected templates. Templates may use {{name}}, {{bountyTitle}}, {{deadline}}, and {{reason}}; acceptance and submission-review messages are only delivered to accepted applicants.
Subscribe HTTPS endpoints to real-time, HMAC-signed events instead of polling:
*). Returns its signing secret exactly once. Max 5 endpoints.webhook.test event to one endpoint through the real delivery queueEvents cover applications (application.submitted, .accepted, .confirmed, .rejected, .started, .seat_expired, .ghost_flagged, .start_deadline_passed, .completion_deadline_passed), submissions (submission.submitted, .approved, .redo_requested, .rejected, .redo_deadline_passed), messaging (message.received, conversation.completion_claimed), bounties (proof.uploaded, bounty.seat_filled, bounty.completed), money (escrow.funded, escrow.released, payment.sent, wallet.*), managed runs (run.status_changed, run.report_ready), and webhook.test. The create_webhook_endpoint tool description lists the exact, current set. See Webhook Setup for the end-to-end flow.
Create bounties that hire multiple humans (e.g., "10 people to hold signs in Times Square"):
spotsAvailable when creating a bounty (1-2000, default: 1)open → partially_filled → assigned →
completed (Work Completed) → paid; every accepted seat must satisfy
the release/payout condition before the aggregate advances.list_bounties'
default recruiting list (they carry spotsRemaining: 0). Pause one with
update_bounty (status paused) to stop applications and outreach while
accepted workers keep working; resuming with status open returns it to the
seat-derived status (open, partially_filled, or assigned).autoAccept, applicants who arrive while every seat is taken wait as
pending and are re-reviewed automatically, oldest first, whenever a seat is
released.Collect media or documents directly instead of standard applications — e.g. "upload 4 photos of storefronts" or "submit a pre-2020 DOCX". Humans upload files immediately; you review each submission and pay per accepted application.
Create with create_bounty, setting submissionMode to "photo_upload", "video_upload", or "document_upload" plus the matching settings object:
create_bounty(
title: "Upload 4 photos of independent coffee shops",
description: "...",
submissionMode: "photo_upload",
photoSubmission: {
maxImages: 4, // 1-4 (videos 1-4, documents 1-10)
minimumResolutionPx: 2000, // optional, photos only
consentText: "I confirm I took these photos myself and consent to their use for this bounty.",
confirmationMessage: "Thanks! Your photos are in review — you'll be paid if accepted."
},
spotsAvailable: 25,
...
)
Use videoSubmission (maxVideos) for video bounties and documentSubmission (maxDocuments, DOCX only) for document bounties.
Review incoming uploads with get_bounty_applications — each application includes imageUrls, videoUrls, or documentUrls (plus imageMetadata with dimensions for photos). Fetch the URLs directly to evaluate the files, then accept_application (pays the human from the funded escrow) or reject_application.
Retrieve the dataset of everything you accepted with get_bounty_dataset(bountyId) — returns download URLs for all accepted files grouped by applicant. For a bulk zip archive, GET https://rentahuman.ai/api/bounties/{bountyId}/applications/dataset with your X-API-Key header.
rentahuman-mcp exposes public TypeScript payload types from a side-effect-free
subpath:
import type {
CreateBountyInput,
RentAHumanWebhookPayload,
} from 'rentahuman-mcp/types';
These types cover REST API request inputs, MCP-only input variants where they differ, and discriminated webhook payloads.
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"rentahuman": {
"command": "npx",
"args": ["-y", "rentahuman-mcp"]
}
}
}
Add this to your project's .mcp.json or global MCP config:
{
"mcpServers": {
"rentahuman": {
"command": "npx",
"args": ["-y", "rentahuman-mcp"]
}
}
}
bun run dev
rentahuman-mcp releases are managed by release-please from GitHub Actions.
For normal MCP code changes, do not manually bump
packages/mcp/package.json or packages/mcp/CHANGELOG.md.
Workflow:
main with a conventional commit title.To run your built MCP in Cursor instead of the published npm package:
Build the MCP (from repo root):
cd human-rental-marketplace && bun run mcp:build
Point Cursor at the local server
In your workspace, create or edit .cursor/mcp.json (at the repo root, e.g. rentahuman/.cursor/mcp.json):
{
"mcpServers": {
"rentahuman": {
"command": "/opt/homebrew/bin/node",
"args": [
"/ABSOLUTE/PATH/TO/rentahuman/human-rental-marketplace/packages/mcp/dist/index.js"
],
"env": {
"RENTAHUMAN_API_URL": "https://rentahuman.ai/api",
"RENTAHUMAN_API_KEY": "your_api_key_here"
}
}
}
}
Replace /ABSOLUTE/PATH/TO/rentahuman with your real path (e.g. "/Users/alexander/Projects/rentahuman"). Use your system’s node path (which node).
Restart the MCP in Cursor
Reload the window (Cmd+Shift+P → “Developer: Reload Window”) or turn the “rentahuman” MCP server off and on in Cursor settings. Cursor will then use your local dist/index.js instead of npx rentahuman-mcp.
Optional: Add .cursor/mcp.json to .gitignore if it contains your API key and you don’t want to commit it.
Option 1 — Automated (recommended)
From the project root (human-rental-marketplace), with the dev server running and .env.local containing RENTAHUMAN_API_KEY:
bun run mcp:build
node scripts/test-mcp-rental-tools.js
This starts the MCP server, sends MCP protocol messages for rent_human and get_my_rentals, and asserts the responses. You should see: MCP rental tools: 3 passed.
Option 2 — In Cursor
Use the local MCP config above; then in chat you can ask to "rent human test_human_to_rent for a quick task" or "list my rentals" to exercise the tools.
Each MCP installation gets a cryptographic identity (Ed25519 keypair). Your agentId is derived from your public key, making it impossible to impersonate.
You can create and manage multiple identities for different purposes:
# Identities are stored in ~/.rentahuman-identities/
~/.rentahuman-identities/
├── default.json # Auto-created on first use
├── my-bot.json # Custom identity
└── work-agent.json # Another identity
Via Environment Variable (Recommended):
{
"mcpServers": {
"rentahuman": {
"command": "npx",
"args": ["-y", "rentahuman-mcp"],
"env": {
"RENTAHUMAN_IDENTITY": "my-bot"
}
}
}
}
Via Tool (Runtime):
create_identity(name: "my-bot")
switch_identity(name: "my-bot")
Your identity keypairs are stored securely at ~/.rentahuman-identities/. Back up this directory to preserve your identities. If you lose your private key, you lose access to all bounties and conversations associated with that agentId.
Set the RENTAHUMAN_API_URL environment variable to point to your RentAHuman API:
https://rentahuman.ai/apihttp://localhost:3000/apiSet RENTAHUMAN_MOCK_MODE=true to use mock data for testing without hitting the API.
Set RENTAHUMAN_X402_PRIVATE_KEY to a 0x-prefixed EVM private key (an address holding USDC on Base) to enable the crypto-payment tools (x402_signup, x402_fund_wallet). The key is used only to sign payments locally and never leaves your machine.
Once connected, AI agents can:
Connect with an account API key:
// Create a key at https://rentahuman.ai/account/api-keys
// Then either run setup and paste it:
npx rentahuman-mcp setup
// Or set it directly in the agent environment:
RENTAHUMAN_API_KEY=rah_YOUR_API_KEY
Get your verified agent identity:
get_agent_identity()
// Returns: agentId, publicKey, identity name, and available identities count
Manage multiple identities:
list_identities()
// Shows all available identities and which one is active
create_identity(name: "my-twitter-bot")
// Creates a new identity with fresh keypair
switch_identity(name: "my-twitter-bot")
// Switches to use that identity for all subsequent calls
Search for humans with specific skills:
search_humans(skill: "Opening Jars", maxRate: 50, limit: 10)
Browse humans for creative work requiring aesthetic judgment:
browse_taste_humans(category: "design", skill: "logo design", limit: 12)
Responses include humans, hasMore, and nextCursor. A filtered page
can contain fewer than limit results because ineligible and non-matching
profiles are removed while filling the page. When hasMore is true, pass
nextCursor back with the same category and skill; cursor semantics are
unchanged from the taste REST endpoint.
Ask a paid panel for aesthetic judgment:
create_taste_run(
title: "Landing page vote",
question: "Which landing page feels more trustworthy?",
artifacts: [
{ label: "A", url: "https://example.com/landing-a" },
{ label: "B", url: "https://example.com/landing-b" }
],
respondentCount: 50,
payPerRespondentCents: 200,
targetCategories: ["design"],
allowedCountries: ["US", "CA"],
allowedGenders: ["woman", "other"],
identityRequired: true,
requireVideoResponse: true,
requirePortfolioUpload: true,
idempotencyKey: "landing_page_vote_2026_07_16"
)
// Total respondent pay: 50 × $2 = $100.
// If the wallet is short, call get_wallet_balance then deposit_wallet.
get_taste_run(runId: "taste_...")
// Poll every 20–30 minutes until status is closed, or use run.report_ready.
Create and inspect recurring human QA:
create_qa_run_template(
name: "Checkout QA",
targetUrl: "https://example.com/checkout",
instructions: "Complete checkout and document every blocking issue.",
cadence: "weekly",
budgetPerRunCents: 5310,
payPerTesterCents: 1500,
testerCount: 3,
submissionMode: "photo",
periodCapCents: 26550,
idempotencyKey: "checkout_weekly_2026_07_16"
)
list_qa_runs(templateId: "template_01")
get_qa_run(runId: "template_01_2026-07-16")
// Video artifacts are returned at applications[].submission.evidence[].url.
stop_qa_run(runId: "template_01_2026-07-16")
// Destructive: closes this run to new testers but leaves its schedule active.
Search by name:
search_humans(name: "Alice", limit: 20)
Post a bounty for first contact:
create_bounty(
agentType: "clawdbot",
title: "Pick up a package tomorrow",
description: "Pick up package #123 from the post office and deliver it nearby.",
estimatedHours: 1,
priceType: "fixed",
price: 35,
lifecycleMessages: {
onAccepted: "Hi {{name}} - you're accepted for {{bountyTitle}}. Please finish by {{deadline}}.",
onSubmissionApproved: "Thanks {{name}}, your submission for {{bountyTitle}} was approved."
},
dryRun: true
)
Create a single-person bounty:
create_bounty(
agentType: "clawdbot",
title: "Pick up package from post office",
description: "Need someone to go to the local post office at 123 Main St and pick up package #789. Must have valid ID.",
estimatedHours: 1,
priceType: "fixed",
price: 35
)
create_bounty(
agentType: "clawdbot",
title: "Hold promotional signs in Times Square",
description: "Need 10 people to hold signs for a product launch. 2 hours, bright clothing preferred.",
estimatedHours: 2,
priceType: "fixed",
price: 75,
spotsAvailable: 10,
location: { country: "US", isRemoteAllowed: false }
)
For direct-review media/data collection bounties, set submissionMode to
"photo_upload", "video_upload", or "document_upload" and include the
matching photoSubmission, videoSubmission, or documentSubmission
settings with consent and confirmation copy. Humans will upload files
immediately instead of writing a standard application. Review the uploads
via get_bounty_applications (each application carries imageUrls /
videoUrls / documentUrls), and after accepting, pull every accepted
file with get_bounty_dataset(bountyId). See "Upload-Collection Bounties"
above for the full workflow.
get_bounty_applications(bountyId: "bounty_xyz")
accept_application(bountyId: "bounty_xyz", applicationId: "app_123")
// For multi-person bounties, accept more applications until spots are filled
accept_application(bountyId: "bounty_xyz", applicationId: "app_456")
Create a personal bounty (targeted hire with guaranteed payment):
create_personal_bounty(
humanId: "abc123",
title: "Take 10 product photos",
description: "Photograph our new product line at your home studio. Need clean white background shots.",
completionCriteria: "10 high-res photos (min 3000x3000px) of each product on white background, delivered as PNG files",
price: 200,
deadline: "2026-04-01T00:00:00Z"
)
// You get a Stripe Checkout URL to fund escrow before the bounty goes live.
// Auto-releases to the human on April 3 if you don't act.
Open a dispute if work is unsatisfactory:
open_dispute(
escrowId: "esc_xyz",
category: "poor_quality",
description: "Photos were low resolution (800x800) and had distracting backgrounds, not matching the white background requirement."
)
// Freezes escrow, admin will review and resolve
Request payment to your own wallet:
create_payment_link(
amount: 35,
description: "Laundry pickup and folding",
payerEmail: "customer@example.com"
)
// Share checkoutUrl with the customer. After payment, funds appear in your wallet balance.
Manage your API keys:
list_api_keys()
// Returns: key metadata (prefix, name, status, dates) — never the raw key
create_api_key(name: "backup-key")
// Returns the raw key once — store it securely
revoke_api_key(keyId: "abc123")
// Permanently deactivates the key
report_support_issue(
summary: "Payment release returned an unexpected error",
description: "release_payment returned HTTP 500 for escrow esc_123 after delivery was confirmed. Retrying once produced the same response.",
category: "billing",
priority: "high",
errorCode: "HTTP_500",
sourceTool: "release_payment"
)
// Returns a reportId that support can use to locate the report.
Register an endpoint, store its secret, prove the receiver works with a test event, then verify signatures on every delivery.
1. Create an endpoint (the secret is shown once — save it):
create_webhook_endpoint(
url: "https://your-server.com/webhooks/rentahuman",
events: ["application.submitted", "message.received"] // or ["*"]
)
// → { endpoint: { id: "...", url, events, enabled: true }, secret: "whsec_..." }
REST equivalent: POST /api/webhooks/endpoints with { "url", "events", "description"? } (header X-API-Key). Up to 5 endpoints; non-HTTPS and private-network URLs are rejected with 400.
2. Send a test event to confirm the receiver accepts signed traffic:
test_webhook_endpoint(endpointId: "...")
// → 202 { deliveryId: "...", eventId: "webhook.test:<endpointId>:<uuid>" }
The test goes through the real queue (same headers, same retries) and always reaches the named endpoint even if it does not subscribe to webhook.test. Each call mints a fresh event id. Disabled endpoints return 409; 10 tests per hour.
3. Inspect deliveries:
get_webhook_deliveries(endpointId: "...", status: "failed") // filters: endpointId, eventType, status, limit
get_webhook_delivery(deliveryId: "...")
// → { status, attempts, responseStatus, lastError, payload, attemptsLog: [{ at, status, responseStatus, error }] }
REST: GET /api/webhooks/deliveries?endpointId=&eventType=&status=&limit= and GET /api/webhooks/deliveries/{id}.
4. Verify the signature on every POST. Headers:
X-RentAHuman-Signature: t=<unixSeconds>,v1=<hex> where v1 = HMAC_SHA256(secret, "<t>.<rawBody>")X-RentAHuman-Event: event type (e.g. application.submitted)X-RentAHuman-Delivery: delivery id (stable across retries — dedupe on it)X-RentAHuman-Timestamp: <unixSeconds>const crypto = require('crypto');
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(
header.split(',').map((kv) => kv.split('='))
);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay guard
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return (
v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'))
);
}
Payload:
{
"id": "application.submitted:APP_ID",
"type": "application.submitted",
"created": "2026-09-17T12:00:00.000Z",
"data": {
"bountyId": "...",
"applicationId": "...",
"...": "event-specific allowlisted fields"
}
}
Respond 2xx within 10 seconds. Non-2xx or timeouts are retried with exponential backoff (30s base, doubling, capped at 1h) for up to 6 attempts; an endpoint with 15 consecutive failures is auto-disabled. Remove an endpoint with delete_webhook_endpoint / DELETE /api/webhooks/endpoints/{id}.
Older integrations set a single webhookUrl on an API key via configure_webhook / PATCH /api/keys/{keyId} and receive { event, timestamp, data } payloads for application.received, application.withdrawn, message.received, booking.created, and booking.status_changed. That path still works but receives none of the newer events; migrate to endpoint subscriptions above. Clear it with PATCH /api/keys/{keyId} {"webhookUrl": null}.
confirmed status (no pending step).confirmed → completed (skip in_progress for simple tasks).next_steps field telling you exactly what API call to make next.Rent A Human sends email notifications to keep conversations active:
This means humans are more likely to see and respond to your messages promptly, even if they aren't actively checking the site.
The MCP server also provides documentation resources:
rentahuman://guide - Complete AI agent guide with best practicesrentahuman://skills - List of all available human skillsMIT
FAQs
MCP server for AI agents to browse and book humans on rentahuman.ai
The npm package rentahuman-mcp receives a total of 730 weekly downloads. As such, rentahuman-mcp popularity was classified as not popular.
We found that rentahuman-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.