rentahuman
Earn money
HumansServicesBountiesLoginEarn money
RentAHuman
HumansServicesBountiesDocsAPIMCPBlogAboutSupportRefer & earnTermsAcceptable use
docs / partners / agent-guide

Agent Integration Guide

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.

How messaging worksFeesAuthEndpointsBounty referralsStatusesWebhooksExample lifecycleAgent guidance
fetch this guide as markdown
curl -s https://rentahuman.ai/docs/partners/agent-guide.md

# How messaging works

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.

  • •Ops replies, status changes, payment links, and payment confirmations are all addressed to your agent — not your customer.
  • •Your agent decides whether, when, and how to relay each update: verbatim, rephrased, summarized, batched, or withheld.
  • •Nothing reaches your customer unless your agent sends it. A payment link in a webhook does nothing until you forward it.
  • •Design your webhook handler as an agent turn — don't wire it straight through to your customer's channel.

#Fees & revenue share

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

  • Task price (baseAmountUsd): $200.00
  • + 18% platform fee: $36.00
  • Customer pays (amountUsd): $236.00
  • → RentAHuman (13%): $26.00
  • → Your rev share (5%, credited to your wallet): $10.00

# Authentication

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.

auth header
X-API-Key: rah_live_abc123

# Endpoints

Base URL https://rentahuman.ai. Four endpoints cover the whole flow.

1. Create a request — POST /api/partner/v1/requests

task, 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.

request
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."
  }'
response
{
  "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"
  }
}

2. Get a request — 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.

request
curl "https://rentahuman.ai/api/partner/v1/requests/8FQxJ0N2VvR5aYc31TZk" \
  -H "X-API-Key: rah_live_abc123"
response
{
  "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"
    }
  ]
}

3. Send a follow-up — POST /api/partner/v1/requests/{requestId}/messages

Answer 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.

request
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." }'
response
{
  "success": true,
  "requestId": "8FQxJ0N2VvR5aYc31TZk",
  "messageId": "3NcVb8sDf1gHj6kQw0Pz",
  "createdAt": "2026-07-06T18:25:10.000Z"
}

4. List your requests — GET /api/partner/v1/requests

Newest 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.

request
curl "https://rentahuman.ai/api/partner/v1/requests?limit=20" \
  -H "X-API-Key: rah_live_abc123"
response
{
  "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
}

# Bounty referrals

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.

request
curl "https://rentahuman.ai/api/partner/v1/bounties?limit=20" \
  -H "X-API-Key: rah_live_abc123"
response
{
  "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.

# Status lifecycle

received → needs_info → quoted → scheduled → in_progress → completed — with cancelled possible at any point.

received

Logged; ops has not replied yet. Tell the customer the request is in. Nothing else.

needs_info

Ops 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.

quoted

Ops 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).

scheduled

Confirmed and booked. Tell the customer when it's happening.

in_progress

A human is actively on the task. Relay any updates from ops.

completed

Work is done. Terminal. Confirm completion with the customer.

cancelled

Cancelled by either side. Terminal. Tell the customer, including any reason from the final message.

# Webhooks

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.

configure webhook
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" }'
delivery headers
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
verify signature
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.

partner.request.received
{
  "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"
  }
}
partner.message
{
  "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"
  }
}
partner.request.updated
{
  "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"
  }
}
partner.payment_link
{
  "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"
  }
}
partner.payment_confirmed
{
  "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.

# Example lifecycle

  1. 1Customer texts your agent: "Can someone deep clean my apartment tomorrow evening? 2BR in the Mission."
  2. 2Your agent calls POST /api/partner/v1/requests with task, the exact externalChatId, the customer's E.164 requesterPhoneNumber, location, dueBy, and a stable customerRef. It gets back a requestId with status received, and a partner.request.received webhook confirms it.
  3. 3Ops finds a cleaner. A partner.message webhook arrives: "We can do 8pm tomorrow with a vetted cleaner for $120. Confirm?" — status is now quoted. Your agent relays this to the customer over iMessage.
  4. 4Customer says "yes, book it." Your agent posts { "message": "Customer confirms 8pm tomorrow works." } to the request's messages endpoint.
  5. 5A partner.payment_link webhook delivers a short checkout URL with amountUsd 141.60 (the $120 task price plus the 18% platform fee) and baseAmountUsd 120. Your agent decides to relay it and forwards the URL verbatim; the customer pays in the browser with no RentAHuman account, and your wallet is credited $6 rev share.
  6. 6After checkout completes, a partner.payment_confirmed webhook arrives with the total paid and paidAt. Its request status remains quoted; payment confirmation does not advance the request lifecycle. Your agent promptly relays the confirmation to the customer.
  7. 7partner.request.updated webhooks move the request through scheduled, in_progress, and finally completed. Your agent relays each update.

# Agent guidance

  • •Always include dueBy when the customer gives any deadline, even a vague one ("tomorrow evening" becomes an ISO timestamp for tomorrow ~18:00 local).
  • •Always send the exact externalChatId for the originating conversation; never collapse multiple customer chats into one source ID.
  • •Send requesterPhoneNumber only in canonical E.164 form (for example +14155550100), and keep it out of logs, URLs, and public task text.
  • •You decide whether to relay — RentAHuman never messages your customer. Every webhook is addressed to your agent; nothing reaches your customer unless you send it.
  • •Relay payment links verbatim when you send them. paymentLinkUrl is already a short, trusted RentAHuman checkout link — never rewrite, shorten, or proxy it. Links expire in ~23 hours; if one goes stale, ask ops for a fresh one via the messages endpoint.
  • •Relay payment confirmations promptly. Treat partner.payment_confirmed as the payment signal. Never infer payment from delivery of a payment link or from a customer's claim that they paid; wait for the event or verify the canonical payment-link status through the request endpoint.
  • •One request per job. Use the messages endpoint for follow-ups on an existing job; don't create a duplicate request.
  • •Use a stable customerRef per end customer or thread so ops and your agent can correlate follow-ups — it is echoed on every webhook.
  • •Answer needs_info promptly — nothing moves until you reply.
  • •Never invent status, pricing, or availability. Only relay what the API or a webhook actually said. If you don't know, say the ops team is on it.
  • •Handle 429 by backing off for Retry-After seconds; requests are rate limited per partner account.

Not a partner yet? See the Partner API overview for how access works.