pokemontcgapi

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2026 License: MIT Imports: 17 Imported by: 0

README

pokemontcgapi Go SDK

Go Reference license CI

Go client for the Pokémon TCG API at pokemontcgapi.com: cards, sets, illustrators, the reference vocabularies and photo recognition, across three print lines, international, Japanese and Simplified Chinese, with card names in eight locales, images, and prices that state their source, basis, grade and sample size. The current counts are live at /v1/status.

Every data route has a method: cards, sets, series, artists, sealed products, the dedicated price routes (current, batch, history, stats, movers, sources), the /v1/changes feed, the reference vocabularies and photo recognition. Account and billing routes (/v1/me, keys, checkout) are not wrapped: they belong to the dashboard.

Zero dependencies. Standard library only (net/http, encoding/json, mime/multipart), Go 1.23 or newer. Every method takes a context.Context.

Unofficial. Not produced, endorsed, supported by or affiliated with Nintendo, Creatures Inc., GAME FREAK inc. or The Pokémon Company International. Pokémon and all related marks are trademarks of their respective owners.

Get a key

Generate the Idempotency-Key once per signup and keep it with the request body:

IDEM=$(uuidgen)
curl -s -X POST "https://api.pokemontcgapi.com/v1/accounts/free" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEM" \
  -d '{"email":"you@example.com"}'

Lost the response? Repeat the exact same request (same Idempotency-Key, same body byte for byte, same network: same public IPv4 or the same IPv6 /64) within 24 hours and the response comes back, if stored, secret included; it is the original response, so a key rotated or revoked since then is not revived. A new Idempotency-Key for the same email returns 409 ACCOUNT_EXISTS; the same key with a different body returns 409 IDEMPOTENCY_CONFLICT.

We store only a hash of the key; the signup response is kept for 24 hours so the same request can be replayed. Save data.key.secret now.

If replay is unavailable, sign in and rotate the key, or use /v1/accounts/recover with an already verified email to get a new secret.

The key comes back in data.key.secret. Confirming the address we email raises the trial from 80 to 800 credits, and the trial ends 30 days after signup. Paid plans start at 29 EUR a month: pricing.

Install

go get github.com/pokemontcgapi/sdk-go

Use

import "github.com/pokemontcgapi/sdk-go"

client := pokemontcgapi.New() // reads PTCG_API_KEY; or pokemontcgapi.WithAPIKey("...")

card, err := client.Cards.Get(ctx, "base1-4", &pokemontcgapi.CardGetParams{Include: []string{"prices"}})
if err != nil {
    return err
}
fmt.Println(card.ID, card.Name, *card.IndexEUR)
// bs-4 Charizard 523.76   ← the index on 16 September 2026; it moves, yours will differ

base1-4 and bs-4 both resolve: the id is the printed coordinate — set code, dash, collector number — and the alternate legacy id resolves on the same route, so a catalogue you already have does not start with a matching problem.

Pagination that you never have to think about

Every list method returns a *Page, whose All(ctx) iterator follows links.next for you. For an initial import of all cards, use the flat card list so pages fill across set boundaries:

page, err := client.Cards.Search(ctx, &pokemontcgapi.CardListParams{Limit: 250, OrderBy: "id"})
if err != nil {
    return err
}
for card, err := range page.All(ctx) {
    if err != nil {
        return err
    }
    fmt.Println(card.ID, card.Name, card.SetCode)
}

For Japanese cards, add Q: "set.region:JP"; for Simplified Chinese cards, use Q: "set.region:CN". Add Include: []string{"translations"} when you need localized names; this keeps the plain catalogue cost. Include of index and of prices have different credit costs. Lang selects a name translation, not a print region.

Use client.Sets.List(ctx, &pokemontcgapi.SetListParams{Region: "JP", Limit: 250}) to browse set metadata and client.Sets.Cards(ctx, "obf", &pokemontcgapi.CardListParams{Limit: 250}) when you need one particular set. For all cards, the flat list uses fewer requests than a card loop for every set. The quickstart includes dated measurements, and the migration guide explains capturing the change feed watermark before an import and keeping the replica current.

The cursor carries a signature of the sort order, so it must never be reconstructed by hand — the SDK follows the URL the API returned, which is the failure mode this avoids. Collect(ctx, max) requires an explicit ceiling, because the catalogue is large enough that an unbounded materialisation is a mistake rather than a choice. Next(ctx) returns nil, nil after the last page.

One call for a hundred cards
result, err := client.Cards.Batch(ctx, []string{"sv8-116", "sv8-100", "inventato-xyz"}, &pokemontcgapi.CardGetParams{
    Include: []string{"index"}, // index_eur on list and batch rows is opt-in: 1 credit per 50 cards
    Select:  []string{"id", "name", "index_eur"},
})
// result.Data, result.Requested, result.Found, result.Missing

Missing is nil when every id resolves. Otherwise each unresolved id appears once as MissingCard{ID, SuggestedID}. A suggestion is included only for an existing historical candidate in a different canonical set. In this example only sv8-100 is returned; sv8-116 suggests ssp-116, and inventato-xyz has no suggestion. A canonical set prefix binds the lookup to that set; base1-4 still resolves to bs-4 because base1 is only a historical alias.

Data contains distinct cards. Repeated ids count towards Requested and credits, but do not repeat rows in Data or Missing. Two valid aliases for one card can make Found smaller than Requested with no missing ids. Missing entries ignore case and retain the first spelling and request order after whitespace trimming. Withheld remains an optional top-level field. More than 100 ids returns an error wrapping ErrTooManyIDs before any request is made.

Japanese, and the other seven locales
page, err := client.Sets.Cards(ctx, "sv8", &pokemontcgapi.CardListParams{Lang: "ja", Limit: 1})
fmt.Println(page.Data[0].Name) // タマタマ

Lang replaces the Name field itself and falls back to English where a translation is missing. Locales, with the rows each one actually has on 16 September 2026: en 57,421, fr 42,858, de 42,604, ja 27,230, it 21,644, es 21,003, pt 13,822, zh 3,492. A thin locale answers mostly in English, because the fallback is per card and not per request.

Conditional requests are free
client := pokemontcgapi.New(pokemontcgapi.WithETagCache())

Every collection carries an ETag. We compute it strong, from the body; the edge rewrites it weak with an encoding suffix when it compresses, so what you receive looks like W/"…-gzip" and you send back exactly that. With the cache on, the client stores it and replays a 304 without a body, and a 304 consumes no quota. A mirror that re-syncs often pays only for what changed.

A photo instead of an id

Included from the Growth plan up. On a trial or a Developer key the call answers 403 PLAN_REQUIRED with details.min_plan, before reading the image and without spending credits.

resp, err := client.Vision.Identify(ctx, file, &pokemontcgapi.IdentifyOptions{Set: "sv3"})
if err != nil {
    return err
}

