Skip to main content
A digital product is sold at a creator-set price (in USD), ready for you to attach downloadable files and images. Create one with the Create a product endpoint (POST /open-api/v1.0/products) by setting type: "digital". Selling printed merch from your own artwork instead? See Create design products.
OAuth scope: offer_write for creating the product and managing its digital files. Attaching product images also needs media_write, since the image bytes are uploaded through the media library first.API keys have full access to these endpoints. See Authentication for API keys or OAuth for multi-shop apps.

Create the product

Send type: "digital" with a creator-set price (a USD amount). Products default to hidden. Set publishOnCreate: true to make the product visible on the storefront as soon as it’s created; otherwise it stays hidden and can only be published from the Fourthwall admin.
curl -u "your_username:your_password" \
  -X POST https://api.fourthwall.com/open-api/v1.0/products \
  -H "Content-Type: application/json" \
  -d '{
    "type": "digital",
    "name": "My Digital Product",
    "description": "A detailed description of my digital product",
    "price": 25.00
  }'
const credentials = btoa("your_username:your_password");

const res = await fetch("https://api.fourthwall.com/open-api/v1.0/products", {
  method: "POST",
  headers: {
    "Authorization": `Basic ${credentials}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    type: "digital",
    name: "My Digital Product",
    description: "A detailed description of my digital product",
    price: 25.0
  })
});
const product = await res.json();
console.log(product.productId); // e.g. "<uuid>"
Response
{
  "productId": "<uuid>",
  "customizationId": null,
  "images": []
}

Attach downloadable files

These are the files your buyers download after purchase. Unlike media-library images — which are shop-wide and reusable across products by imageIda digital file belongs to the one product you upload it against. The upload URL is scoped to a productId, the bytes are stored under that product, and confirming links the file to it. The flow mirrors the media upload used for design artwork — request a pre-signed URL, PUT the bytes to Google Cloud Storage, then confirm — but it’s a product-scoped endpoint and the confirm step links the file to the product instead of registering a reusable image.
Digital files are size-limited per shop: 25 MB on free plans, up to 1 GB otherwise. The size you send is enforced both when the URL is issued and by the signed URL itself — sending more bytes than declared fails the upload.
size is the file’s exact length in bytes — read it from the file rather than hardcoding it, and hold on to the value: you must send the same number again in the x-goog-content-length-range header when you PUT the bytes (step 2). Compute it from whatever you’re uploading:
  • Node (Buffer): fileBytes.byteLength (or (await fs.promises.stat("my-ebook.pdf")).size without reading the file)
  • Browser (File/Blob): file.size
  • Shell: wc -c < my-ebook.pdf
1

Request a pre-signed upload URL

Call POST /open-api/v1.0/products/{productId}/digital-files/upload-url with the file’s metadata. The response is the same { uploadUrl, fileUrl } shape as the media flow: a short-lived uploadUrl to PUT the bytes to, and the fileUrl you’ll confirm in the last step.
# size must be the file's exact byte count — derive it, don't guess
SIZE=$(wc -c < my-ebook.pdf)

curl -u "your_username:your_password" \
  -X POST https://api.fourthwall.com/open-api/v1.0/products/<uuid>/digital-files/upload-url \
  -H "Content-Type: application/json" \
  -d "{
    \"fileName\": \"my-ebook.pdf\",
    \"contentType\": \"application/pdf\",
    \"size\": $SIZE
  }"
const credentials = btoa("your_username:your_password");

const fileBytes = await fs.promises.readFile("my-ebook.pdf");
const size = fileBytes.byteLength; // exact byte count — reused in the PUT below

const res = await fetch(
  `https://api.fourthwall.com/open-api/v1.0/products/${productId}/digital-files/upload-url`,
  {
    method: "POST",
    headers: {
      "Authorization": `Basic ${credentials}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      fileName: "my-ebook.pdf",
      contentType: "application/pdf",
      size
    })
  }
);
const { uploadUrl, fileUrl } = await res.json();
Response
{
  "uploadUrl": "https://storage.googleapis.com/...&X-Goog-Signature=...",
  "fileUrl": "<file>",
  "expiresAt": "2026-05-28T12:34:56Z"
}
expiresAt is when the pre-signed uploadUrl stops accepting PUTs; request a new one if it lapses.
2

Upload the file bytes

PUT the raw bytes to the uploadUrl. This goes directly to Google Cloud Storage, not to Fourthwall — do not send your Fourthwall credentials with it.The URL is signed with the same two conditions as the media upload, and missing either fails with 403 SignatureDoesNotMatch:
  • Content-Type — must equal the contentType you sent in step 1 (here, application/pdf).
  • x-goog-content-length-range: 0,<size> — must use the same size (byte count) you sent in step 1. Reuse the exact value; don’t recompute it a different way.
The x-goog-content-length-range header is required and easy to miss — it’s baked into the URL’s signature. Omitting it (or sending a different size) fails with 403 SignatureDoesNotMatch even when everything else looks right. The uploadUrl is short-lived; if it expires, request a new one.
# $SIZE is the same value you sent to /digital-files/upload-url in step 1
curl -X PUT "<uploadUrl from previous step>" \
  -H "Content-Type: application/pdf" \
  -H "x-goog-content-length-range: 0,$SIZE" \
  --data-binary @my-ebook.pdf
// Reuse fileBytes and size from step 1 — same bytes, same byte count.
await fetch(uploadUrl, {
  method: "PUT",
  headers: {
    "Content-Type": "application/pdf",
    // Required: the same `size` you sent to /digital-files/upload-url. Baked
    // into the signature — a different value fails with 403 SignatureDoesNotMatch.
    "x-goog-content-length-range": `0,${size}`
  },
  body: fileBytes
});
3

Confirm the upload

Link the uploaded file to the product with POST /open-api/v1.0/products/{productId}/digital-files. Pass the fileUrl from step 1 and the fileName buyers will see. The response is the full, updated product.
curl -u "your_username:your_password" \
  -X POST https://api.fourthwall.com/open-api/v1.0/products/<uuid>/digital-files \
  -H "Content-Type: application/json" \
  -d '{
    "fileUrl": "<file>",
    "fileName": "my-ebook.pdf"
  }'
const res = await fetch(
  `https://api.fourthwall.com/open-api/v1.0/products/${productId}/digital-files`,
  {
    method: "POST",
    headers: {
      "Authorization": `Basic ${credentials}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ fileUrl, fileName: "my-ebook.pdf" })
  }
);
const product = await res.json();
Buyers receive digital files as downloads (they’re served with Content-Disposition: attachment). Repeat this flow once per file to attach several downloads to the same product. To remove one, call DELETE /open-api/v1.0/products/{productId}/digital-files with its fileUrl.

Attach product images

Product images are the showcase thumbnails shown on the storefront — separate from the downloadable files above. They are not uploaded through the digital-files endpoint. Instead, upload the image through the shop-wide media library (POST /open-api/v1.0/media/upload-url, then PUT the bytes — exactly as in Create design products), then attach the resulting URL to the product by its pixel dimensions:
cURL
curl -u "your_username:your_password" \
  -X POST https://api.fourthwall.com/open-api/v1.0/products/<uuid>/images \
  -H "Content-Type: application/json" \
  -d '{
    "images": [
      { "url": "<file>", "width": 800, "height": 600 }
    ]
  }'
Pass the fileUrl you uploaded as url; width and height are the image’s pixel dimensions.

Next steps

Create a product reference

Full request and response schema for POST /products.

Digital files

Upload and confirm the downloadable files customers receive.

Attach product images

Add showcase images to a digital product.

Create design products

Render your artwork onto tees, mugs, and more.