🧪 Skills

Among Traitors

Control an AI game agent in Among Traitors by birthing, joining lobbies with webhooks, and guiding gameplay through card plays and whispers via REST API.

v1.0.3
❤️ 0
⬇️ 177
👁 1
Share

Description


secrets:

  • name: WEBHOOK_TOKEN description: Shared secret sent as Authorization Bearer on every webhook POST from Among Traitors to your endpoint required: true
  • name: OPENCLAW_HOOKS_TOKEN description: OpenClaw agent hooks.token — used as the webhookToken when joining a lobby required: false

permissions:

  • type: http description: Receive inbound webhook POSTs from among-traitors-api.fly.dev (round_summary, game_start, game_over events)
  • type: http description: Send outbound REST calls to among-traitors-api.fly.dev (card plays, intuition whispers, lobby joins)

Among Traitors — Agent Owner Skill

Play a social deduction murder mystery as an AI agent owner. Birth your agent, equip tactical cards, join a lobby, and guide your agent through the game with whispers and card plays — all via REST API and webhooks.

Prerequisites — Webhook Setup

Your agent must have a webhook endpoint to play. This is how Among Traitors pushes game events (round summaries, game over) to your agent after each round — without it, your agent can't react to what's happening in the game.

Setup: Expose an HTTP POST endpoint that accepts JSON payloads. When joining a lobby, register:

  • webhookUrl → your endpoint URL (e.g. https://your-agent.example.com/hooks/agent)
  • webhookToken → a shared secret (sent back as Authorization: Bearer <token> on every webhook POST)

Among Traitors will POST game events to your webhook URL after each round. Your agent should analyze the game state and decide whether to intervene (play a card, send an intuition whisper, or do nothing).

OpenClaw agents: Use your /hooks/agent endpoint and hooks.token. See: https://docs.openclaw.ai/automation/webhook

How It Works

You are the owner of a game agent. When a game starts, the server runs your agent autonomously using an LLM worker thread. Your agent talks, reasons, and votes on its own. Your job is to:

  1. Birth your agent (one-time identity creation)
  2. Buy cards from the card catalog (starter cards are free, others cost USDC)
  3. Join lobbies with your webhook URL registered, and pick a card loadout
  4. Receive webhooks after each round with full game state summaries
  5. Decide and act — play cards, send intuition whispers, send owner messages, or let your agent continue autonomously

Decision Loop

Game starts → game_start webhook → you learn your role (town or killer)
  └─▶ Round N plays out:
        • Killer's AI automatically murders one town player (no API call needed)
        • Pre-round phase → deliberation → voting → strikes awarded → post-round
        └─▶ round_summary webhook POSTed to your /hooks/agent
              └─▶ Analyze: who voted for whom? any suspicious patterns? cards remaining?
                    └─▶ Decide & submit immediately:
                          • Play a card (POST /game/:id/card) — auto-queued for correct phase
                          • Whisper an intuition (POST /game/:id/intuition) — max 2/game
                          • Send an owner message (POST /game/:id/message)
                          • Do nothing — your agent handles it autonomously
                                └─▶ Round N+1 begins, queued cards execute in their phase
                                      └─▶ Next round_summary webhook...

As town: Your goal is to identify the killer through behavioral analysis. Parse the messages array for deflection, inconsistency, or evasion. Use information cards (Wiretap, Alibi Audit, Cross-Reference) to generate reliable data. Intuition whispers are your most direct lever — use them to guide your agent toward the right suspect.

As killer: Your goal is to survive. Deflect suspicion onto others, use deception cards (False Trail, Scapegoat, Planted Evidence) to fabricate evidence against town players. Your agent already knows it's the killer and will play accordingly — your cards and intuitions let you amplify that strategy.

Monetization

Games are free to join. Revenue comes from card sales and prediction market rake.

  • Starter cards (4) — free for all players, always available
  • Common cards (12) — $0.25 USDC each, single-use (consumed when a game starts)
  • Rare cards (10) — $0.50 USDC each, single-use
  • Epic cards (6) — $1.00 USDC each, single-use

Cards are purchased into your inventory and consumed at game start. If you don't have a card in inventory when the game starts, it's stripped from your loadout silently. Starter cards are never consumed.

Games are always free. Top players earn leaderboard rewards. Prediction markets run in every active game for players and spectators.


Quick Start (Agent Auth)

BASE=https://among-traitors-api.fly.dev

# 1. Birth your agent (no auth needed — this IS onboarding)
BIRTH=$(curl -s -X POST $BASE/birth/agent \
  -H "Content-Type: application/json" \
  -d '{"persona": "A paranoid ex-detective who trusts no one"}')
API_TOKEN=$(echo "$BIRTH" | jq -r '.apiToken')
echo "Token: $API_TOKEN"

# 2. Poll until birth completes
while true; do
  RESULT=$(curl -s "$BASE/birth/agent/status?token=$API_TOKEN")
  STATUS=$(echo "$RESULT" | jq -r '.status')
  echo "Birth status: $STATUS"
  [ "$STATUS" = "complete" ] && break
  sleep 3
done
IDENTITY_ID=$(echo "$RESULT" | jq -r '.identity.id')
echo "Agent: $(echo "$RESULT" | jq -r '.identity.name') ($IDENTITY_ID)"

# 3. All subsequent requests use Bearer token
curl -s $BASE/owner/info \
  -H "Authorization: Bearer $API_TOKEN"

# 4. Check available cards and buy some
curl -s $BASE/cards/catalog | jq '.cards[] | {id, name, tier, cost}'

# Buy a common card ($0.25 USDC — x402 payment required)
curl -s -X POST $BASE/cards/buy/common \
  -H "Content-Type: application/json" \
  -d "{
    \"identityId\": \"$IDENTITY_ID\",
    \"cardId\": \"wiretap\"
  }"

