How to authenticate, what each endpoint returns, and what to do when a call fails.
Base URL
https://api.africanfreefirecommunity.com/api/v1/partner/Auth header
X-API-Key: afcp_3f9a_...Your first call. Replace the key with the one AFC issued you, and run it from your server, never from a browser.
curl -H "X-API-Key: afcp_3f9a_YOUR_SECRET_HERE" \ "https://api.africanfreefirecommunity.com/api/v1/partner/events/"
A read-only REST API over the tournament data AFC has published to partners, for the events your organisation has been granted. An AFC admin publishes an event explicitly, normally once its results are final, so what you read is settled data rather than a match in progress. There is no write, no delete, and no live match feed. Everything is versioned under /api/v1/, so a future breaking change ships as /api/v2/ and leaves your integration alone.
| Resource | What it is |
|---|---|
| events | The event card: name, slug, dates, tier, status, prize pool. |
| stages | The structure of an event, with each stage's groups nested. |
| matches | Per-match rows: match number, map, MVP, whether the result is in. |
| standings | The final ranked table for an event. |
| teams | Every registered team, with event-wide totals and rosters. |
| players | Everyone who recorded stats, with their per-event stats. |
| designs | Branded leaderboard templates: background art, placed logos, brand colours. |
What the API never returns, on any plan: real names, emails, Discord IDs or any other personal data; room IDs and room passwords; AFC's internal database IDs; events AFC has not published to partners; site-wide rankings and tier ladders.
Events are always addressed by their slug, for example dynasty-cup-nigeria, never by a numeric ID. Store the slug: it is the only stable handle you are given.
Every request carries your key in the X-API-Key header. A key has three parts joined by underscores: the namespace afcp, a short public prefix you can safely quote in a support message, and the secret, which must never leave your servers.
A complete authenticated request.
curl -H "X-API-Key: afcp_3f9a_YOUR_SECRET_HERE" \ "https://api.africanfreefirecommunity.com/api/v1/partner/events/"
Your key is shown to the AFC admin once, at the moment it is created, and is stored only as a hash. Nobody at AFC can retrieve it afterwards. If you lose it, ask for a new one. If you think it has leaked, tell AFC immediately and they will revoke it.
A key can be revoked, deleted, or given an expiry date at issue time. An expiry date means the key works through the end of that day, UTC, which is what makes a key for a single tournament weekend practical. A revoked, deleted or expired key stops working instantly, as does every key belonging to a partner account AFC has suspended. All of those return 401. Send the key over HTTPS only, and never put it in client-side code, a mobile app, or a public repository.
If you present the correct key, the 401 body names the cause, for example Key expired. or Partner suspended., so you can tell an expiry apart from a suspension without asking. If the key itself is wrong, every failure reads Unknown or revoked key. instead. That is deliberate: a stranger guessing at keys must not be able to learn which ones exist.
Your access has three layers, all set by AFC and all closed by default. If something you expected is not in the response, one of these three is the reason, and none of them is a bug.
First, the event has to be published to partners. An AFC admin publishes an event explicitly, and until they do, no partner can read it however broadly scoped. An unpublished event is a 404.
Second, resource toggles decide which endpoints answer at all: events, stages, matches, standings, teams, players, designs. If one is off for you, that endpoint returns 403 resource_not_enabled and the rest keep working.
Third, field toggles decide which fields appear inside a resource you can already read.
| Field | Appears when AFC enables |
|---|---|
| placement | Placements. The team's best finish, on teams and standings. |
| kills | Kills, on teams, players and standings. |
| damage | Damage, on teams, players and standings. |
| assists | Assists, on teams, players and standings. |
| roster | Rosters. The player list on each team. |
| maps, map | Maps played. maps on a group, map on a match. |
| prize_pool | Prize pool, on events. |
| mvp | MVP, on matches. The in-game handle, or null if none was recorded. |
| banner_url, logo_url, esports_image_url, ... | Images and files. Event banners and rules files, team logos, player esport images, and design artwork. |
| rules_text, description | Descriptions and rules text. The event rules blurb and a team's self-description. |
A field you are not entitled to is absent from the JSON, not present and null. Read defensively: check that a key exists rather than assuming it does. A key that is present and null means the field is enabled for you and the underlying value is genuinely empty, for example a team that never uploaded a logo.
Eight endpoints, all GET. Any other verb returns 405. Paths below are relative to the base URL, and the slug placeholder in each path is an event slug returned by the events list. Every list endpoint is paginated.
| Endpoint | Toggle | Returns |
|---|---|---|
| GET events/ | events | The events you may read, newest first. |
| GET events/{slug}/ | events | One event. 404 if it is not yours to read. |
| GET events/{slug}/stages/ | stages | Stages in running order, each with its groups nested. |
| GET events/{slug}/matches/ | matches | The event's matches. Room credentials are never included. |
| GET events/{slug}/standings/ | standings | The final ranked table, winners first. |
| GET events/{slug}/teams/ | teams | Every registered team with its event-wide totals. |
| GET events/{slug}/players/ | players | Everyone who recorded stats, scoped to this event. |
| GET events/{slug}/designs/ | designs | Branded leaderboard templates for the event's owner. |
GET events/
Returns the standard envelope. is_native_afc tells you whether AFC ran the event itself or an organiser did, without revealing which organiser.
{
"results": [
{
"slug": "dynasty-cup-nigeria",
"name": "DYNASTY CUP NIGERIA",
"competition_type": "tournament",
"participant_type": "squad",
"tier": "tier_3",
"status": "completed",
"start_date": "2026-06-29",
"end_date": "2026-07-31",
"is_native_afc": true,
"banner_url": "https://api.africanfreefirecommunity.com/media/event_banner/DYNASTY_CUP_POSTER.png",
"rules_file_url": "https://api.africanfreefirecommunity.com/media/event_rules/AFC_RULESET.pdf",
"rules_text": null
}
],
"has_more": false,
"next_offset": null,
"total_count": 1
}GET events/<slug>/stages/
Nests each stage's groups. order is a 1-based sequence number within the event, not a database ID.
{
"stage_name": "Grand Final",
"order": 3,
"format": "br - normal",
"status": "completed",
"start_date": "2026-06-28",
"end_date": "2026-06-28",
"groups": [
{ "group_name": "Group A", "playing_date": "2026-06-28", "maps": ["bermuda"] }
]
}GET events/<slug>/matches/
One row per match.
{
"match_number": 1,
"result_inputted": true,
"map": "bermuda",
"mvp": "ASN REAPER"
}GET events/<slug>/standings/
The final table, rank ascending.
{
"results": [
{ "rank": 1, "team": "SOLAR FLARE ESPORT", "placement": 1, "kills": 148 },
{ "rank": 2, "team": "BERSERK GENERATION", "placement": 1, "kills": 122 },
{ "rank": 3, "team": "V-ENT ESPORTS", "placement": 1, "kills": 113 }
],
"has_more": true,
"next_offset": 3,
"total_count": 37
}Ranking uses the same metric as AFC's official standings: placement points plus kill points plus bonus points minus penalty points, with first-place finishes and then total kills breaking ties. placement is the team's best finish across the event. Solo events are ranked by player, and each row carries username and in_game_id instead of team.
GET events/<slug>/teams/
Every registered team sorted by name, with its event-wide totals.
{
"team": "ALLSTARS NG",
"team_tag": "ASN",
"status": "played",
"logo_url": "https://api.africanfreefirecommunity.com/media/teams_logos/asn.jpg",
"description": "We grind every night.",
"placement": 1,
"kills": 44,
"roster": [
{ "username": "ASN GABBY", "in_game_id": "3098864559", "kills": 3 }
]
}This list is the full registration list, not the list of competitors. Registering for an AFC event and playing in one are different things, and a team that registered but never played returns zeroed stats and an empty roster, which is easy to mistake for a team that played badly. Read status to tell them apart before you draw a bracket, a standings card, or a team count. status is always present and does not depend on any toggle.
| Value | Meaning |
|---|---|
| played | Turned up and played at least one match. These are the competitors. |
| registered | Accepted into the event, has not played a match. |
| waitlisted | Signed up but holding a waitlist slot, not a playing slot. |
| pending | Registration submitted and awaiting approval. Sponsored events only. |
| no_show | Was expected to play and the organizer marked it absent. |
| withdrawn | Pulled out of the event. |
| left | Left the event. |
| disqualified | Removed by the organizer for a rules breach. |
Only one value is returned per team. Where more than one could apply, the more specific one wins, in this order: disqualified, withdrawn, left, pending, waitlisted, no_show, played, registered. So a team that played two maps and then withdrew comes back as withdrawn, not played, and its stats are a partial record of an event it did not finish. Note also that a team can hold match rows without being played: AFC seeds teams into a map before it is contested, and a team that does not turn up keeps that row with no result.
GET events/<slug>/players/
Scopes every stat to this event rather than to career totals. esports_image_url is the player's posed roster photo, for lower-thirds and versus cards, and is null for players who have not uploaded one.
{
"username": "Ak REBORN",
"in_game_id": "7171703030",
"esports_image_url": "https://api.africanfreefirecommunity.com/media/esports_pictures/reborn.jpg",
"kills": 2
}GET events/<slug>/designs/
The branded templates behind this event's graphics, so you can produce on-brand standings cards yourself. The Designs toggle unlocks the endpoint; the artwork additionally needs Images and files.
{
"name": "DYNASTY CUP",
"design_type": "leaderboard",
"text_color": "#FFFFFF",
"accent_color": "#34d27b",
"transparent_background": false,
"max_rows": 18,
"is_default": true,
"background_instagram_url": "https://api.africanfreefirecommunity.com/media/org_leaderboard_designs/DYNASTY_IG.png",
"background_youtube_url": "https://api.africanfreefirecommunity.com/media/org_leaderboard_designs/DYNASTY_YT.png",
"logos": [
{ "image_url": "https://api.africanfreefirecommunity.com/media/org_leaderboard_logos/sponsor.png",
"x_pct": 12.5, "y_pct": 8.0, "size": "medium" }
]
}The two canvases are Instagram portrait at 1080x1350 and YouTube landscape at 1920x1080. Logo positions are a percentage of the canvas anchored at the logo's centre, so the same pair of numbers places a logo correctly on both sizes. max_rows is how many standings rows the template is designed to hold. You only ever receive designs belonging to the event's owner: the organiser's library for an organiser-run event, or AFC's own library for a native AFC event.
Every list endpoint is paginated, and there is no way to fetch an unbounded list. The event detail endpoint returns a single object and is not paginated.
| Parameter | Default | Notes |
|---|---|---|
| limit | 25 (max 100) | Rows per page. A value above 100 is silently capped at 100. |
| offset | 0 | Rows to skip. A malformed limit or offset falls back to the default rather than erroring. |
Every list response uses the same envelope.
{
"results": [ ... ],
"has_more": true,
"next_offset": 25,
"total_count": 37
}Page through by following next_offset until has_more is false, at which point next_offset is null. An offset past the end returns an empty results array rather than an error.
Requests are limited per key, in a fixed one-minute wall-clock window. Your ceiling is 60 requests per minute unless AFC agreed a different figure for your key.
Every successful response tells you where you stand, so you can slow down before you are blocked.
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 59
Exceed the limit and the next request is refused until the window rolls over.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{ "error": "rate_limit_exceeded" }Watch X-RateLimit-Remaining and pause as it approaches zero, or honour Retry-After after a 429. Because the window is a wall-clock minute, a fresh allowance begins when the minute rolls over rather than sixty seconds after your first call.
Errors raised by the API carry a single error field alongside the matching HTTP status. Switch on the status code rather than on the body: a wrong HTTP verb is refused by the web framework before the API sees it and carries a detail field instead, and a request to a path that does not exist at all is not JSON.
{ "error": "not_found" }| Status | Body | What it means and what to do |
|---|---|---|
| 401 | Unknown or revoked key. | Your key is missing, malformed, unknown, revoked or expired, or your partner account is suspended. Check the header first, then contact AFC. |
| 403 | resource_not_enabled | You are authenticated, but this resource is switched off for your account. Ask AFC to enable it. |
| 404 | not_found | You cannot read that event. Confirm the slug with AFC. |
| 405 | detail, not error | Every endpoint is GET only. This one is refused before the API sees it, so its body carries a detail field rather than error. |
| 429 | rate_limit_exceeded | You are over your per-minute budget. Honour the Retry-After header. |
The difference between 403 and 404 is deliberate. A 403 says the resource type is switched off for you. A 404 says you cannot see that event, and it is returned whether the event is out of your scope, not yet published, or simply does not exist. The API will not confirm that an event exists if you are not allowed to read it, so a 404 is never proof that a slug is wrong.
Every image and file URL the API returns is absolute and publicly fetchable, so you can download it or hot-link it directly. They are plain URLs with no signature and no expiry, which means they keep working, and also means anyone you pass one to can fetch it. Treat a media URL as public, because it is.
A media field, as returned.
"logo_url": "https://api.africanfreefirecommunity.com/media/teams_logos/asn.jpg"
A missing asset is null, never an error. A team with no uploaded logo returns logo_url as null. The field is only absent entirely when the Images and files toggle is off for your account.
Download media once and serve it from your own storage rather than hot-linking it on every page view. Banners, logos and design artwork change rarely, your pages will render faster, and you will not be affected if AFC moves an asset. Media responses carry ETag and Last-Modified, so if you would rather revalidate than re-download, a conditional request with If-None-Match gets you a cheap 304.
Poll, do not hammer. Data only changes when AFC publishes or corrects a result. Once a day during a season, or once an hour around a final, is plenty. There are no webhooks.
Handle absent fields. A field you are not entitled to is missing from the JSON, so read defensively rather than assuming a key exists.
Expect a 404 to be lasting. If an event you were reading starts returning 404, AFC has either unpublished it or changed your scope. Retrying will not help, so ask AFC.
Slugs are the identifiers. Store the event slug, not the position of an event in a list, because a new event changes every position.
Contact your AFC partner manager for a new or rotated key, to change which events or resources you can read, or to report anything the API returns that looks wrong. Include the endpoint, the timestamp, and your key's prefix, the afcp_3f9a part, never the full key.