---
title: "List currently USABLE discount codes/coupons, optionally filtered by shop_domain (e.g. \"otto.de\"). A code is withheld from `results` when it is EXPIRED (valid_until has passed), when a real checkout test PROVED it does not work (verification_status \"verified_rejected\"), or when a display policy suppresses it on this channel/country. Nothing is withheld silently: `total_count` is the REAL number of codes the query found before any of that (for the collection list the full count over everything, never just the window), `examined` is how many codes this call looked at, and `withheld` breaks the difference down by reason ({expired, verified_rejected, reserved_test_domain, policy}) — examined minus results.length always equals the sum of `withheld`, and `note` states the same balance in one sentence. WITHOUT shop_domain the call returns one window of at most `limit` codes (default 100, max 100) starting at `offset` (default 0), ordered by a stable id: `truncated` is true when more codes lie beyond it and `next_offset` is the offset to pass for the next page (null on the last page) — page until `truncated` is false to see every code. limit/offset are rejected together with shop_domain, which already returns every code of that shop. So an empty `results` NEVER has to be guessed at: read `total_count` and `note` to tell \"this shop has no codes at all\" apart from \"it has codes and every one of them is proven dead\". An UNKNOWN or internal-review shop_domain is a different answer again — it is an error (\"shop not found\"), not an empty list; and a backend failure is reported as a failure, never as an empty list. Each result carries validUntil: an ISO timestamp when the code has a stated expiry, or null when it is open-ended — null means unlimited, NOT unknown-and-expired. Re-check validUntil against the current time before relaying a code. Each result ALSO carries verificationStatus, the verdict of a real checkout test against this exact code: \"verified_applied\" means the code worked in an actual checkout attempt — safe to recommend (the response carries no stored proof artifact, so do not promise a screenshot or receipt); \"unverified\" means no proof exists either way yet — treat it as unknown, NOT as a negative signal, and do not imply it was tested and failed. (\"verified_rejected\" is defined as before but no longer appears in `results` — it is counted in `withheld.verified_rejected` instead.) lastVerifiedAt is the ISO timestamp of that most recent test, or null if the code was never tested. A non-null lastVerifiedAt on an \"unverified\" code means it WAS tested but the test was inconclusive — it is not the same as a code that was never attempted."
method: POST
path: "/tool/list_coupons"
---

# List currently USABLE discount codes/coupons, optionally filtered by shop_domain (e.g. "otto.de"). A code is withheld from \`results\` when it is EXPIRED (valid_until has passed), when a real checkout test PROVED it does not work (verification_status "verified_rejected"), or when a display policy suppresses it on this channel/country. Nothing is withheld silently: \`total_count\` is the REAL number of codes the query found before any of that (for the collection list the full count over everything, never just the window), \`examined\` is how many codes this call looked at, and \`withheld\` breaks the difference down by reason ({expired, verified_rejected, reserved_test_domain, policy}) — examined minus results.length always equals the sum of \`withheld\`, and \`note\` states the same balance in one sentence. WITHOUT shop_domain the call returns one window of at most \`limit\` codes (default 100, max 100) starting at \`offset\` (default 0), ordered by a stable id: \`truncated\` is true when more codes lie beyond it and \`next_offset\` is the offset to pass for the next page (null on the last page) — page until \`truncated\` is false to see every code. limit/offset are rejected together with shop_domain, which already returns every code of that shop. So an empty \`results\` NEVER has to be guessed at: read \`total_count\` and \`note\` to tell "this shop has no codes at all" apart from "it has codes and every one of them is proven dead". An UNKNOWN or internal-review shop_domain is a different answer again — it is an error ("shop not found"), not an empty list; and a backend failure is reported as a failure, never as an empty list. Each result carries validUntil: an ISO timestamp when the code has a stated expiry, or null when it is open-ended — null means unlimited, NOT unknown-and-expired. Re-check validUntil against the current time before relaying a code. Each result ALSO carries verificationStatus, the verdict of a real checkout test against this exact code: "verified_applied" means the code worked in an actual checkout attempt — safe to recommend (the response carries no stored proof artifact, so do not promise a screenshot or receipt); "unverified" means no proof exists either way yet — treat it as unknown, NOT as a negative signal, and do not imply it was tested and failed. ("verified_rejected" is defined as before but no longer appears in \`results\` — it is counted in \`withheld.verified_rejected\` instead.) lastVerifiedAt is the ISO timestamp of that most recent test, or null if the code was never tested. A non-null lastVerifiedAt on an "unverified" code means it WAS tested but the test was inconclusive — it is not the same as a code that was never attempted.

`POST /tool/list_coupons`

## Request

- Base URL: `https://api.openshopgraph.com`
- URL: `https://api.openshopgraph.com/tool/list_coupons`
- Auth: one of:
  - none
  - API key in header `x-api-token`

## Request body

- object
  - `shop_domain` string
  - `limit` number
  - `offset` number

## Response `200`

OK

## Other responses

- `400`: invalid input
- `404`: not found

---

[API](https://skmtc.dev/openshopgraph/apis/openshopgraph-mcp-server.md) · [All operations](https://skmtc.dev/openshopgraph/apis/openshopgraph-mcp-server/llms.txt) · [OpenAPI document](https://skmtc.dev/openshopgraph/apis/openshopgraph-mcp-server/revisions/5a5ef86dcc0a?raw)