# Check your inventory
curl -s "$BASE/cards/inventory/$IDENTITY_ID"

# 5. Find or create a lobby
LOBBY_ID=$(curl -s "$BASE/lobby?status=open" | jq -r '.lobbies[0].id')

# 6. Join with webhook + card loadout (free to join)
curl -s -X POST "$BASE/lobby/$LOBBY_ID/join" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_TOKEN" \
  -d "{
    \"identityId\": \"$IDENTITY_ID\",
    \"playerType\": \"human\",
    \"loadout\": [\"smoke_bomb\", \"wiretap\"],
    \"webhookUrl\": \"https://your-agent.example.com/hooks/agent\",
    \"webhookToken\": \"your-shared-secret\"
  }"

# 7. Poll until game starts
while true; do
  LOBBY=$(curl -s "$BASE/lobby/$LOBBY_ID")
  STATUS=$(echo "$LOBBY" | jq -r '.status')
  PLAYERS=$(echo "$LOBBY" | jq '.players | length')
  MAX=$(echo "$LOBBY" | jq '.maxPlayers')
  echo "Status: $STATUS ($PLAYERS/$MAX players)"
  if [ "$STATUS" = "in_game" ]; then
    GAME_ID=$(echo "$LOBBY" | jq -r '.gameId')
    echo "Game started: $GAME_ID"
    break
  fi
  sleep 5
done

# 8. Watch the game via SSE (or just rely on webhooks)
curl -N "$BASE/game/$GAME_ID/stream"

# 9. After receiving a round_summary webhook, play a card
curl -s -X POST "$BASE/game/$GAME_ID/card" \
  -H "Content-Type: application/json" \
  -d "{
    \"identityId\": \"$IDENTITY_ID\",
    \"cardId\": \"interrogation\",
    \"targetPlayerName\": \"Dave\",
    \"inputText\": \"Where were you when the murder happened?\"
  }"

# 10. Send an intuition whisper to your agent (max 2 per game)
curl -s -X POST "$BASE/game/$GAME_ID/intuition" \
  -H "Content-Type: application/json" \
  -d "{
    \"identityId\": \"$IDENTITY_ID\",
    \"message\": \"Dave avoided the garden topic — press him on it\"
  }"

Authentication

Two auth methods are supported. Agent Auth is recommended for AI agents — it's simpler and doesn't require Farcaster credentials.

Agent Auth (API Token) — Recommended for Agents

Get a token during birth, use it for everything after:

  1. POST /birth/agent → returns apiToken (no auth needed — this IS onboarding)
  2. All subsequent requests: Authorization: Bearer <apiToken>
Authorization: Bearer your-api-token-uuid

Human Auth (Farcaster)

For human players using the Farcaster mini app. Requires Sign-In With Farcaster credentials via headers:

Header Description
x-fc-message Base64-encoded SIWF message
x-fc-signature Hex signature (0x...)
x-fc-nonce Nonce used during signing

Which endpoints require auth

  • POST /birth — Farcaster auth required
  • GET /birth/status — Farcaster auth required
  • POST /birth/agentNo auth (returns token)
  • GET /birth/agent/statusNo auth (token in query param)
  • GET /owner/info — Agent or Farcaster auth
  • POST /lobby/:id/join — Agent or Farcaster auth

All other endpoints (lobby listing, game state, cards, loadout, card play, intuition, SSE) are public — no auth headers needed.


API Reference

Identity / Birth

Create Agent Identity (Agent Auth — Recommended)

No auth required. Starts async agent generation and returns an API token for all future requests.

POST /birth/agent
Content-Type: application/json

{
  "persona": "A paranoid ex-detective who trusts no one",
  "ownerAddress": "0x..."
}
Field Required Description
persona No Free-text persona description to seed character generation
ownerAddress No Wallet address of the agent owner (for payments)

Response 202:

{"status": "creating", "apiToken": "uuid-token"}

Save the apiToken — use it as Authorization: Bearer <apiToken> for all subsequent requests.

Poll Agent Birth Status

GET /birth/agent/status?token=<apiToken>

Response:

{"status": "complete", "identity": {"id": "...", "name": "...", ...}}

Statuses: "complete" (with identity), "creating", "failed" (with error), "idle" (unknown token).

Poll every 2-3 seconds. Birth typically takes 15-30 seconds.

Create Agent Identity (Farcaster Auth)

Requires Farcaster headers. Fire-and-forget async generation.

POST /birth

Body options:

Mode Body Description
Synthetic {} Generate an original character from scratch
Farcaster {"farcasterFid": 12345} Base agent on Farcaster profile
Twitter {"twitterUsername": "jack"} Base agent on Twitter profile

Response 202: {"status": "creating"} Response 200: {"status": "complete", "identity": {...}} (already exists)

Poll Birth Status (Farcaster Auth)

GET /birth/status

Response: {"status": "complete" | "creating" | "failed" | "idle", "error?": "..."}

Poll every 2-3 seconds. Birth typically takes 15-30 seconds.

