API Reference

Betahunter API

One REST endpoint takes the conversation so far and returns the next message to send, human-paced, on-persona, and ready to deliver. Plain JSON over HTTPS, no SDK required.

Introduction

Send the thread you have so far for a lead. Betahunter decides whether it is the creator's turn, generates a reply in her voice, and returns the message (or burst of messages) to send back.

Base URLhttps://api.betahunter.app
TransportJSON over HTTPS
AuthSecret API key as a Bearer token

Authentication

Every request authenticates with your secret API key, sent as a Bearer token in the Authorization header:

Authorization header
Authorization: Bearer bh_live_xxxxxxxxxxxxxxxxxxxx

Keys start with bh_live_. You can also send the key in an X-API-Key header instead. Keep it server-side, anyone who has it can spend your credits. A missing, unknown, or disabled key returns 401.

No key yet? Get one on Telegram.

Quickstart

One call generates the next reply for a lead who just messaged you:

curl
curl https://api.betahunter.app/v1/generateResponse \
  -H "Authorization: Bearer bh_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "content-type: application/json" \
  -d '{
    "accountId": "acct_42",
    "recipient": { "id": "u_88", "name": "Bob" },
    "chatHistory": [
      { "role": "user", "content": "hey", "timestamp": 1730000000 }
    ],
    "persona": { "name": "Ava", "ctaLink": "https://onlyfans.com/ava" }
  }'

How billing works

You are billed in conversations, not messages or tokens.

  • A conversation is one lead in one of your accounts, identified by accountId + recipient.id. The API returns its conversationId.
  • You are charged one credit the first time the engine produces a reply in a conversation. Every later reply in that same conversation is free, even after your balance reaches zero.
  • Turns that generate nothing (not your turn, capped, out of credits, or blocked) are never charged.
  • Check your remaining credits any time with GET /v1/balance.

Generate a reply

POST/v1/generateResponse

Send the recent thread you have so far. The engine appends only the genuinely new messages, decides whether to respond, and returns the reply to send.

The engine replies only when it is the creator's turn, that is, when the last message in chatHistory is from the lead ("role": "user"). If the thread is empty or the last message is the creator's, you get notOurTurn: true and nothing to send.

Request body

FieldTypeDescription
accountIdstringrequiredYour account or sub-account id. Together with recipient.id it identifies the conversation.
recipientobjectrequiredThe lead you are talking to. See Recipient.
chatHistoryarrayrequiredThe recent conversation, oldest first. Each item is a Message (up to 200). Send [] for a brand-new thread.
personaobjectThe creator the bot is playing. See Persona.
isFollowUpbooleanMark the turn as a follow-up to a lead who went quiet. Default false.

Recipient object

FieldTypeDescription
idstringrequiredStable unique id for the lead. Part of the conversation key.
namestringThe lead's display name.
usernamestringThe lead's handle.
biostringThe lead's bio, used to personalize.
locationstringThe lead's location, used to personalize.

Message object (each chatHistory item)

FieldTypeDescription
rolestringrequired"user" for the lead, "assistant" for the creator.
contentstringrequiredThe message text.
timestampintegerrequiredUnix time in seconds. Used to order the thread.
idstringYour own id for the message, if you have one. Used to de-duplicate when you resend the window.

Persona object

FieldTypeDescription
namestringThe creator's name.
ageintegerThe creator's age. Default 22.
citystringThe creator's city.
userInfostringShort self-description for the persona (look, vibe, backstory).
ctaInfostringWhat you are selling, used when she pitches the link.
ctaLinkstringThe link to close the lead to. Leave blank to disable pitching.
ctaMinExchangesintegerHow many lead replies before she may pitch the link. Default 8.
photosPhoto[]The media catalog she can send. Each item is a Photo.

Photo object (each persona.photos item)

FieldTypeDescription
urlstringrequiredThe media URL.
typestringCatalog bucket: "Primary" or "Sexy". Default "Primary".

Response

The endpoint always returns 200. The body tells you what to do through content plus a set of outcome flags. When any flag is true, content is empty and you were not charged.

FieldTypeDescription
contentarrayThe messages to send, in order. Each item is a Content item. May be empty.
convertedbooleantrue if the lead was closed to your link this turn.
conversationIdstringOpaque id for this conversation. Use it to correlate turns.
notOurTurnbooleantrue when it is not the creator's turn (empty thread, or the last message was already hers). Nothing to send.
timewastebooleantrue when the conversation hit the message cap (50 lead messages) and was stopped.
aiCreditOverbooleantrue when you are out of credits on a new conversation, so nothing was generated. Top up to continue.
underagebooleantrue when the lead tripped the age-safety check. The thread is blocked permanently. Nothing to send.

Content item

FieldTypeDescription
typestring"text" or "image".
contentstringThe message text (for "text") or the media URL to send (for "image").
mediaPoolstringFor an image, which catalog bucket it came from: "Primary" or "Sexy". Absent for text.

Example

Request
{
  "accountId": "acct_42",
  "recipient": { "id": "u_88", "name": "Bob" },
  "chatHistory": [
    { "role": "assistant", "content": "heyy what are you up to", "timestamp": 1730000000 },
    { "role": "user", "content": "just relaxing, you?", "timestamp": 1730000120 }
  ],
  "persona": { "name": "Ava", "ctaLink": "https://onlyfans.com/ava" }
}
200 Response
{
  "content": [
    { "type": "text", "content": "mmm relaxing sounds nice" },
    { "type": "text", "content": "i could think of a few better ways to relax tho 😉" }
  ],
  "converted": false,
  "notOurTurn": false,
  "timewaste": false,
  "aiCreditOver": false,
  "underage": false,
  "conversationId": "k3p9x2:acct_42:u_88"
}

Send each content item as its own message, in order, with natural delays. That multi-message burst is the human cadence the engine is built around.

Check balance

GET/v1/balance

Returns the remaining conversation credits for your key.

curl
curl https://api.betahunter.app/v1/balance \
  -H "Authorization: Bearer bh_live_xxxxxxxxxxxxxxxxxxxx"
200 Response
{ "creditsRemaining": 920 }
FieldTypeDescription
creditsRemainingintegerConversation credits left on your key.

Errors

Authentication, validation, and capacity problems use standard HTTP status codes. Business outcomes (not your turn, capped, out of credits, blocked) are not errors, they come back as 200 with a flag set, as described above.

StatusMeaningWhen
200OKA reply was generated, or a business-outcome flag is set.
401UnauthorizedMissing, unknown, or disabled API key.
422Unprocessable EntityA required field is missing or malformed (e.g. no recipient.id, or a message without a timestamp).
429Too Many RequestsOver 6,000 requests/min, or over 3,000 media-bearing requests/min, on one key.
503Service UnavailableA temporary global capacity guard. Back off and retry.

Error bodies are JSON: { "error": "unauthorized", "message": "..." }. Validation errors (422) also include a details array of { field, message } entries.

Rate limits

  • 6,000 requests per minute, per key.
  • 3,000 media-bearing requests per minute, per key.

Exceeding either limit returns 429. Need higher limits? Ask on Telegram.