---
schema: "https://creativeintelligencecompany.com/schemas/agents-contract.v1.schema.json"
version: "1.0.0"
name: "Voet Agents Contract"
description: "How agents and other products read voet: the frozen read contract, its audiences, page caps, and the honesty envelope every reference read carries."
readContract: "@repo/backend/contract v1"
publicFunctions: 253
contractFunctions: 66
unreviewedPublicFunctions: 187
anonymousReferences: 48
deprecatedReferences: 6
designContract: "https://www.voet.app/design.md"
soul: "docs/SOUL.md"
---

# Voet Agents Contract

## What voet is

Voet is a public football reference instrument on a never-lie data floor, with Copa as its only agent surface. This document is the read contract for machines: every Convex reference the instrument itself consumes, frozen as v1, with the audience that may call it, the most it returns, and the honesty envelope that travels with every reference read. If a reference is not listed here, it is not a contract, even if it is currently public.

## How to read voet

- Read through the v1 contract references below, never through undocumented functions. Additions require a manifest review; deprecated references keep resolving until their removal marker and are listed with a replacement.
- Every reference read marked `meta: yes` returns `{ data, meta }`. `meta` is `{ asOf, season?, competitionCode?, coverage, truncated, scan? }` where `coverage` is one of `complete`, `partial`, `absent`. Treat `absent` as voet not holding the fact, never as the fact being false. Treat `partial` or `truncated: true` as a bounded view and page rather than infer.
- Anonymous references are readable without an identity and are rate limited per bucket and caller; authenticated references require a voet identity and exist only for personalization (follows, notifications, settings, Copa).
- Season-scoped reads take an explicit campaign; voet never infers a season from the newest row it holds.

## Honesty policy

Voet never fabricates. A missing scorer, minute, statistic or table is served as absent with an honest label. Terminal results are immutable; corrections carry their own receipt. In v1 the depth is what Transfermarkt exposes plus metrics voet derives from its own event stream; expected goals and shot maps are absent.

## Audiences and buckets

| audience | who | bucket |
|---|---|---|
| anonymous | anyone on the open web | public-read, public-search |
| authenticated | a signed-in voet identity | user-read, user-write, agent |
| operator | voet operators only | not published here |

## Reference index

### agents.chat

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `agents.chat.complete` | action | authenticated | single | no | agent | |
| `agents.chat.send` | action | authenticated | single | no | agent | |

### chat.streaming

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `chat.streaming.listMessages` | query | authenticated | 200 | no | user-read | |

### copa.queries

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `copa.queries.findByMatchTeams` | query | anonymous | 20 | yes | public-search | |
| `copa.queries.findByPlayerName` | query | anonymous | 20 | yes | public-search | |
| `copa.queries.findByTeamName` | query | anonymous | 20 | yes | public-search | |
| `copa.queries.getByDay` | query | anonymous | single | yes | public-read | |
| `copa.queries.getEnrichment` | query | anonymous | single | yes | public-read | |
| `copa.queries.listRecent` | query | anonymous | 50 | yes | public-read | |

### knowledge.actions

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `knowledge.actions.search` | action | authenticated | 20 | no | agent | |

### mutations.presence

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `mutations.presence.touch` | mutation | authenticated | single | no | user-write | never mounts signed-out; skipped on the prod host from dev (shouldSkipConvexPresenceWrites) |

### mutations.settings

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `mutations.settings.updateMy` | mutation | authenticated | single | no | user-write | |

### mutations.userNotifications

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `mutations.userNotifications.dismiss` | mutation | authenticated | single | no | user-write | |
| `mutations.userNotifications.markRead` | mutation | authenticated | single | no | user-write | |

### profiles.mutations

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `profiles.mutations.completeOnboarding` | mutation | authenticated | single | no | user-write | |
| `profiles.mutations.removeFavoriteTeam` | mutation | authenticated | single | no | user-write | |

### profiles.queries

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `profiles.queries.getMyFavorites` | query | authenticated | 100 | no | user-read | |
| `profiles.queries.getMyProfile` | query | authenticated | single | no | user-read | |

