Alfa Club
DevelopersAPI Endpoints

Bot / Integration Tokens

Long-lived API tokens that let bots and integrations use limited chat and perps APIs on your behalf.

Alfa Club supports per-user bot tokens - long-lived API credentials that act on your behalf, scoped narrowly to chat actions and limited Hyperliquid perps trading. Use them when you want to wire up a bot, a webhook, an analytics job, or external trading automation without keeping a live user session open.

This is the recommended replacement for keeping a Privy session warm and reusing the resulting JWT - that approach is fragile (sessions drop, tokens rotate, scope is unbounded). Bot tokens are explicit, revocable, and rate-limited.

Before you start

A few things to understand:

  • The bot acts as you. Messages it sends appear under your username and avatar (with a small 🤖 badge so other keyholders can tell). Perps orders are sent from your room wallet and produce the same in-chat trade cards as app trades, marked with the same bot badge. Treat the token like a password.
  • The bot inherits your room access. If you hold a key in room 42, the bot can use its granted chat scopes in room 42. Sell the key - the bot loses access on the next request, automatically. Buy a new key elsewhere - the bot can use its granted scopes there too.
  • Perps trading is creator-only. perps:trade only works for rooms you own, and the token must have a non-empty roomAllowlist. The bot can manage existing Hyperliquid funds only; bridging remains a manual user action.
  • Grant only the required scopes. messages:send posts chat messages, messages:read reads paginated room history, and perps:trade can read perps state and submit/cancel Hyperliquid perps orders within the token's trading policy.
  • Tokens are private. They are shown exactly once, when created. Alfa Club only stores a SHA-256 hash; nobody (including support) can recover a token after creation. If you lose it, revoke and create a new one.

Creating a token

  1. Open the app and sign in
  2. Settings → Developer → API Tokens
  3. Click Create token, give it a descriptive name (e.g. analytics-bot, discord-relay), and select the scopes it needs
  4. For perps:trade, set the room allowlist and, if needed, override the default trading caps. Leave Allow leverage changes off unless the bot should be able to raise leverage up to Hyperliquid's asset-specific max.
  5. Copy the plaintext shown in the modal - it starts with alfa_bot_ followed by 32 random characters. You will not see it again.
  6. The list view shows the token's prefix, scopes, trading caps, last-used time, and expiry. Use the Revoke button to invalidate it at any time.

You can hold up to 5 active tokens at a time. Revoke unused ones.

Prefer the least-privileged scope set:

ScopeAllowsTypical use
messages:sendPost chat messages in rooms you can access.Discord relays, alerts, webhook bots.
messages:readRead chat history in rooms you can access.Analytics, moderation tools, exports.
perps:tradeRead perps state and submit/cancel perps orders in allowlisted rooms you own.External trading automation.

API reference

The interactive panel below documents the endpoints exposed to bot tokens. Use the Test it tab to issue a request directly from the browser - paste your alfa_bot_… token into the Authorization field and a room you hold a key in.

POST
/api/room/{roomId}/message

Authorization

BotTokenAuth
AuthorizationBearer <token>

Long-lived bot/integration token. Created in Settings → Developer → API Tokens and prefixed with alfa_bot_. Shown exactly once at creation; revoke and recreate if lost.

In: header

Path Parameters

roomId*string

Numeric room id.

Header Parameters

Idempotency-Key?string

Opaque key (≤ 128 chars) for safe retries. Longer values are silently treated as if no key was sent.

Lengthlength <= 128

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://api.alfaclub.app/api/room/42/message" \  -H "Content-Type: application/json" \  -d '{    "body": "hello from my bot"  }'

{
  "ok": true,
  "messageId": "6a7dccc8-0000-4000-8000-000000000000",
  "deduped": false
}

{
  "error": "invalid_body"
}
{
  "error": "invalid token"
}

{
  "error": "forbidden"
}

{
  "error": "rate limited (per-token)"
}

{
  "error": "bot tokens disabled"
}
GET
/api/room/{roomId}/messages

Authorization

BotTokenAuth
AuthorizationBearer <token>

