Product files
A product file is a downloadable asset attached to a product — the PDF, ZIP, or image the buyer receives when they complete a digital-product purchase. Files are mounted under their parent product: every endpoint here is nested under /v1/storefront/products/:id/files.
Files only make sense for type: digital products. The schema permits attaching files to physical or license products, but they won't be delivered — delivery hooks read type to decide.
All endpoints require an sk_* key. See Authentication.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/storefront/products/:id/files |
Upload (or register) a file |
DELETE |
/v1/storefront/products/:id/files/:fileId |
Remove a file |
Reads happen implicitly — the parent product returns its files inline under the files field. There is no standalone GET /files; query the product.
Upload a file
POST /v1/storefront/products/:id/files
Accepts two body types:
multipart/form-datawith a singlefilepart. Storlaunch parses withformidable, readsoriginalFilename,size, andmimetype, and writes the file to the configured object store under{accountId}/{productId}/{ulid}/{fileName}.application/json— only registers metadata. Use this when you've already pushed the file to your own CDN and want Storlaunch to track it. You must supplyfileName,fileSize, and optionallymimeType.
The hard cap is 500 MB per file (FILE_TOO_LARGE otherwise). Per-workspace storage quota also applies — QUOTA_EXCEEDED if the workspace is at its tier ceiling.
Path parameters
| Param | Type | Description |
|---|---|---|
id |
string (prod_…) |
The parent product. |
Request body — JSON variant:
| Field | Type | Required | Description |
|---|---|---|---|
fileName |
string | yes | Display name. Buyers see this in the download UI. |
fileSize |
integer | yes | Bytes. Must be positive and ≤ 500 000 000. |
mimeType |
string | no | MIME type (application/pdf, application/zip, etc.). |
For multipart, all three fields come from the file part itself.
Response — 201 Created
{
"data": {
"id": "file_01HXAB7K3M9N2P5QRS8TVWXY3Z",
"productId": "prod_01HXxxxxxxxxxxxxxxxxxxxxxx",
"fileName": "starter-pack.zip",
"fileSize": 12482194,
"mimeType": "application/zip",
"storageKey": "acc_01HX.../prod_01HX.../f_01HX.../starter-pack.zip",
"createdAt": "2026-05-13T10:42:00Z"
},
"error": null,
"meta": { "requestId": "req_01HX...", "timestamp": "2026-05-13T10:42:00Z" }
}
storageKey is the object-store path Storlaunch resolves at delivery time. Don't construct buyer-facing URLs from it directly — delivery URLs are signed at the moment of issue (see Deliveries).
Errors specific to this endpoint
| Status | error.code |
When |
|---|---|---|
400 |
VALIDATION_ERROR |
fileName missing or fileSize invalid. |
400 |
FILE_TOO_LARGE |
File exceeds 500 MB. |
403 |
QUOTA_EXCEEDED |
Workspace at storage tier limit. |
404 |
RESOURCE_NOT_FOUND |
Parent product missing or in another workspace. |
Examples
# curl — multipart
curl -X POST https://storlaunch.forjio.com/api/v1/storefront/products/prod_01HX.../files \
-H "Authorization: Bearer sk_live_xxx" \
-F "file=@./starter-pack.zip"
# curl — JSON registration (file already on your CDN)
curl -X POST https://storlaunch.forjio.com/api/v1/storefront/products/prod_01HX.../files \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"fileName":"starter-pack.zip","fileSize":12482194,"mimeType":"application/zip"}'
// Node
const fd = new FormData();
fd.append('file', fs.createReadStream('./starter-pack.zip'));
const file = await client.products.files.upload('prod_01HX...', fd);
# Python
with open('starter-pack.zip', 'rb') as f:
file = client.products.files.upload('prod_01HX...', f)
Delete a file
DELETE /v1/storefront/products/:id/files/:fileId
Removes the file row. The blob in object storage is GC'd asynchronously — usually within a few minutes. Existing delivery records continue to resolve until the GC runs; after that, a buyer trying to re-download would get a 404 from the signed URL.
Path parameters
| Param | Type | Description |
|---|---|---|
id |
string (prod_…) |
The parent product. |
fileId |
string (file_…) |
The file to delete. |
Response — 204 No Content.
Errors
| Status | error.code |
When |
|---|---|---|
404 |
RESOURCE_NOT_FOUND |
Product or file missing, or file isn't under this product. |
curl -X DELETE https://storlaunch.forjio.com/api/v1/storefront/products/prod_01HX.../files/file_01HX... \
-H "Authorization: Bearer sk_live_xxx"
The product-file object
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
string | no | Always file_ + 26-char ULID. |
productId |
string | no | The parent product ID. |
fileName |
string | no | Buyer-visible filename. |
fileSize |
integer | no | Bytes. JSON-safe number (< 2^53). |
mimeType |
string | yes | MIME type. |
storageKey |
string | no | Object-store path. Don't expose to buyers. |
createdAt |
string (ISO 8601 UTC) | no | Upload timestamp. |
Events
Product files don't emit their own events. Their lifecycle is implicit in the parent product's storlaunch.product.updated.v1 event — the files array on the product payload reflects the current set.
When a buyer completes purchase, Storlaunch issues a delivery and emits product.purchased. That event references the product, not individual files.
Next
- Products — the parent resource.
- Deliveries — how files reach the buyer.
- Inventory — variants and stock for physical products.