API v2 full capability parity plan
The execution plan for making API v2 cover every legacy API capability with coherent resources, explicit security, bounded collections, and complete OpenAPI evidence.
Goal: Make /api/v2 provide every capability available through the legacy
API, including authenticated writes and operational integrations, while giving
v2 coherent resources, predictable pagination, explicit authentication,
Problem Details errors, and complete OpenAPI coverage.
This plan treats capability parity as the target. V2 does not preserve every
legacy URL, parameter name, or response quirk. An unchanged old application
must continue to call legacy /api/*; migrating it to /api/v2/* requires a
client update or an explicit compatibility adapter.
Baseline on 2026-09-02
| Surface | Operations | GET | Other methods |
|---|---|---|---|
| Legacy v1 in the combined OpenAPI document | 299 | 192 | 107 |
| Focused v2 OpenAPI document | 53 | 47 | 6 |
The legacy Next.js source contains 221 route files. Three capabilities are not ported into Go v1 and therefore require direct v2 coverage as part of this project:
- tracker bankrolls;
- fixture unavailability; and
- the value-bets v4 workflow.
The 299 documented v1 operations cover these capability groups:
| Capability group | Operations | Reads | Mutations and protocol methods |
|---|---|---|---|
| Core football and reference data | 123 | 101 | 22 |
| Betting, models, and analytics | 35 | 19 | 16 |
| User workflows and alerts | 39 | 14 | 25 |
| Administration and ingestion | 50 | 22 | 28 |
| Billing, automation, and integrations | 19 | 4 | 15 |
| Basketball | 14 | 14 | 0 |
| Editorial and delivery | 17 | 16 | 1 |
| Platform | 2 | 2 | 0 |
These counts measure the work inventory, not the required number of v2 URLs. One v2 operation can replace several legacy operations when it preserves all their behavior. A legacy operation can also map to several v2 operations when the old route mixed unrelated responsibilities.
Non-negotiable constraints
- Keep legacy
/api/*behavior unchanged while clients migrate. - Map every documented v1 operation and every legacy-only capability to v2.
- Do not use a hidden v1 fallback in any v2 handler or first-party v2 client.
- Do not mark an operation complete from OpenAPI alone. Verify its route, security, database behavior, response schema, and failure behavior.
- Use the configured real dataset for read verification and public previews. Do not add sample-data, empty-data, redaction, or preview-hiding switches.
- Run write verification with dedicated principals, transactions where possible, and deterministic cleanup. Never test destructive writes against uncontrolled production records.
- Preserve deliberate security improvements. Capability parity does not mean reproducing anonymous administrative access, unverified webhooks, or other legacy vulnerabilities.
Deliverables
The project is complete only when it has all of these artifacts:
- A machine-readable capability manifest covering v1 and legacy-only routes.
- Design records for the v2 domain vocabulary and cross-cutting contracts.
- Mounted Go handlers for every approved v2 operation.
- A focused OpenAPI document generated from the mounted v2 surface.
- Contract, authorization, database, mutation, and performance evidence.
- Web and Expo clients that use v2 without implicit v1 fallbacks.
- Separate v1 and v2 explorer and reference documentation.
- Runtime dashboards that show operation use, failures, latency, and v1 callers during migration.
Phase 1: Build the parity manifest
Generate the source inventory from the legacy OpenAPI document, the Go router, and the three remaining Next.js routes. Store it in a checked-in, machine-readable file rather than a prose checklist.
Each manifest entry must contain:
- legacy method, path, operation ID, and source handler;
- stable capability ID and capability group;
- public, user, superadmin, service, or webhook security persona;
- request parameters, body, media type, and validation behavior;
- success payload, errors, side effects, and external integrations;
- proposed v2 operation ID or operation IDs;
- migration strategy: direct, merged, split, or protocol-specific;
- implementation owner module and first-party consumers;
- lifecycle state:
discovered,designed,implemented,contract-verified,data-verified,client-migrated, orrollout-ready; and - links to tests and design records.
Add continuous integration checks that fail when:
- a legacy operation or source route has no manifest entry;
- a manifest entry has no v2 mapping;
- a mapped operation is absent from the router or focused OpenAPI document;
- duplicate operation IDs exist; or
- a removed legacy operation has no approved replacement or retirement record.
Exit gate: zero unmapped v1 operations and zero unmapped legacy-only capabilities. Every entry has an assigned security persona and implementation wave.
Phase 2: Freeze the v2 platform contract
Write design records before expanding the route count. Reuse the rules already implemented by the current v2 surface and close the remaining gaps.
Resource and representation rules
- Use canonical domain terms: competition, competition season, competition phase, fixture, fixture incident, lineup, market, price, value bet, betting model, team, player, referee, and manager.
- Use plural resource nouns and ownership-based nesting. Do not encode actions as route names when a resource or job represents the behavior.
- Treat IDs as opaque strings at the contract boundary, even when the current database stores integers.
- Encode timestamps as RFC 3339 UTC strings and define the timezone for date-only values.
- Return one resource under
data. Return collections underdatawith documented navigation metadata. - Use closed response schemas. Add an explicit extension point only where the data is intentionally open-ended.
- Reject unknown query and body fields so misspellings do not silently change results.
Collection rules
- Use signed cursor pagination for mutable feeds and large entity collections.
- Use page-number pagination for ranked boards that require arbitrary page access across explicit sort columns.
- Leave small, finite reference sets unpaged only when a measured upper bound is part of the contract.
- Add a deterministic ID tie-breaker to every sort.
- Bind cursors to resource, filters, sort, direction, and contract version.
- Apply filters and pagination in the database before enrichment unless a design record proves why that is impossible.
- Return aggregates from aggregate operations. Do not force clients to download detail rows to calculate summary cards.
Authentication and authorization rules
Define and test an authorization matrix for:
- anonymous public reads;
- Supabase bearer tokens;
- Supabase session cookies;
- resource-owning users;
- superadmins;
- internal service identities; and
- signed provider webhooks.
Document security requirements per operation in OpenAPI. Derive ownership from
the authenticated identity, return 401 for missing or invalid credentials,
and return 403 for valid identities without permission. Public preview must
use the same authorization rules and real data as production; environment must
not become an undocumented authorization dimension.
Mutation and integration rules
- Use
POSTto create resources or start jobs,PATCHfor partial updates, andDELETEfor deletion or cancellation. - Define atomicity and conflict behavior for every write.
- Require idempotency keys for retryable creates, ingestion triggers, alerts, billing actions, and other operations where duplicate execution is harmful.
- Deduplicate webhook delivery by provider event ID and verify signatures before parsing the event as trusted input.
- Model long-running imports, analyses, and reports as job resources with a status operation instead of holding requests open.
- Specify concurrency control for updates that can overwrite newer state.
Errors, caching, and limits
- Return RFC 9457 Problem Details as
application/problem+jsonfor every JSON error. - Give each error a stable
typeandcode; never require clients to parse prose. - Mark authenticated and user-specific responses
private, no-store. - Give public responses explicit cache and freshness policies.
- Define route-class rate limits and return structured
429responses with retry information. - Define upload, request-body, date-range, page-size, and response-size limits.
Exit gate: design records are accepted, shared middleware and schema types are tested, and one representative public read, private read, mutation, webhook, and job operation passes the complete contract.
Phase 3: Implement complete domain coverage
Implement in vertical waves. Every wave includes handlers, database queries, OpenAPI, manifest updates, tests, documentation, and first-party client migration. Do not accumulate undocumented handlers for a later specification pass.
Wave A: Core football and reference data
Complete competitions, seasons, phases, standings, fixtures, incidents, statistics, lineups, fixture unavailability, odds, odds movements, teams, players, player season statistics, positions, percentiles, heatmaps, shotmaps, referees, managers, venues, regions, countries, search, comparisons, rosters, and schedule/history views.
Resolve the remaining aggregate and projection routes into explicit resources rather than recreating legacy handler names.
Wave B: Betting, models, and analytics
Complete markets, prices, value bets, value-bets v4 behavior, matchup edges, betting models, model outputs, player and team prop boards, expected-stat models, ranked views, lineup projections, fair-price calculations, data coverage, and analytical visualizations.
Record the formula version, model version, input freshness, and pagination order wherever they affect reproducibility.
Wave C: User workflows, tracker, reports, and alerts
Complete the authenticated account surface: favorites, watchlists, tracker bankrolls and entries, tipster workflows, report and hide actions, suggestions, live foul/card alerts, and value-bet lineup alerts.
Use /me/* only for resources owned by the current user. Make ownership,
subscription requirements, idempotency, and notification delivery state
explicit.
Wave D: Administration and ingestion
Cover every dashboard and admin capability, including entity maintenance, data ingestion, recalculation, replay, job status, and operational inspection. Require superadmin or internal-service authorization per operation.
Replace synchronous command-style routes with auditable job resources where the work can outlive one request. Preserve the capability and result, not an accidental request lifetime.
Wave E: Billing, automation, and external integrations
Cover Stripe account and webhook flows, Model Context Protocol (MCP) tools, bots, chat and analysis workflows, tweets, and other provider callbacks.
Keep provider protocols at explicit integration boundaries. A webhook or MCP transport does not need to look like a normal collection, but it must still be versioned, authenticated, documented, observable, and represented in the capability manifest.
Wave F: Basketball, editorial, and delivery formats
Cover basketball reads, World Cup editorial data, previews, images, and XML sitemaps. Decide in a design record whether multi-sport resources share a canonical top-level model or use a sport namespace.
Document non-JSON response schemas and content types. Verify XML and binary delivery directly; do not force these routes into a JSON envelope.
Wave exit gate: all mapped capabilities in the wave reach
contract-verified and data-verified, and their migrated first-party callers
make no v1 requests for those capabilities.
Phase 4: Prove behavior continuously
Apply these checks to each operation as it lands:
Contract tests
- The router and focused OpenAPI document expose the same method and path.
- Every operation ID is unique and stable.
- Success and error media types, statuses, headers, and bodies match the contract.
- All JSON success schemas are closed and all JSON errors are Problem Details.
- Unknown parameters, invalid cursors, oversized bodies, and unsupported methods fail consistently.
Authorization tests
- Exercise anonymous, unauthenticated, owning-user, other-user, superadmin, service, and webhook personas where applicable.
- Verify row ownership and subscription gates at the database boundary.
- Verify private responses can never receive public cache headers.
Database and mutation tests
- Run representative reads against the configured real dataset and assert meaningful non-empty results for known records.
- Compare v1 and v2 semantics for the same capability, allowing only approved differences in shape, naming, pagination, security, or bug fixes.
- Test create, read-after-write, update, conflict, retry, delete, and cleanup for every mutable resource.
- Verify idempotency and webhook replay behavior.
- Count database queries and prove filters and pagination occur before expensive enrichment.
Performance and resilience tests
- Set latency, query-count, response-size, and memory budgets by route class.
- Test maximum page sizes, date ranges, and filter combinations.
- Verify cancellation, deadlines, pool exhaustion, rate limiting, and provider failures return bounded, structured responses.
Phase 5: Migrate first-party clients
Build generated or typed client modules from the focused v2 OpenAPI document. Migrate the web app and Expo app capability by capability after the matching wave passes its gates.
For each migration:
- replace the legacy call and response adapter;
- add client tests for pagination, authentication, errors, and empty states;
- run the screen against the real preview dataset;
- add a static boundary test that rejects the old route in migrated code; and
- mark the manifest entry
client-migratedonly after both first-party clients are accounted for.
Do not install a global “try v2, then call v1” mechanism. If a staged rollout needs comparison traffic, perform read-only shadow comparison on the server and never use the shadow response as an implicit fallback.
Phase 6: Complete documentation and rollout
- Keep separate v1 and v2 folders, explorers, references, and OpenAPI files.
- Add authentication, pagination, errors, idempotency, webhooks, jobs, and migration guides under v2.
- Show deprecation metadata only after a replacement is implemented and its consumers are known.
- Publish operation-level dashboards for request count, p50/p95/p99 latency, error type, caller, and version.
- Use v1 traffic telemetry to identify external callers. Do not retire v1 on a calendar guess.
- Roll out by capability group, with a rollback that switches first-party callers back through an explicit release change rather than a runtime hidden fallback.
V1 retirement is a separate project. Full v2 parity makes retirement possible; it does not authorize breaking unknown external clients.
Definition of done
API v2 is a complete successor only when all statements below are true:
- The capability manifest contains every one of the 299 v1 operations and all three legacy-only capabilities, with zero unmapped entries.
- Every mapped v2 operation is mounted, present in the focused OpenAPI document, documented, and executable.
- Every operation has closed success schemas, explicit security, and complete Problem Details errors where the response is JSON.
- Every collection is bounded, deterministically ordered, and paginated or explicitly justified as finite.
- Every mutation passes success, authorization, conflict, retry, idempotency, and cleanup tests as applicable.
- Public preview reads the configured real dataset without a hiding or sample data mechanism.
- Web and Expo contain no legacy calls for migrated capabilities and no silent v1 fallback mechanism.
- Performance and resilience budgets pass for every route class.
- Runtime telemetry shows which external callers still use v1.
- The legacy v1 surface remains unchanged until a separately approved deprecation and retirement process completes.