Double Holo Seller API: Manage Pokemon Card Listings and Orders With Code
Updated October 22, 2026
As of October 2026, the Double Holo Seller API lets marketplace sellers at seller Level 2 or higher manage their own asks (your listings for sale), listing photos, and sold orders from their own software. Your software signs in with an API key, which works like a password for software, plus your user ID. Listing and order rules are the same as on the Double Holo website. The API adds daily caps, a shipping label cost guard, and JSON or multipart request bodies only.
Who can use the Seller API
An API is a way for software to work with your Double Holo account directly. To use the Seller API you need all of these:
- An approved seller account.
- A Stripe payout account that is set up.
- A verified phone number.
- Seller Level 2 or higher. See how seller levels work.
- Accepted Seller API terms.
Buying, bids, offers on other sellers' listings, store credit, and payouts are not part of the Seller API.
How to enroll
- Open Settings, then Seller API.
- Read and accept the terms.
- Create a key and copy it right away, because it is shown once.
- You can have up to 2 active keys.
Enrollment opens soon. Everything on this page describes how it will work.
Authentication
Send two headers with every request: Authorization: Bearer <your API key> and X-DH-User-Id: <your user ID>. Your API key is dh_sk_live_ followed by 40 lowercase hex characters (0 to 9 and a to f). Your user ID is shown in Seller API settings. Keep your key on a server you control. The API does not accept calls made from a web browser, so a key placed in a web page will not work and could leak.
curl
curl https://api.doubleholo.com/api/v1/seller-api/me \
-H "Authorization: Bearer $DH_API_KEY" \
-H "X-DH-User-Id: $DH_USER_ID"JavaScript (Node 18 or later)
async function main() {
const res = await fetch('https://api.doubleholo.com/api/v1/seller-api/me', {
headers: {
Authorization: `Bearer ${process.env.DH_API_KEY}`,
'X-DH-User-Id': process.env.DH_USER_ID,
},
})
if (!res.ok) throw new Error(`Seller API error ${res.status}`)
console.log(await res.json())
}
main()Python
import os
import requests
resp = requests.get(
"https://api.doubleholo.com/api/v1/seller-api/me",
headers={
"Authorization": f"Bearer {os.environ['DH_API_KEY']}",
"X-DH-User-Id": os.environ["DH_USER_ID"],
},
timeout=30,
)
resp.raise_for_status()
print(resp.json())Every response carries Cache-Control: no-store. A wrong key, a wrong user ID, and a revoked key all return the same 401 invalid_credentials answer, so the response never says which part was wrong. Twenty failed sign-ins from one network in 10 minutes block requests without a valid key from that network for an hour, with a 429 rate_limited answer.
Endpoint reference
| Area | Method | Path | What it does | Idempotency-Key |
|---|---|---|---|---|
| Account | GET | /me | Your account, level, limits, and terms version | Not accepted |
| Account | GET | /usage | Today's usage against each daily cap | Not accepted |
| Cards | GET | /cards/search | Find card ids to list against | Not accepted |
| Asks | GET | /asks | List your asks | Not accepted |
| Asks | POST | /asks | Create an ask | Required |
| Asks | POST | /asks/bulk | Create up to 50 asks with a result per row | Required |
| Asks | POST | /asks/bulk-price | Reprice up to 100 asks | Optional |
| Asks | GET | /asks/{id} | Get one ask | Not accepted |
| Asks | PATCH | /asks/{id} | Edit price, shipping, quantity, description, or offers | Optional |
| Asks | DELETE | /asks/{id} | Cancel an ask | Optional |
| Photos | GET | /asks/{ask_id}/photos | List an ask's photos | Not accepted |
| Photos | POST | /asks/{ask_id}/photos | Upload JPEG, PNG, or WebP photos up to 10 MB and 50 megapixels each | Not accepted |
| Photos | PUT | /asks/{ask_id}/photos/order | Reorder an ask's photos | Optional |
| Photos | DELETE | /asks/{ask_id}/photos/{id} | Delete a photo | Optional |
| Orders | GET | /orders | List your sold orders | Not accepted |
| Orders | GET | /orders/{id} | Get one sold order | Not accepted |
| Orders | POST | /orders/{id}/ship | Mark shipped with tracking | Optional |
| Orders | POST | /orders/{id}/cancel | Cancel an unshipped order; the buyer refund follows | Required |
| Orders | POST | /orders/{id}/refunds | Refund all or part of an order | Required |
| Labels | POST | /orders/{id}/shipping/rates | Quote shipping label rates | Not accepted |
| Labels | POST | /orders/{id}/shipping/label | Buy a label; its cost comes out of this order's payout | Required |
| Labels | GET | /orders/{id}/shipping/label | Get the label and tracking | Not accepted |
| Labels | DELETE | /orders/{id}/shipping/label | Void the label; its cost is returned only if the carrier refund is accepted | Required |
PATCH /asks/{id} edits an ask that is active or partially filled. Editing an expired ask also relists it, which puts it back on sale. A relist follows the same checks as any ask call, and your phone number must be verified.
Money is in integer cents (price_cents, amount_cents). The marketplace is US dollars only. Send JSON; use multipart form data only to upload photos. A JSON body can be up to 1 MB and a photo upload up to 105 MB. A body larger than its limit, or a chunked body sent without a length, answers 413. A field an endpoint does not take answers 422 unknown_parameter. Languages are lowercase keys such as english, japanese, and chinese_traditional.
Rate limits and daily caps
Rate limits
| Limit | Level 2 | Expedited (Level 3) | Gold (Level 4) |
|---|---|---|---|
| Requests per minute | 30 | 120 | 300 |
| Requests per hour | 600 | 4,000 | 10,000 |
Daily caps
| Limit | Level 2 | Expedited (Level 3) | Gold (Level 4) |
|---|---|---|---|
| New listings | 100 | 1,000 | 5,000 |
| Listing edits, price changes, cancels, and photo changes | 500 | 5,000 | 20,000 |
| Photo uploads | 200 | 2,000 | 10,000 |
| Shipping labels bought | 25 | 200 | 1,000 |
| Shipping labels voided | 5 | 10 | 30 |
| Order cancels | 3 | 15 | 50 |
| Refunds | 3 | 15 | 50 |
| Refunded dollars (refunds and cancels) | $1,000 | $3,000 | $25,000 |
- Limits depend on your seller level.
- Daily caps reset at midnight UTC.
- Daily caps count Seller API calls only. Anything you do on the website or app is not counted.
- A request over a rate limit gets 429
rate_limitedwith aRetry-Afterheader. - A capped action over its daily cap gets 429
daily_limit_reachedwith the action, the cap, what you used, and when it resets. - Responses to authenticated requests carry
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset. - The 429 for too many failed sign-ins and the 413 for a body that is too large are refused before the API reads the request, so they carry no rate limit headers.
- A cancel counts its refund toward the refunded dollars cap.
- A refund larger than the whole daily refund limit is refused with 422
validation_failed(fieldamount_cents, with the limit). A cancel whose refund alone is larger than that limit is refused withinvalid_state. - A request that is refused before anything changes does not use up a cap.
Errors
Every error uses one shape: { "error": { "code": "...", "message": "..." } }.
| Code | HTTP status | What it means |
|---|---|---|
| authentication_required | 401 | The Authorization or X-DH-User-Id header is missing or malformed. |
| invalid_credentials | 401 | The key or user id is not valid, or the key was revoked. |
| seller_api_unavailable | 503 | The Seller API is not open for this account yet. |
| upstream_unavailable | 503 | We could not check your seller account with our payment provider. Retry after the Retry-After time. |
| photo_processing_unavailable | 503 | Photo processing is unavailable or busy right now. Retry after the Retry-After time. |
| account_restricted | 403 | The account is banned or cannot sell, and its keys were revoked. |
| onboarding_required | 403 | Seller setup is not finished; missing lists what is left. |
| level_required | 403 | The Seller API opens at seller level 2. |
| terms_required | 403 | Accept the current Seller API terms in your seller settings. |
| rate_limited | 429 | Too many requests in this minute or hour; wait for the reset. |
| daily_limit_reached | 429 | You reached today's cap for this action; it resets at midnight UTC. |
| not_found | 404 | The ask, order, photo, label, or key is not yours or does not exist. |
| validation_failed | 422 | A field is missing or not valid; field names it. |
| unknown_parameter | 422 | The request sent a field this endpoint does not take. |
| idempotency_key_required | 400 | This endpoint needs an Idempotency-Key header. |
| idempotency_key_reused | 422 | This Idempotency-Key was used for a different request. |
| idempotency_in_progress | 409 | A request with this Idempotency-Key is still running. |
| seller_level_limit | 422 | Your seller level does not allow this many listings, this much listed value, or this price. |
| invalid_state | 422 | The ask or order is not in a state that allows this action. |
| payment_failed | 402 | The payment provider could not complete the refund. |
| price_confirmation_required | 422 | The price looks far from the market; resend with price_confirmed true to list anyway. |
| duplicate_refund | 409 | A refund of this amount was just issued on this order. |
| refund_not_allowed | 422 | This order cannot be refunded from the API, for example after payout. |
| rate_not_for_order | 422 | The rate was not quoted for this order; quote rates again. |
| platform_label_required | 422 | This order must ship on a Double Holo label. |
| label_already_purchased | 409 | This order already has a label, or one is being bought. |
| label_cost_exceeds_payout | 422 | This label costs more than your payout for this order; choose a cheaper rate or ship it yourself. |
| max_active_keys | 422 | You already have two active keys; revoke one to create another. |
| terms_version_mismatch | 422 | These are not the current Seller API terms; reload and accept the current version. |
| internal_error | 500 | Something went wrong on our side; retry later. |
If Double Holo pauses the Seller API, requests answer 503 seller_api_unavailable until it reopens. Retry a 503 upstream_unavailable after the Retry-After time.
Idempotency
These calls need an Idempotency-Key header and answer 400 idempotency_key_required without one: POST /asks (create an ask), POST /asks/bulk (create up to 50 asks), POST /orders/{id}/cancel (cancel an order), POST /orders/{id}/refunds (refund an order), POST /orders/{id}/shipping/label (buy a label), and DELETE /orders/{id}/shipping/label (void a label).
These calls accept one but do not need it: PATCH /asks/{id}, POST /asks/bulk-price, DELETE /asks/{id}, PUT /asks/{ask_id}/photos/order, DELETE /asks/{ask_id}/photos/{id}, and POST /orders/{id}/ship. Every other call, including the photo upload, does not take one and ignores the header (the Idempotency-Key column above shows this as Not accepted).
The key is a unique value you make for each action, such as a UUID. If a network error leaves you unsure whether the call worked, send it again with the same key and the same body: you get the first answer back instead of a second action. Keys are remembered for 48 hours. The same key with a different body returns 422 idempotency_key_reused, and a retry while the first call is still running returns 409 idempotency_in_progress.
Cancels, refunds, label purchases, label voids, and creating asks (single or bulk) never run twice for one key. They answer 409 idempotency_in_progress until the first request settles. If one of them fails with a server error (5xx), or the connection drops before you get an answer, check the order with GET /orders/{id} (or your asks with GET /asks) first and then use a new key, because the old key keeps its stored answer or stays in progress. Other writes that failed with a 5xx can be retried with the same key after 2 minutes.
curl: create an ask with a new key
# Make the key once per action. If you retry, send the same key again.
KEY=$(uuidgen)
curl -X POST https://api.doubleholo.com/api/v1/seller-api/asks \
-H "Authorization: Bearer $DH_API_KEY" \
-H "X-DH-User-Id: $DH_USER_ID" \
-H "Idempotency-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"card_id":123,"price_cents":1250,"condition":"NM"}'JavaScript (Node 18 or later): create an ask with a new key
const crypto = require('node:crypto')
async function createAsk() {
// Make the key once per action. If you retry, send the same key again.
const key = crypto.randomUUID()
const res = await fetch('https://api.doubleholo.com/api/v1/seller-api/asks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DH_API_KEY}`,
'X-DH-User-Id': process.env.DH_USER_ID,
'Idempotency-Key': key,
'Content-Type': 'application/json',
},
body: JSON.stringify({ card_id: 123, price_cents: 1250, condition: 'NM' }),
})
if (!res.ok) throw new Error(`Seller API error ${res.status}`)
console.log(await res.json())
}
createAsk()Python: create an ask with a new key
import os
import uuid
import requests
# Make the key once per action. If you retry, send the same key again.
key = str(uuid.uuid4())
resp = requests.post(
"https://api.doubleholo.com/api/v1/seller-api/asks",
headers={
"Authorization": f"Bearer {os.environ['DH_API_KEY']}",
"X-DH-User-Id": os.environ["DH_USER_ID"],
"Idempotency-Key": key,
},
json={"card_id": 123, "price_cents": 1250, "condition": "NM"},
timeout=30,
)
resp.raise_for_status()
print(resp.json())The request body for each call is in the OpenAPI file.
Pagination
List calls return newest first, up to 100 items (limit, 1 to 100, default 50) and a next_cursor. Pass it back as cursor to get the next page, and stop when next_cursor is null. There are no webhooks yet: to find changes, call GET /orders or GET /asks with updated_since set to the time of your last check.
Listing photos
- Upload JPEG, PNG, or WebP photos, up to 10 MB each.
- A photo can be up to 50 megapixels (its width times its height in pixels). For example, 8,000 by 6,000 pixels is 48 megapixels.
- You can add up to 10 photos per listing.
- Listings of $250 and above need at least one photo, unless your account has a photo waiver.
- Send photos with
POST /asksas multipart form data (photos[]). - The bulk call takes no photos, so it cannot create a listing of $250 or more for an account without a waiver.
- You cannot delete the last photo of a listing of $250 and above, unless the ask is graded and has a cert number or cert images, or your account has a photo waiver.
- Photos are re-encoded, and location and camera details are removed before a photo is stored.
- Photo ids change after you reorder, so read them from the response.
- HEIC photos and importing a photo from a link are not supported.
- If photo processing is temporarily unavailable, the call answers 503
photo_processing_unavailablewith aRetry-Afterheader. Try again after that time.
Orders and shipping labels
The API only shows orders you sold. The buyer's name and shipping address appear only while an order is waiting to ship; buyer email, phone, and account details never appear. You can mark an order shipped with a tracking number, get label rates, buy a label, or void it. The cost of a label comes out of your payout for that order.
Each rate says whether it costs more than your payout for the order, and the API will not buy such a label (it answers label_cost_exceeds_payout); choose a cheaper rate or ship it yourself. A voided label keeps its cost unless the carrier accepts the label refund. On an order that is later cancelled or fully refunded, a kept cost becomes a debt that is taken from your later payouts. You can cancel an order that has not shipped, and refund all or part of an order that has not been paid out. Buyers get the same notifications they would get if you did these things on the website.
If your Stripe setup, seller approval, or seller level lapses, your keys still work for the orders and labels you already sold. A ban or a selling restriction revokes every key.
Changelog
- October 22, 2026: Version 1.0 of the Seller API docs published. Enrollment opens in stages.
Frequently asked questions
Who can use the Double Holo Seller API?
Any Double Holo marketplace seller at seller Level 2 or higher whose seller account is approved, whose Stripe payout account is set up, and whose phone number is verified. You also accept the Seller API terms before you create a key.
What is an API key?
An API key is a long secret code that works like a password for software. Your software sends it with every request so Double Holo knows the request comes from you. Anyone with your key can manage your listings and orders, so keep it private and revoke it in Settings if it leaks.
Can I use the Seller API to buy cards or place bids?
No. The Seller API covers your own asks, listing photos, and the orders you sold. Buying, bids, offers on other sellers' listings, store credit, and payouts are not available through it.
What happens if I reach a daily cap?
Calls for that action return a 429 error with the code daily_limit_reached until the caps reset at midnight UTC. Everything else keeps working, and anything you do on the website or app is not affected. A refund larger than the whole daily refund limit is refused with a 422 validation_failed error that names the limit, and a cancel whose refund alone is larger than the limit is refused with invalid_state.
Does the Seller API send webhooks?
Not yet. Check for changes by calling the order and ask lists with the updated_since filter set to the time of your last check.
What happens to my keys if my account is restricted?
A ban or a selling restriction revokes every key right away. If the restriction is lifted, create a new key in Settings. You can still ship orders you already sold on the website.
Can I upload photos from a link?
No. Upload the image file itself: JPEG, PNG, or WebP, up to 10 MB and 50 megapixels.
How many keys can I have?
Up to 2 active keys at a time, so you can switch to a new key before revoking the old one.