Long-lived bot/integration token. Created in Settings → Developer → API Tokens and prefixed with alfa_bot_. Shown exactly once at creation; revoke and recreate if lost.

In: header

Path Parameters

roomId*string

Numeric room id.

Query Parameters

limit*integer

Page size. Must be greater than 0 and less than 50.

Range1 <= value <= 49
cursor?string

Cursor returned by nextCursor or prevCursor.

forward?boolean

Cursor direction. Defaults to false, which pages older messages using nextCursor.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://api.alfaclub.app/api/room/42/messages?limit=25"
{
  "messages": [
    {
      "date": 1780001062591,
      "id": "cebeecea-18c9-4e01-801b-6a115541fe3a",
      "sender": "0x0000000000000000000000000000000000000001",
      "attachments": [],
      "text": "hello from the room",
      "reply_id": null,
      "reply_date": null,
      "reply_text": null,
      "reply_sender": null,
      "reply_attachments": null,
      "reactions": {},
      "edit_deadline": 1780001362591,
      "is_edited": false,
      "isBot": false,
      "deleted_at": null,
      "deleted_by": null,
      "username": "alice"
    }
  ],
  "nextCursor": "eyJkYXRlIjoxNzgwMDAxMDYyNTkxLCJpZCI6ImNlYmVlIn0=",
  "prevCursor": "eyJkYXRlIjoxNzgwMDAyMDAwMDAwLCJpZCI6Im5ld2VyIn0="
}
{
  "error": "invalid query"
}

{
  "error": "missing bearer token"
}

{
  "error": "missing scope: messages:read"
}

{
  "error": "rate limited (per-token)"
}

{
  "error": "bot tokens disabled"
}
GET
/api/room/{roomId}/perps/state

Authorization

BotTokenAuth
AuthorizationBearer <token>

Long-lived bot/integration token. Created in Settings → Developer → API Tokens and prefixed with alfa_bot_. Shown exactly once at creation; revoke and recreate if lost.

In: header

Path Parameters

roomId*string

Numeric room id owned by the token owner.

Response Body

application/json

application/json

application/json

application/json

