# update_media

Set a photo's caption, alt, credit, focal point or provenance.

`PATCH /v1/media/{mediaId}` · MCP tool `update_media` · scope `write` · stable · since 2026-09-17

Credits render as Photo: Name and are a hard platform rule: never drop or invent one; ask the creator whose photo it is. Only sent fields change; null clears a metadata field (alt, focal point, licence, shows, takenAt and the rest), while caption and credit take text and store it as sent. The response carries warnings that did not stop the write.

## Path parameters

- `mediaId` (string, required): Id of the media row.

## Body parameters

- `alt` (string or null, optional): Alt text: what the photo shows, for screen readers. The caption is the fallback.
- `caption` (string, optional): Caption text, shown under the photo. Stored as sent.
- `capturedOn` (string or null, optional): Capture date (YYYY-MM-DD). Upload reads it from the file's metadata before stripping it when present.
- `credit` (string, optional): Photographer credit, rendered as Photo: Name. Stored as sent.
- `creditUrl` (string or null, optional): The photographer's profile page.
- `focalX` (number or null, optional): Focal point 0 (left) to 1 (right); with focalY the apps keep it in frame when they crop to a card.
- `focalY` (number or null, optional): Focal point 0 (top) to 1 (bottom).
- `licence` (string or null, optional): Licence name (Unsplash License, CC BY 4.0).
- `locationVerifiedBy` (string or null, optional): How the location was confirmed: geotag, landmark, photographer_caption, creator or other. One of `geotag`, `landmark`, `photographer_caption`, `creator`, `other`.
- `reviewNotes` (string or null, optional): Notes for the creator's review (why a value was chosen, what could not be verified). Never shown to buyers; get_guide_readiness lists them. Empty string clears.
- `shows` (PhotoShows or null, optional): What the photo shows, null clears: { kind: spot|view_from_spot|nearby|approach|detail|other, label? }.
  Fields of PhotoShows: https://developers.sceniq.earth/fields/photo-shows.md
- `sourcePageUrl` (string or null, optional): The page the photo was licensed from (an Unsplash photo page). Not the image file.
- `takenAt` (PhotoTakenAt or null, optional): Where it was taken, null clears: { lat, lon, precision: gps|geocode|none }. A gps fix more than 2 km from the pin is flagged by readiness.
  Fields of PhotoTakenAt: https://developers.sceniq.earth/fields/photo-taken-at.md

## Returns

200.

- `ok` (boolean, required): Always true: the write went through. Always `true`.
- `mediaId` (string, required): The photo's id.
- `warnings` (array of strings, optional): Notes about things the write accepted but you should look at (a plain http link). Present only when there are any.

## Examples

### Add alt text, a focal point and where the photo was taken

```bash
curl -X PATCH "https://sceniq.earth/api/v1/media/kg71s3d5f7g9h1j3k5k7z9x1c3v5b7n9" \
  -H "Authorization: Bearer $SCENIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "alt": "A deep blue pool ringed with orange and yellow, steam drifting over the boardwalk below",
  "focalX": 0.52,
  "focalY": 0.46,
  "shows": {
    "kind": "spot"
  },
  "takenAt": {
    "lat": 44.5199,
    "lon": -110.8406,
    "precision": "gps"
  },
  "locationVerifiedBy": "geotag"
}'
```

MCP `tools/call`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_media",
    "arguments": {
      "mediaId": "kg71s3d5f7g9h1j3k5k7z9x1c3v5b7n9",
      "alt": "A deep blue pool ringed with orange and yellow, steam drifting over the boardwalk below",
      "focalX": 0.52,
      "focalY": 0.46,
      "shows": {
        "kind": "spot"
      },
      "takenAt": {
        "lat": 44.5199,
        "lon": -110.8406,
        "precision": "gps"
      },
      "locationVerifiedBy": "geotag"
    }
  }
}
```

Response 200:

```json
{
  "ok": true,
  "mediaId": "kg71s3d5f7g9h1j3k5k7z9x1c3v5b7n9"
}
```

### Record a licensed photo's credit and licence (an http link comes back as a warning)

```bash
curl -X PATCH "https://sceniq.earth/api/v1/media/kg7p2p4j6v8y0t2r4e6w8q0a2s4d6f8g" \
  -H "Authorization: Bearer $SCENIQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "credit": "Brocken Inaglory",
  "licence": "CC BY-SA 3.0",
  "sourcePageUrl": "https://commons.wikimedia.org/wiki/File:Grand_Prismatic_Spring_and_Midway_Geyser_Basin_from_above.jpg",
  "creditUrl": "http://commons.wikimedia.org/wiki/User:Brocken_Inaglory"
}'
```

MCP `tools/call`:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "update_media",
    "arguments": {
      "mediaId": "kg7p2p4j6v8y0t2r4e6w8q0a2s4d6f8g",
      "credit": "Brocken Inaglory",
      "licence": "CC BY-SA 3.0",
      "sourcePageUrl": "https://commons.wikimedia.org/wiki/File:Grand_Prismatic_Spring_and_Midway_Geyser_Basin_from_above.jpg",
      "creditUrl": "http://commons.wikimedia.org/wiki/User:Brocken_Inaglory"
    }
  }
}
```

Response 200:

```json
{
  "ok": true,
  "mediaId": "kg7p2p4j6v8y0t2r4e6w8q0a2s4d6f8g",
  "warnings": [
    "creditUrl uses http, not https: http://commons.wikimedia.org/wiki/User:Brocken_Inaglory"
  ]
}
```

## Errors

- `invalid_argument` (400): A required field is missing, a field is unknown, or a value does not match its type (the Convex validator's path and value are in the message).
- `invalid_request` (400): A writer refused a value: a map link on a host that is not allowed, a title that is too long, a date that does not exist, too many items in a list. The message is the writer's own sentence.
- `unauthenticated` (401): The request carries no key. Send it as Authorization: Bearer sk_sceniq_... (or X-Api-Key).
- `invalid_api_key` (401): The key is unknown, revoked or expired.
- `api_scope_required` (403): A read-only key called a write operation, or a key without the publish option called publish_changes.
- `forbidden` (403): The key belongs to a creator account that is no longer active.
- `not_found` (404): The id does not exist or belongs to another creator. The two answers are identical on purpose, so ids never reveal what exists.
- `payload_too_large` (413): A JSON body over 2 MB, an image over 6 MB, a batch upload over 19 MB, or an MCP message over 2 MB.
- `rate_limited` (429): Over a limit: requests per key per minute, per creator per hour, uploads per key per minute, or check_links per guide per hour.
- `internal_error` (500): An unexpected failure. The message is hidden on purpose.

Reference: https://developers.sceniq.earth/reference/update_media
