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.
https://api.zbwine.site/v2https://api.zbwine.site/openapi.jsonAuthorization 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
| Plan | Requests / min | Concurrent uploads | Burst |
|---|---|---|---|
| Sandbox | 60 | 2 | 90 |
| Standard | 600 | 16 | 900 |
| Scale | 3 000 | 64 | 4 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"
}
}
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Malformed body or unknown field. |
| 401 | unauthorized | Missing, malformed or revoked key. |
| 403 | scope_denied | Key lacks the required scope. |
| 404 | asset_not_found | Asset absent or outside the key's project. |
| 409 | upload_conflict | Part already committed for this session. |
| 429 | rate_limited | Plan limit exceeded — see Retry-After. |
Endpoints
Click an operation to expand parameters and a sample response.
| Parameter | Type | Description |
|---|---|---|
project required | string | Project id, e.g. prj_4a19. |
limit | integer | 1–200, default 50. |
cursor | string | Opaque pagination cursor from the previous page. |
kind | string | video, 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"
}
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>"
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
}
Soft-deletes immediately and purges from storage within 24 hours. Signed links issued for the asset stop resolving at once.
Delivery
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.
| Parameter | Type | Description |
|---|---|---|
token required | string | Signed reference from POST /v2/links/sign. |
disposition | string | inline (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"
403.{
"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
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"
}
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.
| Event | Fires when |
|---|---|
asset.ready | Processing finished, renditions available. |
asset.failed | Source unreadable or codec unsupported. |
upload.completed | All parts committed and checksummed. |
link.expired | A 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
- 2026-07-30 · v2.4 —
ip_lockon signed links; range requests on delivery. - 2026-06-11 · v2.3 — part size raised to 8 MiB;
upload.completedevent added. - 2026-04-02 · v2.2 — cursor pagination replaces offsets on
/v2/assets. - 2026-02-18 · v2.1 —
retention_dayson asset creation.