Skip to content
TCGPlaytestdocsDashboard
Documentation Build your integration
Build your integration

Printing credits

Use your balance for printing, with customer shipping and tax paid through Stripe.

Printing credit + customer shipping checkout

Automatic API and widget checkout: keep your existing API key and the normal create → poll → embedded checkout integration. Both server-API carts and standalone widget carts automatically use available partner credit for the full printing cost. The customer pays shipping and fulfillment tax through Stripe. If there is not enough eligible credit, checkout charges the customer for printing, shipping and tax normally. No additional prepaid API calls or demo-app changes are needed.

Accept the prepaid terms and keep enough available credit to cover printing. The platform chooses the payment path when the customer continues to payment, saves it, and reuses it on retries. It never partially spends credit or switches a previously created payment to another funding path. A payment setup error does not silently trigger a second full-price charge. Existing widget snippets use the same automatic credit selection; no API key or frontend change is needed. Test-mode widget previews never spend credit.

Buy printing credit in your partner platform under Buy credits. Accept the prepaid terms, choose an amount and continue to Stripe. Your balance updates after payment confirmation.

You charge the customer for cards in your store. Your USD credit pays TCGPlaytest’s printing cost only. The customer then pays shipping and quoted fulfillment tax directly to TCGPlaytest through Stripe. Printing starts once both are funded. No Stripe Connect account is needed.

Rate limits: all printing-credit endpoints share the 60 requests per API key per minute allowance with uploads, cart creation and order reads. Quote creation, quote GET polling, reserve, release, prepaid orders, balance/ledger reads and retries all count. Only authenticated cart-status GETs are exempt. Poll quotes every five seconds or slower (12 requests/minute per quote at five seconds), budget across concurrent orders, and honor Retry-After on HTTP 429. Retry mutations with their original body and idempotency key where required.

Optional advanced integration: reserve credit yourself

Use the endpoints below only when your own store needs to reserve credit before taking a separate card payment. The automatic hosted flow above does not require these steps. Your store remains responsible for any separate retail charge; automatic credit selection does not collect your retail card price for you.

  1. Accept the current prepaid terms in the dashboard and buy $10–$5,000 credit. Keep your existing live partner API key; it works with both Stripe test and live credentials.
  2. Create a cart with settlement: "prepaid". Upload and validate artwork as usual. Poll until open; the cart itself has no checkout URL.
  3. Quote the final shipping address. credit_cents is the printing cost; customer_payment_cents is shipping plus fulfillment tax. Amounts are integer USD cents at current prices, without coupons or affiliate rewards.
  4. Reserve printing credit before charging for cards. The quote and unused reservation expire at expires_at, up to 30 minutes; retries do not extend it. Stop on insufficient credit. Do not also collect the quoted shipping/fulfillment tax in your store.
  5. Collect the rights statement and quality acknowledgment. Confirm your store’s card-sale payment on your server. Submit the reserved quote with saved acceptance and a stable idempotency key.
  6. Submission spends printing credit once and returns 202, order_id: null, a quote status URL and a checkout_url. Redirect that customer to the returned Stripe URL for shipping and tax. Checkout lasts one hour after submission.
  7. Poll the quote through your server every five seconds or slower, honoring rate limits. Once both payments are confirmed, order_id identifies the manufacturing order. Use the existing order API and signed order webhooks to track printing/shipping.

Example: $20 printing, $6.95 shipping and $2 fulfillment tax spends $20 partner credit and collects $8.95 from the customer through Stripe. This tax calculation covers the quoted fulfillment amounts; it does not calculate your retail markup or replace your own retail tax setup.

I confirm I own these images or have permission to reproduce them, and I accept responsibility for the content of this order.

acceptance.accepted_at is the actual customer acceptance time, on/after cart creation. Use terms_version: "2026-10-04-v2", the shipping email and image_quality_accepted: true when warnings were shown and acknowledged. Persist the exact body/key; do not invent acceptance on the customer’s behalf.

