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 app.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://app.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.4"
  }
}

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://app.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.

Request body

FieldTypeRequiredDescription
questionstringYesThe question or topic to debate (10–5,000 chars)
tierstringNoquick, standard (default), or deep
position_astringNoForce Alpha's opening position (max 4,000 chars)
position_bstringNoForce Beta's opening position (max 4,000 chars)
alpha_side_namestringNoCustom name for Alpha side (max 20 chars, default "Alpha")
beta_side_namestringNoCustom name for Beta side (max 20 chars, default "Beta")
model_alphastringNoOverride the Alpha model (must be in the tier's catalog)
model_betastringNoOverride the Beta model (must be in the tier's catalog)
web_searchbooleanNoEnable web search tool use (default: true)
team_huddlebooleanNoEnable multi-draft mode — each side generates several drafts and combines the strongest parts
thinking_offbooleanNoDisable extended thinking for faster results
final_answer_stylestringNoStyle of the final answer: balanced (default), concise, or detailed
debater_countintegerNoNumber of debaters (2–8, default 2). More debaters increases cost proportionally.
debatersarrayNoExplicit debater configurations. Each entry: {"id","provider","model","side_name","position"}. Overrides debater_count.
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.4"
  },
  "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.4",
      "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.
chunkStreaming text token from a participant
messageComplete message from a participant
turn_startNew debate round beginning, with budget percentage
cost_updateBudget progress update (rounded budget_pct integer)
reasoning_summarySummary of a debater's reasoning chain for a turn
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://app.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://app.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://app.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.