Download a Purchased Downloadable Product
Stream the file a customer bought as a downloadable product.
Endpoint
GET /api/shop/customer-downloadable-products/{id}/downloadThe {id} is the purchase id from Get Downloadable Products — not a product or order id.
Request Headers
| Header | Required | Description |
|---|---|---|
X-STOREFRONT-KEY | Yes | Your storefront API key |
Authorization | Yes | Bearer token of the customer who bought the product |
Response
200 OK with the raw file as the body. The content type follows the stored file, and Content-Disposition carries its original filename. Nothing about the response is JSON.
Both link types are served the same way: a file purchase streams from the store's private storage, and a url purchase is fetched by the store and passed through, so a client never receives the external address.
Each Download Is Counted
A successful call increments the used-download counter for the purchase, and the purchase flips to expired once nothing remains. Two separate limits apply, and each has its own error:
| Limit | When it trips |
|---|---|
| Invoiced quantity | Downloads are released in step with what the store has invoiced. Using more than the invoiced share fails with Download limit exceeded. even though the purchase still shows remaining downloads. |
| Purchased quantity | Once used plus canceled downloads reach the bought quantity, the purchase is exhausted and fails with No more downloads available. |
A purchase whose order has not been invoiced at all is pending and refuses with Download is pending. Please wait for the order to be invoiced.
Use Cases
- "Download" button in the account area — call with the purchase id, then hand the returned blob to the user.
- Show the remaining count after a download — the counter changes server-side, so re-read Get Downloadable Products rather than decrementing locally.
Best Practices
- Do not put this URL in an anchor tag — a browser navigation sends no
Authorizationor storefront-key header, and the request fails with401. - Check the status code and content type before saving — every failure answers with JSON, so writing the body blind produces a file containing an error message.
- Do not retry a failed download automatically — a call that reaches the file consumes a download, and a blind retry loop can exhaust the customer's allowance.
- Read
remainingDownloadsbefore offering the button — a purchase already at zero fails with403rather than a friendly message.
Related Resources
- Get Downloadable Products — the customer's purchased downloads
- Get Downloadable Product — one purchase with its remaining download count
- Get Customer Orders — the customer's order history

