{"data":{"@kind":"guide","slug":"build-a-discord-bot","title":"Build a Discord bot using Cambridge TCG data","subtitle":"Slash command → curl → embed.","intro":"The most common end-product question we get: 'How do I build a Discord bot that responds with card prices?' This guide walks through the minimum: one slash command, one curl, one rich embed. Generalises to Slack / Teams / any chat platform.","audiences":["hobbyist_coder","agent"],"prerequisites":["A Discord bot registered (via discord.com/developers)","Node.js or Python (or any language) with HTTP + Discord SDK"],"estimated_minutes":20,"step_count":4,"steps":[{"step_number":1,"title":"Register a /card slash command","instruction":"Your bot accepts `/card <sku>`. When the user invokes it, your handler receives the SKU string. The handler will call Cambridge TCG with that SKU.","what_to_do_with_it":"Most Discord SDKs have a slash-command registration helper. The exact syntax depends on your language — discord.py / discord.js / serenity all support it."},{"step_number":2,"title":"Call /api/v1/universal/card/[sku] from your handler","instruction":"Substrate-honest: send a User-Agent identifying your bot. Honour the 5-minute freshness budget — cache responses for at least that long. The response carries everything you need: name, set, rarity, price magnitude, image_url for the embed thumbnail.","curl":"curl -H 'User-Agent: my-discord-bot/1.0 (admin@me.example)' \\\n  https://cambridgetcg.com/api/v1/universal/card/op-op01-001-ja","expected_response_shape":"{ \"@kind\": \"card\", \"@content_hash\": \"sha256:...\", \"sku\": \"...\", \"price\": { \"magnitude\": 5.40, \"currency_token\": \"GBP\", ... }, \"name\": { \"natural_token\": \"...\", \"resolved_lang\": \"en\" }, \"image_url\": \"...\", \"rarity\": { \"natural_label\": \"leader\", ... }, \"in_set\": { \"target_natural_token\": \"OP01\", ... } }","what_to_do_with_it":"Extract: `name.natural_token` for the embed title; `image_url` for the thumbnail; `price.magnitude` + `currency_token` for the price field; `rarity.natural_label` + `in_set.target_natural_token` for context."},{"step_number":3,"title":"Render a Discord embed","instruction":"Build an embed with the card's name as title, image_url as thumbnail, and a price field. Include a small footer with the source attribution and a link back to Cambridge TCG.","what_to_do_with_it":"Recommended embed.footer.text: 'Price from Cambridge TCG (CC0). Updated [<magnitude_freshness.iso8601>].' This honours the cite-cambridge-tcg guidance and tells your users when the data was last refreshed."},{"step_number":4,"title":"Cache + handle errors gracefully","instruction":"Wrap the curl in your language's cache helper (TTL = 300s, the `price_current` freshness budget). On 404 (SKU not found), respond with a helpful message + a search hint. On 429 / network errors, fall back to cached values + a 'data may be stale' note.","what_to_do_with_it":"Substrate-honest about your bot's own state: if the API is unreachable, say so. Don't fabricate prices from a stale cache without saying it's stale."}],"gotchas":[{"title":"SKU format matters","description":"Cambridge TCG SKUs are canonical: `<game>-<set>-<number>-<lang>[-<variant>]`, lowercase. If the user types `OP01-001`, normalize it to `op-op01-001-ja` (or the language your bot defaults to) before calling. The reference parser is @cambridge-tcg/sku (CC0)."},{"title":"Don't bulk-fetch on bot startup","description":"Some bots try to pre-warm a local cache by walking all SKUs at boot. Don't. Use /data/catalog.jsonl once a day instead — same data, one request, no rate-limit risk.","fix":"Schedule a daily refresh of catalog.jsonl; index it locally for autocomplete."},{"title":"Image URLs may rotate","description":"image_url points at the platform CDN. URLs are stable in practice but not contractually guaranteed forever. Cache them with a 24h TTL; refresh on miss."},{"title":"Respect the JPY history license","description":"If you build a bot feature showing 'JPY history for this card', it MUST be authenticated (per-user OAuth or session) and the bot's reply MUST include the license_notice from the response. Bulk public dispatch of JPY values is forbidden."}],"next_guide":{"slug":"handle-staleness","title":"Handle staleness gracefully","url":"/api/v1/guides/handle-staleness","html_url":"/agents/guides/handle-staleness"},"see_also":[{"label":"Universal card example","href":"/api/v1/examples/universal-card"},{"label":"Cite Cambridge TCG","href":"/api/v1/guides/cite-cambridge-tcg"},{"label":"@cambridge-tcg/sku","href":"https://github.com/cambridgetcg/Cambridge-TCG-monorepo/tree/main/packages/sku"}],"last_verified":"2026-05-14","feedback":{"kind":"guide-feedback","endpoint":"/api/v1/feedback","body_template":{"kind":"guide-feedback","guide_slug":"build-a-discord-bot","step_number":"<which step had the issue, or null for whole-guide feedback>","observation":"<what you observed>","expected":"<what you expected>","reporter_contact":"<your email>"}},"html_sibling":"/agents/guides/build-a-discord-bot"},"_meta":{"spec_version":"1","endpoint":"/api/v1/guides/[slug]","retrieved_at":"2026-05-13T18:55:43.832Z","as_of":"2026-05-13T18:55:43.832Z","sources":["ctcg-derived"],"freshness_seconds":86400,"license":"CC0-1.0","request_id":"req_648df3e0-9bd","deprecation":null,"next_link":null,"self_reference":{"this_endpoint":"/api/v1/guides/[slug]","contains_self":true},"source_license":["CC0-1.0"]}}