Upload files
Init, stream bytes to signed storage, then finalize.
Uploads are intentionally fast: Zog issues a short-lived signed URL, your client sends bytes straight to object storage, then you finalize the file.
Flow
1. POST /api/v1/uploads → fileId + uploadUrl (or multipart)
2. PUT uploadUrl → raw file bytes
3. POST /api/v1/uploads/complete → status: readyFiles larger than 100 MiB automatically use multipart — see Multipart uploads.
Init upload
POST /api/v1/uploads
Authorization: Bearer zog_sk_...
Content-Type: application/jsonBody
| Field | Type | Required | Notes |
|---|---|---|---|
filename | string | yes | Sanitized server-side |
mimeType | string | yes | Declared content type |
sizeBytes | number | yes | Exact byte length of the object |
checksumSha256 | string | no | 64 hex chars |
folderId | uuid | null | no | Must be a folder you own |
Success response
{
"ok": true,
"data": {
"multipart": false,
"fileId": "…",
"objectKey": "…",
"uploadUrl": "https://…",
"completeUrl": "/api/v1/uploads/complete"
}
}Put the bytes
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpgUse the same Content-Type you declared in mimeType. The signed URL expects the declared sizeBytes.
Complete upload
POST /api/v1/uploads/complete
Authorization: Bearer zog_sk_...
Content-Type: application/json{ "fileId": "…" }{
"ok": true,
"data": {
"fileId": "…",
"status": "ready"
}
}Zog verifies the object exists and matches the declared size before marking it ready. Quota is reserved at init and committed on complete.
Abort
If you change your mind before completing:
POST /api/v1/uploads/abort{ "fileId": "…" }This releases the reservation and deletes any partial object.
Node example
const init = await fetch("https://zog.watch/api/v1/uploads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ZOG_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
filename: "report.pdf",
mimeType: "application/pdf",
sizeBytes: buffer.byteLength,
}),
}).then((r) => r.json());
if (!init.ok) throw new Error(init.error.message);
await fetch(init.data.uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: buffer,
});
await fetch("https://zog.watch/api/v1/uploads/complete", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ZOG_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ fileId: init.data.fileId }),
});