API Reference

Debating Bots API

Submit questions, get stress-tested answers. Multiple frontier AI models argue, critique, and refine through structured rounds — producing verified results via a simple REST API.

Quick start

Create an account at debatingbots.com and add balance to your account.

Generate an API key from your avatar menu → API Keys. The key starts with db_ and is shown once — copy it immediately.

Start a debate with a single request:

curl
curl -X POST \
  https://debatingbots.com/api/v1/debate.php?action=start \
  -H "Authorization: Bearer db_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-first-debate" \
  -d '{"question": "Is Rust better than Go for backend services?"}'
Response · 202
{
  "debate_id": "abc123",
  "status": "queued",
  "stream_url": "/api/v1/debate.php?action=stream&id=abc123",
  "poll_url": "/api/v1/debate.php?action=status&id=abc123",
  "cost": "$0.69",
  "models": {
    "alpha": "claude-sonnet-4-6",
    "beta": "gpt-5.5"
  }
}

Then poll poll_url, connect to stream_url for live SSE events, or provide a webhook_url to receive results when the debate finishes.

Model names in the examples are illustrative snapshots. Call /api/v1/models.php to discover the current catalog for each tier.

Authentication

All API requests require a bearer token in the Authorization header:

Authorization: Bearer db_a3f29e1c4b...

Keys use the format db_ followed by 40 hex characters (43 characters total). Only the SHA-256 hash is stored on our servers — if you lose a key, generate a new one.

Generate and manage keys from your avatar menu → API Keys in the app. Each account supports up to 10 active keys.

Keep your keys secret. Don't commit them to source control. Use environment variables or a secrets manager.

Endpoints

All endpoints live under /api/v1/. Responses are JSON with Content-Type: application/json, except for the SSE stream endpoint which uses text/event-stream.

Base URL: https://debatingbots.com

Start a debate

POST /api/v1/debate.php?action=start
Start a new adversarial debate. Requires an Idempotency-Key header. Balance is charged upfront and refunded if the debate fails to start. API callers must send every debater slot, the main judge, and the 3 panel judges explicitly.

Request body

FieldTypeRequiredDescription
questionstringYesThe question or topic to debate (10–5,000 chars)
tierstringNoquick, standard (default), or deep
positionsobjectNoForce explicit starting positions keyed by debater id (alpha, beta, gamma, …). For 3+ debaters this is the preferred override contract.
position_astringNoLegacy bridge for Alpha's opening position (max 4,000 chars)
position_bstringNoLegacy bridge for Beta's opening position (max 4,000 chars)
Position contract: for 2 debaters, you may still provide position_a/position_b as a full distinct pair or provide neither. For N-side debates, send a full distinct positions map with one distinct non-empty position per debater id. Partial maps, partial pairs, and duplicate positions are rejected at the API boundary. In the first-party Debate UI, the screener proposes these positions and the user can review/edit them before launch. If the user clicks Regenerate, the modal stays open and refreshes the screener positions in place. During that pre-launch screening phase, the composer stays mostly present instead of washing out heavily, and the Start Debate button becomes the main live progress cue. That busy state should stay visually steady: do not scale the whole composer or button, keep the busy button compact instead of reserving space for long phrases, and use short busy-phase labels so the processing CTA stays close to its normal shape instead of turning into a wide slab. Also lock the other debate composer controls while screening is in flight so the request cannot drift under the user, and keep the Start Debate icon balanced with the label so the chat bubble stays put without turning the button into a split left-heavy layout.
alpha_side_namestringNoCustom name for Alpha side (max 20 chars, default "Alpha")
beta_side_namestringNoCustom name for Beta side (max 20 chars, default "Beta")
web_searchbooleanNoEnable web search tool use (default: true)
team_huddlebooleanNoEnable multi-draft mode — each side generates several drafts and combines the strongest parts
final_answer_stylestringNoStyle of the final answer: balanced (default), concise, or detailed
debater_countintegerNoNumber of debaters (2–8). When present, it must exactly match the number of explicit debaters you send.
debatersarrayYesExplicit debater configurations in launch order. Each entry: {"provider","model","reasoning_option"}. API callers must send every debater slot explicitly; there is no hidden app-decides fallback.
judgeobjectYesExplicit main judge selection: {"provider","model","reasoning_option"}.
panel_judgesarrayYesExactly 3 explicit panel judge selections. Each entry: {"provider","model","reasoning_option"}. All 3 must use different providers.
contextstringNoAdditional context for the debate (max 100,000 chars)
repo_zip_base64stringNoBase64-encoded zip file for code review debates (max 50 MB compressed, 200 MB uncompressed, 2,000 files)
webhook_urlstringNoHTTPS URL to receive the result when the debate finishes