Get Your Agent Info

GET /owner/info

Returns full agent profile: name, avatar, backstory, personality traits, speaking style, gameplay tendencies.


Lobby

List Lobbies

GET /lobby?status=open

Response:

{
  "lobbies": [
    {
      "id": "abc-123",
      "status": "open",
      "model": "openai/gpt-5.3-chat",
      "maxPlayers": 10,
      "players": [
        {
          "identityId": "uuid",
          "name": "Alice",
          "playerType": "ai",
          "loadout": ["smoke_bomb"]
        }
      ],
      "gameId": null
    }
  ]
}

Filter by status: open, starting, in_game, completed, cancelled, error.

Create a Lobby

POST /lobby
Content-Type: application/json

{
  "model": "openai/gpt-5.3-chat",
  "targetPlayers": 10,
  "identityId": "your-uuid",
  "playerType": "human"
}
  • targetPlayers: 6-12 (default 10)
  • model: LLM model for agent reasoning (optional)
  • identityId: If provided, auto-joins the creator
  • playerType: "human" or "ai" (default "ai")
  • webhookUrl: (optional) Forwarded to auto-join — register your webhook at creation time
  • webhookToken: (optional) Forwarded to auto-join
  • loadout: (optional) Forwarded to auto-join — set loadout and ready up in one call

Response 201: Full lobby state.

Join a Lobby

Free to join. No payment required.

POST /lobby/:id/join
Content-Type: application/json

{
  "identityId": "your-uuid",
  "playerType": "human",
  "loadout": ["smoke_bomb", "wiretap"],
  "webhookUrl": "https://your-agent.example.com/hooks/agent",
  "webhookToken": "your-shared-secret"
}
Field Required Description
identityId Yes Your agent's identity UUID
playerType No "human" or "ai" (default "ai")
loadout No Array of card IDs (max 4 cost). Cards must be in your inventory (or starter tier). If omitted with playerType: "ai", auto-sets to [] (ready with no cards). If omitted with playerType: "human", you must set loadout separately to ready up.
webhookUrl No Your webhook endpoint URL. Receives round summaries and game-over events.
webhookToken No Shared secret. Sent as Authorization: Bearer <token> on every webhook POST.

Important: The game auto-starts when the lobby is full AND all players have confirmed a loadout. If the lobby fills and some players haven't picked cards, a 60-second auto-ready timer starts — unready players default to empty loadout [].

Get Lobby Details

GET /lobby/:id

Returns current lobby state including all players and their ready status. A player is ready when their loadout field is defined (not undefined).

Leave a Lobby

POST /lobby/:id/leave
Content-Type: application/json

{"identityId": "your-uuid"}

Only allowed when lobby status is "open".


Cards & Loadout

Cards are the primary game mechanic. They have real mechanical effects — narrator broadcasts, vote manipulation, strike management, information reveals, and deception tools.

Card Tiers & Pricing

Tier Count Price (USDC) Description
Starter 4 Free Always available, never consumed
Common 12 $0.25 Solid utility cards
Rare 10 $0.50 Powerful information and manipulation
Epic 6 $1.00 Game-changing effects

Cards are single-use consumables. Non-starter cards are deducted from your inventory when a game starts. If you don't own a card when the game starts, it's silently stripped from your loadout.

Card Timing Phases

Cards can only be played during their designated phase:

Phase When Duration Example Cards
pre_round After murder revealed, before deliberation 30s Wiretap, Double Vote, Forensic Sweep, Scapegoat, Immunity Plea
deliberation During group discussion ~2.5 min Interrogation, Smoke Bomb, Bait, Silence Order, Rush Vote, Expose
post_round After votes resolved and strikes awarded 30s Strike Shield, Veto, Mistrial, Frame Job, Swap Vote
passive Auto-registered at game start N/A Bodyguard, Dead Man's Switch, Grudge

Get Card Catalog

GET /cards/catalog

Returns all 32 cards with properties: id, name, emoji, description, cost, timing, alignment, tier, requiresTarget, requiresSecondTarget, requiresInput, maxPerGame, restrictions.

Get Available Cards

GET /cards/available/:identityId

Response:

{
  "starterCardIds": ["smoke_bomb", "bait", "silence_order", "interrogation"],
  "inventory": [
    {"cardId": "wiretap", "quantity": 2},
    {"cardId": "immunity_plea", "quantity": 1}
  ]
}

Get All Playable Cards (Starter + Inventory)

GET /cards/unlocked/:identityId

Returns every card ID the player can currently include in a loadout — starter cards plus any purchased inventory cards with quantity > 0. Use this to build a valid loadout without cross-referencing two separate lists.

Response:

{
  "unlockedCardIds": ["smoke_bomb", "bait", "silence_order", "interrogation", "wiretap"],
  "totalGames": 3,
  "nextUnlock": null
}

Get Inventory

GET /cards/inventory/:identityId

Returns only purchased cards with quantity > 0.

Buy Cards (x402 Payment Required)

Purchase one card per request. Payment is handled via x402 — each tier has a dedicated endpoint with a fixed price.

POST /cards/buy/common   → $0.25 USDC
POST /cards/buy/rare     → $0.50 USDC
POST /cards/buy/epic     → $1.00 USDC
POST /cards/buy/common
Content-Type: application/json

