{"endpoints":[{"path":"/ping","method":"GET","auth":"none","response":"{\"ok\":true,\"api\":\"v1\",\"time\":1786734577051}","scope":null,"about":"Connectivity check. Use it to prove you can reach the API before debugging credentials.","fields":[],"example":"curl https://gestic.org/api/v1/ping"},{"path":"/tokens","method":"POST","auth":"password, or a signed-in Gestic browser session","response":"{\"token\":\"gst_EXAMPLE_TOKEN\",\"label\":\"CardTrader sync\",\n \"scopes\":[\"lists:read\",\"collection:read\"],\n \"note\":\"Copy this now: it is stored hashed and cannot be shown again.\"}","scope":null,"about":"Creates an API key. This is the only response that ever contains the secret: it is stored hashed and cannot be shown again. A key cannot create another key -- that needs your password or your browser session, so a leaked key cannot promote itself.","fields":[{"name":"login","about":"Your Gestic e-mail. Omit when calling from a signed-in browser session.","type":"string","required":false},{"name":"password","about":"The SHA-256 hex of your password, the same as the login endpoint expects. Never the plaintext.","type":"string","required":false},{"name":"label","about":"What this key is for (\"CardTrader sync\"). Shown in your settings so an unused or leaked key can be identified.","type":"string","required":false},{"name":"scopes","about":"Defaults to [\"collection:read\"]. Valid: collection:read, collection:write, lists:read, lists:write, offers:write.","type":"array of string","required":false},{"name":"expiresInDays","about":"Optional expiry. Omit for a key that does not expire.","type":"integer","required":false}],"example":"curl -X POST https://gestic.org/api/v1/tokens \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"login\":\"you@example.com\",\"password\":\"<sha256 hex>\",\n       \"label\":\"CardTrader sync\",\"scopes\":[\"collection:read\",\"lists:read\"]}'"},{"path":"/me","method":"GET","auth":"token or session","response":"{\"ok\":true,\"userId\":1719,\"login\":\"you@example.com\",\n \"authenticatedBy\":\"token\",\"tokenId\":1,\n \"scopes\":[\"lists:read\",\"collection:read\"],\"rateLimitPerMinute\":60}","scope":null,"about":"Confirms a key works and reports what it may do. This is the call to make first when something is not working.","fields":[],"example":"curl -H 'Authorization: Bearer gst_...' https://gestic.org/api/v1/me"},{"path":"/tokens","method":"GET","auth":"token or session","response":"{\"tokens\":[{\"id\":1,\"label\":\"CardTrader sync\",\"prefix\":\"gst_EXAMPLE\",\n   \"scopes\":[\"collection:read\"],\"created\":\"2026-08-14T10:52:11Z\",\n   \"lastUsed\":\"2026-08-14T10:53:02Z\",\"calls\":3,\n   \"expires\":null,\"revoked\":false,\"revokedAt\":null}]}","scope":null,"about":"Your keys. Secrets are never returned -- only the first 12 characters, so you can tell them apart.","fields":[],"example":"curl -H 'Authorization: Bearer gst_...' https://gestic.org/api/v1/tokens"},{"path":"/tokens/{id}","method":"DELETE","auth":"token or session","response":"{\"revoked\":1}","scope":null,"about":"Revokes a key immediately. Irreversible; issue a new one instead of un-revoking.","fields":[{"name":"id","about":"The id from GET /tokens. It must be one of your own keys.","type":"integer (in the path)","required":true}],"example":"curl -X DELETE -H 'Authorization: Bearer gst_...' https://gestic.org/api/v1/tokens/1"},{"path":"/lists","method":"GET","auth":"token or session","response":"{\"apiVersion\":1,\"inventoryVersion\":\"lists1-0b7d\u2026\",\"nextCursor\":null,\"items\":[\n  {\"listId\":\"10090\",\"name\":\"Collection\",\"kind\":\"collection\",\n   \"physicalCopyCount\":35757,\"variantCount\":21410,\"inventoryVersion\":\"inv1-9c41\u2026\"}]}","scope":"lists:read","about":"Your own lists, a page at a time (ascending list id). Never other members' lists, public or shared. kind is the first of collection, selling, wishlist, deck the list is flagged as, else list. physicalCopyCount counts copies (20 identical cards = 20); variantCount counts distinct card + language + condition + finish. The envelope's inventoryVersion covers all your lists: if one is created, renamed, re-flagged, deleted or its cards change during a walk, the next page answers 409 inventory_changed: start again.","fields":[{"name":"limit","about":"Items per page, 1-500. Default 100.","type":"integer (query)","required":false},{"name":"cursor","about":"nextCursor of the previous page; omit for the first page. Opaque, bound to your account and this list, valid 15 minutes. 400 invalid_cursor: not one of ours for this walk. 409 cursor_expired or inventory_changed: start again from the first page.","type":"string (query)","required":false}],"example":"curl -H 'Authorization: Bearer gst_...' 'https://gestic.org/api/v1/lists?limit=100'"},{"path":"/lists/{listId}/cards","method":"GET","auth":"token or session","response":"{\"apiVersion\":1,\"inventoryVersion\":\"inv1-9c41\u2026\",\"nextCursor\":\"c1.\u2026\",\"items\":[\n  {\"copyId\":\"2004886\",\"cardId\":\"05402b51-8cf9-4f69-aa12-8a9d5981f8b1\",\n   \"catalogStatus\":\"ok\",\"name\":\"Example Card\",\"setCode\":\"mh3\",\n   \"collectorNumber\":\"17\",\"catalogLanguage\":\"en\",\"rarity\":\"rare\",\n   \"language\":\"EN\",\"languageStatus\":\"ok\",\"condition\":\"NM\",\"conditionStatus\":\"ok\",\n   \"finish\":\"foil\",\"storedFinish\":\"F\",\"finishStatus\":\"ok\",\"location\":\"Box 3\"}]}","scope":"collection:read","about":"The physical copies of one of your lists: one item per copy, ascending copyId, each copyId stable for the life of the copy. A list that is not yours answers 404 exactly like one that does not exist, even if it is public. language, condition and storedFinish are exactly what Gestic stores (null when empty), never mapped; languageStatus, conditionStatus and finishStatus say ok, missing or unrecognized (a value Gestic does not use). finish is normal, foil, etched or null. catalogStatus unknown_card: the card id has no catalog row, so name, setCode and collectorNumber are null; card_id_unsupported: an id resolve cannot take (over 128 characters or not printable ASCII; none exist), cardId null. No prices, comments or purchase data. If the list's copies change during a walk (added, removed, edited, moved in or out) the next page answers 409 inventory_changed: start again; nothing was skipped or repeated on the pages you already have.","fields":[{"name":"listId","about":"From GET /lists.","type":"string of digits (in the path)","required":true},{"name":"limit","about":"Items per page, 1-500. Default 100.","type":"integer (query)","required":false},{"name":"cursor","about":"nextCursor of the previous page; omit for the first page. Opaque, bound to your account and this list, valid 15 minutes. 400 invalid_cursor: not one of ours for this walk. 409 cursor_expired or inventory_changed: start again from the first page.","type":"string (query)","required":false}],"example":"curl -H 'Authorization: Bearer gst_...' 'https://gestic.org/api/v1/lists/10090/cards?limit=500'"},{"path":"/cardmarket/products/resolve","method":"POST","auth":"token or session (same-origin)","response":"{\"apiVersion\":1,\"catalogVersion\":\"cat1-5e2a\u2026\",\"sourceUpdatedAt\":null,\n \"guideSnapshotVersion\":\"cmg1-20261105T014944Z-09f09ab2c1f2\",\"items\":[\n  {\"requestId\":\"r1\",\"cardId\":\"05402b51-\u2026\",\"status\":\"exact\",\"productId\":\"12345\",\n   \"mappingSource\":\"scryfall\",\"finishes\":[\"normal\",\"foil\"],\n   \"quoteFinishes\":[\"normal\",\"foil\"],\"rarity\":\"rare\",\"categoryId\":\"1\",\n   \"reason\":null,\"candidates\":[]}]}","scope":"collection:read","about":"Gestic card -> its Cardmarket product, up to 500 cards per call, one result per item in your order. Reads only Gestic's own catalog (Scryfall's cardmarket_id plus ids Gestic harvested from Cardmarket); never asks Cardmarket or Scryfall, never matches names. status: exact (productId set), ambiguous (no productId; reason shared_by_printings with candidates, unverified_match or sources_disagree), missing (unknown_card, no_cardmarket_id) or unsupported (digital_only). Normal and foil are the same Cardmarket product; quoteFinishes lists the finishes the price guide covers (never etched). rarity is Scryfall's word, unchanged. categoryId is Cardmarket's category when the current guide holds the product (\"1\" = Magic Single). sourceUpdatedAt is null: the catalog keeps no time of its source.","fields":[{"name":"apiVersion","about":"1.","type":"number","required":true},{"name":"items","about":"{requestId, cardId}. requestId: 1-80 characters, unique in the request, returned as is. cardId: the cardId from GET /lists/{listId}/cards.","type":"array (1-500)","required":true}],"example":"curl -X POST https://gestic.org/api/v1/cardmarket/products/resolve -H 'Authorization: Bearer gst_...' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"apiVersion\":1,\"items\":[{\"requestId\":\"r1\",\"cardId\":\"05402b51-\u2026\"}]}'"},{"path":"/cardmarket/metadata","method":"GET","auth":"token or session","response":"{\"apiVersion\":1,\"games\":[{\"id\":\"1\",\"name\":\"Magic: The Gathering\"}],\n \"availability\":\"available\",\"reason\":null,\"source\":\"cardmarket-price-guide\",\n \"sourceUrl\":\"https://downloads.s3.cardmarket.com/productCatalog/priceGuide/price_guide_1.json\",\n \"sourceUpdatedAt\":\"2026-11-05T01:49:44Z\",\n \"importedAt\":\"2026-11-05T02:35:10Z\",\n \"quoteSnapshotVersion\":\"cmg1-20261105T014944Z-09f09ab2c1f2\",\n \"retainedSnapshotVersions\":[\"cmg1-20261105T014944Z-09f09ab2c1f2\",\"cmg1-20261104T\u2026\"],\n \"catalogVersion\":\"cat1-5e2a\u2026\",\"quoteFields\":[\"avg\",\"low\",\"trend\",\"avg1\",\"avg7\",\"avg30\"],\n \"quoteFinishes\":[\"normal\",\"foil\"],\"unsupportedFinishes\":[\"etched\"],\n \"competitionAvailable\":false,\"freshness\":{\"staleAfterHours\":48,\"rule\":\"\u2026\"},\n \"limits\":{\"maxItems\":500,\"maxBodyBytes\":262144,\"maxPageSize\":500,\n           \"cursorTtlSeconds\":900,\"maxResponseBytes\":2097152,\"maxPrice\":\"99999.99\",\n           \"accountRateLimitPerMinute\":120}}","scope":"collection:read","about":"What the price-guide quotes can offer right now. The values come from Cardmarket's public daily Magic price guide, downloaded by Gestic: no language, condition, seller or offers (competitionAvailable is false). sourceUpdatedAt is the guide's own createdAt, importedAt when Gestic downloaded it. availability: available, stale (48 hours or older, or dated in the future) or unavailable (no guide yet; still 200). Send If-None-Match with the ETag to get 304 when nothing changed. Read it before a run of quotes and pass quoteSnapshotVersion to POST /cardmarket/quotes.","fields":[],"example":"curl -H 'Authorization: Bearer gst_...' https://gestic.org/api/v1/cardmarket/metadata"},{"path":"/cardmarket/quotes","method":"POST","auth":"token or session (same-origin)","response":"{\"apiVersion\":1,\"snapshotVersion\":\"cmg1-20261105T014944Z-09f09ab2c1f2\",\n \"sourceUpdatedAt\":\"2026-11-05T01:49:44Z\",\"items\":[\n  {\"requestId\":\"q1\",\"productId\":\"12345\",\"finish\":\"normal\",\"field\":\"trend\",\n   \"status\":\"ok\",\"price\":\"2.35\",\"currency\":\"EUR\",\"source\":\"cardmarket-price-guide\",\n   \"sourceUpdatedAt\":\"2026-11-05T01:49:44Z\",\n   \"snapshotVersion\":\"cmg1-20261105T014944Z-09f09ab2c1f2\",\"reason\":null}]}","scope":"collection:read","about":"Price-guide values, up to 500 per call, all from the one snapshot you name. A snapshot no longer kept answers 409 snapshot_changed (read metadata again). status: ok (price set, usable), missing (product_not_in_guide, no_value, zero_value: a 0 in the guide means no data), unsupported (etched_not_in_guide; price_above_maximum: over EUR 99,999.99, placeholder listings) or stale (price for information only; reason source_too_old or source_time_in_future). A value is fresh while 0 <= now - sourceUpdatedAt < 48 hours. EUR, exact decimal strings with two decimals, never a substitute from another source.","fields":[{"name":"apiVersion","about":"1.","type":"number","required":true},{"name":"snapshotVersion","about":"quoteSnapshotVersion from metadata.","type":"string","required":true},{"name":"items","about":"{requestId, productId, finish, field}. productId: a string of digits, from resolve. finish: normal, foil or etched. field: avg, low, trend, avg1, avg7 or avg30.","type":"array (1-500)","required":true}],"example":"curl -X POST https://gestic.org/api/v1/cardmarket/quotes -H 'Authorization: Bearer gst_...' \\\n  -H 'Content-Type: application/json' -d '{\"apiVersion\":1,\n  \"snapshotVersion\":\"cmg1-20261105T014944Z-09f09ab2c1f2\",\n  \"items\":[{\"requestId\":\"q1\",\"productId\":\"12345\",\"finish\":\"normal\",\"field\":\"trend\"}]}'"}],"rateLimit":{"headers":["X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset","Retry-After"],"exceeded":"429 with {\"error\":\"rate_limited\"}","account":"120 requests per minute across all of one account's keys","token":"60 requests per minute"},"versioning":"Breaking changes get /api/v2. Fields may be added to v1 responses, so parse defensively and ignore what you do not know.","timestamps":"ISO-8601 in UTC (2026-08-14T10:52:11Z), or null. Safe for new Date().","api":"Gestic API","scopes":["collection:read","collection:write","lists:read","lists:write","offers:write"],"planned":["POST /lists/{id}/cards -- add copies","DELETE /lists/{id}/cards -- remove copies (by versionid, or by card+finish+condition)"],"version":"v1","errors":{"404 unknown_endpoint":"No such path in v1","401 no_credential":"No Authorization header and no session","409 snapshot_changed":"That price-guide snapshot is no longer kept: read metadata again","shape":"{\"error\":\"code\",\"message\":\"...\"} -- branch on the code, not the text","400 bad_json":"The body is not one strict JSON object in UTF-8","401 invalid_token":"The token is unknown, revoked or expired","409 cursor_expired":"The cursor is too old or from before a server update: start again","400 duplicate_request_id":"Two items share a requestId","503 unavailable":"Temporarily unable to serve; retry","403 forbidden_scope":"The key lacks the scope this endpoint needs","400 invalid_cursor":"Not a cursor this API issued for this walk","400 invalid_limit":"limit is not 1-500","413 body_too_large":"Body over 256 KiB","404 not_found":"No such list among yours","403 plan_required":"From 2027-01-01: the account's plan does not include API keys","429 rate_limited":"Too many requests this minute","403 cross_site":"A cookie-authenticated write that did not come from gestic.org","400 unsupported_api_version":"apiVersion is not the number 1","400 invalid_request":"A field is missing or invalid; the message names the item","400 bad_scopes":"A scope in the request is not one we define","409 inventory_changed":"The inventory changed during a paged walk: start again"},"limits":{"maxItems":500,"accountRateLimitPerMinute":120,"maxBodyBytes":262144,"cursorTtlSeconds":900,"maxPageSize":500,"maxResponseBytes":2097152,"maxPrice":"99999.99"},"base":"https://gestic.org/api/v1","authentication":{"browserExtensions":"A packaged browser extension (chrome-extension:// or moz-extension:// origin) may call GET /me, GET /lists, GET /lists/{listId}/cards, GET /cardmarket/metadata, POST /cardmarket/products/resolve and POST /cardmarket/quotes with Authorization: Bearer and credentials: \"omit\". Such a call is authenticated by its key only: the Gestic browser session is never used for it. No CORS headers are sent, and none are needed with the extension's host permission for https://gestic.org/*; other routes and other sites' web pages stay refused.","session":"A signed-in gestic.org browser session also authenticates GET requests; cookie-authenticated writes must be same-origin.","plans":"From 2027-01-01 keys need the Advanced or Elite plan; until 2026-12-31 every plan has them. Revoking a key always works.","header":"Authorization: Bearer gst_<64 hex>","createKey":"POST /tokens with your login and the SHA-256 hex of your password"}}