EndpointPurpose
GET /v1/prepaid/balanceAvailable/reserved/total credit and latest 50 ledger entries. Use next_before as the before query parameter for older entries.
POST /v1/prepaid/quotesJSON {cart_id, shipping}. Requires Idempotency-Key; immutable address and pricing.
GET /v1/prepaid/quotes/{id}Read customer_payment_status, checkout_url while open and order_id once manufacturing is funded.
POST /v1/prepaid/quotes/{id}/reserveNo body. Reserve printing cost once. Repeated calls are safe.
POST /v1/prepaid/quotes/{id}/releaseNo body. Release unused pre-submission credit. Released quotes cannot be reserved again.
POST /v1/prepaid/ordersJSON {quote_id, acceptance}; requires Idempotency-Key. Spends printing credit and returns customer shipping/tax checkout. Manufacturing waits for verified payment.

Shipping: required name, email, line1, city, postal_code, country; optional line2, phone. State is required for US/CA/AU. Use a supported two-letter country. Changing the address requires a new quote/key before submission.

Payment states: pending/open: wait or resume the same checkout; paid with order_id: funded; expired: no manufacturing and printing credit returned; review/refund_pending/refunded: stop and contact support. Quote status consumed means printing credit was spent, not that manufacturing is paid.

Retries and expiry: retry ambiguous failures with the saved body/key or read the quote. Never create a second checkout because of a timeout. Submitting requires more than 65 minutes of cart life remaining. After submission, credit cannot be released manually; Stripe-confirmed unpaid expiry returns it automatically. A fresh attempt needs a new cart. Your store must handle its separate card-sale refund or continuation. An uncertain checkout creation that cannot be recovered safely needs support reconciliation before another charge.

Payment confirmation: order.paid confirms printing credit and customer shipping/tax payment. It does not verify the card purchase charged by your store. Production delivery retries if commerce is temporarily unavailable. Both hosted payment paths automatically return to the cart’s validated return_url after payment and order confirmation. A short confirmation screen waits for delayed payment notifications. Your custom success page must still verify the order API or signed webhook.

Refunds: contact support to cancel/review fulfillment. An approved full cancellation refund returns printing credit and separately queues the customer’s shipping/tax refund. The partner refunds its own card sale. Reversals put unshipped manufacturing on hold; partial customer refunds require operator review and tax reconciliation. Cancellation alone does not refund credit. Previously funded all-credit orders keep their original refund amounts. No partial credit-refund or cash-withdrawal API is provided.

Testing with Stripe

The platform uses its server-side STRIPE_SECRET_KEY automatically. Keep the same tcgp_live_ partner API key when switching Stripe credentials. Both environments use the same wallet tables and accepted terms; test payments never add real printing credit. Change the Stripe keys and matching webhook secrets on the partner and commerce deployments, then redeploy. No database switch or repeated SQL setup is needed.

After a verified test payment, the quote and order API return mode: "test" and simulated: true. The Test order webhook can receive a simulated order.paid event with data.simulated: true. No production or shipment events are generated. Hosted test carts and the standalone widget still open a non-paying preview.

Platform setup: use Stripe credentials from the same account and environment on both the partner and commerce apps, and set STRIPE_CREDIT_WEBHOOK_SECRET to that environment’s credit webhook secret. Live quotes require Stripe Tax setup. With the current commerce quote handler, prepaid quotes using Stripe test credentials return zero tax without calling Stripe Tax; this does not change live calculations or ordinary checkout tax settings. Keep PARTNER_PREPAID_ENABLED=true. No extra mode variable is needed.

To go live, finish pending test checkouts, switch both apps to live Stripe credentials and the live credit webhook secret, configure live Stripe Tax, and redeploy. Use a tcgp_live_ partner key and buy real credit. Test credit never becomes real credit; any existing live balance stays intact. Each deployment runs one Stripe environment at a time. Use separate deployments if both must remain active.

cURL: complete prepaid flow

