# Photos

Upload local photo files before creating an order, then associate the URL from
each successful upload result with a catalog photo index. If review requests
more evidence, resolve every current request through the order-specific update
endpoint. Both writes require `orders:write`; complete schemas are in the
[API reference](/api).

## Choose photo indexes

Query `GET /v1/integrations/catalog/photo_indexes` with the selected category,
brand, and optional model:

```bash
curl "$LEGITGRAILS_BASE_URL/catalog/photo_indexes?category_code=bag&brand_code=gucci&model_code=gucci-bag-gg-marmont&locale=en" \
  --header "Authorization: Bearer $LEGITGRAILS_API_KEY"
```

Use each entry's machine-readable `code` as `index_code`. Include every entry
marked `required: true`, and do not exceed its `photo_limit`. See
[Catalog](/catalog) for the full classification lookup.

## Upload photo files

`POST /v1/integrations/photos` accepts one or more `photos` parts as
`multipart/form-data`:

```bash
curl --request POST "$LEGITGRAILS_BASE_URL/photos" \
  --header "Authorization: Bearer $LEGITGRAILS_API_KEY" \
  --form "photos=@./overall.jpg" \
  --form "photos=@./inside-label.jpg"
```

The HTTP `200` response preserves file order. Check each result's `status`:

```json
[
  {
    "status": "uploaded",
    "url": "https://media.legitgrails.com/uploads/tusd/example-overall",
    "original_name": "overall.jpg"
  },
  {
    "status": "uploaded",
    "url": "https://media.legitgrails.com/uploads/tusd/example-label",
    "original_name": "inside-label.jpg"
  }
]
```

File type, size, and image-dimension validation happens before upload. If any
file fails validation, the entire request is rejected. Once validation passes,
HTTP `200` means the batch was processed, even if an individual upload-gateway
operation failed. A failed result has no `url`:

```json
{
  "status": "failed",
  "original_name": "inside-label.jpg",
  "error": {
    "code": "photo-upload-failed",
    "message": "Photo could not be uploaded"
  }
}
```

Supported image formats are JPEG, PNG, HEIC, HEIF, WebP, and AVIF. Send no more
than 10 files in one request. Each file can be up to 100 MiB. Images are also
limited to 8,000 pixels on either side and 50,000,000 total pixels.

The upload does not assign an index or attach anything to an order. Match each
successful result to its local file and use only URLs from `uploaded` results in
the create request. Order photo URLs from other hosts are rejected.

```json
{
  "brand_code": "gucci",
  "category_code": "bag",
  "photos": [
    {
      "index_code": "overall-picture",
      "url": "https://media.legitgrails.com/uploads/tusd/example-overall"
    },
    {
      "index_code": "inside-label",
      "url": "https://media.legitgrails.com/uploads/tusd/example-label"
    }
  ]
}
```

## Respond to an `update-photos` request

Retrieve the order to read its current `photos_to_resubmit` request.

```bash
curl "$LEGITGRAILS_BASE_URL/orders/<order-id>?locale=en" \
  --header "Authorization: Bearer $LEGITGRAILS_API_KEY"
```

```json
{
  "id": "<order-id>",
  "status": "update-photos",
  "outcome": null,
  "outcome_locale": null,
  "delayed": false,
  "classification": {
    "brand_code": "gucci",
    "category_code": "bag",
    "model_code": "gucci-bag-gg-marmont",
    "model_name": "GG Marmont",
    "model_name_locale": "GG Marmont"
  },
  "due_date": "2026-08-05T15:30:00.000Z",
  "updated_at": "2026-08-04T17:05:11.000Z",
  "photos_to_resubmit": [
    {
      "index_code": "overall-picture",
      "name": "Overall picture",
      "name_locale": "Overall picture",
      "condition": "unclear",
      "condition_locale": "Unclear photo",
      "requirements": ["better-lighting", "zoom-in"],
      "requirements_locale": [
        "Use better lighting",
        "Move closer to the item"
      ],
      "comment": "Avoid reflections and include the full item.",
      "reference_photo_url": null,
      "max_photos": 2
    }
  ]
}
```

Use `index_code`, `condition`, and `requirements` for machine logic; use the
localized fields, `comment`, and optional `reference_photo_url` for display.
`max_photos` is the limit for this index in the current update round.

### Submit the current round

Upload local replacements first, then send their URLs to
`POST /v1/integrations/orders/{id}/update_photos` using the returned order ID.

```bash
curl --request POST "$LEGITGRAILS_BASE_URL/orders/<order-id>/update_photos" \
  --header "Authorization: Bearer $LEGITGRAILS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "photos": [
      {
        "index_code": "overall-picture",
        "url": "https://media.legitgrails.com/uploads/tusd/example-replacement-1"
      },
      {
        "index_code": "overall-picture",
        "url": "https://media.legitgrails.com/uploads/tusd/example-replacement-2"
      }
    ],
    "note": "Two clearer overall photos are attached."
  }'
```

A valid submission returns `202 Accepted`:

```json
{
  "id": "<order-id>",
  "status": "accepted"
}
```

Acceptance is not an authenticity result. Continue monitoring the detail
endpoint or webhooks.

### Update rules

- Resolve **every** `index_code` in the current `photos_to_resubmit`; partial
  submissions are rejected together.
- Each entry supplies exactly one resolution: a managed `url` returned by
  `POST /photos`, or `is_unable_to_provide: true`.
- Repeat an `index_code` for multiple URLs, up to its `max_photos`.
- For one index, `is_unable_to_provide: true` must be the only entry; do not mix
  it with URLs.
- Do not submit indexes absent from the current request.
- `note` is optional and limited to 1,000 characters.

An order can have multiple update rounds. Resolve only the current round, then
keep monitoring. See [Errors](/errors#photo-errors) for error responses.
