Quick links

Images

Attach up to five PNG, JPEG or WebP screenshots with multipart/form-data, stay inside the plan's byte budget, and retry without duplicates.

A failing test, a broken dashboard, a console full of red: sometimes a screenshot says more than the body text. To attach images, send the alert as multipart/form-data instead of JSON.

Request format

Link to this section
  • Send each normal field (title, body, category, severity) as its own form part.
  • Send data and todo, if you use them, as form parts whose value is a JSON-encoded string, not as nested form fields.
  • Add one part named attachment per image, repeating the name, up to five times.
  • Keep the Authorization and Idempotency-Key headers exactly as for JSON.

What is accepted

Link to this section
Table, scrolls horizontally when narrow
Rule Detail
Count Up to five images per alert.
Formats PNG, JPEG and WebP. SVG, GIF, PDF and every other type are rejected with 400 INVALID_PAYLOAD.
Detection The server reads each file’s leading bytes. File extensions and the MIME type you send are ignored.
Image budget All images together must fit the plan’s byte budget: 1 MiB for guest accounts and 5 MiB for standard accounts by default, 20 MiB on Zona Plus.
Request size The whole multipart request may be at most the image budget plus 64 KiB. Anything larger returns 413 PAYLOAD_TOO_LARGE.

The budget is shared, not per file. Five 1.5 MiB screenshots do not fit a 5 MiB standard budget; downscale or crop them, or send fewer.

curl --request POST \
  "https://gerncrjtrdjtjvybvseb.supabase.co/functions/v1/notify" \
  --header "Authorization: Bearer $ZONA_SOURCE_TOKEN" \
  --header "Idempotency-Key: build-20260726-15" \
  --form "title=Build failed" \
  --form "body=Unit tests failed on the release branch; screenshots attached." \
  --form "category=build" \
  --form "severity=high" \
  --form 'data={"buildId":"2026.07.26.15","branch":"release"}' \
  --form "attachment=@failure-screenshot.png" \
  --form "attachment=@failure-console.png"

Each attachment=@file is one image. There is no Content-Type header on purpose.

Images and idempotency

Link to this section

The image set is part of the payload. Zona fingerprints each file with SHA-256, so:

  • Retrying with the same key, the same fields and the same files in the same order returns 200 and the original alert.
  • Reusing the key with any different set of images, whether one was added, removed, swapped or reordered, returns 409 IDEMPOTENCY_CONFLICT.

When the upload fails after acceptance

Link to this section

Image storage happens after the alert itself is accepted, and it is best effort. If storage fails, you still get 202, with the alert in the inbox and no images:

{
  "idempotentReplay": false,
  "attachmentAccepted": false,
  "attachmentError": "UPLOAD_FAILED"
}

A replay with the same key does not try the upload again; it only returns the stored result. If the images matter, send a new alert with a new key.

When attachments are switched off

Link to this section

The operator can pause image attachments. While they are paused, a multipart request with images returns 403 ATTACHMENTS_DISABLED. Send the same event without images, as JSON, using a new idempotency key.

Privacy and retention

Link to this section

Images are private. Only the owning account can view them, through short-lived signed links in the app, and they follow the same retention window as their alert. Like the title and body, images end up on your phone, so never attach a screenshot that shows a token, a password or other secrets.