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:

  1. multipart/form-data with a single file part. Storlaunch parses with formidable, reads originalFilename, size, and mimetype, and writes the file to the configured object store under {accountId}/{productId}/{ulid}/{fileName}.
  2. 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 supply fileName, fileSize, and optionally mimeType.

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.

Response201 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.

Response204 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