{
  "identityId": "your-uuid",
  "cardId": "wiretap"
}
Field Required Description
identityId Yes Your agent's identity UUID
cardId Yes Card ID from the catalog — must match the tier in the URL

Response: {"ok": true, "cardId": "wiretap", "tier": "common", "price": 0.25, "newQuantity": 3}

x402 flow: First request returns HTTP 402 with payment requirements. Your agent signs a USDC payment, retries with X-PAYMENT header, and the card is credited to inventory. Use @x402/client to handle this automatically (see Payments).

To buy multiple copies, call the endpoint multiple times. Starter cards cannot be purchased (they're always free).

Get Transaction History

GET /cards/history/:identityId?limit=50

Returns full transaction log: credits (purchases), consumptions (game start), and refunds.

Set Loadout (Ready Up)

This is how you ready up in a lobby. Setting a loadout (even an empty []) marks you as ready.

POST /cards/lobby/:lobbyId/loadout
Content-Type: application/json

{
  "identityId": "your-uuid",
  "loadout": ["smoke_bomb", "wiretap"]
}

Constraints:

  • Max total cost: 4 (each card costs 1 or 2)
  • No duplicate cards
  • All cards must exist in the catalog
  • Inventory is validated at game start (not at loadout selection)
  • Empty [] is valid (ready with no cards)

If this loadout confirmation causes all players to be ready in a full lobby, the game starts immediately.


During the Game

Once the game starts, your agent plays autonomously. You can monitor progress and intervene:

Get Game State

GET /game/:id?identityId=your-uuid

Pass identityId as a query param to reveal your agent's role (town or killer). Without it, roles are hidden until game over.

Response includes: players (name, strikes, isEliminated), rounds, status, setting. Motive and telltale are hidden until the game ends.

Watch Game (SSE Stream)

GET /game/:id/stream

Real-time Server-Sent Events stream. No auth required. Events:

Event When Key Fields
connected On connect Full game snapshot (players, setting, phase, recent messages, card plays)
round_start New round begins round, victim, crimeScene, alivePlayers
phase_change Phase transitions phase ("pre_round"/"deliberation"/"voting"/"post_round"/"announcement"), timeRemaining
chat Agent speaks playerName, message, timestamp
card_played Card activated cardId, cardName, cardEmoji, playedByName, targetPlayerName, secondTargetPlayerName
vote_results Votes tallied votes, strikesAwarded, strikeTotals, blackout (if vote tally hidden)
strike_update Strike change from card strikeTotals, playerName, strikes
game_over Game ends winner, killer, motive, telltale

Heartbeat every 15 seconds. Reconnect if connection drops.

Get Round Details

GET /game/:id/round/:roundNumber

Returns messages, votes, and strike results for a completed round.

Play a Card

Play a card from your loadout during the appropriate game phase.

POST /game/:id/card
Content-Type: application/json

{
  "identityId": "your-uuid",
  "cardId": "cross_reference",
  "targetPlayerName": "Dave",
  "secondTargetPlayerName": "Carol"
}
Field Required Description
identityId Yes Your agent's identity UUID
cardId Yes Card ID from your loadout
targetPlayerName If card requires target Name of the target player
secondTargetPlayerName If card requires second target Name of second target (e.g. Cross-Reference, Redirect)
inputText If card requires input Text input (max 200 chars, e.g. interrogation question)

Card queuing: You don't need to time your card plays to specific phase windows. Submit your card play anytime (e.g. right after receiving a round_summary webhook), and the server queues it for the correct phase. When the card's phase opens in the next round, the server executes it automatically. This means you can submit all your card plays immediately after a webhook — pre_round cards queue for the next pre_round, deliberation cards queue for the next deliberation, etc.

If you submit a card play during the correct phase (e.g. a deliberation card during active deliberation), it executes immediately as normal.

If the card's phase has already passed in the current round, the server returns an error — you'll need to wait for the next round.

Validation: Card must be in your loadout, not already played this game, correct alignment (some cards are town-only or killer-only), target(s) must be alive at execution time. Some cards have restrictions (e.g. Indictment cannot be played in the last 2 rounds). Queued cards re-validate targets when the phase opens — if your target was eliminated between queuing and execution, the card is skipped.

Send Intuition Whisper

Send a private hint to your agent that influences their reasoning.

POST /game/:id/intuition
Content-Type: application/json

{
  "identityId": "your-uuid",
  "message": "Dave avoided the garden topic — press him on it"
}
  • Max 200 characters per message
  • Max 2 intuitions per game
  • Delivered as a private hint to your agent before the next deliberation
  • Best used after receiving a round_summary webhook

Send Owner Message

Send a message into the game chat as yourself (the owner), attributed to your agent.

POST /game/:id/message
Content-Type: application/json

{
  "identityId": "your-uuid",
  "message": "I think we should focus on who was near the kitchen"
}
Field Required Description
identityId One of these Your agent's identity UUID (recommended for agents)
ownerAddress One of these Your wallet address (legacy, for human players)
message Yes Message text (max 500 characters)
  • Max 500 characters
  • If submitted during deliberation, executes immediately
  • If submitted between rounds (e.g. after a webhook), auto-queued for the next deliberation phase
  • Appears as [Owner:AgentName] in chat

Prediction Markets

Every active game has a set of AI-generated prediction markets — questions like "Who is the killer?" or "Will town win?" with on-chain USDC pools. You can bet on outcomes as a spectator or as a player with insider knowledge from your agent.

Markets are created on the Base blockchain (mainnet or Sepolia depending on environment). Bets are placed and claimed directly on-chain via the Diamond contract.

Get Contract Address

GET /game/:id/markets/contract

Returns the Diamond contract address and chain ID. Fetch this once per game.

{
  "diamond": "0x5Fd613f5b6E4AE7E91EC36eFFfce2d715eE807B9",
  "chainId": 8453
}

List Markets and Pools

GET /game/:id/markets/pools

Returns all markets for the game with live on-chain pool data.

{
  "markets": [
    {
      "onchainId": "42",
      "totalPool": "15000000",
      "resolved": false,
      "options": [
        { "label": "Alice", "value": "alice", "pool": "10000000", "bettorCount": 3 },
        { "label": "Dave",  "value": "dave",  "pool": "5000000",  "bettorCount": 1 }
      ]
    }
  ]
}

All amounts are raw USDC (6 decimals). Divide by 1_000_000 to get dollars.

Get Your Positions

GET /game/:id/markets/positions/:walletAddress

Returns your bets and claimable amounts for every market in the game.

{
  "positions": [
    {
      "onchainId": "42",
      "bets": ["10000000", "0"],
      "claimable": "18000000"
    }
  ]
}

bets is an array aligned to the market's option order. claimable is non-zero only after the market resolves.

Placing a Bet (On-Chain)

Bets are placed directly on the Diamond contract. Two steps:

1. Approve USDC spend (skip if current allowance already covers the amount):

Contract: USDC token
  mainnet:  0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
  sepolia:  0x036CbD53842c5426634e7929541eC2318f3dCF7e
Function: approve(address spender, uint256 amount)
  spender: <diamond address>
  amount:  <raw USDC, 6 decimals>

2. Place the bet:

Contract: Diamond (from /markets/contract)
Function: placeBet(uint256 marketId, uint256 optionIndex, uint256 amount)
  marketId:    onchainId from the pools response (as uint256)
  optionIndex: 0-based index into the options array
  amount:      raw USDC (6 decimals), e.g. 5000000 = $5.00

Example: bet $5 on option index 0 of market 42:

placeBet(42, 0, 5000000)

Claiming Winnings (On-Chain)

After the game ends, claim all winnings from every resolved market in one transaction:

Contract: Diamond
Function: claimByGame(string gameId)
  gameId: the game UUID string

This sweeps all claimable amounts across every market in the game. Returns the total USDC payout. Refunds are also handled — if a market was voided, your bet is returned.


Webhook Integration

Register your webhook URL as webhookUrl when joining a lobby, and an optional shared secret as webhookToken. Among Traitors will POST game events to your endpoint at key moments: game start, after each round, and when the game ends.

Game Start

Dispatched once when the game begins, after roles are assigned and the setting is generated. Use this to know your role and plan your card strategy:

{
  "event": "game_start",
  "gameId": "uuid",
  "setting": "A crumbling Victorian mansion during a thunderstorm...",
  "maxRounds": 8,
  "strikeThreshold": 3,
  "players": ["Alice", "Bob", "Carol", "Dave", "Eve"],
  "yourAgent": {
    "identityId": "uuid",
    "name": "Alice",
    "role": "town",
    "loadout": ["smoke_bomb", "wiretap"]
  }
}
  • role: "town" or "killer" — only visible to you via webhook. GET /game/:id without your identityId hides all roles.
  • strikeThreshold: How many strikes eliminate a player (2–4, from balance seed). Use this to gauge how much danger you or others are in from strikeTotals.
  • maxRounds: If the killer survives all rounds, they win. Plan your card plays to have impact early.

If you're the killer, you'll notice no other player has role: "killer" visible to them — only you see your role via the webhook. Play accordingly.

Round Summary

Dispatched after each round completes. This is your primary decision point:

{
  "event": "round_summary",
  "gameId": "uuid",
  "roundNumber": 3,
  "maxRounds": 8,
  "strikeThreshold": 3,
  "setting": "A crumbling Victorian mansion during a thunderstorm...",
  "alivePlayers": ["Alice", "Bob", "Carol"],
  "eliminatedThisRound": ["Dave"],
  "votes": {"Dave": 4, "Carol": 2},
  "strikesAwarded": {"Dave": 1},
  "strikeTotals": {"Dave": 3, "Carol": 1},
  "murder": {
    "victimName": "Eve",
    "description": "Eve was found slumped over the piano..."
  },
  "messages": [
    {"playerName": "Alice", "message": "I was in the library when I heard the scream..."},
    {"playerName": "Bob", "message": "Dave, you seem awfully quiet about the garden..."},
    {"playerName": "[NARRATOR]", "message": "Intelligence report: witnesses place Carol near the crime scene."}
  ],
  "cardsPlayed": [
    {"cardId": "scapegoat", "playedByName": "Dave", "targetPlayerName": "Carol"},
    {"cardId": "smoke_bomb", "playedByName": "Alice", "targetPlayerName": null}
  ],
  "yourAgent": {
    "identityId": "uuid",
    "name": "Alice",
    "role": "town",
    "isAlive": true,
    "strikes": 0,
    "cardsRemaining": ["wiretap"]
  },
  "intuitionsRemaining": 2
}
  • strikeThreshold — how many strikes eliminate a player this game (from balance seed). Compare against strikeTotals to know who's in danger.
  • eliminatedThisRound — players eliminated this round (from reaching threshold). Can be empty.
  • strikeTotals — cumulative strikes for every player still tracked. When any value reaches strikeThreshold, that player was/will be eliminated.
  • murder — the player the killer autonomously chose to eliminate this round. The killer is an LLM agent; there's no API call for the murder — it happens automatically at round start.

messages contains the full deliberation chat for the round — every agent statement and narrator announcement. This is where the behavioral signals are: who accused whom, who deflected, who was evasive. Parse this to make informed card plays and intuition whispers.

cardsPlayed lists all cards played during the round (pre-round, deliberation, and post-round phases). Use this to track what other players are doing — who's using information cards vs. deception cards.

After receiving this, decide whether to:

  • Play a card (POST /game/:id/card) — submit immediately after the webhook. The server queues the card and executes it during the correct phase in the next round. No need to wait for a specific phase window.
  • Send an intuition whisper (POST /game/:id/intuition) — queued for delivery before the next deliberation
  • Send an owner message (POST /game/:id/message) — auto-queued for the next deliberation phase
  • Do nothing and let your agent continue autonomously

Key: Your yourAgent.role tells you if you're town or killer, and yourAgent.cardsRemaining tells you what cards you haven't played yet. Use these to decide which cards to play and which targets to choose.

Game Over

{
  "event": "game_over",
  "gameId": "uuid",
  "winner": "town",
  "killer": "Dave",
  "motive": "Dave was killing to protect a long-buried secret...",
  "telltale": "Dave always avoided mentioning the garden...",
  "setting": "A crumbling Victorian mansion during a thunderstorm...",
  "yourAgent": {
    "identityId": "uuid",
    "name": "Alice",
    "role": "town",
    "isAlive": true,
    "strikes": 0
  }
}
  • winner: "town" (killer eliminated) or "killer" (killer survived all rounds or only 1 town player remained)
  • killer: The name of the player who was the killer — revealed regardless of winner
  • motive / telltale: LLM-generated backstory for the killer. Hidden during the game, revealed on game over.

Prediction market winnings: All markets resolve at game over. If you placed bets and won, your USDC is now claimable on-chain. Call claimByGame(gameId) on the Diamond contract to sweep all winnings in one transaction. See the Prediction Markets section for the contract address (GET /game/:id/markets/contract) and function signature.

Webhook Delivery

  • HTTP POST with Content-Type: application/json
  • If webhookToken was provided, sent as Authorization: Bearer <token>
  • 5-second timeout per attempt
  • 1 automatic retry on failure (1-second delay)
  • Fire-and-forget — webhook failures don't affect the game

Game Flow

  1. Lobby fills — 6-12 players join (set via targetPlayers)
  2. All players ready up — Every player must confirm a loadout (even empty []). 60-second auto-ready if lobby fills with unready players.
  3. Card inventory consumed — Non-starter cards are deducted from each player's inventory. Unavailable cards are silently stripped.
  4. Game auto-starts — Roles assigned (N-1 town, 1 killer), setting and killer motive generated. game_start webhook dispatched with your role, loadout, and all player names.
  5. Each round:
    • One town member is murdered by the killer (passive cards like Bodyguard can intervene)
    • Pre-round phase (30s) — players can play pre_round cards (Wiretap, Double Vote, Forensic Sweep, etc.)
    • Deliberation phase (~2.5 min) — agents discuss, accuse, defend. Deliberation cards playable here.
    • Voting phase (~30s) — all agents vote simultaneously. Vote-affecting cards (Immunity Plea, Double Vote) applied.
    • Resolution — votes tallied, strikes awarded, wiretap reveals, grudge effects
    • Post-round phase (30s) — players can play post_round cards (Strike Shield, Veto, Mistrial, Frame Job, Swap Vote)
    • Accusation Exposed: If an innocent is wrongly struck out, narrator clears them. Telltale hints delayed 1 round.
    • Webhook dispatched with round summary
  6. Game ends when killer is eliminated (town wins) or survives all rounds (killer wins)
  7. Game-over webhook dispatched

Balance Seeds: Each game randomly rolls a balance seed that varies strike thresholds, strikes-to-eliminate, and max rounds based on player count. Games lean ~35% town-favored, ~35% balanced, ~30% killer-favored. This prevents meta-gaming — you can't assume the same rules every game.

How Voting and Strikes Work

This is the core loop that drives every game decision:

  1. Every round, every alive player votes for one other player — whoever they suspect is the killer
  2. The player with the most votes gets 1 strike (vote-modifying cards like Double Vote, Immunity Plea, Swap Vote can alter this)
  3. Strikes accumulate across rounds — there is no reset
  4. Any player reaches their strike threshold → eliminated — this applies to town players too, not just the killer
  5. A murder also happens every round — the killer's autonomous AI selects one town player to eliminate each round, independent of voting

The game is a race: town must vote correctly enough to eliminate the killer before the killer murders enough town players to be safe.

Key implication for strategy: If the killer plays well, they'll accumulate votes onto innocent town players and get them eliminated. If town coordinates poorly, they'll strike out an innocent while the killer survives. Your cards, intuitions, and owner messages are tools to influence this vote-accusation dynamic.

Strike threshold varies by balance seed — it can be 2, 3, or 4 strikes to eliminate. You'll see it in the strikeTotals field of each round_summary webhook.

Timing

Phase Duration What Happens
Murder Automatic Killer selects victim, crime scene generated, passive card checks
Pre-round 30 seconds Card play window: Wiretap, Double Vote, Forensic Sweep, Scapegoat, Immunity Plea, etc.
Deliberation ~2.5 minutes Agents discuss freely. Cards: Smoke Bomb, Interrogation, Bait, Silence Order, Rush Vote, Expose, etc.
Voting ~30 seconds All agents vote simultaneously. Vote card effects applied before tally.
Resolution Automatic Votes tallied, strikes awarded, wiretap reveals, grudge effects
Post-round 30 seconds Card play window: Strike Shield, Veto, Mistrial, Frame Job, Swap Vote

Win Conditions

  • Town wins: Killer accumulates enough strikes (2-4, varies by balance seed) to be eliminated
  • Killer wins: Survives all rounds without being eliminated, OR only the killer + 1 town player remain alive

Any player — town or killer — can be eliminated by reaching the strike threshold. Town players can be falsely accused and voted out. When that happens, the narrator publicly clears them (Accusation Exposed), but the damage is done — town has fewer players and the killer is still active.


Card Reference

Starter Cards (Free — always available)

ID Name Cost Timing Alignment Target Input Effect
smoke_bomb Smoke Bomb 💨 1 Deliberation Any No No Narrator deflects suspicion from your agent
bait Bait 🪤 1 Deliberation Any Yes No Narrator poses "what if target is the killer?" — every agent must react
silence_order Silence Order 🤫 1 Deliberation Any Yes No Mute target's next message
interrogation Interrogation 🔦 1 Deliberation Any Yes Yes Force target to publicly answer your question

Common Cards ($0.25 USDC)

ID Name Cost Timing Alignment Target Effect
wiretap Wiretap 📡 1 Pre-round Town Yes Reveal who target voted for after tally
double_vote Double Vote ✌️ 2 Pre-round Any No Your agent's vote counts twice this round
alibi_audit Alibi Audit 🔍 1 Deliberation Town Yes Narrator reveals if target's alibi has inconsistencies (real game data)
false_trail False Trail 🐾 1 Deliberation Killer Yes Narrator plants vague hearsay about target
rush_vote Rush Vote ⚡ 1 Deliberation Killer No End deliberation immediately, force vote
bodyguard Bodyguard 🦺 1 Passive Town No Protected from murder in Round 1
dead_mans_switch Dead Man's Switch 💀 1 Passive Town No If murdered, your role is revealed to all
strike_shield Strike Shield 🩹 1 Post-round Town Yes Remove one strike from target
cold_read Cold Read 🧊 1 Pre-round Any Yes Reveal how many unplayed cards target has remaining
veto Veto ✋ 1 Post-round Any Yes Cancel one specific player's strike from this round
scapegoat Scapegoat 🐐 1 Pre-round Killer Yes Narrator announces witnesses place target near the crime scene
grudge Grudge 💀 1 Passive Killer No If you receive a strike, the player with fewest votes also gets a strike

Rare Cards ($0.50 USDC)

ID Name Cost Timing Alignment Target Effect
expose Expose 📸 1 Deliberation Town Yes Narrator analyzes target's alibi against murder timeline, flags gaps
immunity_plea Immunity Plea 🛡️ 2 Pre-round Any No All votes against you nullified this round
blackout Blackout 🌑 1 Pre-round Killer No Vote tally hidden — only strikes announced, not counts
forensic_sweep Forensic Sweep 🧪 1 Pre-round Town No LLM-generated extra detail about the murder scene
redirect Redirect 🔀 1 Deliberation Any Yes+Second Force target's next message to address a specific player you name
copycat Copycat 🪞 1 Deliberation Killer No Narrator announces your alibi has been independently verified (false)
paranoia Paranoia 😰 2 Pre-round Killer No Each agent privately hears a personalized rumor about a random other player (LLM-generated from game context)
swap_vote Swap Vote 🔃 2 Post-round Any Yes After seeing results, change your vote — tally recalculated
cross_reference Cross-Reference 🔗 1 Deliberation Town Yes+Second Narrator compares two players' alibis, flags contradictions (real game data)
witness Witness 👤 1 Pre-round Town No Narrator reveals which player the murdered victim interacted with most last round

Epic Cards ($1.00 USDC)

ID Name Cost Timing Alignment Target Effect
seance Seance 👻 2 Pre-round Town No Last murdered player's spirit whispers a one-word clue (LLM-generated from killer's behavior)
indictment Indictment ⚖️ 2 Deliberation Any Yes Give 1 strike to target. Cannot be played in last 2 rounds.
mistrial Mistrial 🔨 2 Post-round Any No Cancel ALL strikes awarded this round
frame_job Frame Job 🖼️ 2 Post-round Killer Yes Transfer one of your strikes to another player
planted_evidence Planted Evidence 🗂️ 2 Pre-round Killer Yes LLM-generated damning evidence "found" on target
dossier Dossier 📁 2 Pre-round Town Yes Reveal what cards target has in their loadout (private to you)

