---
title: "Upload a photo"
method: POST
path: "/api/trips/{id}/photos"
tags: ["Photos"]
---

# Upload a photo

`POST /api/trips/{id}/photos`

## Request

- Base URL: `https://trvlrr.app`
- URL: `https://trvlrr.app/api/trips/{id}/photos`
- Auth: HTTP bearer

Raw image bytes in the body (JPEG, PNG, WebP, HEIC or HEIF; sniffed, not trusted from the header), metadata in the query. Upload the original, then a small JPEG rendition with kind=thumb under the same photo_id — in either order. A source_id already on the trip returns the existing photo (200) instead of a duplicate. Free accounts hold 100 photos.

## Path parameters

- `id` string, required: The trip id.

## Query parameters

- `photo_id` string: Client-chosen id so the original and the thumbnail can arrive in any order.
- `kind` 'original' | 'thumb': thumb for the small JPEG rendition; original otherwise.
- `source_id` string: SHA-256 of the source image, for de-duplication.
- `taken_at` string: ISO 8601 instant the photo was taken.
- `lat` number, nullable
- `lon` number, nullable

## Response `200`

Already present (same source_id): the existing photo.

- Photo
  - `id` string, required
  - `trip_id` string, required
  - `experience_id` string, nullable, required
  - `r2_key` string, required
  - `content_type` string, nullable, required
  - `size_bytes` integer, nullable, required
  - `width` integer, nullable, required
  - `height` integer, nullable, required
  - `taken_at` string, nullable, required
  - `lat` number, nullable, required
  - `lon` number, nullable, required
  - `uploaded_by` string, nullable, required
  - `created_at` string, required
  - `thumb_r2_key` string, nullable, required
  - `caption` string, nullable, required
  - `tags` string, nullable, required
  - `landmark` string, nullable, required
  - `ocr_text` string, nullable, required
  - `indexed_at` string, nullable, required
  - `user_tags` string, nullable, required: JSON array, as text.
  - `faces_at` string, nullable, required
  - `source_id` string, nullable, required: The client's hash of the source image, for de-duplication.

## Other responses

- `201`: Stored.
- `400`: The request is malformed or fails validation.
- `401`: No usable credential: a Clerk session token, a device token or an OAuth access token is required.
- `402`: Trvlrr Plus is required, or this month's AI allowance is used up.
- `403`: The caller may not do this to this account or trip.
- `404`: No such thing, or not one the caller can see.
- `413`: The upload is too large.
- `415`: The bytes are not a supported type.

---

[API](https://skmtc.dev/trvlrr/apis/trvlrr-api.md) · [All operations](https://skmtc.dev/trvlrr/apis/trvlrr-api/llms.txt) · [OpenAPI document](https://skmtc.dev/trvlrr/apis/trvlrr-api/revisions/ea024946b293?raw)