Response 202 Accepted

JSON
{
  "debate_id": "abc123",
  "status": "queued",
  "stream_url": "/api/v1/debate.php?action=stream&id=abc123",
  "poll_url": "/api/v1/debate.php?action=status&id=abc123",
  "cost": "$0.69",
  "models": {
    "alpha": "claude-sonnet-4-6",
    "beta": "gpt-5.5"
  },
  "debater_count": 2,
  "side_names": {
    "alpha": "Alpha",
    "beta": "Beta"
  }
}

Poll for result

GET /api/v1/debate.php?action=status&id={debate_id}
Check the current status of a debate. Returns the full result when complete.

In-progress response

JSON
{
  "debate_id": "abc123",
  "status": "running"
}

Possible status values: queued, running, complete, error, cancelled.

Complete response

JSON
{
  "debate_id": "abc123",
  "status": "complete",
  "result": {
    "winner": "alpha",
    "winner_debater_id": "alpha",
    "winner_side_name": "Alpha",
    "consensus_by": "Unanimous agreement",
    "contested": false,
    "answer": "The final synthesized answer text...",
    "alpha_position": "Alpha's final position...",
    "beta_position": "Beta's final position...",
    "labels": { "alpha": "Pro-Rust", "beta": "Pro-Go" },
    "side_names": { "alpha": "Alpha", "beta": "Beta" },
    "models": {
      "alpha": "claude-sonnet-4-6",
      "beta": "gpt-5.5",
      "judge": "gemini-3.1-pro-preview"
    },
    "tier": "standard",
    "thinking_level": "high",
    "cost": "$0.69"
  },
  "transcript": [ ... ]
}

Stream events (SSE)

GET /api/v1/debate.php?action=stream&id={debate_id}
Server-Sent Events stream for live debate progress. Connect after starting a debate to receive real-time updates.

Events are standard SSE format with event: and data: fields. Key event types:

EventDescription
initDebate initialized — models, tier, side names
statusPhase change — "Initial arguments", "Rebuttal round", etc.
card_startedBackend-owned card lifecycle started; live UI buffers metadata and updates progress only
card_deltaStreaming update buffered for a card; live transcript waits for completion
card_completedFinal content/render payload; creates the polished debate card
card_cancelledOptional cleanup signal for buffered card lifecycle state
chunkLegacy streaming text event; new visible cards use card_delta
messageLegacy complete message event; new visible cards use card_completed
turn_startNew debate round beginning, with budget percentage
cost_updateBudget progress update (rounded budget_pct integer)
reasoning_summaryDebug-only reasoning summary event; normal UI does not display it
tool_activityTool use counts (web search, code interpreter) for a debater's turn
judge_panel_resultJudge votes and decision for a round
convergence_resultConvergence score and pattern after analysis
huddle_statusMulti-draft progress (drafting, synthesizing, complete)
huddle_draftsDraft content from multi-draft ensemble (when team_huddle is enabled)
resultFinal debate result (same shape as status endpoint)
heartbeatKeep-alive during idle periods
errorFatal error notice
doneStream complete — disconnect. If cancelled: { "success": false, "cancelled": true }
curl
curl -N \
  "https://debatingbots.com/api/v1/debate.php?action=stream&id=abc123" \
  -H "Authorization: Bearer db_YOUR_KEY_HERE"

Supports reconnection with ?seq=N to resume from a specific sequence number. The stream disconnects automatically after 10 minutes — reconnect with the last received seq value to resume.

Cancel a debate

POST /api/v1/debate.php?action=cancel
Cancel a running or queued debate. Queued debates are cancelled immediately with a full refund. Running debates receive a cancel signal — the worker stops at the next safe point.
curl
curl -X POST \
  https://debatingbots.com/api/v1/debate.php?action=cancel \
  -H "Authorization: Bearer db_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"debate_id": "abc123"}'
Response · 200 (queued)
{
  "debate_id": "abc123",
  "status": "cancelled",
  "cancelled": true,
  "refunded": true
}

Check balance

GET /api/v1/balance.php
Returns your current account balance. No sensitive account information is exposed.
Response · 200
{
  "balance": "12.5000",
  "currency": "USD"
}