// Read Decision before ID. Always.
switch resp.Data.Decision {
case pokemontcgapi.VisionMatch:
    // One candidate, close, and clear of the next.
    add(*resp.Data.ID)
case pokemontcgapi.VisionAmbiguous:
    // Two printings share this illustration. resp.Data.ID is nil on purpose.
    showPicker(resp.Data.Candidates)
case pokemontcgapi.VisionNoMatch:
    askForABetterPhoto()
}

Reprints and regional twins share their artwork, so artwork alone cannot name a printing — not here and not anywhere. The endpoint returns candidates with a Distance (0–512, lower is closer; real matches land well under 150) and refuses to pick when two are within a few bits of each other. Passing Set or Region when your workflow knows them is what resolves the tie.

It costs 25 credits a call against 1 for a lookup: it is the whole image index answering, not a row being read. Do not put it in a loop.

Errors you can branch on
_, err := client.Cards.Get(ctx, "nope-1", nil)

var notFound *pokemontcgapi.NotFoundError
var limited *pokemontcgapi.RateLimitedError
var quota *pokemontcgapi.QuotaExceededError
switch {
case errors.As(err, &notFound):
    // ...
case errors.As(err, &limited):
    // limited.RetryAfter, when limited.HasRetryAfter
case errors.As(err, &quota):
    // retrying will never help
}

Every error carries Code, Status, Details and RequestID — quote the request id in a support message, it is the only thing that can be looked up. Retries use exponential backoff with full jitter on 429, 5xx and network failures, honour Retry-After, and never retry a quota exhaustion. Every typed error unwraps to *pokemontcgapi.APIError, so one errors.As on that type catches them all; PlanRequiredError and TrialExpiredError also unwrap to *PermissionDeniedError. Network failures are *ConnectionError, and a per-attempt timeout is a *TimeoutError that unwraps to it.

Commercial refusals include details.next_step, exposed as the typed err.NextStep(). If err.NextStep() is not nil, show err.Handoff() and its URL to the account owner verbatim and do not retry. err.ActionURL() returns the URL for any action: checkout_url for subscribe, manage_url for upgrade, verify_url for email verification, or contact_url for sales and support. Show it alongside err.Handoff(). Upgrades point to the account page, where the owner opens the billing portal to change plan. err.CheckoutURL() remains a shortcut for subscribe only.

var apiErr *pokemontcgapi.APIError
if errors.As(err, &apiErr) && apiErr.NextStep() != nil {
    showToUser(apiErr.Handoff())
}

What this API does not have

Stated up front so you find out here rather than three days into an integration:

  • No Korean cards. Zero KR sets, zero ko translations. Both are modelled in the schema and carry no data.
  • Card game text is English, and uneven. Attacks, Abilities, Weaknesses, Resistances, Subtypes, RetreatCost, Rules and FlavorText carry rows since 3 September 2026, on the 20,725 Western printings. Measured on 16 September 2026 against 57,450 cards: attacks on 29.9% of the whole catalogue and 82.9% of the Western part, subtypes 35.0%, abilities 7.0%. Japanese and Chinese printings carry none. The types in this package keep them nullable (nil slices and pointers), so the part that is absent has to be handled.
  • No format legalities. The card object has no legalities field and Include rejects the value with a 400. If you are building a deck checker, this is not the data source you need.

What it does have: the printing itself — set, number, rarity, region, release date, illustrator, image, marketplace ids, names in eight locales — and prices.

Prices

card, err := client.Cards.Get(ctx, "base1-4", &pokemontcgapi.CardGetParams{Include: []string{"prices"}})
for _, price := range card.Prices {
    fmt.Println(price.Source, price.Basis, price.Amount, price.Currency, price.AsOf, price.SampleN)
}

The dedicated price routes have their own methods, and they are the ones to use when prices are the point of the call:

prices, err := client.Prices.Card(ctx, "base1-4", nil)                                        // index + quotes, 2 credits
many, err := client.Prices.Current(ctx, []string{"base1-4", "sv3-125"}, nil)                    // up to 50 ids, 4 credits per 25
history, err := client.Prices.History(ctx, "base1-4", &pokemontcgapi.HistoryParams{Bucket: "week"}) // 5 credits
stats, err := client.Prices.Stats(ctx, "base1-4", &pokemontcgapi.StatsParams{Window: "30d"})       // 2 credits
movers, err := client.Prices.Movers(ctx, &pokemontcgapi.MoversParams{Window: "7d", Direction: "gainers"}) // Growth and up
box, err := client.Sealed.Prices(ctx, "evolving-skies-booster-box", nil)

History is bounded by your plan (7 days on the trial, 30 on Developer, everything from Growth): a wider window returns *UpgradeRequiredError, whose PermittedWindow() says what you may ask for. Movers below Growth returns *PlanRequiredError, and a trial past its 30 days returns *TrialExpiredError on every route that costs credits. Both unwrap to *PermissionDeniedError.

There is no printing filter on Include: []string{"prices"}: first edition, holofoil and graded rows come back together, so read Printing, Condition and Grading per row. Basis separates GUIDE (published upstream) from DERIVED (computed by us). PTCG_INDEX is a composite index in EUR carrying SampleN, and the same number sits on the card row as IndexEUR wherever we have enough observations to compute one: 51,636 cards of 57,450 on 16 September 2026, so treat it as nullable. On a list or batch it comes with Include: []string{"index"} (1 credit per 50 rows), so a list still has a comparable number without a second request per card.

What your plan withholds is named rather than hidden, but it is named in three different places, so read the one that matches the call you made:

call where the exclusions are
client.Prices.Card(ctx, id, nil) Meta.Withheld
client.Cards.Get(ctx, id, &CardGetParams{Include: []string{"prices"}}) the X-Plan-Withheld header: client.LastResponse().PlanWithheld
client.Cards.Batch(ctx, ids, …) a top-level Withheld field

The values are graded and non_english_locales: a trial key gets both, Developer keeps graded, and from Growth up nothing is withheld, in which case the field is absent rather than an empty array. Read it before concluding that a card has no graded observations: it may be your plan, not the catalogue. Prices also carry their own Locale, and a card read with Include: []string{"prices"} returns every locale your plan allows, so the currency does not tell you the language.

Credits and quota

Every response says what it cost. The SDK keeps the headers of the last one, and hands each one to WithOnResponse if you want a running total:

var spent int
client := pokemontcgapi.New(pokemontcgapi.WithOnResponse(func(r pokemontcgapi.ResponseInfo) {
    if r.CreditsCost != nil {
        spent += *r.CreditsCost
    }
}))

_, err := client.Cards.Search(ctx, &pokemontcgapi.CardListParams{Q: "name:charizard", Include: []string{"index"}})
info := client.LastResponse()
fmt.Println(*info.CreditsCost, *info.QuotaRemaining)

The trial is 800 credits, once, for 30 days, with at most 400 spent in a day; TrialExpiresAt on the same struct says when it ends.

The change feed