cURL · advanced prepaid flow
# YOUR SERVER only. Store TCGP_API_KEY in an environment variable.
API_BASE="https://partner.tcgplaytest.com"
# Keep the same tcgp_live_ partner key when switching Stripe credentials.
# 1. Create artwork cart; poll it using GET /v1/carts/{id} until open.
curl --fail-with-body "$API_BASE/v1/carts" \
  -H "Authorization: Bearer $TCGP_API_KEY" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: shop-order-1042-artwork-v1' \
  -d '{"settlement":"prepaid","external_ref":"shop-order-1042","items":[{"image_url":"https://your-shop.example/front.png","quantity":10}]}'

# Replace CART_ID with the returned cart ID after validation is open.
curl --fail-with-body "$API_BASE/v1/prepaid/quotes" \
  -H "Authorization: Bearer $TCGP_API_KEY" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: shop-order-1042-quote-v1' \
  -d '{"cart_id":"CART_ID","shipping":{"name":"Customer Name","email":"buyer@example.com","line1":"123 Main St","city":"Seattle","state":"WA","postal_code":"98101","country":"US"}}'

# Save the returned quote ID and expires_at. Reserve BEFORE charging customer.
QUOTE_ID="REPLACE_WITH_QUOTE_UUID"
curl --fail-with-body -X POST "$API_BASE/v1/prepaid/quotes/$QUOTE_ID/reserve" \
  -H "Authorization: Bearer $TCGP_API_KEY"

# Confirm payment for CARDS in your store (do not also collect the quoted
# shipping/fulfillment tax). Then prepare and persist order.json:
# {"quote_id":"the saved UUID","acceptance":{"accepted":true,
# "terms_version":"2026-10-04-v2","email":"buyer@example.com",
# "accepted_at":"the actual saved RFC3339 customer acceptance timestamp",
# "image_quality_accepted":true}}
# Show the exact rights statement and any warnings before recording acceptance.
curl --fail-with-body "$API_BASE/v1/prepaid/orders" \
  -H "Authorization: Bearer $TCGP_API_KEY" -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: shop-order-1042-submit-v1' --data-binary @order.json

# HTTP 202: send the customer to checkout_url to pay shipping + fulfillment tax.
# order_id stays null until Stripe confirms payment. Poll the quote on your server:
curl --fail-with-body "$API_BASE/v1/prepaid/quotes/$QUOTE_ID" \
  -H "Authorization: Bearer $TCGP_API_KEY"
# If still open, honor checkout_expires_at; do not create another payment.
# In live mode only, order_id + confirmed funding or signed order.paid confirms manufacturing.
# In test mode these identify a simulated order; mode:test and simulated:true are returned.

# BEFORE submission, if the customer's card purchase fails, release the reservation:
# curl --fail-with-body -X POST "$API_BASE/v1/prepaid/quotes/$QUOTE_ID/release" \
#   -H "Authorization: Bearer $TCGP_API_KEY"
# AFTER submission, release is unavailable. Verified unpaid Stripe expiry returns
# printing credit automatically. Use a new cart for a fresh attempt; handle your
# own card-sale refund separately. Never charge your customer twice.

These snippets replace the submission step. Complete validation, reservation, rights acceptance and your store’s card-sale payment first. Accept HTTP 202, redirect the owning customer to checkout_url, then wait for both payments before showing manufacturing success.

Node.js
// savedOrder is loaded from YOUR database, owned by this customer.
// Its body and key were saved once, after verified customer payment.
const response = await fetch(process.env.TCGP_API_BASE + '/v1/prepaid/orders', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + process.env.TCGP_API_KEY,
    'Content-Type': 'application/json', 'Idempotency-Key': savedOrder.submissionKey },
  body: JSON.stringify(savedOrder.submissionBody)
});
const result = await response.json();
if (!response.ok) throw new Error(result.error.message);
// Save result.quote.id and result.status_url. HTTP 202 is a successful submission.
// If result.checkout_url exists, redirect this customer there for shipping/tax.
// Poll the quote on YOUR server; save order_id only once it is non-null.
// On timeout, retry this exact body and key.
Python
import os, requests
# Load saved_body and saved_submission_key from your database.
# Confirm your customer payment before reaching this step.
response = requests.post(
    os.environ['TCGP_API_BASE'] + '/v1/prepaid/orders',
    headers={'Authorization': 'Bearer ' + os.environ['TCGP_API_KEY'],
             'Idempotency-Key': saved_submission_key},
    json=saved_body, timeout=30)
