# UFCalendar Fight API — full reference > The long-form, agent-readable manual for https://api.ufcalendar.com. > Short index: https://api.ufcalendar.com/llms.txt. Machine contract: https://api.ufcalendar.com/openapi.json. Human docs: https://api.ufcalendar.com/docs. Fight data API by UFCalendar — MMA + bare-knuckle boxing: events, full cards, results, per-fight and per-round statistics, fighters with complete multi-promotion careers, official rankings history (UFC 2013→), judges' scorecards (every round, every official, UFC back to 1995), UFCalendar Power Index ratings, AI win probabilities and per-country broadcast rights — for UFC, PFL, OKTAGON, BKFC and RIZIN. Keys live at ufcalendar.com/account/api — an active plan or the free 1-day trial (100 requests, no card) is required. Plans and quotas: ufcalendar.com/developers. Conventions: snake_case JSON in a {"data": ..., "meta": ...} envelope; cursor pagination; UTC ISO-8601 timestamps plus venue IANA tz; errors as {"error": {"code", "message", "request_id"}}; monthly-quota headers X-RateLimit-Limit/-Remaining/-Reset and Retry-After on 429. No betting odds are served, by design. Fighter images are Wikimedia Commons/CC only — the license/artist fields you receive MUST be displayed as a credit. Not affiliated with UFC/Zuffa/TKO or any promotion. Both official SDKs (pip install ufcalendar, npm install @ufcalendar/sdk) are configured with the base URL https://api.ufcalendar.com/v1 — the same base every code sample on this page uses. Base URL: https://api.ufcalendar.com/v1 Auth: `Authorization: Bearer ` (keys at https://www.ufcalendar.com/account/api) or an OAuth access token. MCP: https://api.ufcalendar.com/mcp Terms: https://www.ufcalendar.com/developers/terms ## For AI agents: how to get access 1. Read https://api.ufcalendar.com/v1/plans first — plans, quotas, the trial rule and this server's MCP endpoint, with NO credential. 2. Human at the keyboard: send them to https://www.ufcalendar.com/account/api?trial=1 — free 24-hour trial, 100 requests, no card, one per account. The key it mints goes in `Authorization: Bearer ` on every REST call and on https://api.ufcalendar.com/mcp. 3. No human at the keyboard: use the device flow. POST `https://www.ufcalendar.com/api/auth/device/code` with a JSON body carrying `client_id` (and optional `scope`), show the human the `verification_uri_complete` it returns, poll the token endpoint with the `device_code` until it stops answering `authorization_pending`, then send the access token as a bearer. 4. In an MCP client that speaks OAuth (Claude, ChatGPT): add https://api.ufcalendar.com/mcp as a connector and sign in. Calling a gated tool unauthenticated answers 401 with `WWW-Authenticate: Bearer resource_metadata="https://api.ufcalendar.com/.well-known/oauth-protected-resource"`, which is the whole discovery chain. Signing in for the first time starts the free trial automatically when the account has never held a plan. There is no free tier: after the trial, a plan is required. Protected-resource metadata: https://api.ufcalendar.com/.well-known/oauth-protected-resource Authorization-server metadata: https://www.ufcalendar.com/.well-known/oauth-authorization-server Plans and checkout: https://www.ufcalendar.com/account/api (deep link `?plan=`) Enterprise / bulk snapshots: api@ufcalendar.com Agent quickstart page: https://www.ufcalendar.com/developers/agents ## Endpoints (34) ### GET /v1/plans — Plans, quotas and how to connect (no key required) The only endpoint that answers without a credential. Returns every plan with its monthly request quota, per-minute limit, key limit and a checkout_url; the terms of the free 1-day trial and the URL that starts it; the MCP endpoint with both of its auth doors; and the documentation links. Static per deploy and cached an hour — poll it never, read it once. ### GET /v1/orgs — List launch orgs with capability flags Each org carries flags (stats, rounds, rankings, broadcasts, predictions, scorecards) so clients discover coverage programmatically — plus sport (mma, bare-knuckle-boxing). Staged orgs (ONE, KSW, ACA) appear the day they clear the reliability bar. RIZIN publishes no official board, so its rankings flag is false. scorecards is true for UFC, PFL, OKTAGON and RIZIN; no commission covers bare-knuckle, so BKFC is false. ### GET /v1/orgs/{slug} — One org Single org by slug. * `slug` (path) ### GET /v1/events — List events (schedule + results) A bare listing is the upcoming calendar: events from the last ~6h onward, soonest first. Ordering defaults: explicit order always wins; else from= or a forward status (announced|scheduled|live) → asc; else (completed|cancelled|to=) → desc, newest first. order=desc with no filters browses the full archive newest-first. Live results land within ≤5 minutes (UFC ≤2). * `org` (query) — Launch org slug: ufc, pfl, oktagon, bkfc or rizin. Staged orgs 400 until promoted. * `status` (query) * `from` (query) * `to` (query) * `order` (query) — Sort by start time. Defaults are contextual — see endpoint description. * `cursor` (query) — Opaque cursor from meta.pagination.next_cursor. * `limit` (query) ### GET /v1/events/{idOrSlug} — Event with full card Full fight card (both corners, results when completed), venue and event-scoped broadcast rows. Every event and card entry carries sport (e.g. mma, bare-knuckle-boxing). Renamed events 308-redirect from stale slugs. * `idOrSlug` (path) — Numeric id or slug of the event. Stale slugs 308-redirect to the canonical URL. ### GET /v1/events/{idOrSlug}/changes — Card-change log The diff log behind "card updated": fight added/removed, opponent swapped, card order or date moved, or a fighter profile merged (fighter-merged: two of our fighter ids turned out to be one athlete, so a live bout now carries the surviving id — same humans, new ids). Each entry has kind, before, after, observed_at. * `idOrSlug` (path) — Numeric id or slug of the event. Stale slugs 308-redirect to the canonical URL. ### GET /v1/fights/{id} — One bout Bout with corners, sport (bout override, else the org sport), result (method/round/time/referee) and bonus flags. * `id` (path) ### GET /v1/fights/{id}/stats — Per-fight totals Both corners: knockdowns, significant/total strikes, takedowns, submission attempts, reversals, control time, and target/position splits (head/body/leg, distance/clinch/ground) where the source provides them. distance_time_sec, clinch_time_sec, ground_time_sec (integers, nullable) are seconds spent at distance / in the clinch / on the ground — Sofascore-sourced, and can appear alongside official strike counts on any org (Sofascore fills position time even where another source owns the rest of the row). * `id` (path) ### GET /v1/fights/{id}/rounds — Round-by-round stats Per-round stat lines for both corners, including target/position splits (head/body/leg, distance/clinch/ground) where the source logs them per round (BKFC for head/body/distance/clinch; Sofascore additionally for leg/ground). distance_time_sec, clinch_time_sec, ground_time_sec (integers, nullable) are seconds spent at distance / in the clinch / on the ground, per round — Sofascore-sourced. * `id` (path) ### GET /v1/fights/{id}/scorecards — Judges' scorecards for a bout The official athletic-commission record: every judge, their score for each round, their card total, the decision type and any point deductions. UFC back to 1995, PFL to 2018, OKTAGON to 2025, RIZIN from March 2026 (Japan has no athletic commission — the record is published by JMOC, the officials body that runs RIZIN). UFC cards also come from ufc.com's own official scorecard images (Dana White's Contender Series and Road to UFC included, images from March 2022) — mmadecisions is preferred wherever both cover a bout, and the ufc.com extraction fills the rest. total_a/total_b and every round are oriented to the bout's fighter_a_id/fighter_b_id, which are repeated on the payload so you never have to fetch the fight to read a card. winner_fighter_id is who THAT judge gave it to. Check scores_known before charting totals. When it is false the commission published only the outcome, and total_a/total_b are a 1-0 / 1-1 / 0-0 placeholder rather than a score (66 fights, nearly all pre-2005); rounds is empty on those cards. A bout that did not go to the judges 404s. Some cards carry no judge identity. PFL's US events have no published commission scorecard, so those bouts are served from PFL's own results post: three cards with judge_id/judge_name null, total_a/total_b set and rounds empty. scores_known is still true — the totals are real. Europe/UAE PFL cards and every other org carry the named, round-by-round record. Media-member and fan scorecards are not part of this dataset. * `id` (path) ### GET /v1/judges — Judge directory Every official who has scored a launch-org bout (~550), busiest first, with their career shape: fights, rounds scored, rounds scored 10-8 or wider, three-judge cards that came back split, and lone_dissents — cards where this judge alone picked the other corner. Filter with q (name), org, and min_fights (the rates mean little below ~10 fights). * `q` (query) — Name substring, case-insensitive. * `org` (query) — Launch org slug: ufc, pfl, oktagon, bkfc or rizin. Staged orgs 400 until promoted. * `min_fights` (query) — Only judges with at least this many scored fights. * `cursor` (query) — Opaque cursor from meta.pagination.next_cursor. * `limit` (query) ### GET /v1/judges/{id} — One judge A single official's career aggregates. Same shape as a directory row. * `id` (path) ### GET /v1/judges/{id}/scorecards — Everything a judge has scored This judge's card for every launch-org bout they worked, newest first — the bout, its event, the decision type, and their own card only (the full panel is on /v1/fights/{id}/scorecards). Cursor-paginated. * `id` (path) * `cursor` (query) — Opaque cursor from meta.pagination.next_cursor. * `limit` (query) ### GET /v1/fighters — Roster search Fighters with at least one launch-org fight (~4.5k). q is accent-insensitive. * `q` (query) * `org` (query) — Launch org slug: ufc, pfl, oktagon, bkfc or rizin. Staged orgs 400 until promoted. * `country` (query) — ISO-2 code or country name. * `cursor` (query) — Opaque cursor from meta.pagination.next_cursor. * `limit` (query) ### GET /v1/fighters/{idOrSlug} — Fighter profile Bio, per-scope career records (one row per scope, source-stamped), UFCalendar Power Index summary, and CC-licensed images with attribution metadata (displaying the credit is a license requirement). * `idOrSlug` (path) — Numeric id or slug of the fighter. Stale slugs 308-redirect to the canonical URL. ### GET /v1/fighters/{idOrSlug}/history — Complete career timeline The fighter's FULL multi-promotion career: native tracked bouts (richest data, source: "native") merged with fill-source history rows (source: "history"). Spans every promotion they fought in — not just launch orgs. * `idOrSlug` (path) — Numeric id or slug of the fighter. Stale slugs 308-redirect to the canonical URL. ### GET /v1/fighters/{idOrSlug}/stats — Career statistics Per-scope career stats (pro-mma, amateur-mma, …): record, strikes landed/absorbed per minute, accuracy/defense, takedown and submission averages. Exactly ONE row per scope; source names the publisher it came from (tapology — the complete multi-promotion career — is preferred, then sofascore, then the promotion's own site). A Tapology record is a crawl-time snapshot advanced by every settled bout UFCalendar tracked after it, so it matches the record on ufcalendar.com. Org-scoped rows (ufc-only, bkfc-only, one-only, oktagon-only, aca-only) are the fighter's record INSIDE that promotion, not a career record. Scope tracked rows are computed by UFCalendar from per-fight totals when no scraped panel carries data (e.g. Contender Series debutants, PFL/OKTAGON rosters); their record is always null. * `idOrSlug` (path) — Numeric id or slug of the fighter. Stale slugs 308-redirect to the canonical URL. ### GET /v1/fighters/{idOrSlug}/rankings — Ranking history Official-board rows over time (org, board, division, snapshot_date, rank). Rank 0 = champion. * `idOrSlug` (path) — Numeric id or slug of the fighter. Stale slugs 308-redirect to the canonical URL. ### GET /v1/fighters/{idOrSlug}/power-index — Power Index trajectory Per-bout rating history from the UFCalendar Power Index engine (rating before/after, model win probability, outcome). * `idOrSlug` (path) — Numeric id or slug of the fighter. Stale slugs 308-redirect to the canonical URL. ### GET /v1/rankings/{org} — Official board (point-in-time) The time machine: pass ?date=YYYY-MM-DD for the board as it stood on any date (UFC official back to 2013, 543 snapshots). A snapshot is valid until superseded. Rank 0 = champion; P4P has no rank 0. * `org` (path) * `date` (query) * `board` (query) — Default official (for UFC that is the media-panel vote ufc.com publishes). UFC additionally has meta. ### GET /v1/rankings/{org}/{division} — One division Same as the board endpoint, filtered to a division (lightweight, womens-strawweight, pound-for-pound, …). * `org` (path) * `division` (path) * `date` (query) ### GET /v1/champions — Current champions Rank-0 rows of each launch org's latest official board. ### GET /v1/power-index/{org} — Power Index board Top-rated active fighters whose latest bout was in this org, by the UFCalendar Power Index (our own rating engine, hourly refresh). * `org` (path) * `limit` (query) ### GET /v1/predictions/upcoming — Model win probabilities (UFC) UFCalendar model win probabilities for upcoming UFC bouts, refreshed every 30 minutes. Corner-guarded: a probability is only served while the stored pair matches the bout's current pair — a late opponent swap removes the row rather than mislabeling it. Not betting advice. ### GET /v1/broadcast-rights/{org} — Who airs it, per country Org-level standing media rights per country (PFL harvested weekly across 212 countries; UFC ~110 curated). WORLD rows apply globally. * `org` (path) * `country` (query) — ISO-2 country code. ### GET /v1/venues/{id} — Venue Venue with city, country, coordinates, capacity and IANA tz. * `id` (path) ### GET /v1/search — Typeahead search Accent-insensitive search across roster fighters and events. Great for building pickers. * `q` (query) ### GET /v1/calendar/{org}.ics — ICS calendar feed Subscribe your calendar app to an org's schedule. Calendar apps can't send headers, so pass the key as ?key=. Refresh hourly at most. * `org.ics` (path) — ufc.ics, pfl.ics, oktagon.ics, bkfc.ics or rizin.ics * `key` (query) ### GET /v1/webhook-endpoints — List your webhook endpoints Pro and above. Counts as one request against your quota; the deliveries themselves do not. ### POST /v1/webhook-endpoints — Register a webhook endpoint Pro+. Body: {"url": "https://...", "events": ["event.announced", "fight.result", "card.changed", "event.completed"]}. Subscribe to event.announced and you do not need to poll /v1/events to discover new cards — it fires once, within a minute of a card first landing in our data, and carries the same event object the list endpoint returns. The response contains the signing secret ONCE. Deliveries carry X-UFCalendar-Signature: t=,v1= where the HMAC-SHA256 input is ".". Verify the timestamp too: reject anything where |now - t| exceeds 300 seconds, otherwise a captured delivery can be replayed at you forever with a valid HMAC. Delivery is at-least-once — deduplicate on the event body, and expect a retry roughly every 60s until your endpoint returns 2xx. Endpoints auto-disable after 20 consecutive failures, and you may hold at most 3 active endpoints. Latency: results are dispatched within ~1 minute of our scrapers writing them. event.announced only fires for cards that have not started yet, so archive backfills never reach you as new-card noise. card.changed is filtered the same way: a change whose before and after read identically (a venue row re-minted for the same arena, a source dropping a sub-time it never knew) is never delivered, and when a source flip-flops a value we deliver the move and its undo once, not the twentieth repeat. Deliveries are free — they never count against your monthly request quota, only the calls you make to manage endpoints do. ### DELETE /v1/webhook-endpoints/{id} — Delete a webhook endpoint Pro and above. * `id` (path) ### POST /v1/webhook-endpoints/{id}/rotate-secret — Rotate an endpoint signing secret Pro and above. Issues a fresh whsec_ secret for the endpoint and returns it ONCE, keeping the endpoint id and its delivery history. The previous secret stops verifying the moment this returns, so deploy the new one to your receiver first (or accept both during the cutover). Use this instead of delete-and-recreate when a secret leaks. * `id` (path) ### GET /v1/bulk/{kind} — Bulk snapshot (Enterprise only) Enterprise only — the full dataset is licensed, not self-serve; contact api@ufcalendar.com. Downloads the full launch-scope dataset as gzipped JSON: events.json.gz, fights.json.gz or fighters.json.gz. Responds 302 to a presigned URL; snapshots regenerate at most every 6 hours. One request per download (the redirect is free). * `kind` (path) ### GET /v1/usage — Your quota usage Current-month usage for the calling key: tier, requests used/limit, rpm limit, reset. ## Conventions - JSON, snake_case, `{"data": ..., "meta": ...}` envelope - Errors: `{"error": {"code", "message", "request_id"}}` with the matching HTTP status; quote `request_id` in support mail - Cursor pagination: `?cursor=` from `meta.pagination.next_cursor`, `?limit=` up to 100 - Timestamps are UTC ISO-8601; events also carry the venue IANA `tz` - Rate headers: `X-RateLimit-Limit` / `-Remaining` / `-Reset` (monthly quota), `Retry-After` on 429 - Plan gating: webhook endpoints need Pro+, bulk snapshots are Enterprise only; every other endpoint is open to any active plan. A key below the required tier gets 403 `tier_required` - List endpoints return identity fields; the full record lives on the detail route. There is no `/v1/fights` list — fetch a bout by id or read it off the event - Webhook deliveries do not count against the monthly quota; only the calls that register, list, rotate or delete endpoints do - Webhook delivery is at-least-once: verify `X-UFCalendar-Signature` (HMAC-SHA256 over `"."`), reject a `t` older than 300s, deduplicate on the body and answer 2xx fast - No betting odds are served, by design - Scorecards are the official commission record (judges, rounds, totals, deductions). Media-member and fan scorecards are not part of the API - Fighter images are Wikimedia Commons / CC only and carry `license` and `artist`: displaying the credit is a licence requirement - Not affiliated with UFC/Zuffa/TKO or any promotion ## MCP The same data behind a Model Context Protocol server, so an agent can call it without writing an HTTP client. Endpoint: https://api.ufcalendar.com/mcp Transport: stateless Streamable HTTP. POST JSON-RPC only; GET answers 405. Send `Accept: application/json, text/event-stream` — the transport answers 406 without it. Metering: 1 tool call = 1 metered request against the same plan quota as REST. `get_plans`, `list_orgs` and `how_to_connect` need no credential and cost nothing. | Tool | What it does | REST equivalent | Access | |---|---|---|---| | `get_plans` | Plans and quotas | — | no credential | | `list_orgs` | List promotions | `GET /v1/orgs` | no credential | | `how_to_connect` | How to connect | — | no credential | | `list_events` | List events | `GET /v1/events` | plan | | `get_event_card` | Get an event and its full card | `GET /v1/events/{idOrSlug}` | plan | | `get_event_changes` | Get an event’s change log | `GET /v1/events/{idOrSlug}/changes` | plan | | `get_fight` | Get a bout | `GET /v1/fights/{id}` | plan | | `search` | Search fighters and events | `GET /v1/search` | plan | | `search_fighters` | Search the roster | `GET /v1/fighters` | plan | | `get_fighter` | Get a fighter | `GET /v1/fighters/{idOrSlug}` | plan | | `get_rankings` | Get a rankings board | `GET /v1/rankings/{org}` | plan | | `get_champions` | Get current champions | `GET /v1/champions` | plan | | `get_power_index` | Get the UFCalendar Power Index board | `GET /v1/power-index/{org}` | plan | | `get_predictions_upcoming` | Get model win probabilities | `GET /v1/predictions/upcoming` | plan | | `get_broadcast_rights` | Get broadcast rights | `GET /v1/broadcast-rights/{org}` | plan | | `list_judges` | List judges | `GET /v1/judges` | plan | | `get_judge_scorecards` | Get a judge’s scorecards | `GET /v1/judges/{id}/scorecards` | plan | | `get_usage` | Get your API usage | `GET /v1/usage` | plan | | `get_venue` | Get a venue | `GET /v1/venues/{id}` | plan | | `list_webhook_endpoints` | List webhook endpoints | `GET /v1/webhook-endpoints` | plan | | `create_webhook_endpoint` | Register a webhook endpoint | `POST /v1/webhook-endpoints` | Pro+, write | | `rotate_webhook_secret` | Rotate a webhook signing secret | `POST /v1/webhook-endpoints/{id}/rotate-secret` | Pro+, write | | `delete_webhook_endpoint` | Delete a webhook endpoint | `DELETE /v1/webhook-endpoints/{id}` | Pro+, write | ### Connect an MCP client Claude Code: ``` claude mcp add --transport http ufcalendar https://api.ufcalendar.com/mcp --header "Authorization: Bearer $UFCALENDAR_API_KEY" ``` Cursor (`~/.cursor/mcp.json`): ```json {"mcpServers":{"ufcalendar":{"url":"https://api.ufcalendar.com/mcp","headers":{"Authorization":"Bearer $UFCALENDAR_API_KEY"}}}} ``` Codex (`~/.codex/config.toml`): ```toml [mcp_servers.ufcalendar] url = "https://api.ufcalendar.com/mcp" http_headers = { Authorization = "Bearer $UFCALENDAR_API_KEY" } ``` Claude.ai custom connector: Settings → Connectors → Add custom connector, paste `https://api.ufcalendar.com/mcp`, then sign in with your UFCalendar account (no key to copy). ChatGPT: enable developer mode, add `https://api.ufcalendar.com/mcp` as a connector and sign in. Every read tool is annotated read-only and closed-world. ## SDKs and skills - Python: `pip install ufcalendar` (https://pypi.org/project/ufcalendar/) - TypeScript: `npm install @ufcalendar/sdk` (https://www.npmjs.com/package/@ufcalendar/sdk) - Agent skill: `npx skills add UFCalendar/fight-api-skill` (https://github.com/UFCalendar/fight-api-skill) ## Links - Plans (no credential): https://api.ufcalendar.com/v1/plans - Free 24-hour trial: https://www.ufcalendar.com/account/api?trial=1 - Agent quickstart: https://www.ufcalendar.com/developers/agents - OpenAPI 3.1: https://api.ufcalendar.com/openapi.json - Short index: https://api.ufcalendar.com/llms.txt - This file: https://api.ufcalendar.com/llms-full.txt - Terms: https://www.ufcalendar.com/developers/terms · support: api@ufcalendar.com