Delivery contract — Game Value, player board
The reference for every call. To get from registering to a first score, follow onboarding first; for what the board measures and why, the overview; for how a published number is computed, the scoring page.
GET /api/tasks/game-value-player/players who a player_id is public
GET /api/tasks/game-value-player/appearances who played, and by when Bearer <key>
POST /api/tasks/game-value-player/ratings deliver ratings Bearer <key>
GET /api/tasks/game-value-player/status what you still owe Bearer <key>
Contract version in force: 2026-09-game-value-player-v0. It is checked on every delivery.
All paths are relative to https://api.footballanalyticsarena.com. The interactive reference
for every endpoint is at
api.footballanalyticsarena.com/docs.
The unit here is an appearance, not a match. A round is about 1,400 of them and a season about 54,000. Almost everything below follows from that number: why the list is paged, why what you owe is counted rather than listed, and why there is a cap on deliveries per round.
Why you deliver, and nothing calls you
Participation is submission mode: your scheduled job posts ratings to the Arena. There is no endpoint for you to expose, nothing to keep running at our convenience, and no timeout of ours that can turn your infrastructure into part of your score.
Run it daily, not weekly. A midweek round is due at 23:59 the day after it is played, so a job that ran once a week would miss one deadline in every midweek week.
The player directory
GET /api/tasks/game-value-player/players — public, no key. Every player you may be asked to rate
this season: player_id, name, first_name, last_name, birth_year, nationality,
position, and the team_id and competition of the club he last appeared for, with his
appearances so far. ?team_id= and ?competition= narrow it; a competition is named exactly as
published, and anything else is refused with the valid names.
Map it onto your own database once, at enrolment, and re-read it weekly for new players. Every
player_id the appearance list can send you is on it. Club ids are the team board's:
GET /api/tasks/game-value-team/teams resolves
them. No provider identifier is published.
The appearance list
GET /api/tasks/game-value-player/appearances
One row per player per appearance, for matches played and not yet past their deadline.
| Field | Meaning |
|---|---|
match_id | the Arena's match identifier |
player_id | an Arena player identifier, never a name |
team_id | the side he played for |
minutes_played | what the Arena recorded, on a 90-minute scale |
is_starter | whether he started |
competition, round, kickoff | where and when it was played |
deadline | by when a rating for it is due |
updated_at | when this row last changed |
An unused substitute is not on it. He made no appearance, so there is nothing to rate and nothing to weight a rating by, and a rating for him is refused. A three-minute substitute is on it: he played. Declining to rate him is a product boundary the coverage column reports, not a row that does not exist.
The minutes are the Arena's own. You may disagree with them; deliver your rating anyway. They are published for two reasons: every participant is weighted by the same numbers, and holding this list, your own ratings and the published segment tables you can recompute your score yourself. A board whose arithmetic cannot be reproduced has to be trusted rather than checked.
Paging, and why the watermark is a cursor
The response carries count, more and next_since. Send next_since back to get what follows:
curl -s "$ARENA_API/api/tasks/game-value-player/appearances?limit=1000" \
-H "Authorization: Bearer $ARENA_KEY"
# { "count": 1000, "more": true, "next_since": "2026-09-27T01:12:44+00:00|fam_…_0042|FAP0000731", … }
Mid-list next_since is an opaque cursor naming the exact row you stopped on. At the end of
the list it is a plain updated_at, which is also what a job should keep between runs to read only
what has changed since.
Both forms are accepted. A timestamp of your own means everything changed strictly after that instant.
Why a cursor and not just a timestamp. A match's appearances are all written at once, under one clock reading. A page that ends in the middle of a match therefore ends on an instant that several unserved rows also carry — and resuming on the instant alone would skip them, silently and for good. This is the single easiest thing to get wrong when integrating, and the reason the field is not simply a time.
Reads are free and count toward no delivery limit.
Delivering ratings
POST /api/tasks/game-value-player/ratings — one number per player per appearance.
{
"task": "game_value_player",
"contract_version": "2026-09-game-value-player-v0",
"model_version": "my-ratings-2.1",
"ratings": [
{ "match_id": "fam_2627_premierleague_0042", "player_id": "FAP0000731", "rating": 7.4 },
{ "match_id": "fam_2627_premierleague_0042", "player_id": "FAP0004120", "rating": 5.9 }
]
}
match_id and player_id come from the appearance list — never a name. Not one rating per
segment: the Arena decides who was on the pitch for each segment, from the event stream.
Nothing is normalised. Out of 10, out of 100, z-scores: the score depends only on the order your ratings put players in, so rescaling changes nothing — and the Arena cannot be accused of having rescaled you wrongly.
A missing rating is missing, never zero. Declining to rate an appearance is a coverage fact and is published as one. A zero for a player you did not judge is a wrong number, and on a mean over eleven players a very wrong one.
?dry_run=true runs every check and stores nothing. The receipt is identical to a real delivery's,
so a clean dry run guarantees acceptance. Dry runs count toward no delivery limit.
The receipt
The response is the receipt: an outcome per entry, the Arena's receipt time, and a checksum of the payload as received. Time is the Arena's; your timestamps are not read.
| Outcome | Meaning |
|---|---|
accepted | stored, and it counts |
replaced | a new version of a rating you had already sent, before the deadline |
unchanged | identical to what we hold; kept, and not a new version |
late | a first rating after its deadline. Stored, and scored only toward matches kicking off after it arrived |
rejected | not stored, with a reason |
Rounds, deadlines and the freeze
Ratings for a round are due at 23:59 Paris on the day after its last match — the same rule, to the minute, as the team board. A vendor on both should not have to check.
- Replaceable until the deadline. The full day exists for exactly this: an estimate on Sunday, a correction on Monday.
- Frozen at it. After the deadline a first rating still lands, as
late; a different rating for something you already rated is refused asfrozen. The version in force at the deadline is the one that scores. - A rating counts only toward matches kicking off after it was due. You may legitimately deliver after a later match has been played, and a rating that can be sent once a result is known must not be allowed to inform it.
Ten deliveries per round
Stored deliveries, per participant, per round. Dry runs are exempt. Batch your ratings and check
status before resending: a job that posts the same round nightly will spend the cap inside a
fortnight and be refused on the eleventh.
What you still owe
GET /api/tasks/game-value-player/status answers for your own participant: every open round with
something missing, its deadline, how many ratings are expected, how many arrived, how many are
absent, which matches they are absent from, and how many of that round's deliveries remain.
What is missing is counted, not listed. A round is about 1,400 appearances, so a list of every
outstanding one would be longer than the delivery that answers it. matches_incomplete says where
the gaps are and how many each match has.
missed_this_season counts ratings that were not there when a deadline passed. Those can still be
delivered, as late.
Reminders
At deadline − 12 h and − 3 h, to your contact address, and only when something is missing. A job that is working never hears from us.
Certification
Before anything counts: a dry run of one completed round with every starter in it, nothing rejected. Passing it is the moment you are enrolled, and ratings count for matches played after it.
Send the whole round — every appearance you rate, cameos included. The gate asks only that no starter is missing.
Why the XI and not every appearance. The XI is what a signal is built from: a segment is priced on the mean prior rating of the players on the pitch, and the starters are that set at kick-off. A participant that rates every starter can be scored. One that cannot is not exercising a product boundary, it is failing to cover the football.
The rule used to be every appearance, and it made a stated boundary unreachable: a source that declines to rate short substitute appearances was refused enrolment by the gate meant to prove its integration worked. Declining a cameo still costs coverage, which is published, and still costs the rated-XI floor at scoring time. Neither needs a closed door here.
Entries come back late on a completed round. That is expected and is not a failure.
Send one round per competition, in a single dry run. The competitions a certification proves are the ones you are scored on, so certifying on one league enters you for that league alone. Certifying again later adds a competition the first run could not prove: the enrolment moment never moves, and competitions are added to rather than replaced.
Coverage is all or nothing within a competition
You choose which competitions you enter. Inside one, you do not choose which appearances to rate: picking appearances is picking a sample, and no coverage column undoes it, because the players a rating system finds hardest to judge are exactly the ones it would have most reason to omit.
A partial XI is accepted — rating nine of a side's eleven is a product boundary, not an error — and it is published. Goalkeepers are not optional: a keeper's rating is as much of a product claim as an outfielder's, and an unrated keeper counts against the rated share like anyone else.
Refusals
The whole delivery is refused for: no valid key (401), a participant that has not entered or
not certified (403), the wrong task (400), a contract_version not in force (409), or a body
that is not shaped like the contract (422).
A single entry is rejected, leaving the rest of the delivery intact, for: an unknown match_id
or player_id; a player who did not appear in that match; a rating that is not a finite number; a
duplicate (match_id, player_id) within one delivery; a different rating for something already
frozen; or a round whose ten deliveries are spent.
The split is deliberate: one bad entry never costs the others.
Versioning
2026-09-game-value-player-v0. A change to what is asked of you, or to what a field means, is a new
version with a published date; a change to how the Arena computes a score is a scoring version and
appears on the snapshot instead. Deliveries built against a version no longer in force are refused
with 409 rather than reinterpreted.
What to read next
- Scoring — how a delivered rating becomes a published number, and how to recompute one.
- Overview — what the board measures, and what it does not claim.
- Reading a leaderboard — the two rankings, and why level is the expected first-season result.