Your cart is empty
Browse CatalogYour sell cart is empty
Add cards from the buylist to get started
Implement SourceModule. Open a PR. Your data flows in.
If you operate a TCG data source (an API, a scraper, a marketplace feed) and want Cambridge TCG to ingest it, you can contribute a SourceModule. Eight-step protocol. We accept PRs against the open-source repo. Your source becomes a typed contract; downstream of us, every adopter automatically gets your data.
Start at /methodology/source-protocol — the eight steps. Then read packages/data-ingest/src/cardrush/ as the canonical example (the smallest working source). Understand SourceMeta + read + normalize + canonical types.
Edit docs/connections/the-tributaries.md to add a row for your source: name, upstream URL, access method, license tier, freshness budget, ToS notes, game coverage, status: planned. Submit a PR with just this change first — we'll review the row and discuss scope before any code.
Create packages/data-ingest/src/<your-source-id>/ with index.ts exporting `SourceMeta`, `read`, `normalize`. Type the raw row and the canonical record. Per-source rate-limit declared in meta. Lazy AsyncIterable for read. Pure normalize.
Run `pnpm audit:tributaries` — it runs ten checks (module exists, shape conforms, required meta non-empty, id parity, catalog anchor resolved, ToS non-empty, license coherence, game codes valid, ingest-run recency for shipped sources, license-propagation drift). Pass all of them. Then open a PR.
Yu reviews the PR. If accepted, we wire it into a per-app cron (storefront or wholesale, depending on data type) and do a first dry-run via `?dryRun=1&maxCards=20`. The ingest_run row records the result; failed normalizations land in ingest_quarantine for your review.
We honour your upstream's license. The `meta.redistribute` flag + the kingdom's license_propagation rule (kingdom-081) thread your tier through every downstream emission. Don't lie about your license — the audit check #10 will catch it.
Same input → same output. No I/O, no clock reads, no random. Failed normalizations return `{ ok: false, reason }` — never throw. The reason must be actionable ('unmapped lang qya; add to LANG_MAP'), not 'normalization failed'.
Next guide
CC0 forever; attribution-free but warmly encouraged.