image.crop.add

bearer / PAT

Add a custom crop (max 5 per image)

Adds a per-image crop beyond the shared presets and enqueues its render. code must match ^[a-z0-9][a-z0-9-]{0,31}$ and differ from a preset code; at least one of width/height must be positive (0-15000); mode defaults to fill, format to webp, quality to 80.

Parameters

Passed by name in the params object.

Name Type Required Description
id string yes Image UUID
code string yes Crop code, ^[a-z0-9][a-z0-9-]{0,31}$ and not a preset code
width integer yes Target width in px (0-15000); 0 keeps aspect from height
height integer yes Target height in px (0-15000); 0 keeps aspect from width
mode string no Fit mode; defaults to fill
format string no Output format (webp/jpg/png); defaults to webp
quality integer no Encoder quality 1-100; defaults to 80

Result

Returns ImageDTO:

Field Type Description
album_id string
created_at * string
custom_crops * array<customCropDTO>
description * string
exif * exifDTO
exif_visibility visibilityDTO
format * string
height * integer
id * string
renders * array<renderDTO>
size_bytes * integer
status * string
title * string
width * integer

Errors

Code Message When
2002 validation_failed Malformed request; see error.data.
2001 not_found No such resource owned by the caller.
4001 custom_crop_limit_exceeded The image already has five custom crops.

See the error reference for the full catalog, including the authentication codes.

Example

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "image.crop.add",
  "params": {
    "id": "018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d",
    "code": "square",
    "width": 1080,
    "height": 1080,
    "mode": "fill",
    "format": "webp",
    "quality": 82
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d",
    "title": "Golden Gate at dawn",
    "description": "Long exposure from the Marin Headlands.",
    "status": "ready",
    "width": 6000,
    "height": 4000,
    "size_bytes": 8452016,
    "format": "jpg",
    "exif": {
      "taken_at": "2026-02-11T06:42:18Z",
      "camera_make": "FUJIFILM",
      "camera_model": "X-T5",
      "lens": "XF16-55mmF2.8"
    },
    "renders": [
      {
        "crop": "thumb",
        "format": "webp",
        "width": 256,
        "height": 256,
        "size_bytes": 14320,
        "url": "https://img.peinture.gumeniuk.com/r/018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d/thumb.webp?v=3",
        "pending": false
      },
      {
        "crop": "large",
        "format": "webp",
        "width": 2048,
        "height": 1365,
        "size_bytes": 412880,
        "url": "https://img.peinture.gumeniuk.com/r/018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d/large.webp?v=3",
        "pending": false
      }
    ],
    "custom_crops": [],
    "exif_visibility": {
      "gps": false,
      "camera": true,
      "datetime": true,
      "settings": false
    },
    "created_at": "2026-02-11T07:03:00Z"
  }
}

curl

curl -s https://peinture.gumeniuk.com/rpc \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"image.crop.add","params":{"id":"018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d","code":"square","width":1080,"height":1080,"mode":"fill","format":"webp","quality":82}}'

JavaScript

const res = await fetch("https://peinture.gumeniuk.com/rpc", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${token}`,
  },
  body: JSON.stringify({
    jsonrpc: "2.0", id: 1, method: "image.crop.add", params: {"id":"018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d","code":"square","width":1080,"height":1080,"mode":"fill","format":"webp","quality":82},
  }),
});
const { result, error } = await res.json();

Go

body := []byte(`{"jsonrpc":"2.0","id":1,"method":"image.crop.add","params":{"id":"018f3a7d-2b8c-7f1a-a4d5-6e7f8a9b0c1d","code":"square","width":1080,"height":1080,"mode":"fill","format":"webp","quality":82}}`)
req, _ := http.NewRequest("POST", "https://peinture.gumeniuk.com/rpc", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)

Try it

Sends a real request to /rpc from your browser. Paste a bearer token above — a JWT access token or a personal access token (pnt_…).