since := store.GetInt64("ptcg_since")
for {
    page, err := client.Changes(ctx, &pokemontcgapi.ChangesParams{Since: since, Limit: 500})
    if err != nil {
        return err
    }
    for _, change := range page.Data {
        apply(change) // Kind, EntityID, Op, Version
    }
    since = page.Meta.NextSince
    store.SetInt64("ptcg_since", since)
    if !page.Meta.HasMore {
        break
    }
}

Also available

Build from source

go vet ./...
go test -race ./...

Go >= 1.23, no dependencies. The tests run offline against the recorded responses in testdata/fixtures; go test -tags live ./... with PTCG_API_KEY set runs a short smoke test against the real API (about four credits). CI enforces gofmt, go vet, staticcheck and the tests on Go 1.23 to 1.25, and that a fresh module can import the package.

This package is developed inside the private monorepo that runs pokemontcgapi.com and mirrored here on each release, so a merged pull request travels back by hand rather than by merge button. That is not a reason to send patches elsewhere — open the issue or the PR here, it is the address that gets read.

README synced from sdk-typescript README as of commit 86a725a.

Licence

MIT. Data served by the API carries per-source redistribution terms — see https://pokemontcgapi.com/legal/attribution.

Documentation

Overview

Package pokemontcgapi is a thin client for the Pokémon TCG API at https://pokemontcgapi.com: cards, sets, artists, series, sealed products, prices with stated provenance, the change feed, the reference vocabularies and photo recognition.

It has no dependencies outside the standard library. Every method takes a context.Context; list methods return a *Page that follows the API's own links.next URL, so pagination never has to be rebuilt by hand.

Index

Examples

Constants

View Source
const (
	LocaleEN = "en"
	LocaleFR = "fr"
	LocaleDE = "de"
	LocaleJA = "ja"
	LocaleIT = "it"
	LocaleES = "es"
	LocalePT = "pt"
	LocaleZH = "zh"
)

Locale values with rows in the table (measured 2026-09-16): en, fr, de, ja, it, es, pt, zh. The API accepts ko too, but it has zero rows.

View Source
const (
	RegionWest = "WEST"
	RegionJP   = "JP"
	RegionCN   = "CN"
	RegionKR   = "KR"
)

Print regions. KR exists in the server schema but matches no set: filtering on it returns an empty page, not an error.

View Source
const (
	SourceTCGplayer     = "TCGPLAYER"
	SourcePriceCharting = "PRICECHARTING"
	SourceCardmarket    = "CARDMARKET"
	SourceCardTrader    = "CARDTRADER"
	SourceEbay          = "EBAY"
	SourcePTCGIndex     = "PTCG_INDEX"
	SourceCommunity     = "COMMUNITY"
)

Price sources.

View Source
const (
	BasisGuide   = "GUIDE"
	BasisDerived = "DERIVED"
	BasisSold    = "SOLD"
	BasisAsking  = "ASKING"
)

Price bases. DERIVED is computed by us; GUIDE is a figure published upstream.

View Source
const (
	IncludeIndex        = "index"
	IncludePrices       = "prices"
	IncludeTranslations = "translations"
	IncludeImages       = "images"
	IncludeSet          = "set"
	IncludeArtist       = "artist"
)

Values accepted by CardGetParams.Include and CardListParams.Include.

View Source
const (
	VisionMatch     = "match"
	VisionAmbiguous = "ambiguous"
	VisionNoMatch   = "no_match"
)

Vision decisions. Ambiguous is not a failure: it is the normal case on reprints, where two printings share the illustration and cannot be told apart from the image alone.

View Source
const Version = "0.1.0"

Version is the SDK version. It travels in the User-Agent header of every request and must match the git tag of a release.

Variables

View Source
var ErrTooManyIDs = errors.New("pokemontcgapi: too many ids")

ErrTooManyIDs is returned, wrapped with the count, when a batch method receives more ids than the API accepts in one request.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	// Code is the stable code of the error taxonomy, e.g. CARD_NOT_FOUND.
	Code    string
	Status  int
	Message string
	// RequestID is set whenever the response came from the API. Quote it in a
	// support message: it is the only thing that can be looked up in the logs.
	RequestID string
	Details   map[string]any
}

APIError is the error envelope the API returns on every 4xx and 5xx. The typed wrappers in this file (NotFoundError, RateLimitedError, ...) embed it, so errors.As works both on the wrapper and on *APIError itself.

QuotaExceededError is separate from RateLimitedError on purpose: both are 429, but a rate limit passes by waiting and an exhausted quota never does. The client retries the first and never the second.

func (*APIError) ActionURL

func (e *APIError) ActionURL() string

ActionURL is the URL for the next step's action: checkout for subscribe, the account page for upgrade, the verification link for verify_email, the contact address for sales and support. Empty when there is none.

func (*APIError) CheckoutURL

func (e *APIError) CheckoutURL() string

CheckoutURL is the subscribe checkout, when the next step carries one.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Handoff

func (e *APIError) Handoff() string

Handoff is the sentence to show the account owner verbatim.

func (*APIError) NextStep

func (e *APIError) NextStep() *NextStep

NextStep returns details.next_step when it is well formed, nil otherwise. Malformed blocks are ignored rather than half-read.

type Artist

type Artist struct {
	Slug      string `json:"slug"`
	Name      string `json:"name"`
	CardCount int    `json:"card_count"`
	// Links is set only by Artists.Get: the ready-made search for the artist's cards.
	Links *struct {
		Cards string `json:"cards,omitempty"`
	} `json:"links,omitempty"`
}

Artist is an illustrator.

type ArtistsService

type ArtistsService struct {
	// contains filtered or unexported fields
}

ArtistsService wraps /v1/artists.

func (*ArtistsService) Get

func (s *ArtistsService) Get(ctx context.Context, slug string) (*Artist, error)

Get reads one illustrator by slug.

func (*ArtistsService) List

func (s *ArtistsService) List(ctx context.Context, p *ListParams) (*Page[Artist], error)

List returns the illustrators. Select is ignored on this route.

type AuthenticationError

type AuthenticationError struct{ *APIError }

AuthenticationError is a 401: the key is missing, malformed or revoked.

func (*AuthenticationError) Unwrap

func (e *AuthenticationError) Unwrap() error

type BatchResult

type BatchResult[T any] struct {
	Data      []T `json:"data"`
	Requested int `json:"requested"`
	Found     int `json:"found"`
	// Missing is absent when every requested id resolves; repeated ids appear once.
	Missing  []MissingCard `json:"missing,omitempty"`
	Withheld []string      `json:"withheld,omitempty"`
}

BatchResult is the answer of the batch routes.

type Card

