API reference
Upload artwork
Request a temporary upload URL. Send file bytes directly to private storage.
Direct uploads — no partner storage setup
POST
/v1/uploadsIf your website generates or receives image files, request a temporary upload link from your server. Files go straight to TCGPlaytest-owned private storage. Existing integrations that already have public HTTPS images can skip this step.
Request upload link
// YOUR server: check customer/session ownership first.
const response = await fetch("https://partner.tcgplaytest.com" + "/v1/uploads", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.TCGP_API_KEY,
"Content-Type": "application/json" },
body: JSON.stringify({ content_type: "image/png", size: fileByteCount })
});
const grant = await response.json();
if (!response.ok) throw new Error(grant.error.message);
// Bind grant.image_url to this customer's session/order.
// Return the grant to your browser, never the API key.HTTP 201 returns these fields. URLs below are illustrative; use the actual response unchanged.
Upload grant response
{
"upload_url": "https://storage.example/temporary-upload?token=SIGNED_UPLOAD",
"method": "PUT",
"headers": {
"Content-Type": "image/png"
},
"image_url": "https://partner.tcgplaytest.com/v1/uploads/source?token=SIGNED_REFERENCE",
"upload_expires_at": "2026-10-06T14:00:00.000Z",
"expires_at": "2026-10-14T12:00:00.000Z",
"max_bytes": 20971520
}Browser direct upload
// Browser: grant came from YOUR authenticated/session-protected server.
const uploaded = await fetch(grant.upload_url, {
method: grant.method,
headers: grant.headers,
body: file, // raw PNG/JPEG File or Blob, not FormData, JSON or base64
credentials: "omit",
redirect: "error"
});
if (!uploaded.ok) throw new Error("Upload failed; request a fresh link and retry.");
// Tell YOUR server the upload finished. It checks ownership, then puts
// grant.image_url into items[].image_url or back_image_url in POST /v1/carts.
// Upload every required front/back before creating the cart. No finalize call.| Field / limit | Description |
|---|---|
| POST /v1/uploads body | Only content_type ("image/png" or "image/jpeg") and size (integer 1–20,971,520 bytes). Metadata only. |
| upload_url / method / headers | PUT raw file bytes using the returned headers. No partner API key, storage account credentials or cookies are sent to storage. Browser uploads bypass the application server's body-size limit. |
| upload_expires_at | RFC 3339 timestamp: upload link expires after 2 hours. It permits one new object and cannot overwrite it. On a failed/uncertain PUT, request a new grant and upload again. |
| image_url / expires_at | Opaque signed image reference, valid for 8 days. Keep it private and use it unchanged. It belongs to the issuing partner and key mode; source access stops if that key is revoked. Create the cart promptly. |
| Validation | Successful upload does not mean print-ready. The cart's background worker still checks format, full decoding, size, dimensions, hashes and content. Test keys support uploads and validation. Hosted test checkout supports simulated completion and order events; prepaid sandbox payments use Stripe test credentials only. |
| Storage and retention | TCGPlaytest stores the source and validated artwork. Maintenance removes expired source staging files, normally within the following day. Each cart gets its own checked files under normal cart/order retention; partners need no cleanup job. |
| Errors | 400 invalid_request: fix metadata. 403 invalid_upload: wrong/tampered partner reference. 404 upload_not_found: PUT not completed. 410 upload_expired: upload again. 503 uploads_unavailable: platform configuration needs attention. Standard authentication and rate-limit errors also apply. |
Do not share or log signed URLs. The source-reference GET is exempt from key rate limits and redirects to a short-lived read link. Upload grants count toward the shared write limit; honor Retry-After. There is no /finalize endpoint.