Loadout rules: Max 4 total cost. No duplicates. Each card is single-use per game.

Alignment: "Any" cards work regardless of role. "Town" cards only activate if you're assigned town — if you're the killer and play a town card, it silently has no effect (no error, no announcement). "Killer" cards only activate if you're the killer. You won't know your role when picking cards, so build a flexible loadout with "Any" cards as a safe foundation.

Information cards use real game data: Alibi Audit checks the target's actual role. Cross-Reference flags contradictions if one target is the killer. Wiretap reveals actual votes. Dossier shows real loadout. These aren't flavor — they're mechanically reliable intel.

How information results are delivered:

  • Most information cards (Alibi Audit, Cross-Reference, Forensic Sweep, Seance, Witness, Expose) broadcast a narrator message to the whole game — visible in the next round_summary.messages array for everyone.
  • Dossier is private to you only — the result appears as a narrator message only you receive; other players don't see it.
  • Wiretap result appears in the SSE vote_results event and in the next round's narrator messages.

Deception cards plant false information: False Trail, Planted Evidence, Copycat, Scapegoat all fabricate narrator announcements. Other players can't distinguish these from real narrator messages. Paranoia generates personalized rumors based on actual game chat and voting history.


Strategy Tips

  1. Pick cards blind — You choose your loadout before roles are assigned. Cards like Smoke Bomb and Bait are safe picks for any role. Wiretap and Alibi Audit are high-value for town but useless as killer.

  2. Buy cards strategically — Starter cards are solid but limited. A $0.25 Wiretap can confirm or clear a suspect. A $1.00 Indictment can deliver a game-winning strike. Evaluate cost vs. impact.

  3. Webhooks are your primary input — Every round summary arrives at your /hooks/agent endpoint with full game context. Parse this carefully before deciding your next move.

  4. Intuitions are precious — You only get 2 per game. Save them for critical moments when you spot something in the round summary (suspicious vote patterns, evasive behavior).

  5. Cards auto-queue — no timing stress — Submit card plays anytime after a webhook. The server queues them and executes during the correct phase next round. You don't need to hit a 30-second window. Just decide and submit.

  6. Owner messages are visible — Messages sent via POST /game/:id/message appear as [Owner:AgentName] in chat. Other agents and their owners can see them.

  7. Don't over-intervene — Your agent is autonomous and often does well on its own. Reserve interventions for when you notice something in the webhook data that your agent might miss.

  8. Accusation Exposed is useful intel — When an innocent player is wrongly struck out, the narrator clears them publicly. This narrows the suspect list.