type Card struct {
	ID string `json:"id"`
	// LegacyID is an alternate identifier in the same shape. It resolves on the same route.
	LegacyID       *string  `json:"legacy_id"`
	Name           string   `json:"name"`
	Number         string   `json:"number"`
	NumberSort     *int     `json:"number_sort"`
	Supertype      *string  `json:"supertype"`
	HP             *int     `json:"hp"`
	Level          *string  `json:"level"`
	EvolvesFrom    *string  `json:"evolves_from"`
	EvolvesTo      []string `json:"evolves_to"`
	Rarity         *string  `json:"rarity"`
	RegulationMark *string  `json:"regulation_mark"`

	SetCode     string  `json:"set_code"`
	SetName     string  `json:"set_name"`
	SetTotal    *int    `json:"set_total"`
	PtcgoCode   *string `json:"ptcgo_code"`
	Series      *string `json:"series"`
	ReleaseDate *string `json:"release_date"`
	PrintRegion string  `json:"print_region"`

	ArtistName *string `json:"artist_name"`
	ArtistSlug *string `json:"artist_slug"`

	// IndexEUR is the composite index in euro. Always present on a single
	// card; on list and batch rows only with Include index (1 credit per 50
	// cards). Without it the field is absent and Meta.Withheld contains "index".
	IndexEUR    *float64 `json:"index_eur,omitempty"`
	LastPriceAt *string  `json:"last_price_at,omitempty"`

	TCGplayerID  *int `json:"tcgplayer_id"`
	CardmarketID *int `json:"cardmarket_id"`
	// JPTwinID is the Japanese printing of the same card, where the pairing is known.
	JPTwinID *string `json:"jp_twin_id"`

	RowVersion int    `json:"row_version"`
	CreatedAt  string `json:"created_at"`
	UpdatedAt  string `json:"updated_at"`

	// Relations, only with Include.
	Prices       []Price       `json:"prices,omitempty"`
	Images       []CardImage   `json:"images,omitempty"`
	Translations []Translation `json:"translations,omitempty"`
	Set          *CardSet      `json:"set,omitempty"`
	Artist       *Artist       `json:"artist,omitempty"`

	// Game text. Present in English on Western printings and unevenly:
	// attacks on about a third of the catalogue, nothing on Japanese and
	// Chinese printings. nil means "not held", never "the card has no attacks".
	Attacks                []json.RawMessage `json:"attacks"`
	Abilities              []json.RawMessage `json:"abilities"`
	Weaknesses             []json.RawMessage `json:"weaknesses"`
	Resistances            []json.RawMessage `json:"resistances"`
	Subtypes               []string          `json:"subtypes"`
	RetreatCost            []string          `json:"retreat_cost"`
	ConvertedRetreatCost   *int              `json:"converted_retreat_cost"`
	Rules                  []string          `json:"rules"`
	FlavorText             *string           `json:"flavor_text"`
	Types                  []string          `json:"types"`
	NationalPokedexNumbers []int             `json:"national_pokedex_numbers"`
}

Card is one printing.

type CardGetParams

type CardGetParams struct {
	Select  []string
	Include []string
	Lang    string
}

CardGetParams are the parameters of Cards.Get and Cards.Batch.

type CardImage

type CardImage struct {
	Face        string  `json:"face"`
	Size        string  `json:"size"`
	Locale      *string `json:"locale"`
	URL         string  `json:"url"`
	ImageSource *string `json:"image_source"`
	// Width and Height are modelled but not populated: reserve space with a 5:7 ratio.
	Width  *int `json:"width"`
	Height *int `json:"height"`
}

CardImage is one rendition of a card face.

type CardListParams

type CardListParams struct {
	Q       string
	Select  []string
	OrderBy string
	Limit   int
	Cursor  string
	Include []string
	// Lang replaces Name with the name in that locale; falls back to en.
	Lang string
	// Set narrows to one or more sets by code, slug or alternate id, instead of q=set.id:...
	Set []string
}

CardListParams are the parameters of Cards.Search and Sets.Cards.

type CardPrices

type CardPrices struct {
	CardID string      `json:"card_id"`
	Index  *PriceIndex `json:"index"`
	Quotes []Price     `json:"quotes"`
}

CardPrices is the index and current quotes of one card.

type CardSet

type CardSet struct {
	ID           string  `json:"id"`
	Code         string  `json:"code"`
	Slug         string  `json:"slug"`
	LegacyID     *string `json:"legacy_id"`
	Name         string  `json:"name"`
	Series       *string `json:"series"`
	Region       string  `json:"region"`
	ReleaseDate  *string `json:"release_date"`
	Total        *int    `json:"total"`
	PrintedTotal *int    `json:"printed_total"`
	PtcgoCode    *string `json:"ptcgo_code"`
	SymbolURL    *string `json:"symbol_url"`
	LogoURL      *string `json:"logo_url"`
	UpdatedAt    string  `json:"updated_at,omitempty"`
}

CardSet is one set.

type CardsService

type CardsService struct {
	// contains filtered or unexported fields
}

CardsService wraps /v1/cards.

func (*CardsService) Batch

func (s *CardsService) Batch(ctx context.Context, ids []string, p *CardGetParams) (*BatchResult[Card], error)

Batch reads up to 100 cards in one request. The answer carries Requested and Found, and Missing when something did not resolve: one entry per id, with SuggestedID where the id is a historical alias of a card now in another set.

func (*CardsService) Get

func (s *CardsService) Get(ctx context.Context, id string, p *CardGetParams) (*Card, error)

Get reads one card by id. Both the canonical id and the alternate legacy id resolve.

func (*CardsService) Search

func (s *CardsService) Search(ctx context.Context, p *CardListParams) (*Page[Card], error)

Search runs a catalogue search and returns the first page, iterable to the end.

type CatalogStatus

type CatalogStatus struct {
	Status  string `json:"status"`
	Catalog struct {
		Sets    int `json:"sets"`
		Cards   int `json:"cards"`
		Sealed  int `json:"sealed"`
		Artists int `json:"artists"`
	} `json:"catalog"`
	Sources []struct {
		Source        string   `json:"source"`
		LastSuccessAt *string  `json:"last_success_at"`
		AgeHours      *float64 `json:"age_hours"`
		// State is fresh, stale, critical or never_run.
		State string `json:"state"`
	} `json:"sources"`
	Upstream struct {
		ContractOK bool    `json:"contract_ok"`
		Error      *string `json:"error"`
	} `json:"upstream"`
	Version string `json:"version"`
}

CatalogStatus is the answer of /v1/status: counts and freshness per source.

type Change

type Change struct {
	ID int64 `json:"id"`
	// Kind is SET, CARD, ...: the real list is change_kinds in /v1/reference.
	Kind      string `json:"kind"`
	EntityID  string `json:"entity_id"`
	Op        string `json:"op"`
	Version   int    `json:"version"`
	ChangedAt string `json:"changed_at"`
}

Change is one row of the incremental feed.

type ChangesParams

type ChangesParams struct {
	// Since is the last next_since received. Zero means from the oldest available.
	Since int64
	Kind  string
	Limit int
}

ChangesParams are the parameters of Client.Changes.

type ChangesResponse

