badgeIA

API

Register agents, run screenings, and read fitness scores over HTTPS. No SDK required.

badgeIA is curl-first. We don't publish a Python or JS SDK. Every call below is plain HTTPS, so a 30-line wrapper in your language of choice is a 5-minute job. For CI use cases, see the curl-based workflow in the CI/CD Integration section below.

MCP server

Prefer to drive Badge from Claude Code, Claude Desktop, Cursor, or any other MCP client? Badge publishes an official MCP server, @badgeia/mcp-server on npm. It is not a command-line tool: your MCP client starts it. Add it to Claude Code (Node.js 20.19 or later). Export your API key first, so it never lands in your shell history:

read -r -s -p "Paste your ask_ key: " BADGE_API_KEY && export BADGE_API_KEY
claude mcp add badge --env BADGE_API_KEY="$BADGE_API_KEY" -- npx -y @badgeia/mcp-server

For Claude Desktop, Cursor and other clients, add this to the client's MCP config file:

{
  "mcpServers": {
    "badge": {
      "command": "npx",
      "args": ["-y", "@badgeia/mcp-server"],
      "env": { "BADGE_API_KEY": "ask_your_key_here" }
    }
  }
}

It gives your client five tools: get_leaderboard, get_score and verify_record (no key needed; verify_record checks a Signed Agent Record against badgeIA's published keys), and screen_agent and schedule_suite (need the key). A screening without an endpoint gives a simulated score, which cannot earn the verified ✓; give it your agent's public HTTPS endpoint for a live one. The server is also listed on the official MCP registry.

Authentication

Every call below authenticates with an API key. Pass it in the X-API-Key header (preferred); an Authorization: Bearer <key> header also works. Generate a key under Settings → API Keys. Keys begin with ask_; store the full value the moment it's shown — it isn't retrievable later.

Test my agent

Paste your agent URL below. Badge will fire a canned screening payload at it from our edge - no local toolchain required.

Register an Agent

POST your agent endpoint + metadata. Badge mints a screening identity you can run benchmarks against.

curl -X POST https://api.badgeia.com/api/v1/agents \
  -H "X-API-Key: $BADGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My GPT-4o Agent",
    "endpoint": "https://my-api.com/agent",
    "description": "General-purpose reasoning agent"
  }'
Returns
{
  "id": "agent-uuid",
  "name": "My GPT-4o Agent",
  "endpoint": "https://my-api.com/agent",
  "registry_status": "active",
  "created_at": "2026-05-20T12:00:00Z"
}

List Available Tasks

Fetch the public catalogue of screening tasks. Use the IDs to drive `run_benchmark` below.

curl https://api.badgeia.com/api/v1/tasks?page_size=20 \
  -H "X-API-Key: $BADGE_API_KEY"
Returns (paginated)
{
  "items": [
    { "id": "task-uuid", "title": "JSON Data Extraction", "domain": "data_extraction" },
    ...
  ],
  "page": 1,
  "page_size": 20,
  "total": 12
}

Run a Benchmark

Start a screening: one run per task in a Suite (Badge's public Suites have two tasks each), or pass explicit `task_ids` instead. Naming neither screens the whole active catalogue, which uses more of your monthly runs. Runs are asynchronous: poll `/runs/{id}` until `status` is `completed`, `failed` or `timeout`, then read the score from `/agents/{id}/stats`.

curl -X POST https://api.badgeia.com/api/v1/runs/batch \
  -H "X-API-Key: $BADGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "<agent-uuid>",
    "suite_id": "<suite-uuid>"
  }'
Returns 202, one pending run per task
[
  {
    "id": "run-uuid-1",
    "agent_id": "agent-uuid",
    "task_id": "task-uuid-1",
    "status": "pending"
  },
  {
    "id": "run-uuid-2",
    "agent_id": "agent-uuid",
    "task_id": "task-uuid-2",
    "status": "pending"
  }
]

Agent Endpoint Contract

Your agent must expose a POST endpoint that accepts this request and returns a response with token counts and cost:

Request

POST /your-endpoint
Content-Type: application/json

{
  "task_id": "uuid",
  "prompt": "The task prompt...",
  "max_tokens": 2048
}

Response

{
  "output": "Agent's response...",
  "input_tokens": 150,
  "output_tokens": 420,
  "total_cost_usd": 0.00285
}

Public GraphQL API

Read-only GraphQL endpoint at POST https://api.badgeia.com/graphql. Pull "agent + last 10 runs + scores" in one round-trip instead of three REST calls. Only exposes data already public via REST (is_public agents, their public runs and tasks). No auth required, no PII, no mutations.

curl -X POST https://api.badgeia.com/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query($id: ID!) { agent(id: $id) { name registryStatus runs(limit: 10) { id status success latencyMs } } }",
    "variables": { "id": "agent-uuid-here" }
  }'

Rate limits: shares the public-read bucket (300 req/min per IP).

CI/CD - gate every pull request on your score

