Zog

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: ready

Files larger than 100 MiB automatically use multipart — see Multipart uploads.

Init upload

POST /api/v1/uploads
Authorization: Bearer zog_sk_...
Content-Type: application/json

Body

FieldTypeRequiredNotes
filenamestringyesSanitized server-side
mimeTypestringyesDeclared content type
sizeBytesnumberyesExact byte length of the object
checksumSha256stringno64 hex chars
folderIduuid | nullnoMust 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.jpg

Use 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 }),
});