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

  1. Open Settings, then Seller API.
  2. Read and accept the terms.
  3. Create a key and copy it right away, because it is shown once.
  4. 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

Endpoints. Paths are relative to https://api.doubleholo.com/api/v1/seller-api.
AreaMethodPathWhat it doesIdempotency-Key
AccountGET/meYour account, level, limits, and terms versionNot accepted
AccountGET/usageToday's usage against each daily capNot accepted
CardsGET/cards/searchFind card ids to list againstNot accepted
AsksGET/asksList your asksNot accepted
AsksPOST/asksCreate an askRequired
AsksPOST/asks/bulkCreate up to 50 asks with a result per rowRequired
AsksPOST/asks/bulk-priceReprice up to 100 asksOptional
AsksGET/asks/{id}Get one askNot accepted
AsksPATCH/asks/{id}Edit price, shipping, quantity, description, or offersOptional
AsksDELETE/asks/{id}Cancel an askOptional
PhotosGET/asks/{ask_id}/photosList an ask's photosNot accepted
PhotosPOST/asks/{ask_id}/photosUpload JPEG, PNG, or WebP photos up to 10 MB and 50 megapixels eachNot accepted
PhotosPUT/asks/{ask_id}/photos/orderReorder an ask's photosOptional
PhotosDELETE/asks/{ask_id}/photos/{id}Delete a photoOptional
OrdersGET/ordersList your sold ordersNot accepted
OrdersGET/orders/{id}Get one sold orderNot accepted
OrdersPOST/orders/{id}/shipMark shipped with trackingOptional
OrdersPOST/orders/{id}/cancelCancel an unshipped order; the buyer refund followsRequired
OrdersPOST/orders/{id}/refundsRefund all or part of an orderRequired
LabelsPOST/orders/{id}/shipping/ratesQuote shipping label ratesNot accepted
LabelsPOST/orders/{id}/shipping/labelBuy a label; its cost comes out of this order's payoutRequired
LabelsGET/orders/{id}/shipping/labelGet the label and trackingNot accepted
LabelsDELETE/orders/{id}/shipping/labelVoid the label; its cost is returned only if the carrier refund is acceptedRequired

Download the OpenAPI file

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

Rate limits by seller level
LimitLevel 2Expedited (Level 3)Gold (Level 4)
Requests per minute30120300
Requests per hour6004,00010,000

Daily caps

Daily caps by seller level
LimitLevel 2Expedited (Level 3)Gold (Level 4)
New listings1001,0005,000
Listing edits, price changes, cancels, and photo changes5005,00020,000
Photo uploads2002,00010,000
Shipping labels bought252001,000
Shipping labels voided51030
Order cancels31550
Refunds31550
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_limited with a Retry-After header.
  • A capped action over its daily cap gets 429 daily_limit_reached with the action, the cap, what you used, and when it resets.
  • Responses to authenticated requests carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-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 (field amount_cents, with the limit). A cancel whose refund alone is larger than that limit is refused with invalid_state.
  • A request that is refused before anything changes does not use up a cap.

Errors

Every error uses one shape: { "error": { "code": "...", "message": "..." } }.

Error codes, their HTTP status, and what they mean
CodeHTTP statusWhat it means
authentication_required401The Authorization or X-DH-User-Id header is missing or malformed.
invalid_credentials401The key or user id is not valid, or the key was revoked.
seller_api_unavailable503The Seller API is not open for this account yet.
upstream_unavailable503We could not check your seller account with our payment provider. Retry after the Retry-After time.
photo_processing_unavailable503Photo processing is unavailable or busy right now. Retry after the Retry-After time.
account_restricted403The account is banned or cannot sell, and its keys were revoked.
onboarding_required403Seller setup is not finished; missing lists what is left.
level_required403The Seller API opens at seller level 2.
terms_required403Accept the current Seller API terms in your seller settings.
rate_limited429Too many requests in this minute or hour; wait for the reset.
daily_limit_reached429You reached today's cap for this action; it resets at midnight UTC.
not_found404The ask, order, photo, label, or key is not yours or does not exist.
validation_failed422A field is missing or not valid; field names it.
unknown_parameter422The request sent a field this endpoint does not take.
idempotency_key_required400This endpoint needs an Idempotency-Key header.
idempotency_key_reused422This Idempotency-Key was used for a different request.
idempotency_in_progress409A request with this Idempotency-Key is still running.
seller_level_limit422Your seller level does not allow this many listings, this much listed value, or this price.
invalid_state422The ask or order is not in a state that allows this action.
payment_failed402The payment provider could not complete the refund.
price_confirmation_required422The price looks far from the market; resend with price_confirmed true to list anyway.
duplicate_refund409A refund of this amount was just issued on this order.
refund_not_allowed422This order cannot be refunded from the API, for example after payout.
rate_not_for_order422The rate was not quoted for this order; quote rates again.
platform_label_required422This order must ship on a Double Holo label.
label_already_purchased409This order already has a label, or one is being bought.
label_cost_exceeds_payout422This label costs more than your payout for this order; choose a cheaper rate or ship it yourself.
max_active_keys422You already have two active keys; revoke one to create another.
terms_version_mismatch422These are not the current Seller API terms; reload and accept the current version.
internal_error500Something 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 /asks as 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_unavailable with a Retry-After header. 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.