Direct event submissions¶
POST /api/v1/submissions is a privileged legacy/import endpoint restricted to authenticated
admin accounts. Ordinary submitters cannot use this synchronous path because submitter-provided
event data must pass through the staged batch validation and review lifecycle before publication.
Client-supplied roles or scope values are ignored.
The current schema version is 1.0. A request contains one fixture and 1–1,000 ordered cricket
delivery events. Events for each innings must appear in ascending sequenceNumber order. Statistics
are not accepted: the platform derives them from accepted deliveries.
Each event requires explicit zero-based overNumber and positionInOver
coordinates. ballNumber is optional display data and is never used for
identity or ordering. If supplied, it must use <over>.<ball> form and its over
component must match overNumber; the ball component may repeat for wides and
no-balls.
POST /api/v1/submissions
Authorization: Bearer <supabase-access-token>
Content-Type: application/json
{
"fixtureId": "42",
"schemaVersion": "1.0",
"events": [
{
"eventId": "123e4567-e89b-42d3-a456-426614174000",
"inningsId": "81",
"sequenceNumber": 1,
"overNumber": 0,
"positionInOver": 0,
"ballNumber": "0.1",
"strikerId": "101",
"nonStrikerId": "102",
"bowlerId": "201",
"runs": {
"offBat": 4,
"extras": 0,
"total": 4,
"nonBoundary": false
},
"extras": {},
"wickets": []
}
]
}
Successful validation and storage are synchronous and atomic:
HTTP/1.1 201 Created
{
"data": {
"submissionId": "300",
"fixtureId": "42",
"submitterId": "17",
"status": "accepted",
"receivedAt": "2026-08-13T16:00:00.000Z",
"schemaVersion": "1.0",
"eventCount": 1
}
}
The backend records the submitter, fixture, received timestamp, schema version, original event ID, and zero-based event-array position. A globally unique event UUID protects against accidental retry or replay. If any event is invalid or conflicts, the transaction rolls back and stores neither the submission nor any of its events.
Responses are 401 for missing or invalid authentication, 403 for any non-administrator, 409 for event conflicts, 413 above the 1 MB JSON limit, 422 for
contract or reference validation, and 429 after 30 requests from one account in 60 seconds.
Validation details include a field path and eventIndex where applicable.
File uploads¶
The unified /submissions/new interface accepts single-fixture, season and back-catalogue packages
using the durable batch pipeline documented in
Batch submission packages. In single-fixture mode, the
submitter chooses a fixture by date, team names, competition and season; the browser verifies that a
JSON or CSV package contains exactly that readable fixture context before upload. Season and
back-catalogue modes use readable competition and optional season context and retain NDJSON support.
All guided modes use the same maintained JSON and spreadsheet templates, and database IDs are not
required. The interface displays upload progress and the durable receipt, and links to the batch
report where background validation, source-row errors and ambiguous-reference mapping remain
available after navigation.
Single-fixture mode also provides a separate Propose a new fixture action. The submitter selects
an authorised competition, enters the fixture date, season, teams and the complete canonical-fixture
metadata, then uploads a matching JSON or CSV fixture package. The browser generates the existing
version 1.1 fixture-proposal contract and sends it through the batch pipeline; it never inserts
canonical fixture data directly. Selecting the package first prefills its competition, season,
fixture date and team names in the proposal form, leaving the submitter to confirm that context and
enter the proposal-only metadata. Before upload, the browser warns when that readable context matches
an accessible existing fixture and offers the existing-fixture path; batch reference resolution
remains the authoritative server-side check.
Match type is retained because version 1.1 and the canonical fixture require it, but the UI fixes
the available value to T20, matching the platform scope. Team type is selected as club or
international, and gender as female or male.
An administrator can use the batch report's Create canonical fixture from proposal action, after
which normal reference resolution and validation run again for the affected submission.
The advanced technical JSON editor preserves the canonical identifier-based input format. For an
ordinary submitter, the browser converts that input to a batch package using explicit app:*
application references and uploads it to POST /api/v1/batches, so it receives a durable receipt and
must pass review before publication. Administrators retain the synchronous direct endpoint only as a
clearly privileged import path.
POST /api/v1/submissions/uploads is likewise restricted to authenticated administrators and exists
only for legacy/internal imports. Normal submitter file uploads use POST /api/v1/batches. It accepts only a .json file with
application/json media type or a .csv file with text/csv media type, and limits the file to
1 MB (the MAX_SUBMISSION_UPLOAD_BYTES limit exported by @sport-analytics/contracts). Both formats are normalised into the same fixtureId, schemaVersion, and ordered events
contract shown above before the existing scope, cricket-rule, reference, replay, and transaction
checks run.
JSON files contain the direct-submission JSON object. CSV files contain one event per row and must use this exact header order:
fixtureId,schemaVersion,eventId,inningsId,sequenceNumber,overNumber,positionInOver,ballNumber,strikerId,nonStrikerId,bowlerId,runsOffBat,runsExtras,runsTotal,runsNonBoundary,extraWides,extraNoBalls,extraByes,extraLegByes,extraPenalty,wickets
wickets is a JSON array in the CSV cell; blank optional extras are treated as absent and a blank
wickets cell is an empty array. Every CSV row must name the same fixture and schema version.
Invalid file or normalised row errors return 422 with actionable details and an eventIndex for
row-specific failures; oversized files return 413. Uploads remain atomic and persist original
filename, canonical media type, byte length, and a SHA-256 checksum of the original uploaded bytes on
the accepted submission. Direct JSON submissions persist a deterministic SHA-256 checksum of the
validated request payload as well. These retained checksums are used by the protected
provenance and audit API.
Correct an accepted event¶
PUT /api/v1/submissions/events/{eventId} corrects an accepted direct-submission event. The path
event ID is the original client UUID; the request supplies the fixture, contract version, corrected
delivery content, and a required non-blank reason. The occurrence sequence is inherited from the live
source event and is not client-editable.
Only an authenticated submitter or admin may correct it. An ordinary submitter needs the
fixture's server-owned competition scope; an administrator may correct any eligible fixture without a
scope assignment. The replacement is validated against the same cricket contract, participant,
innings, and dismissal-kind rules as a new submission. Invalid, unknown, unauthorised, or out-of-scope
corrections leave the live event unchanged.
The database transaction inserts a new immutable delivery revision, links it explicitly to its predecessor,
marks the previous live row superseded, and appends an immutable audit record with the requester, timestamp,
reason, before/after states, and original submission/batch-item provenance. Revision numbers increase by one
under a per-event transaction lock, including for concurrent requests. It never accepts statistic totals.
Fixture, participant, and public-event reads use live
deliveries, so the affected derived statistics change automatically while unrelated delivery statistics
remain unchanged. The successful correction response also contains refreshedScopes: the durable,
deduplicated refresh targets for the fixture and for the affected players' season, competition, and
career aggregates. This is an observable dependency journal rather than a second source of statistic
values; public statistics continue to derive from the current accepted revisions.
{
"fixtureId": "42",
"schemaVersion": "1.0",
"reason": "Correct scorer transcription from the signed scorebook.",
"event": {
"inningsId": "81",
"overNumber": 0,
"positionInOver": 0,
"ballNumber": "0.1",
"strikerId": "101",
"nonStrikerId": "102",
"bowlerId": "201",
"runs": { "offBat": 6, "extras": 0, "total": 6 }
}
}
GET /api/v1/submissions/events/{eventId}/history exposes ordered audit history to an in-scope
submitter or an administrator. Each entry includes explicit previous and replacement delivery identifiers
and revision numbers, requester identity, correction time and reason, complete previous/resulting event
states, original submission position, and optional batch-item identity. Reviewer, decision, review time and
review reason are returned together where review applies; the immediate accepted-correction flow returns
review: null. Public event endpoints continue to return only the current accepted revision and omit audit
metadata.
Rejection format¶
A rejected submission returns an error object carrying a code, a human-readable
message, and — where the failure can be attributed to a particular field or event
— a details array. Every detail names the field path that failed and, for a
failure inside a submitted event, the zero-based index of that event.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The submission is invalid.",
"details": [
{
"code": "INVALID_PARTICIPANT",
"message": "The participant does not belong to the submitted fixture.",
"field": "wickets.0.fielders.2.participantId",
"eventIndex": 3
}
]
}
}
Validation does not stop at the first failure. Every problem the platform can detect in one pass is returned together, so a submitter can correct a submission without discovering its faults one at a time.
Response codes¶
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_JSON |
The request body is not valid JSON. |
| 401 | UNAUTHORIZED |
Authentication is missing or could not be verified. |
| 403 | FORBIDDEN |
The account is authenticated but lacks the submitter/admin role, or an ordinary submitter is outside its competition scope. |
| 409 | DUPLICATE_EVENT_ID |
One or more event identifiers have already been accepted. The submission is a replay rather than an invalid payload. |
| 413 | PAYLOAD_TOO_LARGE |
The request exceeds the 1 MB limit. |
| 422 | VALIDATION_FAILED |
The submission is structurally or referentially invalid. See details. |
| 429 | RATE_LIMIT_EXCEEDED |
More than 30 requests from one account in 60 seconds. Retry-After gives the wait in seconds. |
| 500 | INTERNAL_SERVER_ERROR |
An unexpected failure. No detail is returned, and the cause is recorded server-side. |
Detail codes¶
Returned inside details on a 422.
| Code | Field | Raised when |
|---|---|---|
INVALID_FIELD |
The path that failed | The payload does not satisfy the submission contract: a missing or mistyped field, a value outside the supported range, an unexpected field, a printed ball number not in the published form, or an ordering rule broken within the submission. The message is the contract's own. |
FIXTURE_NOT_FOUND |
fixtureId |
The submitted fixture does not exist. |
INVALID_INNINGS |
inningsId |
The innings does not exist, or belongs to a different fixture. The check is scoped to the submitted fixture, so an innings identifier from elsewhere is rejected rather than accepted. |
INVALID_PARTICIPANT |
strikerId, nonStrikerId, bowlerId, wickets.N.playerOutId, or wickets.N.fielders.M.participantId |
The participant is not in the squad for the submitted fixture. Existing as a person is not sufficient. |
UNKNOWN_DISMISSAL_KIND |
wickets.kind |
The dismissal kind is absent from the dismissal_kind lookup table. The vocabulary is held in that table rather than in the contract, so a new kind requires a row and no code change. |
DUPLICATE_EVENT_ID |
eventId |
The event identifier has already been accepted. Also returned as the top-level code on a 409. |
| 409 | EVENT_CONFLICT |
A delivery position, within-innings sequence, or event identifier conflicts with data already accepted. Unlike DUPLICATE_EVENT_ID, this is raised by the database rather than the pre-submission checks: it means the conflicting data was accepted between validation and storage, or that another submission holds the position. |
Atomicity¶
A rejected submission stores nothing. Validation and storage share one transaction, so a failure at any point leaves neither the submission record nor any of its events behind. A submitter may correct and resubmit without first removing a partial result.
AI Declaration¶
The direct submission API documentation was generated with the assistance of Codex[GPT-5.6 Sol]. The correction workflow and file-upload submission support were added with the assistance of Codex[GPT-5]. The Issue #311 administrator submission rule was documented with the assistance of Codex[GPT-5]. The Issue #284 correction audit contract was documented with the assistance of Codex[GPT-5]. The Issue #286 selective refresh dependency contract was documented with the assistance of Codex[GPT-5]. The Issue #435 guided single-fixture upload behavior was documented with the assistance of Codex[GPT-5]. The Issue #437 unified submission workflow was documented with the assistance of Codex[GPT-5]. The Issue #571 new-fixture proposal workflow was documented with the assistance of Codex[GPT-5].