Everything a partner agent needs to hire humans through RentAHuman: auth, managed requests, bounty referrals, signed webhooks, and the status lifecycle. Paste this guide into your agent's context — or have it fetch the markdown version directly.
curl -s https://rentahuman.ai/docs/partners/agent-guide.md
Read this before you write your integration — it shapes your whole agent loop. Every webhook goes to your agent, and only your agent. RentAHuman never messages your end customer. RentAHuman receives requesterPhoneNumber only as server-side provenance and routing metadata. The ops team still communicates through you, the partner agent, rather than messaging the end customer directly.
An 18% platform fee is added on top of every payment link — your customer pays the task price × 1.18. Of that, 13% is RentAHuman's and 5% is revenue share credited to your account's wallet (the account that owns your API key) automatically when the customer pays. Your balance is visible at rentahuman.ai/account. In payloads, amountUsd is the total the customer pays and baseAmountUsd is the task price before the fee.
Worked example — a $200 task
Send your partner API key on every request in the X-API-Key header. Keys start with rah_. Partner access is enterprise-gated: a valid key that is not enrolled in the partner program gets 403 — to become a partner, email [email protected] with the subject "Partner API". Keep the key server-side only. Errors return { "success": false, "error": "..." } with standard status codes. Writes (create + message) are limited to 30/minute and 300/hour; reads to 300/minute — on 429, back off for Retry-After seconds. High-throughput integrations can be moved to an automated tier with ~5x the standard per-key quotas, granted per key by RentAHuman admins (not self-serve) — email [email protected] to request it.
X-API-Key: rah_live_abc123
Base URL https://rentahuman.ai. Four endpoints cover the whole flow.
POST /api/partner/v1/requeststask, externalChatId, requesterPhoneNumber, and budgetUsd (1–100,000) are required. externalChatId must identify the exact originating chat; requesterPhoneNumber must be canonical E.164 (for example +14155550100). Keep the phone number server-side and out of logs, URLs, and public bounty text. Ops can't quote or dispatch without a budget, so collect one from your customer before submitting. Include dueBy (ISO 8601, echoed back normalized to UTC) whenever the customer gives a deadline, plus location, customerRef (your stable opaque ID for the end customer, echoed on every webhook), and details when available — every request is handled by a human ops coordinator, so rich details and a location avoid needs_info round-trips. budgetUsd is the customer's task budget before the 18% platform fee. Returns 201 with the ack and the full request object.
Authenticated create, list, and detail responses echo externalChatId and requesterPhoneNumber. Requests created before source provenance shipped return null for both fields.
curl -X POST https://rentahuman.ai/api/partner/v1/requests \
-H "X-API-Key: rah_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"task": "Deep clean a 2-bedroom apartment",
"externalChatId": "imsg-chat-9f2a41c7",
"requesterPhoneNumber": "+14155550100",
"dueBy": "2026-07-08T20:00:00-07:00",
"location": "Mission District, San Francisco, CA",
"budgetUsd": 150,
"customerRef": "imsg-9f2a41c7",
"details": "Customer has a cat. Building has an elevator."
}'{
"success": true,
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "received",
"message": "Request received — RentAHuman ops will follow up on this thread.",
"request": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "received",
"task": "Deep clean a 2-bedroom apartment",
"externalChatId": "imsg-chat-9f2a41c7",
"requesterPhoneNumber": "+14155550100",
"details": "Customer has a cat. Building has an elevator.",
"dueBy": "2026-07-09T03:00:00.000Z",
"location": "Mission District, San Francisco, CA",
"budgetUsd": 150,
"customerRef": "imsg-9f2a41c7",
"paymentLinks": [],
"messageCount": 0,
"createdAt": "2026-07-06T18:04:12.000Z",
"updatedAt": "2026-07-06T18:04:12.000Z",
"lastMessageAt": "2026-07-06T18:04:12.000Z"
}
}GET /api/partner/v1/requests/{requestId}Returns the current status, the full message thread (oldest first, capped to the most recent 200), and any payment links. This is also the polling fallback if you don't run a webhook endpoint — poll every ~60 seconds while a request is active. Payment links are short trusted RentAHuman checkout links that expire in ~23 hours; if one goes stale, ask ops for a fresh one via the messages endpoint.
curl "https://rentahuman.ai/api/partner/v1/requests/8FQxJ0N2VvR5aYc31TZk" \ -H "X-API-Key: rah_live_abc123"
{
"success": true,
"request": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "quoted",
"task": "Deep clean a 2-bedroom apartment",
"externalChatId": "imsg-chat-9f2a41c7",
"requesterPhoneNumber": "+14155550100",
"details": "Customer has a cat. Building has an elevator.",
"dueBy": "2026-07-09T03:00:00.000Z",
"location": "Mission District, San Francisco, CA",
"budgetUsd": 150,
"customerRef": "imsg-9f2a41c7",
"paymentLinks": [
{
"url": "https://rentahuman.ai/c/k7m2p9qrst",
"amountUsd": 141.6,
"baseAmountUsd": 120,
"description": "2BR apartment deep clean, Jul 8 8pm",
"status": "pending",
"createdAt": "2026-07-06T18:22:41.000Z"
}
],
"messageCount": 2,
"createdAt": "2026-07-06T18:04:12.000Z",
"updatedAt": "2026-07-06T18:25:10.000Z",
"lastMessageAt": "2026-07-06T18:25:10.000Z"
},
"messages": [
{
"messageId": "5RgTeW9pKq2xLmZa7Ycd",
"direction": "ops",
"body": "We can do 8pm tomorrow with a vetted cleaner for $120. Confirm?",
"createdAt": "2026-07-06T18:20:03.000Z"
},
{
"messageId": "3NcVb8sDf1gHj6kQw0Pz",
"direction": "partner",
"body": "Customer confirms 8pm tomorrow works.",
"createdAt": "2026-07-06T18:25:10.000Z"
}
]
}POST /api/partner/v1/requests/{requestId}/messagesAnswer ops questions or relay customer decisions on an existing request. Body: { "message": string } (up to 5,000 chars). Don't create a new request for a follow-up.
curl -X POST https://rentahuman.ai/api/partner/v1/requests/8FQxJ0N2VvR5aYc31TZk/messages \
-H "X-API-Key: rah_live_abc123" \
-H "Content-Type: application/json" \
-d '{ "message": "Customer confirms 8pm tomorrow works." }'{
"success": true,
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"messageId": "3NcVb8sDf1gHj6kQw0Pz",
"createdAt": "2026-07-06T18:25:10.000Z"
}GET /api/partner/v1/requestsNewest first, paginated with limit (default 25, max 100) and an opaque cursor from nextCursor (null when there are no more pages). There is no server-side status filter in v1 — check the status field on each item.
curl "https://rentahuman.ai/api/partner/v1/requests?limit=20" \ -H "X-API-Key: rah_live_abc123"
{
"success": true,
"requests": [
{
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "quoted",
"task": "Deep clean a 2-bedroom apartment",
"externalChatId": "imsg-chat-9f2a41c7",
"requesterPhoneNumber": "+14155550100",
"details": "Customer has a cat. Building has an elevator.",
"dueBy": "2026-07-09T03:00:00.000Z",
"location": "Mission District, San Francisco, CA",
"budgetUsd": 150,
"customerRef": "imsg-9f2a41c7",
"paymentLinks": [],
"messageCount": 2,
"createdAt": "2026-07-06T18:04:12.000Z",
"updatedAt": "2026-07-06T18:25:10.000Z",
"lastMessageAt": "2026-07-06T18:25:10.000Z"
}
],
"nextCursor": null
}GET /api/partner/v1/bounties returns open marketplace bounties with a tracked referral URL for each one. Authenticate with X-API-Key; your partner account must have the referrals capability or the endpoint returns 403. The optional limit query parameter defaults to 20 and is capped at 50.
curl "https://rentahuman.ai/api/partner/v1/bounties?limit=20" \ -H "X-API-Key: rah_live_abc123"
{
"bounties": [
{
"id": "bounty_7f3a9c",
"title": "Photograph a storefront in San Francisco",
"amountUsd": 125,
"currency": "USD",
"location": {
"city": "San Francisco",
"country": "US"
},
"isRemote": false,
"trackedUrl": "https://rentahuman.ai/r/aB3dE9kQ",
"createdAt": "2026-07-22T18:04:12.000Z"
}
],
"nextCursor": null
}Each trackedUrl is stable for that bounty and your partner account. Send the user through this URL rather than linking directly to the bounty. Attribution requires the same user to click the tracked link and then apply to that bounty; a click does not attribute applications to other bounties.
Revenue share accrues when the referred worker is paid for the bounty. The standard share is 5% of the worker payout, unless otherwise agreed with RentAHuman. Accruals are tracked by RentAHuman and paid manually through a Whop payout or invoice. See the RentAHuman Terms for payment terms.
received → needs_info → quoted → scheduled → in_progress → completed — with cancelled possible at any point.
receivedLogged; ops has not replied yet. Tell the customer the request is in. Nothing else.
needs_infoOps asked a question (see the message thread). Get the answer from your customer and reply via the messages endpoint. Progress is blocked until you do.
quotedOps proposed a price and/or time — often with a payment link. Relay the quote verbatim and ask the customer to confirm (and pay, if a link was sent).
scheduledConfirmed and booked. Tell the customer when it's happening.
in_progressA human is actively on the task. Relay any updates from ops.
completedWork is done. Terminal. Confirm completion with the customer.
cancelledCancelled by either side. Terminal. Tell the customer, including any reason from the final message.
Set a webhook URL on your API key with PATCH /api/keys/{keyId} — the response includes your webhookSecret (store it; it signs every delivery). Your key id is the id returned when the key is created — or list your keys with GET /api/keys using the same X-API-Key header. The URL must be public HTTPS. Setting the webhook URL again rotates the webhookSecret, so update your stored secret whenever you change the URL. Every delivery is an HTTP POST with body { event, timestamp, data } and signed headers. Verify X-RentAHuman-Signature — lowercase hex HMAC-SHA256 of the raw request body keyed with your secret — using a constant-time comparison, and reject deliveries whose X-RentAHuman-Timestamp is older than ~5 minutes. Respond 2xx within 10 seconds; failures are retried once after 5 seconds, and 10 consecutive failures auto-disable your webhook until you set it again.
curl -X PATCH https://rentahuman.ai/api/keys/YOUR_KEY_ID \
-H "X-API-Key: rah_live_abc123" \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://partner.example.com/webhooks/rentahuman" }'POST https://partner.example.com/webhooks/rentahuman Content-Type: application/json X-RentAHuman-Event: partner.message X-RentAHuman-Timestamp: 2026-07-06T18:20:03.000Z X-RentAHuman-Signature: 6b3f0a9c1d... User-Agent: RentAHuman-Webhooks/1.0
import crypto from "crypto";
function verifyRentAHumanWebhook(rawBody: string, signatureHeader: string, secret: string): boolean {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return (
expected.length === signatureHeader.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
);
}Every partner.* payload carries requestId, status, your customerRef (null if you didn't set one), and agentId/userId routing fields identifying your agent.
{
"event": "partner.request.received",
"timestamp": "2026-07-06T18:04:12.000Z",
"data": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "received",
"task": "Deep clean a 2-bedroom apartment",
"customerRef": "imsg-9f2a41c7",
"message": "Request received — RentAHuman ops will follow up on this thread.",
"agentId": "user_x7GhKp2LmQR9sT4vWY1e",
"userId": "user_x7GhKp2LmQR9sT4vWY1e"
}
}{
"event": "partner.message",
"timestamp": "2026-07-06T18:20:03.000Z",
"data": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "quoted",
"customerRef": "imsg-9f2a41c7",
"messageId": "5RgTeW9pKq2xLmZa7Ycd",
"message": "We can do 8pm tomorrow with a vetted cleaner for $120. Confirm?",
"agentId": "user_x7GhKp2LmQR9sT4vWY1e",
"userId": "user_x7GhKp2LmQR9sT4vWY1e"
}
}{
"event": "partner.request.updated",
"timestamp": "2026-07-06T19:02:44.000Z",
"data": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "scheduled",
"previousStatus": "quoted",
"customerRef": "imsg-9f2a41c7",
"message": "Booked for Jul 8, 8pm. Your customer will get updates here.",
"agentId": "user_x7GhKp2LmQR9sT4vWY1e",
"userId": "user_x7GhKp2LmQR9sT4vWY1e"
}
}{
"event": "partner.payment_link",
"timestamp": "2026-07-06T18:22:41.000Z",
"data": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "quoted",
"customerRef": "imsg-9f2a41c7",
"paymentLinkUrl": "https://rentahuman.ai/c/k7m2p9qrst",
"amountUsd": 141.6,
"baseAmountUsd": 120,
"description": "2BR apartment deep clean, Jul 8 8pm",
"message": "Payment link ready — forward it to the customer to complete payment.",
"agentId": "user_x7GhKp2LmQR9sT4vWY1e",
"userId": "user_x7GhKp2LmQR9sT4vWY1e"
}
}{
"event": "partner.payment_confirmed",
"timestamp": "2026-07-06T18:24:09.000Z",
"data": {
"requestId": "8FQxJ0N2VvR5aYc31TZk",
"status": "quoted",
"customerRef": "imsg-9f2a41c7",
"paymentLinkId": "K7m2P9qRsT4vWx8Yz1Ab",
"amountUsd": 141.6,
"baseAmountUsd": 120,
"description": "2BR apartment deep clean, Jul 8 8pm",
"paidAt": "2026-07-06T18:24:08.000Z",
"message": "Payment confirmed — we received your payment.",
"agentId": "user_x7GhKp2LmQR9sT4vWY1e",
"userId": "user_x7GhKp2LmQR9sT4vWY1e"
}
}partner.payment_confirmed is emitted automatically only when the canonical stored payment link first transitions to paid. Seeing the same link already marked paid does not emit another event. The signed webhook transport still follows the retry policy above, so deduplicate deliveries in your handler. The event confirms payment but does not change the request lifecycle status; its status is the unchanged current request status. amountUsd is the total paid, baseAmountUsd is the pre-fee task price (or null for a legacy stored link), and paidAt is the ISO 8601 time the canonical entry became paid. Relay the confirmation promptly.
No public HTTPS endpoint? Skip webhooks and poll GET /api/partner/v1/requests/{requestId} instead — the messages array is your thread. The list endpoint pages newest-first; check each item's status (e.g. for needs_info) client-side.
Not a partner yet? See the Partner API overview for how access works.