type ChangesResponse struct {
	Data []Change `json:"data"`
	Meta struct {
		Count   int  `json:"count"`
		HasMore bool `json:"has_more"`
		// NextSince is to be stored and sent back as Since: the feed has no other state.
		NextSince       int64 `json:"next_since"`
		Watermark       int64 `json:"watermark"`
		OldestAvailable int64 `json:"oldest_available"`
		Behind          int64 `json:"behind"`
	} `json:"meta"`
	Links *struct {
		Next string `json:"next,omitempty"`
	} `json:"links,omitempty"`
}

ChangesResponse is the answer of Client.Changes.

type Client

type Client struct {
	Cards     *CardsService
	Sets      *SetsService
	Artists   *ArtistsService
	Series    *SeriesService
	Sealed    *SealedService
	Prices    *PricesService
	Reference *ReferenceService
	Vision    *VisionService
	// contains filtered or unexported fields
}

Client is the entry point. Resources mirror the API paths (client.Cards.Get, client.Sets.Cards) so that going from the documentation to the code needs no conversion table. Every list method returns a *Page that walks the collection by following links.next.

client := pokemontcgapi.New(pokemontcgapi.WithAPIKey(os.Getenv("PTCG_API_KEY")))
card, err := client.Cards.Get(ctx, "base1-4", &pokemontcgapi.CardGetParams{Include: []string{"prices"}})
Example

ExampleClient needs PTCG_API_KEY in the environment; it is compiled, not run.

ctx := context.Background()
client := pokemontcgapi.New() // reads PTCG_API_KEY

card, err := client.Cards.Get(ctx, "base1-4", &pokemontcgapi.CardGetParams{Include: []string{pokemontcgapi.IncludePrices}})
if err != nil {
	var notFound *pokemontcgapi.NotFoundError
	if errors.As(err, &notFound) {
		log.Fatalf("no such card: %s", notFound.Message)
	}
	log.Fatal(err)
}
fmt.Println(card.ID, card.Name)

page, err := client.Sets.List(ctx, &pokemontcgapi.SetListParams{Region: pokemontcgapi.RegionJP, Limit: 250})
if err != nil {
	log.Fatal(err)
}
for set, err := range page.All(ctx) {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(set.Code, set.Name)
}

func New

func New(opts ...Option) *Client

New builds a client. The transport is the standard library's; there are no dependencies to carry.

func (*Client) Changes

func (c *Client) Changes(ctx context.Context, p *ChangesParams) (*ChangesResponse, error)

Changes reads the incremental feed: what changed after Since. Store Meta.NextSince and send it back on the next call.

func (*Client) Health

func (c *Client) Health(ctx context.Context) (*Health, error)

Health is the liveness probe. Separate from Status: a stalled ingest is not a service down.

func (*Client) LastResponse

func (c *Client) LastResponse() *ResponseInfo

LastResponse returns the headers of the last response this client received, or nil before the first one. With concurrent calls it is the last to arrive: to count them all, use WithOnResponse.

func (*Client) Status

func (c *Client) Status(ctx context.Context) (*CatalogStatus, error)

Status returns catalogue counts and freshness per source.

type Collection

type Collection[T any] struct {
	Data  []T            `json:"data"`
	Meta  CollectionMeta `json:"meta"`
	Links *struct {
		Next string `json:"next,omitempty"`
	} `json:"links,omitempty"`
}

Collection is the envelope of every list route.

type CollectionMeta

type CollectionMeta struct {
	Limit      int  `json:"limit"`
	Count      int  `json:"count"`
	TotalCount *int `json:"total_count,omitempty"`
	HasMore    bool `json:"has_more"`
	// Withheld names what the response left out: "index" when select names
	// index_eur without Include index, or the price rows the plan does not cover.
	Withheld []string `json:"withheld,omitempty"`
	// Warnings are grace-period parameter warnings; distinct from search hints.
	Warnings []string `json:"warnings,omitempty"`
	// Hints are present when a search by number or set name has a useful suggestion.
	Hints []SearchHint `json:"hints,omitempty"`
}

CollectionMeta describes a page of a collection.

type ConnectionError

type ConnectionError struct {
	URL string
	Err error
}

ConnectionError means the request never got an answer: DNS, TLS, socket.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type Grading

type Grading struct {
	Company string `json:"company"`
	Score   string `json:"score"`
}

Grading is the company and score of a graded copy.

type Health

type Health struct {
	Status  string  `json:"status"`
	DB      bool    `json:"db"`
	UptimeS float64 `json:"uptime_s"`
	Version string  `json:"version"`
}

Health is the liveness probe.

type HistoryParams

type HistoryParams struct {
	Source   string
	Variant  string
	Locale   string
	Printing string
	// From and To are YYYY-MM-DD. A window wider than the plan gives *UpgradeRequiredError.
	From   string
	To     string
	Bucket string // day, week or month
}

HistoryParams are the parameters of Prices.History.

type HistoryPoint

type HistoryPoint struct {
	Date     string  `json:"date"`
	Source   string  `json:"source"`
	Variant  string  `json:"variant"`
	Locale   *string `json:"locale"`
	Printing *string `json:"printing"`
	Amount   float64 `json:"amount"`
	Currency string  `json:"currency"`
	SampleN  *int    `json:"sample_n"`
}

HistoryPoint is one bucket of the price history.

type HistoryResponse

type HistoryResponse struct {
	Data []HistoryPoint `json:"data"`
	Meta struct {
		CardID    string `json:"card_id"`
		From      string `json:"from"`
		To        string `json:"to"`
		Bucket    string `json:"bucket"`
		Count     int    `json:"count"`
		Truncated bool   `json:"truncated"`
		Capped    bool   `json:"capped"`
		// PlanWindowDays is nil when the plan gives the whole history.
		PlanWindowDays *int `json:"plan_window_days"`
	} `json:"meta"`
}

HistoryResponse is the answer of Prices.History.

type IdentifyOptions

type IdentifyOptions struct {
	// TopK is how many candidates, 1..10.
	TopK int
	// Set restricts to one set. It is the hint that resolves a reprint.
	Set string
	// Region restricts to a print region. Same purpose.
	Region string
}

IdentifyOptions narrow a photo recognition.

type InvalidRequestError

type InvalidRequestError struct{ *APIError }

InvalidRequestError is a 400 or 422: the request is wrong. Field names the parameter when the API says which one.

func (*InvalidRequestError) Field

func (e *InvalidRequestError) Field() string

Field is details.field, when present.

func (*InvalidRequestError) Unwrap

func (e *InvalidRequestError) Unwrap() error

type ListParams

type ListParams struct {
	Q       string
	Select  []string
	OrderBy string
	Limit   int
	Cursor  string
}

ListParams are the parameters shared by the simple list routes.

Q is the search grammar. Field names there are camelCase and dotted (set.code, nationalPokedexNumbers) while response keys are snake_case (set_code): two vocabularies, and mixing them gives a 400 listing the valid ones.

type MissingCard

type MissingCard struct {
	ID string `json:"id"`
	// SuggestedID is present only for an existing historical alias in a different canonical set.
	SuggestedID string `json:"suggested_id,omitempty"`
}

MissingCard is one id a batch could not resolve.

