Three surfaces move raw bytes rather than JSON-RPC. They are plain HTTP because they stream binary data.

Upload — POST /api/v1/images

Multipart form upload of an original. Requires a bearer token (an access token or a personal access token).

  • file (required) — the image file (png, jpg, or webp).
  • meta (optional) — a JSON string, {"title":"…","description":"…","album_id":"UUID","visibility":"private"}. All fields are optional. visibility accepts private or public and defaults to public for API compatibility. The cabinet explicitly sends private for new photos.

On success returns 201 with the created image (the same shape as image.get). The original is stored privately and its renders are enqueued; the image starts in a non-ready state and becomes ready once its renders are generated.

curl -s https://peinture.gumeniuk.com/api/v1/images \
  -H "Authorization: Bearer $TOKEN" \
  -F 'file=@sunset.jpg' \
  -F 'meta={"title":"Sunset","description":"From the pier"}'

Existing photos remain public. Independent access is managed by image.setVisibility; album publication is a separate grant. Hiding an album or resetting its link does not revoke access to an independently public photo. Generated file URLs remain public until deletion or moderation.

Errors: 400 (missing/empty/unsupported image), 401 (bad token), 413 (too large).

Archive — GET /api/v1/images/{id}/archive?token=…

Downloads a zip of every render plus the original. The token comes from image.archiveToken — a 5-minute, owner-scoped link. Ready images only. Returns application/zip.

Render serve — GET /i/{id}/{crop}.{ext}

Streams a single public render through the app (see serving modes). Never serves originals. Public — no token required.