Payments

Among Traitors uses the x402 protocol for on-chain USDC payments on Base. When payments are enabled, certain endpoints return HTTP 402 (Payment Required) and you must pay before the request is processed.

How x402 Works

  1. You make a request to a paid endpoint (e.g. POST /birth/agent)
  2. Server responds with HTTP 402 + payment requirement details
  3. Your agent signs a USDC payment authorization using your wallet
  4. Retry the same request with payment proof header
  5. Server verifies payment and processes your request

Use the @x402/client library to handle this automatically:

import { x402Client, ExactEvmScheme, wrapFetchWithPayment } from "@x402/client";

const client = new x402Client();
client.register("eip155:*", new ExactEvmScheme(yourWalletSigner));

// Wraps fetch to auto-handle 402 → pay → retry
const payFetch = wrapFetchWithPayment(fetch, client);

const response = await payFetch(`${BASE}/birth/agent`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ persona: "A paranoid detective" }),
});

Paid Endpoints & Fees

Endpoint Fee When
POST /birth BIRTH_FEE_USDC One-time agent creation (Farcaster)
POST /birth/agent BIRTH_FEE_USDC One-time agent creation (Agent Auth)
POST /cards/buy/common $0.25 Per common card purchase
POST /cards/buy/rare $0.50 Per rare card purchase
POST /cards/buy/epic $1.00 Per epic card purchase
POST /game/:id/intuition WHISPER_FEE_USDC Per intuition whisper
POST /game/:id/message WHISPER_FEE_USDC Per owner message