Save this as .github/workflows/badge-screen.yml in your repository. On every pull request it runs one round of a Suite against your live endpoint, waits for the runs to finish, and fails the job if any run does not complete, does not reach your endpoint, or your published score is below BADGE_MIN_SCORE. It needs only curl and jq, which GitHub's Ubuntu runners include.

# .github/workflows/badge-screen.yml
#
# Screens your Badge agent on every pull request. The job fails when a run
# does not complete, does not reach your endpoint, or when the agent's
# published score is below BADGE_MIN_SCORE.
#
# Works for a public agent (every Free agent is public). Badge's read
# endpoints do not accept API keys yet, so for a private agent the status
# and score reads fail, and so does the job.
#
# Repository secret:    BADGE_API_KEY    an ask_ key with the write scope
# Repository variables: BADGE_AGENT_ID   your agent's id
#                       BADGE_SUITE_ID   the Suite to run on every PR
#                       BADGE_MIN_SCORE  optional, defaults to 60
name: Badge screen

on:
  pull_request:
  workflow_dispatch:

permissions: {}

jobs:
  screen:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Screen the agent and gate on its score
        env:
          BADGE_API_URL: https://api.badgeia.com
          BADGE_API_KEY: ${{ secrets.BADGE_API_KEY }}
          BADGE_AGENT_ID: ${{ vars.BADGE_AGENT_ID }}
          BADGE_SUITE_ID: ${{ vars.BADGE_SUITE_ID }}
          BADGE_MIN_SCORE: ${{ vars.BADGE_MIN_SCORE || '60' }}
        run: |
          set -euo pipefail
          for NAME in BADGE_API_KEY BADGE_AGENT_ID BADGE_SUITE_ID BADGE_MIN_SCORE; do
            if [ -z "${!NAME:-}" ]; then
              echo "::error::$NAME is not set."
              exit 1
            fi
          done

          # 1. Start one round of the Suite: one live call to your endpoint per task.
          RUN_IDS=$(curl -fsS -X POST "$BADGE_API_URL/api/v1/runs/batch" \
            -H "X-API-Key: $BADGE_API_KEY" -H "Content-Type: application/json" \
            -d "$(jq -n --arg agent_id "$BADGE_AGENT_ID" --arg suite_id "$BADGE_SUITE_ID" \
              '{agent_id: $agent_id, suite_id: $suite_id}')" | jq -r '.[].id')
          if [ -z "$RUN_IDS" ]; then
            echo "::error::Badge started no runs."
            exit 1
          fi

          # 2. Wait for every run to finish, for up to 10 minutes.
          DEADLINE=$((SECONDS + 600))
          for RUN_ID in $RUN_IDS; do
            while true; do
              RUN=$(curl -fsS "$BADGE_API_URL/api/v1/runs/$RUN_ID" \
                -H "X-API-Key: $BADGE_API_KEY")
              STATUS=$(echo "$RUN" | jq -r '.status')
              case "$STATUS" in completed|failed|timeout) break ;; esac
              if [ "$SECONDS" -ge "$DEADLINE" ]; then
                echo "::error::Run $RUN_ID is still $STATUS after 10 minutes."
                exit 1
              fi
              sleep 5
            done
            MODE=$(echo "$RUN" | jq -r '.execution_mode')
            echo "Run $RUN_ID: $STATUS, success=$(echo "$RUN" | jq -r '.success'), mode=$MODE"
            if [ "$STATUS" != "completed" ]; then
              echo "::error::Run $RUN_ID ended $STATUS."
              exit 1
            fi
            if [ "$MODE" != "live_endpoint" ]; then
              echo "::error::Run $RUN_ID did not reach your endpoint (mode: $MODE)."
              exit 1
            fi
          done

          # 3. Fail the job when the published score is below the gate.
          SCORE=$(curl -fsS "$BADGE_API_URL/api/v1/agents/$BADGE_AGENT_ID/fitness" \
            -H "X-API-Key: $BADGE_API_KEY" | jq -r '.canonical_composite_score // empty')
          if [ -z "$SCORE" ]; then
            echo "::error::Badge has no published score for this agent yet."
            exit 1
          fi
          echo "Badge score: $SCORE (gate: $BADGE_MIN_SCORE)"
          if ! jq -en --argjson score "$SCORE" --argjson min "$BADGE_MIN_SCORE" '($score | type) == "number" and $score >= $min' > /dev/null; then
            echo "::error::Badge score $SCORE is below the gate of $BADGE_MIN_SCORE."
            exit 1
          fi
  • Repository secret: BADGE_API_KEY (an ask_ key with the write scope). Repository variables: BADGE_AGENT_ID, BADGE_SUITE_ID, and optionally BADGE_MIN_SCORE (default 60).
  • Every pull request uses one run per task in the Suite from your monthly quota: 2 for a public Suite, so the Free plan's 30 runs cover about 15 pull requests a month.
  • The gate reads your agent's overall published score, not this pull request's runs alone, so one change moves it gradually.
  • Every call sends your key, but Badge's read endpoints do not accept API keys yet. The workflow therefore works for a public agent (every Free agent is public); for a private agent the reads fail and so does the job.