Batch ingestion receipt API¶
POST /api/v1/batches is a distinct asynchronous receipt route for whole-season and back-catalogue packages. It does not extend the synchronous Basic submission or file-upload routes, and it never publishes events merely because a package was accepted.
The request body is streamed directly to the private object-store adapter. Supply Authorization: Bearer <token>, Idempotency-Key, X-Competition-Id, X-Batch-Package-Version (1.0, or 1.1 for packages that include fixture proposals), and X-File-Name headers. Its Content-Type must be application/json, text/csv, or application/x-ndjson. A corrected upload made in response to a review decision also supplies the original batch's opaque UUID in X-Replaces-Batch-Reference.
The endpoint returns 202 Accepted with an opaque UUID batchReference, a relative statusUrl (also sent in the Location header), and stored status. The reference is not a database ID. GET /api/v1/batches/{batchReference} lets the owning submitter or an administrator retrieve the receipt status.
The server authenticates and checks the persisted submitter role and competition scope before receiving source bytes. Raw source is streamed with a 50 MB limit, retained privately for 90 days, and receives a SHA-256 checksum. The idempotency key is unique within the submitter scope: replaying it with the same checksum returns the original receipt, while changed bytes return 409 BATCH_CONFLICT. Receipt creation serializes each submitter's key lookup, active-batch limit, queue insertion, and correction linkage, so concurrent equivalent requests cannot enqueue duplicate work or claim the same correction request twice. A replacement must belong to the same submitter and competition and must identify a batch whose current state is correction_requested. The transaction retains that original, changes its state to superseded, populates superseded_by, and records the lifecycle transition. Invalid, stale, or competing relationships return 409 BATCH_CONFLICT. Up to three non-terminal batches are permitted per submitter. Unsupported metadata is 422, size is 413, storage failures are 503, and the active-batch limit is 409. More than six uploads by one account in 60 seconds return 429 with Retry-After.
Package expansion, event validation, review and publication are implemented as asynchronous
follow-on stages. After receipt, the worker expands and validates the retained package, records
resumable progress and produces the item-level report. Batches that satisfy the blocking validation
rules can enter awaiting_review, where a competition-scoped administrator may approve, reject or
return them for correction. Approval publishes the accepted subset through the resumable publication
path. Stored or staged data remains non-public until publication, and rejected or invalid source
items are never exposed as accepted events or statistics.
The unified submitter interface at /submissions/new provides single-fixture, season and
back-catalogue choices. Season and back-catalogue modes obtain the competition identifier from a
readable, server-scoped choice. The interface explains JSON, CSV and NDJSON support, the 50 MB,
50,000-item and three-active-batch limits, and required human-readable package context before upload.
It links the maintained JSON and spreadsheet templates, shows transfer progress, and presents the
durable receipt with a link to the later report. A correction-requested report provides an
Upload corrected replacement action that carries the original reference and fixed competition
scope into the normal season upload. Retrying the unchanged selection retains its idempotency key;
selecting corrected content generates a new key. Status, list, report, and reviewer responses expose
both lineage.replacesBatchReference and lineage.supersededByBatchReference; submitter and reviewer
interfaces render those UUIDs as navigation links. The retired
/submissions/batches/new route redirects to /submissions/new for existing bookmarks.
Reference mapping¶
An item report exposes every ambiguous or unresolved reference in referenceResolutions. Candidate
choices contain readable labels and opaque UUID references; internal canonical identifiers are not
returned. A reference with no safe existing candidate reports contact_reviewer and cannot be
silently created or fuzzy-matched.
The owning submitter or an administrator with the batch competition in their persisted scope may
send the report item itemOrdinal, referencePath, candidateReference, and a caller-stable decisionKey to
POST /api/v1/batches/{batchReference}/reference-mappings. The API verifies the candidate against
the current stored resolution evidence, retains the actor and selection, and returns 202 after it
durably queues revalidation from the original private source. The status URL remains available after
the caller leaves. Identical decisions are idempotent; stale candidates, reused keys with different
content, competing choices, and decisions made while validation is active return
409 BATCH_REFERENCE_MAPPING_CONFLICT. A malformed batch reference returns 404, and a request body that is not valid JSON or exceeds 16 KB returns 400 or 413.
Revalidation uses the normal package resolver, event schema, cricket rules, and durable worker checkpoint. Previous validation results remain retained as superseded evidence while status, counts, reports, and approval checks use only the current validation attempt.
Reviewer workspace¶
Administrators use /reviews/batches to find only awaiting_review batches in their persisted
competition scope. The authenticated batch list supports a status filter, while all list, report,
mapping, and decision authorization remains enforced by the backend.
Status and report responses include source filename, submitter label, received time, SHA-256 checksum, and package version. Reports also include validation and reference-resolution counts, stable rule-code groups, fixture-level counts, and at most 15 accepted samples. The browser workspace therefore never renders a season-scale accepted dataset. Candidate reference tokens remain opaque; reviewers act on their human-readable labels through the existing reference-mapping endpoint.
The report response keeps ordinary items cursor-paginated and separately returns blockingItems.
That action-required collection is selected server-side from the same active batch records used by
approval checks, so unresolved references and published-delivery conflicts remain directly
available to reviewers even when they occur beyond the current ordinary report page.
Ordinary item-level validation rejections do not prevent approval: the accepted subset publishes and the rejected source records remain unpublished in the report. Approval is still rejected by both the interface and repository transaction while batch-level or accepted-item validation errors, publication conflicts, ambiguous, unresolved, or invalid references remain.
Each report item also identifies whether it is an ordinary upsert or a correction. Correction items
show the submitted correctsEventId and, when resolution succeeded, the published delivery revision
selected during validation. Submitter reports and the bounded reviewer sample both present this
target before a publication decision.
Every decision requires a reason; rejection and return-for-correction reasons require at least 10
characters. The interface adds an explicit modal confirmation before approve, reject, or
return-for-correction and clearly presents publishing, failure, partial-publication,
correction-requested, rejection, and publication states. Repeated identical decisions remain
idempotent; competing or stale decisions return 409.
Approved correction items publish through immutable delivery revision and supersession history, including the submitting account, approving reviewer, review reason and affected-statistics dependencies. Publication rechecks that the target is still the current revision in the declared fixture and competition. Ordinary upserts retain their existing exact-duplicate skip and different-content conflict behaviour.
See Batch submission packages and the Batch ingestion pipeline for the package and lifecycle contracts.
AI Declaration¶
The Issue #277 receipt API documentation was produced with the assistance of Codex[GPT-5]. The Issue #280 idempotency behaviour was documented with the assistance of Codex[GPT-5]. The Issue #425 reference-mapping API was documented with the assistance of Codex[GPT-5]. The Issue #362 reviewer workspace documentation was produced with the assistance of Codex[GPT-5]. The Issue #361 guided batch-upload interface was documented with the assistance of Codex[GPT-5]. The Issue #437 unified submission route was documented with the assistance of Codex[GPT-5]. The Issue #539 correction-resubmission lineage was documented with the assistance of Codex[GPT-5]. The Issue #537 reviewer blocking-item response was documented with the assistance of Codex[GPT-5]. The Issue #297 Intermediate batch-documentation audit was reviewed and edited with the assistance of ChatGPT-Web[GPT-5.6 Sol]. The Issue #609 package-version, rate-limit, Location and mapping error details were added with the assistance of Claude-Code[Claude Opus 5].