Skip to content
Prepaid, no subscription trap. Top up a balance and we draw the monthly fee from it.
tcggraph

Create a price watch

Fire price.threshold.crossed when one quote of one card crosses a price.

POSThttps://api.tcggraph.com/v1/watchesNo credits · metering

A watch fires on the move that crosses the threshold, not on every day the price sits beyond it: a price already below a below threshold when the watch is created fires only after it goes back above and falls through again.

A localized printing quoted as its English twin is watched on the English printing, because that is where its quotes are. The response's cardId is the English id and requestedCardId is the one you sent.

Plans cap how many watches an account holds: 500 on Starter, 25,000 on Growth, 100,000 on Scale.

Request body

JSON, with Content-Type: application/json. A body that is not valid JSON is a 400 reading “Send a JSON body.”

ParameterTypeDescription
cardIdrequiredstringThe card to watch.
sourcerequiredstringThe marketplace. The threshold is in its currency: EUR on Cardmarket, USD elsewhere.

One of: cardmarket, tcgplayer, cardkingdom, manapool

directionrequiredstringFire when the price rises above, or falls below, the threshold.

One of: above, below

thresholdnumberThe price in major units (12.5). Send this or thresholdCents.
thresholdCentsintegerThe price in minor units (1250). Wins over threshold.
finishstringWhich printing's quote. Without it, the finish of the card's current quote on the source and list, else normal.

One of: normal, foil, etched, reverse-holo, cosmos-holo, pokeball, masterball, energy-symbol, duskball, loveball, staff, prerelease, league, 1st-edition, 1st-edition-foil, shadowless, limited

Default: the finish the card is quoted in on source

listTypestringbuylist only on cardkingdom, the one source that publishes one.

One of: retail, buylist

Default: retail

Response

201 The watch.

FieldTypeDescription
dataWatch (Watch reference)A Watch with the card's game and name.
data.gamestringThe card's game.

One of: pokemon, magic-the-gathering, one-piece, yugioh, disney-lorcana, star-wars-unlimited, digimon, grand-archive, dragon-ball-super

data.namestringThe card's name.
data.requestedCardIdstringThe id you sent, when the watch was placed on its English twin.

Absent unless it applies.

Examples

Real responses from the production API, recorded on 2026-09-24 by the script that also checks every field above against them. Arrays are shown in full.

Tell me when Charizard ex drops under €20

curl -X POST "https://api.tcggraph.com/v1/watches" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardId":"pkm_sv3pt5_6","source":"cardmarket","direction":"below","threshold":20}'
201 · X-Credits-Cost: 0
{
  "data": {
    "id": "c245e388-33da-446a-89b1-b6afc908cf05",
    "cardId": "pkm_sv3pt5_6",
    "source": "cardmarket",
    "finish": "foil",
    "listType": "retail",
    "currency": "EUR",
    "direction": "below",
    "thresholdCents": 2000,
    "threshold": 20,
    "lastFiredAt": null,
    "createdAt": "2026-09-24T07:47:45.432Z",
    "game": "pokemon",
    "name": "Charizard ex"
  }
}

A German printing, watched on its English twin

curl -X POST "https://api.tcggraph.com/v1/watches" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardId":"pkm_sv3pt5_6_de","source":"cardmarket","direction":"above","threshold":100}'
201 · X-Credits-Cost: 0
{
  "data": {
    "id": "37f6d8df-ac25-438c-90a8-7cfd63892f80",
    "cardId": "pkm_sv3pt5_6",
    "source": "cardmarket",
    "finish": "foil",
    "listType": "retail",
    "currency": "EUR",
    "direction": "above",
    "thresholdCents": 10000,
    "threshold": 100,
    "lastFiredAt": null,
    "createdAt": "2026-09-24T07:47:45.687Z",
    "game": "pokemon",
    "name": "Charizard ex",
    "requestedCardId": "pkm_sv3pt5_6_de"
  }
}

Errors

Every error has the same body; switch on error.code. Any endpoint can also answer 401, 402, 403 or 429 for reasons of key, plan or rate; those are on Errors.

StatusCodeWhen
400invalid_requestA field is missing or invalid, including a buylist watch on a source without a buylist.
403forbiddenThe account holds its plan's allowance of watches. details carries allowance and held.
404not_foundNo card has that id.
409conflictThe same watch (card, source, finish, list, direction and threshold) exists.

Buylist on Cardmarket

curl -X POST "https://api.tcggraph.com/v1/watches" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardId":"pkm_sv3pt5_6","source":"cardmarket","direction":"above","threshold":5,"listType":"buylist"}'
400
{
  "error": {
    "code": "invalid_request",
    "message": "Cardmarket publishes no buylist; only cardkingdom does.",
    "details": {
      "field": "listType",
      "allowed": [
        "retail"
      ]
    }
  }
}

Unknown card

curl -X POST "https://api.tcggraph.com/v1/watches" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardId":"pkm_nope_1","source":"cardmarket","direction":"below","threshold":5}'
404
{
  "error": {
    "code": "not_found",
    "message": "No card with id \"pkm_nope_1\"."
  }
}

Same watch twice

curl -X POST "https://api.tcggraph.com/v1/watches" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardId":"pkm_sv3pt5_6","source":"cardmarket","direction":"below","threshold":20}'
409
{
  "error": {
    "code": "conflict",
    "message": "That watch already exists on this account."
  }
}