Delivery API reference

Programmatic access to asset storage, signed delivery links and upload sessions. REST over HTTPS, JSON request and response bodies, one API key per environment.

Base URLhttps://api.zbwine.site/v2
Spechttps://api.zbwine.site/openapi.json
Keys issued before v2.0 stop working on 1 October 2026. Rotate them from the dashboard — the migration is a drop-in replacement, only the Authorization header format changed.

Authentication

Every request carries a bearer token in the Authorization header. Keys are environment-scoped: a live_ key never resolves against sandbox data and vice versa.

# all requests are authenticated the same way
curl https://api.zbwine.site/v2/assets \
  -H "Authorization: Bearer live_sk_<your key>" \
  -H "Accept: application/json"

Unauthenticated calls return 401 and are not counted against your quota. Keys are shown once at creation time and stored hashed — if you lose one, rotate it.

Rate limits

PlanRequests / minConcurrent uploadsBurst
Sandbox60290
Standard60016900
Scale3 000644 500

Each response carries X-RateLimit-Remaining and X-RateLimit-Reset. On 429, honour Retry-After — retrying sooner extends the cooldown.

Error handling

Errors use conventional status codes and a stable machine-readable code. Do not parse message — it is written for humans and changes without notice.

{
  "error": {
    "code": "asset_not_found",
    "message": "No asset with id ast_7f2c91ba.",
    "request_id": "req_01JC4Y8K2M"
  }
}
StatusCodeWhen
400invalid_requestMalformed body or unknown field.
401unauthorizedMissing, malformed or revoked key.
403scope_deniedKey lacks the required scope.
404asset_not_foundAsset absent or outside the key's project.
409upload_conflictPart already committed for this session.
429rate_limitedPlan limit exceeded — see Retry-After.

Endpoints

Click an operation to expand parameters and a sample response.

GET/v2/assetsList assets in a project
ParameterTypeDescription
project requiredstringProject id, e.g. prj_4a19.
limitinteger1–200, default 50.
cursorstringOpaque pagination cursor from the previous page.
kindstringvideo, image, archive, other.
{
  "data": [
    {
      "id": "ast_7f2c91ba",
      "kind": "video",
      "filename": "northfield_shoot_raw.mov",
      "bytes": 5046140928,
      "duration_ms": 1842000,
      "created_at": "2026-08-13T09:41:02Z"
    }
  ],
  "has_more": true,
  "next_cursor": "cur_9dK2p"
}
GET/v2/assets/{id}Retrieve a single asset

Returns the full asset record including derived renditions. Renditions appear only after processing finishes — poll or subscribe to asset.ready.

curl https://api.zbwine.site/v2/assets/ast_7f2c91ba \
  -H "Authorization: Bearer live_sk_<your key>"
POST/v2/assetsRegister an asset record

Creates the metadata record. Bytes are attached separately through an upload session — see POST /v2/uploads.

{
  "project": "prj_4a19",
  "kind": "video",
  "filename": "drone_pass.mp4",
  "retention_days": 180
}
DELETE/v2/assets/{id}Delete an asset and its renditions

Soft-deletes immediately and purges from storage within 24 hours. Signed links issued for the asset stop resolving at once.

Delivery

GET/video/downloadStream or download the original file

Serves the stored bytes for a signed asset reference. Supports HTTP range requests, so players can seek without fetching the whole file, and keeps the connection open for long transfers.

ParameterTypeDescription
token requiredstringSigned reference from POST /v2/links/sign.
dispositionstringinline (default) or attachment.
# resumable download of a large original
curl -L -C - -o northfield.mov \
  "https://api.zbwine.site/video/download?token=dl_9f31c7&disposition=attachment"
Delivery traffic is served from the edge and does not count towards API rate limits. Unsigned or expired tokens receive 403.
POST/v2/links/signIssue a time-limited delivery token
{
  "asset": "ast_7f2c91ba",
  "expires_in": 3600,
  "ip_lock": false
}

Maximum lifetime is 7 days. With ip_lock the token binds to the first client address that uses it.

Uploads

POST/v2/uploadsOpen a multipart upload session

Returns a session id and the part size to use. Sessions stay open for 24 hours; unfinished sessions are discarded automatically.

{
  "id": "upl_2b81ee",
  "part_size": 8388608,
  "parts_expected": 602,
  "expires_at": "2026-08-15T09:41:02Z"
}
PUT/v2/uploads/{id}Upload a part

Send parts in any order with Content-Range. A part may be re-sent while the session is open; the last write wins.

Webhooks

Subscribe an HTTPS endpoint and receive events as they happen. Delivery retries with exponential backoff for 24 hours before the subscription is paused.

EventFires when
asset.readyProcessing finished, renditions available.
asset.failedSource unreadable or codec unsupported.
upload.completedAll parts committed and checksummed.
link.expiredA signed delivery token reached its TTL.

Every request carries X-Zbw-Signature — an HMAC-SHA256 of the raw body with your endpoint secret. Compare in constant time and reject anything older than five minutes.

# verify before you trust the payload
expected = hmac_sha256(endpoint_secret, raw_body)
if not constant_time_equals(expected, header) : reject()

Changelog