List models

GET /api/v1/models.php
Returns the available debate models grouped by tier, with pricing.
Response · 200
{
  "price_per_debate": "$0.69",
  "tiers": {
    "quick": {
      "models": [
        { "provider": "claude", "model": "claude-sonnet-4-6", "label": "Claude Sonnet 4.6" },
        { "provider": "openai", "model": "gpt-5.4-mini", "label": "GPT-5.4 Mini" }
      ]
    },
    "standard": { "models": [ ... ] },
    "deep": { "models": [ ... ] }
  }
}

Integration patterns

Choose the pattern that fits your use case:

1. Poll (simplest)

Start a debate, then poll the status endpoint every few seconds until status is complete. Best for batch jobs and scripts where latency isn't critical.

pseudocode
response = POST /start { question: "..." }
while response.status != "complete":
    sleep(5)
    response = GET /status?id=response.debate_id
print(response.result.answer)

2. Stream (real-time)

Connect to the SSE stream endpoint immediately after starting. You'll receive live tokens as each model argues, judge votes as they happen, and the final result. Best for interactive UIs.

3. Webhook (fire-and-forget)

Pass a webhook_url when starting. Your server receives a POST with the complete debate result when it finishes. Best for async pipelines, CI/CD, and Slack bots. See the Webhooks section for signature verification.

Idempotency

The Idempotency-Key header is required on POST /debate.php?action=start. It prevents double-charges if you retry a request due to a network timeout.

Idempotency-Key: order-42-debate-001

Use any unique string up to 255 characters. If you send the same key with the same request body, you'll get the original response back without being charged again. If you send the same key with a different body, you'll get a 409 Conflict.

Tip: Use a deterministic key like {your-app-id}-{task-hash} so retries are automatically safe.

Webhooks

When you include webhook_url in your start request, the server POSTs the complete debate result to that URL when the debate finishes.

Requirements

Signature verification

Every webhook request includes two headers for verification:

HeaderDescription
X-Debate-TimestampUnix timestamp of when the webhook was sent
X-Debate-SignatureHMAC-SHA256 signature: sha256={hex}

Each API key has a webhook signing secret (a 64-character hex string), returned once when you create the key alongside the key itself. Use this secret to verify webhook payloads: compute HMAC-SHA256(timestamp + "." + raw_body, your_webhook_secret) and compare it to the signature value (without the sha256= prefix).

Save your webhook secret. It is shown only once at key creation, just like the API key. If you lose it, revoke the key and create a new one.
Existing keys: API keys created before webhook signing was introduced do not have a signing secret. To get verifiable webhooks, revoke the old key and generate a new one.
Python
import hmac, hashlib

def verify_webhook(body: bytes, timestamp: str, signature: str, webhook_secret: str) -> bool:
    expected = hmac.new(
        webhook_secret.encode(),
        f"{timestamp}.{body.decode()}".encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
Fire-and-forget: Webhook delivery is best-effort. If your endpoint is down, the result is still available via the status endpoint. Webhook failures never affect the debate outcome.

File uploads

For code review debates, include a base64-encoded zip file in the repo_zip_base64 field of your start request. The AI models can then browse and reference files during the debate.

bash
# Encode a zip file and include it
ZIP_B64=$(base64 -w 0 my-repo.zip)

curl -X POST \
  https://debatingbots.com/api/v1/debate.php?action=start \
  -H "Authorization: Bearer db_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: review-pr-42" \
  -d "{
    \"question\": \"Review this codebase for security issues\",
    \"repo_zip_base64\": \"$ZIP_B64\"
  }"

Error codes

All errors return a JSON object with an error field. Common HTTP status codes:

StatusMeaningExample
400Bad requestMissing required field, invalid tier, Idempotency-Key issues
401UnauthorizedMissing or invalid API key
402Payment requiredInsufficient balance to start a debate
403ForbiddenAccessing another user's debate
404Not foundDebate ID doesn't exist or isn't yours
405Method not allowedWrong HTTP method (e.g. GET on a POST endpoint)
409ConflictIdempotency-Key reused with a different request body
429Rate limitedToo many requests — 60 requests/minute per IP across all endpoints, plus 20 debates/hour per account
500Server errorSomething broke on our end — retry with same Idempotency-Key
503UnavailableTemporary outage — retry shortly
Error response shape
{
  "error": "Insufficient balance",
  "balance": "0.2300"
}

The balance field is only present on 402 responses. All other errors return just the error string.