# `image.crop.add`

<span class="badge">bearer / PAT</span>

**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](/docs/errors) for the full catalog, including the authentication codes.

## Example

Request:

```json
{
  "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:

```json
{
  "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**

```bash
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**

```js
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**

```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)
```

