Delivery contract — Game Value, team board

The reference for every call. If you are integrating for the first time, read onboarding first: it puts these calls in order and tells you how to know each one worked.

GET  /api/tasks/game-value-team/fixtures     the schedule, with deadlines      public
GET  /api/tasks/game-value-team/teams        the team registry                 public
POST /api/tasks/game-value-team/values       deliver values                    Bearer <key>
GET  /api/tasks/game-value-team/status       what you still owe                Bearer <key>

Contract version in force: 2026-09-game-value-team-v0. It is checked on every delivery.

Why you deliver, and nothing calls you

Participation is submission mode: your scheduled job posts values 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.

What a live handshake would have proved — that your integration works end to end — is proved better by certification, which runs a complete round through the real checks before anything counts.

The fixture list

GET /api/tasks/game-value-team/fixtures?since=<iso>&competition=<name>

Public and unauthenticated. It is the single source of truth your job reads every run: postponements, rescheduled kick-offs and awarded results appear as they happen, and every match carries the deadline its round is due at before the match is played, so one call plans your week.

FieldWhat it is
match_idThe Arena's identifier, fam_<season>_<competition>_<n>. What values are delivered against, and stable from publication to scoring through any reschedule.
competition, season, roundWhere the fixture sits.
kickoffEurope/Paris, with its offset.
home_team_id, away_team_idArena team ids, with home_team_name and away_team_name beside them for reading.
statusscheduled, played, postponed, abandoned or awarded.
deadlineWhen that round's values are due and freeze.
updated_atWhen this record last changed. What ?since= filters on.

Integrate on the ids. Names are published so a row is readable and are not accepted in a delivery: a name match is a guess, and a guess attaches a value to the wrong club silently.

?since= takes the largest updated_at you already hold — those are UTC with microseconds, e.g. 2026-09-12T14:13:21.105435+00:00; a plain date works too — and returns only what changed since. Omit it on a first run to read the whole season.

?competition= takes the exact published name: Premier League, LaLiga, Serie A, Bundesliga or Ligue 1. Anything else is refused with the valid names, rather than answered with an empty list that looks like a season with no football in it.

What each status means for you

statusDeliverableMeaning
scheduledNoNot played yet. Includes a match currently in progress: it is not over, so it cannot be valued.
playedYesPlayed and its result validated.
awardedYesDecided off the pitch. Values are stored like any other and the fixture is never scored.
postponedNoNot being played on this date. It keeps its match_id and returns with a new kick-off and its own deadline.
abandonedNoStarted and not completed.

The team registry

GET /api/tasks/game-value-team/teams

Every team with a fixture this season: team_id, club name, country and competition. Read it once, map each team_id to your own identifier, and keep the mapping. No provider identifiers appear here or anywhere else in this benchmark.

Delivering values

POST /api/tasks/game-value-team/values          Authorization: Bearer <your key>
POST /api/tasks/game-value-team/values?dry_run=true
{
  "task": "game_value_team",
  "contract_version": "2026-09-game-value-team-v0",
  "model_version": "my-model-1.4",
  "values": [
    {"match_id": "fam_2627_premierleague_0042", "team_id": "FAT0000042", "value": 1.83},
    {"match_id": "fam_2627_premierleague_0042", "team_id": "FAT0000117", "value": 1.21}
  ]
}

All fields are required. model_version is yours to choose and is recorded with the delivery, so a score can always be attributed to the model that produced it.

One number per team per match: what that team's performance in that match was worth.

  • On your own scale. Nothing is normalised and there are no units to agree on, because the score depends only on the order your values put fixtures in.
  • Both sides of a fixture must travel together. A fixture priced on one side has no net value for either team — and a participant able to describe half a match could shape a rival's signal by choosing which halves to send.
  • Played matches only, as the table above defines it.
  • Any number of matches per delivery, from one round or several.

The receipt

The response is the receipt, and it is what you keep.

{
  "receipt_id": "rcpt_2026-09-22T04-05-11Z_0007_8ecde1",
  "received_at": "2026-09-22T04:05:11.318402+00:00",
  "payload_sha256": "1304bb09…",
  "dry_run": false,
  "certified": false,
  "summary": {"accepted": 18, "replaced": 2, "unchanged": 0, "late": 0, "rejected": 0},
  "entries": [{"match_id": "…", "team_id": "…", "outcome": "accepted", "deadline": "…"}]
}

received_at is the Arena's clock; your own timestamps are not read, so a wrong clock on your side cannot move a deadline. payload_sha256 is taken over the body exactly as received: check it against your own and you have proof of what you sent, not a claim about it.

One bad entry never costs the others. Whole-delivery problems are refused before any entry is judged; everything else is per entry.

OutcomeMeans
acceptedStored. Nothing was held for this team in this match.
replacedStored. It supersedes a different value you sent before the deadline.
unchangedThe same value you already held. Stored state is untouched — and this is allowed after the deadline, because resending the same number is not a change.
lateStored. Your first value for this team in this match, arriving after the deadline. It is kept and it is not scored.
rejectedNot stored, with a reason.

Rounds, deadlines and the freeze

This is the mechanic the benchmark turns on, and the one worth reading twice.

When values are due

A round's values are due at 23:59 Europe/Paris, a full day after its last match.

The round's last match is played onValues are due
Friday, Saturday, Sunday or MondayTuesday 23:59
Tuesday, Wednesday or Thursday23:59 the next day

A full day, because match data is not final at the whistle. A weekend round is due on the Tuesday whether or not it has a Monday match, so you have one deadline a weekend rather than one per match.