response.raise_for_status()
order = response.json()
# Save order['quote']['id'] and order['status_url']; 202 means customer payment pending.
# Redirect this customer to order['checkout_url'] when present.
# Poll the quote server-side until order_id exists or payment expires/needs review.
# Reuse the same body/key after network errors.
PHP
<?php
// Load $savedBody and $savedSubmissionKey from your database.
// Call only after your server confirms the customer's payment.
$curl = curl_init(getenv('TCGP_API_BASE') . '/v1/prepaid/orders');
curl_setopt_array($curl, [
  CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('TCGP_API_KEY'),
    'Content-Type: application/json', 'Idempotency-Key: ' . $savedSubmissionKey],
  CURLOPT_POSTFIELDS => json_encode($savedBody, JSON_THROW_ON_ERROR)
]);
$raw = curl_exec($curl);
if ($raw === false) throw new RuntimeException('Network error; retry the saved request.');
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$result = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
if ($status >= 400) throw new RuntimeException($result['error']['message']);
// Persist $result['quote']['id'] and $result['status_url']; accept HTTP 202.
// Redirect the owning customer to $result['checkout_url'] if present.
// Poll the quote through your server. order_id is null until both payments clear.
// Do not generate new acceptance timestamps on retry.

Errors: 402 insufficient_credit: top up before charging for cards. 403 prepaid_terms_required: accept current terms in the dashboard. 403 prepaid_mode_mismatch: use a live partner API key for real payments; that same key also works with Stripe test credentials. 409 reservation_required / quote_expired: obtain a new quote. 409 credit_blocked / payment_reconciliation_required: contact support. 409 idempotency_conflict: instructions changed. Honor Retry-After on 429. Retry network/5xx failures with the same body/key.

24 topics

Get startedOverview

Everything you need to turn artwork into printed cards.

Get startedFirst order in 10 minutes

A runnable sandbox example from test key to verified webhook delivery.

Get startedOrder with AI · MCP

Connect ChatGPT, Claude, Gemini or your developer tools, preview your cards and check out without a TCGPlaytest account or customer API key.

Get startedTest checkout & events

Simulate checkout, verify the order, return to your store and trigger signed test events.

Get startedQuickstart & authentication

Set up your account, protect your API key and make your first integration.

Get startedHow checkout works

Create a cart, wait for artwork validation and open checkout on your website.

Get startedCode examples

Working server examples in your preferred language.

Build your integrationCheckout widget

Connect finished images to a ready-made review and checkout experience.

Build your integrationEmbedded checkout

Keep delivery details and artwork confirmation inside your own website.

Build your integrationCheckout branding

Make checkout feel like your store with saved branding and style presets.

Build your integrationCustom success page

Return customers to your registered website after verified payment.

Build your integrationStripe Connect payouts

Set your card selling price and receive earnings, with a 5% reserve for 15 days.

Build your integrationPrinting credits

Use your balance for printing, with customer shipping and tax paid through Stripe.

API referenceUpload artwork

Request a temporary upload URL. Send file bytes directly to private storage.

API referenceCreate a cart

Submit finished artwork, quantities and your success URL for validation.

API referenceRetrieve a cart

Read validation progress, quality warnings and checkout availability.

API referenceRetrieve an order

Track a funded manufacturing order from payment to shipment.

API referenceWebhooks

Receive signed order updates on your server.

ResourcesArtwork & API limits

File specifications, card limits and request allowances for this deployment.

ResourcesErrors & retries

Understand failures and retry safely without creating duplicate carts.

ResourcesGoing live

Partner launch checklist: verified orders, allowed origins and live checkout.

ResourcesAffiliate codes

Apply your approved affiliate preference to new carts.

ResourcesExpiry & retention

Understand cart lifetimes, artwork storage and cleanup.

ResourcesTerms & image rights

Know your responsibilities and the customer acceptance requirements.