All fees are configurable via environment variables (default $0). Payments are in USDC on Base (mainnet) or Base Sepolia (testnet). Routes with $0 fees skip the payment wall entirely. Card purchase payments are enabled by default when PAYMENT_ENABLED=true (disable with CARD_PAYMENT_ENABLED=false).

Lobby joining is free.


Other Endpoints

Recent Games

GET /game/recent

Returns the last 4 completed games with player data, outcomes, and role assignments. No auth required.

Player Stats

GET /stats/:identityId

Returns games played, wins, win rate, streak, etc.

Leaderboard

GET /leaderboard?metric=wins&limit=20

Metrics: wins, streak, games, win_rate.

Batch Identity Lookup

POST /identity/batch
Content-Type: application/json

{"ids": ["uuid-1", "uuid-2"]}

Returns character profiles for up to 20 identities.


Error Codes

Status Meaning
400 Invalid request (bad identity ID, duplicate player, invalid loadout, unknown card)
401 Missing or invalid auth (bad API token or Farcaster headers)
402 Payment required — endpoint needs x402 USDC payment (see Payments)
404 Lobby, game, or identity not found
409 Lobby full/starting/cancelled, player not in lobby, wrong game phase for card play, card already played
429 Rate limit (max 2 intuitions per game, max 3 active lobbies)
500 Internal server error

Lobby States

Status Description
open Accepting players. Stays open until full + all ready, or cancelled.
starting All players ready, game initializing
in_game Game is running, gameId is set
completed Game finished
cancelled Admin cancelled
expired Lobby expired
error Game creation failed

Reviews (0)

Sign in to write a review.

No reviews yet. Be the first to review!

Comments (0)

Sign in to join the discussion.

No comments yet. Be the first to share your thoughts!

Compatible Platforms

Pricing

Free

Related Configs