type Mover

type Mover struct {
	CardID    string  `json:"card_id"`
	Name      string  `json:"name"`
	SetCode   string  `json:"set_code"`
	From      float64 `json:"from"`
	To        float64 `json:"to"`
	ChangePct float64 `json:"change_pct"`
	Currency  string  `json:"currency"`
}

Mover is one card in the movers list.

type MoversParams

type MoversParams struct {
	Window    string
	Direction string // gainers or losers
	// MinValue in euro keeps cards worth a few cents out.
	MinValue float64
	Locale   string
	// Limit is 1..50.
	Limit int
}

MoversParams are the parameters of Prices.Movers.

type MoversResponse

type MoversResponse struct {
	Data []Mover `json:"data"`
	Meta struct {
		Window    string  `json:"window"`
		From      string  `json:"from"`
		To        string  `json:"to"`
		Direction string  `json:"direction"`
		MinValue  float64 `json:"min_value"`
		Count     int     `json:"count"`
		Source    string  `json:"source"`
	} `json:"meta"`
}

MoversResponse is the answer of Prices.Movers.

type NextStep

type NextStep struct {
	Action            string // subscribe, upgrade, contact_sales, contact_support, verify_email
	Actor             string // always account_owner
	Plan              *NextStepPlan
	CheckoutURL       string
	CheckoutURLYearly string
	ManageURL         string
	ContactURL        string
	VerifyURL         string
	PlansURL          string
	Handoff           string
}

NextStep is the human handoff attached to commercial refusals (details.next_step): what the account owner has to do, and where.

type NextStepPlan

type NextStepPlan struct {
	Code            string
	Name            string
	MonthlyEUR      string
	YearlyEUR       string
	CreditsPerMonth *int
}

NextStepPlan describes the plan a next step points at, when the API names one.

type NotFoundError

type NotFoundError struct{ *APIError }

NotFoundError is a 404, or any code ending in _NOT_FOUND. Not a network error: it is never retried.

func (*NotFoundError) Unwrap

func (e *NotFoundError) Unwrap() error

type Option

type Option func(*Client)

Option configures a Client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the key. Without it, PTCG_API_KEY is read from the environment.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL points the client somewhere else, e.g. a test server.

func WithETagCache

func WithETagCache() Option

WithETagCache remembers ETags and sends If-None-Match. Worth it: a 304 has no body and costs no quota, so a mirror that re-syncs often pays only for the pages that changed. The cache is in memory and per client, on purpose.

func WithHTTPClient

func WithHTTPClient(httpc *http.Client) Option

WithHTTPClient injects the transport. Its Timeout is not used: the SDK applies its own per-attempt timeout through the context.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets how many times a GET is retried after the first attempt. 0 disables.

func WithOnResponse

func WithOnResponse(fn func(ResponseInfo)) Option

WithOnResponse registers a callback for every response received, error responses included: the place for a credit counter.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the timeout of a single attempt, not of the whole call.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent replaces the default User-Agent.

type Page

type Page[T any] struct {
	Data []T
	Meta CollectionMeta
	// contains filtered or unexported fields
}

Page is one page of a collection that knows how to fetch the next one.

The cursor in links.next carries a signature of the sort order, so it must never be rebuilt by hand: Next requests exactly the URL the API returned, which is the failure mode this type exists to avoid.

func (*Page[T]) All

func (p *Page[T]) All(ctx context.Context) iter.Seq2[T, error]

All walks every item of every page, starting from this one.

for card, err := range page.All(ctx) {
    if err != nil { return err }
    ...
}

func (*Page[T]) Collect

func (p *Page[T]) Collect(ctx context.Context, max int) ([]T, error)

Collect materialises up to max items. max is mandatory because the catalogue has tens of thousands of cards, and an unbounded collect is the fastest way to fill a process's memory by mistake.

func (*Page[T]) HasMore

func (p *Page[T]) HasMore() bool

HasMore reports whether the API returned a links.next URL.

func (*Page[T]) Next

func (p *Page[T]) Next(ctx context.Context) (*Page[T], error)

Next fetches the following page, or returns (nil, nil) when there is none.

type PermissionDeniedError

type PermissionDeniedError struct{ *APIError }

PermissionDeniedError is a 403: the key is valid but may not do this.

func (*PermissionDeniedError) Unwrap

func (e *PermissionDeniedError) Unwrap() error

type PlanRequiredError

type PlanRequiredError struct{ *PermissionDeniedError }

PlanRequiredError is 403 PLAN_REQUIRED: the route is not in the plan (movers and photo recognition start at Growth). It unwraps to *PermissionDeniedError, so code that catches that keeps working.

func (*PlanRequiredError) Unwrap

func (e *PlanRequiredError) Unwrap() error

type Price

type Price struct {
	Source  string `json:"source"`
	Variant string `json:"variant"`
	Basis   string `json:"basis"`
	// Amount is the figure, with its currency in the sibling field.
	Amount    float64  `json:"amount"`
	Currency  string   `json:"currency"`
	Locale    *string  `json:"locale"`
	Condition *string  `json:"condition"`
	Printing  *string  `json:"printing"`
	Grading   *Grading `json:"grading"`
	// AsOf is the day the observation refers to. Never today: every source is delayed.
	AsOf string `json:"as_of"`
	// SampleN is how many observations sit behind the figure, where the source says.
	SampleN *int `json:"sample_n"`
	// Provenance is the attribution string to print next to the number.
	Provenance string `json:"provenance"`
}

Price is one observed price row: who saw it, on what basis, in which currency, for which printing and condition, and on which day.

type PriceFilterParams

type PriceFilterParams struct {
	Source  string
	Variant string
	Locale  string
}

PriceFilterParams narrow a price read to a source, variant or locale.

type PriceIndex

type PriceIndex struct {
	EUR     float64 `json:"eur"`
	AsOf    string  `json:"as_of"`
	SampleN *int    `json:"sample_n"`
	// ByLocale has one series per (locale, printing): the head index is the English one.
	ByLocale []struct {
		Locale   string  `json:"locale"`
		Printing *string `json:"printing"`
		EUR      float64 `json:"eur"`
		AsOf     string  `json:"as_of"`
		SampleN  *int    `json:"sample_n"`
	} `json:"by_locale"`
}

PriceIndex is the composite index of a card, with one series per (locale, printing).

type PriceSourceInfo

type PriceSourceInfo struct {
	Source        string `json:"source"`
	Label         string `json:"label"`
	MinDelayHours int    `json:"min_delay_hours"`
	IsOwn         bool   `json:"is_own"`
}

PriceSourceInfo describes one price source and its declared delay.

type PriceStats

type PriceStats struct {
	Window    string   `json:"window"`
	From      string   `json:"from"`
	To        string   `json:"to"`
	Low       *float64 `json:"low"`
	High      *float64 `json:"high"`
	Median    *float64 `json:"median"`
	First     *float64 `json:"first"`
	Last      *float64 `json:"last"`
	ChangePct *float64 `json:"change_pct"`
	SampleN   int      `json:"sample_n"`
	Currency  string   `json:"currency"`
}