A postponed match is a round of its own. When a match is moved more than five days from the rest of its round, it stops sharing their deadline and carries one derived from the date it is actually played — with its own delivery budget, so it does not spend one its original round already spent. Every fixture carries its deadline in the fixture list, so your job never computes this itself.

Replaceable until the deadline, frozen at it

Before the deadline you may send a value again and the last one counts. That window is what the extra day is for: correct a value as the match data settles.

After the deadline:

  • a first value for that team in that match still lands, marked late, and is not scored;
  • the same value again is unchanged and harmless;
  • a different value is rejected as frozen.

Every version is kept either way, so which one counted is readable rather than asserted.

What a value counts toward

A value counts only toward fixtures that kick off after its deadline.

This is the rule that makes the benchmark a forecast rather than a description. The one-day window means a value may legitimately arrive after the team's next match has been played — and a value that could be written knowing a result must never be allowed to predict it.

The practical consequence: delivering history after the fact achieves nothing. Values for matches that kicked off before you certified are refused outright, and a value delivered late is stored but never scored.

Three deliveries per round

You may store three deliveries per round: a first and two corrections. It is also loop protection — a job stuck resending stops rather than filling the record.

Counted per round, not per match: one delivery carrying four matches of the same round spends one of its three. A delivery spanning several rounds spends one from each. Dry runs are exempt and count toward nothing.

What you still owe

GET /api/tasks/game-value-team/status        Authorization: Bearer <your key>
{
  "as_of": "2026-09-22T04:06:00+00:00",
  "certified": true,
  "due": [
    {
      "competition": "Premier League",
      "round": 6,
      "deadline": "2026-09-22T23:59:00+02:00",
      "expected": 20,
      "delivered": 18,
      "deliveries_left": 2,
      "missing": [{"match_id": "fam_2627_premierleague_0104", "team_id": "FAT00066"}]
    }
  ],
  "missed_this_season": 0
}

due lists every open round with something outstanding, naming the exact (match_id, team_id) pairs absent. A round whose deadline has passed with values missing is counted, not listed, in missed_this_season: nothing can be done about it now, and a list that never shrinks stops being read.

certified is false until certification passes, and until then nothing is due. Reads are free and count toward no limit — call it after every run.

Reminders

WhenWhat
Deadline − 12 hEmail to your contact address, listing what is still missing
Deadline − 3 hThe same, if anything is still missing

Twelve hours rather than twenty-four, because a day before a Tuesday deadline is Monday night, when the round's last match may barely be over and every participant would look "missing".

A reminder is sent only when something is actually missing, so a participant whose job works never receives one. Reminders are a safety net; status is how a well-built job checks itself.

Certification

Certification is a dry run against one completed round, put through every check a real delivery faces and storing nothing.

POST /api/tasks/game-value-team/values?dry_run=true

To pass, the dry run must cover every match of one completed round in a competition, both sides of each, with nothing rejected. The receipt then carries "certified": true.

Certification runs against a round whose deadline has passed, so its entries come back late. That is expected and correct. The only outcome that prevents certification is rejected.

Two consequences:

  • Passing guarantees acceptance. A dry run runs the real checks, so what passes here passes for real. That is what makes it worth doing.
  • Certifying declares what you are entering. The competitions your dry run covered are the ones you are scored on. Send one complete round per competition you intend to cover.

Certification is enrolment. You are scored from that moment — not from when you registered, and not from when you first delivered.

Coverage is all or nothing within a competition

You choose which competitions to enter. Inside one, you deliver every match of every round: a participant who priced only the fixtures it found easy would be scored on a different set of matches from everyone else, and the scores would not be comparable.

Joining mid-season

You may certify at any point in the season. Values for matches that kicked off before you certified are refused as before enrolment — they could be priced knowing every result since, so they would not be forecasts.

This is not a penalty for arriving late; it is the rule everyone is under. It does mean your delivered history starts the day you certify, and the first fixtures can only be scored once both sides have enough of it.

Refusals

The whole delivery

CodeCause
401No valid API key
403The participant has not entered this benchmark, or has not certified
400task is not game_value_team
409contract_version is not the one in force — the refusal names the version that is
422The body is not shaped like the contract

A single entry

Checked in this order; the first that applies is the reason given.

reasonCauseFix
unknown match_idNo such fixture this seasonRe-read the fixture list; never construct an identifier
team did not play in this matchThe team is not one of the two sidesCheck your id mapping — usually a name match gone wrong
match not playedThe fixture's status is not played or awardedDeliver after the match
before enrolmentThe match kicked off before you certifiedNot accepted, by design
value is not a numberNot a finite numberSend a number — not a string, null, boolean or NaN
fixture described on one side onlyOnly one of the two teams is in this deliverySend both sides together
delivery limit reached for this roundA fourth stored delivery for that roundBatch your corrections; dry runs are free
frozenA different value after the deadlineThe value in force stands

Versioning

contract_version is checked on every delivery, and a mismatch is rejected rather than silently accepted — a payload built against different rules is not a payload we can score.

What forces a new version: a change to the payload, the fixture-list fields, the receipt, the deadline rule, what the Arena derives, the burn-in, the horizons, or the statistic.

What does not: a new competition entering the fixture list, or a change to the published calendar. Those arrive through the fixture list itself, which is why your job reads it every run.

Every weekly snapshot records the contract version it was scored under, so a published row always says which rules produced it.

  • Onboarding — these calls in order, with a way to tell each one worked.
  • Overview — what is measured, and why agreeing with the scoreline is not evidence.
  • The team board — what a published participant looks like.