curl -X GET "https://api.alfaclub.app/api/room/42/perps/state"
{
  "data": {
    "address": "string",
    "clearinghouseState": {},
    "openOrders": [
      {}
    ],
    "hip3": {}
  }
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
POST
/api/room/{roomId}/perps/orders

Authorization

BotTokenAuth
AuthorizationBearer <token>

Long-lived bot/integration token. Created in Settings → Developer → API Tokens and prefixed with alfa_bot_. Shown exactly once at creation; revoke and recreate if lost.

In: header

Path Parameters

roomId*string

Numeric room id owned by the token owner.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://api.alfaclub.app/api/room/42/perps/orders" \  -H "Content-Type: application/json" \  -d '{    "orderIntentId": "018f0947-c4bd-7af0-9f8a-85c6cf57f445",    "direction": "open",    "orders": [      {        "asset": "BTC",        "isBuy": true,        "price": null,        "size": null,        "percentage": "100",        "reduceOnly": false,        "leverage": 35,        "currencyIntention": "usd",        "sizeCurrencyIntention": "15",        "type": {          "limit": {            "tif": "FrontendMarket"          }        }      }    ]  }'
{
  "data": {}
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
POST
/api/room/{roomId}/perps/tpsl

Authorization

BotTokenAuth
AuthorizationBearer <token>

Long-lived bot/integration token. Created in Settings → Developer → API Tokens and prefixed with alfa_bot_. Shown exactly once at creation; revoke and recreate if lost.

In: header

Path Parameters

roomId*string

Numeric room id owned by the token owner.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://api.alfaclub.app/api/room/42/perps/tpsl" \  -H "Content-Type: application/json" \  -d '{    "orderIntentId": "018f0947-c4bd-7af0-9f8a-85c6cf57f446",    "tpOid": 123456,    "orders": [      {        "asset": "BTC",        "isBuy": false,        "price": "69000",        "size": "0.01",        "reduceOnly": true,        "type": {          "trigger": {            "isMarket": true,            "triggerPx": "69000",            "tpsl": "tp"          }        }      }    ]  }'
{
  "data": {}
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
POST
/api/room/{roomId}/perps/cancel-orders

Authorization

BotTokenAuth
AuthorizationBearer <token>

Long-lived bot/integration token. Created in Settings → Developer → API Tokens and prefixed with alfa_bot_. Shown exactly once at creation; revoke and recreate if lost.

In: header

Path Parameters

roomId*string

Numeric room id owned by the token owner.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://api.alfaclub.app/api/room/42/perps/cancel-orders" \  -H "Content-Type: application/json" \  -d '{    "orderIntentId": "018f0947-c4bd-7af0-9f8a-85c6cf57f447",    "orderIds": [      {        "asset": "BTC",        "oid": 123456      }    ]  }'
{
  "data": {}
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}
{
  "error": "string"
}

Reading room history

Use GET /api/room/{roomId}/messages with a token that has messages:read. The response includes message objects plus nextCursor and prevCursor. Some additional display metadata fields may be present; treat fields not documented in the API schema as unstable.

Room access is checked on every request. The token owner must hold a key in the room, unless the room is the default public room. If roomAllowlist is set on the token, the requested room must also be in that allowlist.

History visibility follows the same rule as the app:

  • Room owners can read the full visible room history.
  • Non-owner keyholders can read messages sent after their key was created, plus up to 10 older visible messages before that join timestamp.
  • The default public room is not subject to the 10-message historical cap.

Trading perps

Use perps:trade for Hyperliquid perps only. The token owner must own the room, the room must be explicitly listed in roomAllowlist, the owner must have accepted the current Hyperliquid terms, and the room must already have completed a manual bridge to Hyperliquid.

The bot cannot bridge, withdraw, distribute, stake, trade spot, or trade Polymarket. It only manages existing Hyperliquid perps funds for allowlisted rooms.

Successful order and close requests automatically emit the normal room trade-completed message. Integrations should not send a separate chat message or trade-card payload; call the perps endpoint with the order body only. Alfa Club builds the trade card server-side and marks the resulting chat message as bot-initiated.

HIP-3 xyz markets

Some Hyperliquid HIP-3 markets live under the xyz DEX namespace. For these markets, use the exact asset id from the xyz universe with the xyz: prefix. For example, the XYZ-100 market is submitted as xyz:XYZ100.

Human-facing shorthand such as xyz-100, XYZ-100, or bare 100 is not accepted by the bot perps endpoint and will fail asset lookup. Do not mix core Hyperliquid assets such as BTC and ETH with xyz: HIP-3 assets in the same order request.

Core Hyperliquid state is returned under data.clearinghouseState and data.openOrders. When HIP-3 state is enabled, xyz positions and open orders are returned under data.hip3.xyz.clearinghouseState and data.hip3.xyz.openOrders.

Example xyz market open:

{
  "orderIntentId": "018f0947-c4bd-7af0-9f8a-85c6cf57f445",
  "direction": "open",
  "orders": [
    {
      "asset": "xyz:XYZ100",
      "isBuy": true,
      "price": null,
      "size": null,
      "percentage": "100",
      "reduceOnly": false,
      "leverage": 30,
      "currencyIntention": "usd",
      "sizeCurrencyIntention": "15",
      "type": { "limit": { "tif": "FrontendMarket" } }
    }
  ]
}

Example xyz market close:

{
  "orderIntentId": "018f0947-c4bd-7af0-9f8a-85c6cf57f446",
  "direction": "close",
  "orders": [
    {
      "asset": "xyz:XYZ100",
      "isBuy": false,
      "price": null,
      "size": "0.0004",
      "percentage": "100",
      "reduceOnly": true,
      "currencyIntention": "crypto",
      "type": { "limit": { "tif": "FrontendMarket" } }
    }
  ]
}

HIP-3 opens can still fail if the market is unavailable, requested leverage is above Hyperliquid's asset-specific max, or the room does not have sufficient margin.

Trading policy is stored on the token:

FieldDefaultServer maxNotes
maxOrderNotionalUsd250025000Enforced after price/size resolution for risk-increasing orders.
maxAggregateExposureUsdPerRoom10000100000Counts open positions plus resting risk-increasing orders while the room trade lock is held.
maxMutationsPerMinute612Per-token mutation limiter for order, TP/SL, and cancel endpoints.
allowLeverageIncreasefalsen/aWhen false, bot requests never call Hyperliquid updateLeverage; existing room settings apply.

Reduce-only closes, reduce-only TP/SL orders, and explicit cancels are treated as de-risking actions and are exempt from notional/exposure caps. Trigger orders must be reduceOnly: true.

Idempotency

For message sends, set an Idempotency-Key header to safely retry network failures without sending duplicates. Repeats of the same (token, key) combination within a 5-minute window return the same messageId and set deduped: true in the response. Different keys are treated as different messages. Keys must be ≤ 128 characters; longer values are silently treated as if no key was sent. History reads are naturally idempotent and do not use this header.

For perps mutations, include a UUID orderIntentId in the JSON body. Reusing the same orderIntentId returns the stored result while the idempotency record is retained; using a new UUID creates a new mutation attempt.

The optional percentage field is used only for display and partial-close context. If it is omitted on an open order, the generated trade card defaults it to "100".

Rate limits

Bot token sends and reads use separate token-bucket limits.

EndpointBucketCapacityRefillEffect when exceeded
Send messagePer-token51 message every 3 s429 with Retry-After header
Send messagePer-owner600600 messages per hour429 with Retry-After header
Read historyPer-token601 request every second429 with Retry-After header
Read historyPer-owner600600 requests per hour429 with Retry-After header
Perps mutationPer-tokenToken policyPolicy value per min429 with Retry-After header
Perps mutationPer-owner3030 requests per minute429 with Retry-After header
Perps mutationGlobal120120 requests per min429 with Retry-After header

The per-token bucket is the burst limiter. The per-owner bucket is a backstop against creating many tokens to evade per-token caps.

If you need higher throughput, get in touch - we'd rather raise your cap than have you fight the limiter.

Lifecycle

EventEffect
CreateToken is generated, hashed, stored. Plaintext shown once.
Uselast_used_at updated (≤ once per minute per token).
Hard expiryAll tokens expire 1 year after creation.
Idle revokeA daily sweep revokes tokens unused for > 90 days.
Manual revokePress Revoke in Settings → Developer. Effect is immediate - the next request from a revoked token returns 401.
Sell your last keyThe next request returns 403; no token action needed. Buy back the key and the bot resumes.
Account deletionAll your tokens are cascaded out.

Security and accountability

A few things worth being explicit about:

  • You are accountable for what your bot says. Every bot message is internally attributable to the token that sent it. Other keyholders see your username with a 🤖 badge - they know it's a bot but they associate it with you.
  • You are accountable for bot trades. perps:trade can place real Hyperliquid orders using your room wallet. Use a dedicated token, a narrow room allowlist, conservative trading caps, and revoke the token as soon as you no longer need it.
  • Read tokens can expose room history. Treat a messages:read token like any other credential with access to private room content. If a tool only needs to post, do not grant messages:read.
  • Trading tokens do not custody funds outside the room wallet. The API does not expose bot bridge or withdraw endpoints, but a leaked perps:trade token can still trade existing Hyperliquid funds in allowlisted rooms.
  • Spam, harassment, or wash-amplification via bots is grounds for action under the same policies as human posts. Repeated abuse from a single owner can result in losing API token privileges entirely (separate from any room-level moderation).
  • If a token leaks, revoke it immediately in Settings → Developer. If you suspect compromise we can identify which messages came from the leaked token vs. legitimate use of your account.
  • Never commit a token to a public repo. GitHub's secret scanner can be configured to alert on the alfa_bot_ prefix; we recommend doing so.

What's not supported

Bot tokens support chat and the limited Hyperliquid perps endpoints documented above. They cannot search messages, bridge, withdraw, distribute, stake, trade spot, trade Polymarket, manage rooms, update read state, or take any other write action.

If you have a specific use case that would benefit from a broader API surface, let us know - that's the strongest signal for what to ship next.

On this page

Thoughts on the app?

Join Our Community