### queries.chat

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.chat.getMyThread` | query | authenticated | single | no | user-read | |
| `queries.chat.listMessages` | query | authenticated | 200 | no | user-read | |

### queries.competitions

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.competitions.atlas` | query | anonymous | 250 | yes | public-read | paginated snapshot rows (DMG-6); the drilldown stays operator-only |
| `queries.competitions.bracket` | query | anonymous | 2000 | yes | public-read | stage -> round -> tie for any competition with a knockout (DMG-3) |
| `queries.competitions.fixtureShells` | query | anonymous | single | yes | public-read | deprecated 2026-09-02, removed after M8 (flat World Cup projection; superseded by competitions.bracket (DMG-3)) |
| `queries.competitions.getByCode` | query | anonymous | single | yes | public-read | |
| `queries.competitions.getById` | query | anonymous | single | yes | public-read | |
| `queries.competitions.hasKnockoutStage` | query | anonymous | single | yes | public-read | |
| `queries.competitions.list` | query | anonymous | 250 | yes | public-read | |
| `queries.competitions.listTeams` | query | anonymous | 250 | yes | public-read | |
| `queries.competitions.readiness` | query | anonymous | single | yes | public-read | one indexed get over competitionSnapshots (DMG-6); absent is served |
| `queries.competitions.worldCupActiveWindow` | query | anonymous | single | yes | public-read | deprecated 2026-09-02, removed after M2 (generalizes to competitions.activeWindow(code)) |

### queries.leagues

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.leagues.currentStatus` | query | anonymous | single | yes | public-read | deprecated 2026-09-02, removed after M8 (alias namespace; use queries.competitions.* (floor architecture D5)) |
| `queries.leagues.getByCode` | query | anonymous | single | yes | public-read | deprecated 2026-09-02, removed after M8 (alias namespace; use queries.competitions.* (floor architecture D5)) |
| `queries.leagues.getById` | query | anonymous | single | yes | public-read | deprecated 2026-09-02, removed after M8 (alias namespace; use queries.competitions.* (floor architecture D5)) |
| `queries.leagues.list` | query | anonymous | 250 | yes | public-read | deprecated 2026-09-02, removed after M8 (alias namespace; use queries.competitions.* (floor architecture D5)) |

### queries.matchesPublic

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.matchesPublic.byUtcDayRange` | query | anonymous | 500 | yes | public-read | |
| `queries.matchesPublic.getById` | query | anonymous | single | yes | public-read | |
| `queries.matchesPublic.list` | query | anonymous | 100 | yes | public-read | |
| `queries.matchesPublic.live` | query | anonymous | 100 | yes | public-read | |
| `queries.matchesPublic.recent` | query | anonymous | 100 | yes | public-read | |
| `queries.matchesPublic.upcoming` | query | anonymous | 100 | yes | public-read | |

### queries.matchNotes

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.matchNotes.finishedWorldCup` | query | anonymous | 20 | yes | public-read | |

### queries.news

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.news.byPlayer` | query | anonymous | 100 | yes | public-read | |
| `queries.news.byTeam` | query | anonymous | 100 | yes | public-read | |
| `queries.news.competitionCoverageStatus` | query | anonymous | single | yes | public-read | |
| `queries.news.getById` | query | anonymous | single | yes | public-read | |
| `queries.news.list` | query | anonymous | 100 | yes | public-read | |
| `queries.news.neighbors` | query | anonymous | 2 | yes | public-read | |

### queries.players

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.players.competitionCoverage` | query | anonymous | 250 | yes | public-read | |
| `queries.players.findByName` | query | anonymous | 20 | yes | public-search | |
| `queries.players.getById` | query | anonymous | single | yes | public-read | |
| `queries.players.search` | query | anonymous | 50 | yes | public-search | |
| `queries.players.searchPage` | query | anonymous | 100 | yes | public-search | |
| `queries.players.topRatedPage` | query | anonymous | 100 | yes | public-read | |

### queries.playerSeasonStats

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.playerSeasonStats.getSeasonStats` | query | anonymous | 250 | yes | public-read | |

### queries.preferenceGraph

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.preferenceGraph.getConsumerContext` | query | authenticated | single | no | user-read | |

### queries.settings

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.settings.getMy` | query | authenticated | single | no | user-read | |

### queries.standings

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.standings.list` | query | anonymous | 250 | yes | public-read | |

### queries.teams

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.teams.findByName` | query | anonymous | 20 | yes | public-search | |
| `queries.teams.getById` | query | anonymous | single | yes | public-read | |
| `queries.teams.list` | query | anonymous | 250 | yes | public-read | |

### queries.transfers

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.transfers.competitionCoverageStatus` | query | anonymous | single | yes | public-read | |
| `queries.transfers.list` | query | anonymous | 100 | yes | public-read | |
| `queries.transfers.listPaginated` | query | anonymous | 100 | yes | public-read | |

### queries.userNotifications

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `queries.userNotifications.countsMine` | query | authenticated | single | no | user-read | |
| `queries.userNotifications.listMine` | query | authenticated | 100 | no | user-read | |

### workflows.entityNarratives

| reference | kind | audience | page cap | meta | bucket | notes |
|---|---|---|---|---|---|---|
| `workflows.entityNarratives.getLatestEntityNarrative` | query | anonymous | single | yes | public-read | |

## Surface counts

253 public Convex functions exist in the deployment; 66 are contract references listed above; 187 are unreviewed and may only decrease. The ratchets that enforce this live in the repository proofs.