PriceStats summarises the index over a window.

type PricesResponse

type PricesResponse[T any] struct {
	Data T `json:"data"`
	Meta struct {
		Quotes       int `json:"quotes"`
		DelayedHours int `json:"delayed_hours"`
		// Withheld names the rows the plan does not cover: graded, non_english_locales. Absent if nothing is withheld.
		Withheld []string `json:"withheld,omitempty"`
	} `json:"meta"`
}

PricesResponse is the envelope of the dedicated price routes.

type PricesService

type PricesService struct {
	// contains filtered or unexported fields
}

PricesService wraps the dedicated price routes.

Every row says where it comes from (Source), what it rests on (Basis: sold, asking, guide, derived) and which day it is for (AsOf). The rows the plan does not cover are missing from the body: LastResponse().PlanWithheld says which.

func (*PricesService) Card

Card returns the index and current quotes of one card. 2 credits.

func (*PricesService) Current

Current returns up to 50 cards in one call, 4 credits per 25.

func (*PricesService) History

History returns the daily history. 5 credits. The window depends on the plan (7 days on the trial, 30 on Developer, everything from Growth): asking for a wider one gives *UpgradeRequiredError with PermittedWindow.

func (*PricesService) Movers

Movers returns the cards that moved the most. 3 credits, from the Growth plan (*PlanRequiredError below it).

func (*PricesService) Sources

func (s *PricesService) Sources(ctx context.Context) ([]PriceSourceInfo, error)

Sources lists the price sources with the declared delay of each. Free.

func (*PricesService) Stats

Stats returns low, high, median and change of the index over a window. 2 credits.

type QuotaExceededError

type QuotaExceededError struct{ *APIError }

QuotaExceededError is a 429 for an exhausted period quota: it does NOT pass by waiting, and the client never retries it.

func (*QuotaExceededError) Unwrap

func (e *QuotaExceededError) Unwrap() error

type RateLimitedError

type RateLimitedError struct {
	*APIError
	// RetryAfter is the wait the API asked for, valid when HasRetryAfter is true.
	RetryAfter    time.Duration
	HasRetryAfter bool
}

RateLimitedError is a 429 with Retry-After: it passes by waiting.

func (*RateLimitedError) Unwrap

func (e *RateLimitedError) Unwrap() error

type ReferenceService

type ReferenceService struct {
	// contains filtered or unexported fields
}

ReferenceService reads the vocabularies, to populate a UI's filters without guessing strings. One network request per client no matter how many of the methods are called: the answer is the same and is kept for the life of the client. A failed request is not kept, so the next call tries again.

func (*ReferenceService) All

func (s *ReferenceService) All(ctx context.Context) (map[string][]string, error)

All returns every vocabulary at once: besides the four below, locales, print_regions, conditions, printings, grading_companies, price_variants, price_bases, change_kinds and the others /v1/reference lists.

func (*ReferenceService) Rarities

func (s *ReferenceService) Rarities(ctx context.Context) ([]string, error)

Rarities returns the rarities.

func (*ReferenceService) Subtypes

func (s *ReferenceService) Subtypes(ctx context.Context) ([]string, error)

Subtypes returns the card subtypes.

func (*ReferenceService) Supertypes

func (s *ReferenceService) Supertypes(ctx context.Context) ([]string, error)

Supertypes returns the card supertypes.

func (*ReferenceService) Types

func (s *ReferenceService) Types(ctx context.Context) ([]string, error)

Types returns the energy types.

type ResponseInfo

type ResponseInfo struct {
	URL       string
	Status    int
	RequestID string
	// ErrorCode equals the error's Code on API errors; empty on success.
	ErrorCode string
	// CreditsCost is the credits charged by this call. 0 on free routes, 304s and client errors.
	CreditsCost        *int
	QuotaLimit         *int
	QuotaRemaining     *int
	QuotaReset         string
	RateLimitRemaining *int
	PlanWithheld       []string
	// TrialExpiresAt is set on trial keys only.
	TrialExpiresAt string
}

ResponseInfo is what the response says in its headers and the body does not carry: what the call cost, what is left of the quota, and what the plan withheld. PlanWithheld is why it exists: on Cards.Get with Include prices, the rows the plan does not cover are missing from the body in silence, and only the X-Plan-Withheld header says so.

type SealedListParams

type SealedListParams struct {
	Q       string
	Set     []string
	Kind    string
	Lang    string
	Include []string
	OrderBy string
	Limit   int
	Cursor  string
}

SealedListParams are the parameters of Sealed.List.

type SealedPrices

type SealedPrices struct {
	SealedID string      `json:"sealed_id"`
	Index    *PriceIndex `json:"index"`
	Quotes   []Price     `json:"quotes"`
}

SealedPrices is the current quotes of one sealed product. Index is always nil: the composite index does not cover sealed products.

type SealedProduct

type SealedProduct struct {
	ID   string `json:"id"`
	SKU  string `json:"sku"`
	Slug string `json:"slug"`
	Name string `json:"name"`
	// Kind is BOOSTER_BOX and the like.
	Kind        string   `json:"kind"`
	SetCode     *string  `json:"set_code"`
	SetName     *string  `json:"set_name"`
	ImageURL    *string  `json:"image_url"`
	ReleaseDate *string  `json:"release_date"`
	PackCount   *int     `json:"pack_count"`
	Languages   []string `json:"languages"`
	// IndexEUR is on lists only with Include index; on the single product always.
	IndexEUR    *float64 `json:"index_eur,omitempty"`
	LastPriceAt *string  `json:"last_price_at,omitempty"`
	CreatedAt   string   `json:"created_at"`
	UpdatedAt   string   `json:"updated_at"`
}

SealedProduct is a booster box, ETB, tin, blister or collection.

type SealedService

type SealedService struct {
	// contains filtered or unexported fields
}

SealedService wraps /v1/sealed: booster boxes, ETBs, tins, blisters, collections.

func (*SealedService) Get

Get reads one sealed product.

func (*SealedService) List

List returns sealed products.

func (*SealedService) Prices

Prices returns the current quotes of a product. 2 credits. The composite index does not cover sealed products: Data.Index is nil.

type SearchHint

type SearchHint struct {
	Code       string `json:"code"`
	Message    string `json:"message"`
	Received   string `json:"received,omitempty"`
	Matched    string `json:"matched,omitempty"`
	SuggestedQ string `json:"suggested_q,omitempty"`
	// Matches is capped at 50; read AtLeast when the real count is higher.
	Matches int    `json:"matches,omitempty"`
	AtLeast bool   `json:"at_least,omitempty"`
	SetCode string `json:"set_code,omitempty"`
	SetName string `json:"set_name,omitempty"`
}

SearchHint is one search suggestion. Code says which fields are filled: NUMBER_NORMALIZED (Received, Matched), TRY_POKEDEX_NUMBER (SuggestedQ, Matches, AtLeast), SET_ALIAS_MATCHED / SET_WORDS_MATCHED (Matched), SET_ALIAS_ELSEWHERE (SetCode, SetName).

type Series

type Series struct {
	ID       string `json:"id"`
	Slug     string `json:"slug"`
	Name     string `json:"name"`
	SetCount int    `json:"set_count"`
}

Series is one series (Scarlet & Violet, Sword & Shield, ...).

type SeriesService

type SeriesService struct {
	// contains filtered or unexported fields
}

SeriesService wraps /v1/series.

func (*SeriesService) List

func (s *SeriesService) List(ctx context.Context, p *ListParams) (*Page[Series], error)

List returns the series (Scarlet & Violet, Sword & Shield, ...) with their set counts. Only OrderBy, Limit and Cursor apply on this route.

type ServerError

type ServerError struct{ *APIError }

ServerError is any 5xx.

func (*ServerError) Unwrap

func (e *ServerError) Unwrap() error

type SetGetParams

type SetGetParams struct {
	Lang string
}

SetGetParams are the parameters of Sets.Get and Sealed.Get.

type SetListParams

type SetListParams struct {
	Q       string
	OrderBy string
	Limit   int
	Cursor  string
	Region  string
	Series  string
	Lang    string
}

SetListParams are the parameters of Sets.List.

type SetsService

type SetsService struct {
	// contains filtered or unexported fields
}

SetsService wraps /v1/sets.

func (*SetsService) Cards

func (s *SetsService) Cards(ctx context.Context, code string, p *CardListParams) (*Page[Card], error)

Cards returns the cards of one set, in collection order. The Set field of the params is ignored: the set is the one in the path.

func (*SetsService) Get

func (s *SetsService) Get(ctx context.Context, code string, p *SetGetParams) (*CardSet, error)

Get reads one set by code, slug or alternate id.

func (*SetsService) List

func (s *SetsService) List(ctx context.Context, p *SetListParams) (*Page[CardSet], error)

List returns the sets. Region is the filter worth knowing: JP returns the Japanese releases, which are the largest part of the catalogue and not translations of the Western ones.

type StatsParams

type StatsParams struct {
	Window string
	Locale string
}

StatsParams are the parameters of Prices.Stats.

type StatsResponse

type StatsResponse struct {
	Data PriceStats `json:"data"`
	Meta struct {
		CardID string `json:"card_id"`
		Source string `json:"source"`
	} `json:"meta"`
}

StatsResponse is the answer of Prices.Stats.

type TimeoutError

type TimeoutError struct {
	*ConnectionError
	Timeout time.Duration
}

TimeoutError means the request was abandoned by the client after Timeout. It unwraps to *ConnectionError.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

func (*TimeoutError) Unwrap

func (e *TimeoutError) Unwrap() error

type Translation

type Translation struct {
	Locale string `json:"locale"`
	Name   string `json:"name"`
}

Translation is a card name in one locale.

type TrialExpiredError

type TrialExpiredError struct{ *PermissionDeniedError }

TrialExpiredError is 403 TRIAL_EXPIRED: the trial is over and the route costs credits. Waiting and retrying do not help; a plan does.

func (*TrialExpiredError) Unwrap

func (e *TrialExpiredError) Unwrap() error

type UpgradeRequiredError

type UpgradeRequiredError struct{ *APIError }

UpgradeRequiredError is 403 UPGRADE_REQUIRED: the window asked for is wider than the plan allows.

func (*UpgradeRequiredError) PermittedWindow

func (e *UpgradeRequiredError) PermittedWindow() any

PermittedWindow is the window the current plan grants, when the API says.

func (*UpgradeRequiredError) Unwrap

func (e *UpgradeRequiredError) Unwrap() error

type VisionCandidate

type VisionCandidate struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Number string `json:"number"`
	Set    struct {
		Code        string `json:"code"`
		Name        string `json:"name"`
		PrintRegion string `json:"print_region"`
	} `json:"set"`
	Rarity   *string `json:"rarity"`
	ImageURL *string `json:"image_url"`
	// Distance is a Hamming distance, 0..512: real matches sit under 150 even on a noisy photo, and nothing above 170 is returned.
	Distance int `json:"distance"`
	// Confidence is the same information rescaled to 0..1.
	Confidence float64 `json:"confidence"`
}

VisionCandidate is one ranked candidate.

type VisionMeta

type VisionMeta struct {
	Count            int    `json:"count"`
	CardsIndexed     int    `json:"cards_indexed"`
	IndexBuiltAt     string `json:"index_built_at"`
	IndexLoadedAt    string `json:"index_loaded_at"`
	SignatureVersion int    `json:"signature_version"`
	// RegionsDetected is how many card-like quadrilaterals were isolated. Zero with a no_match means the card was not found in the photo, not that it is not in the catalogue.
	RegionsDetected int `json:"regions_detected"`
	HypothesesTried int `json:"hypotheses_tried"`
	ElapsedMs       int `json:"elapsed_ms"`
	OCR             *struct {
		Applied   bool   `json:"applied"`
		Eligible  int    `json:"eligible"`
		Level     int    `json:"level,omitempty"`
		Number    int    `json:"number,omitempty"`
		Ms        int    `json:"ms"`
		CropMs    int    `json:"crop_ms"`
		Reason    string `json:"reason,omitempty"`
		SetCode   string `json:"set_code,omitempty"`
		SetReason string `json:"set_reason,omitempty"`
	} `json:"ocr,omitempty"`
}

VisionMeta describes how the recognition went.

type VisionResponse

type VisionResponse struct {
	Data VisionResult `json:"data"`
	Meta VisionMeta   `json:"meta"`
}

VisionResponse is the answer of Vision.Identify.

type VisionResult

type VisionResult struct {
	Decision       string `json:"decision"`
	DecisionReason string `json:"decision_reason"` // clear, reprint, close_call, none, ocr_number, ocr_set_number
	// ID is set ONLY when Decision is match. Otherwise nil.
	ID         *string           `json:"id"`
	Candidates []VisionCandidate `json:"candidates"`
}

VisionResult is the outcome of a photo recognition.

type VisionService

type VisionService struct {
	// contains filtered or unexported fields
}

VisionService recognises a card from a photograph.

It costs 25 credits a call against 1 for a read: it is the only route that does not return a row but the outcome of a comparison against the whole image index. Worth knowing before putting it in a loop.

func (*VisionService) Identify

func (s *VisionService) Identify(ctx context.Context, image io.Reader, opts *IdentifyOptions) (*VisionResponse, error)

Identify sends a photo and returns the ranked candidates.

Read Data.Decision before Data.ID. ID is set only on a match; on ambiguous it is nil on purpose, because two printings of the same illustration cannot be told apart from the image alone and picking one means being wrong half the time, on exactly the cards that are worth the most. If your flow knows the set (whoever inventories a just-opened pack does), pass it in IdentifyOptions.Set: that is what resolves the tie.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL