---
title: "Add photos to a project"
method: POST
path: "/projects/{project_id}/photos"
tags: ["Photos"]
---

# Add photos to a project

`POST /projects/{project_id}/photos`

## Request

- Base URL: `https://api.propertypixel.app/v1`
- URL: `https://api.propertypixel.app/v1/projects/{project_id}/photos`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://llflgtovnkrsjqydycyu.supabase.co/auth/v1/oauth/authorize`, token URL `https://llflgtovnkrsjqydycyu.supabase.co/auth/v1/oauth/token`)

Add photos to a project from public https image URLs (`image_urls`) or from files the user attached in ChatGPT (`files`). Up to 25 photos per call; a project holds at most 100. Each file must be JPEG, PNG, WebP, HEIC or HEIF and at most 25 MB. Dropbox and Google Drive share links work if they are shared publicly. HEIC is converted to JPEG and large photos are downscaled to a 2048px long edge, like the web app. Returns the new photos (use their IDs with enhance_photos, stage_photos or create_video) and a `failed` list with a reason per source; report failures to the user rather than retrying blindly. If the user has photos on their phone or computer and no links, call create_upload_link instead.

Requires a connection with edit access.

## Path parameters

- `project_id` string, uuid, required: The project to add photos to.

## Headers

- `Idempotency-Key` string: A unique key (for example a UUID) that makes retries safe. A retry with the same key and body returns the stored response; the same key with a different body returns 422. Keys last 24 hours.

## Request body

- object
  - `image_urls` string[]: Public https image URLs (max 25).
  - `files` ChatGptFile[]: Files the user attached in ChatGPT.
    - `download_url` string, required: Temporary download URL supplied by ChatGPT.
    - `file_id` string, required: ChatGPT file ID.
    - `mime_type` string
    - `file_name` string

## Response `201`

Add photos to a project

- PhotoList
  - `object` 'photo_list', required
  - `photos` Photo[], required
    - `object` 'photo', required
    - `id` string, uuid, required
    - `project_id` string, uuid, nullable, required
    - `filename` string, nullable, required
    - `room_type` string, nullable, required
    - `width` integer, nullable, required
    - `height` integer, nullable, required
    - `original` MediaUrl, required
      - `url` string, uri, required
      - `expires_at` string, date-time, required: Signed URL expiry (about 1 hour). Re-fetch for a fresh URL.
    - `thumbnail` MediaUrl, required
      - `url` string, uri, required
      - `expires_at` string, date-time, required: Signed URL expiry (about 1 hour). Re-fetch for a fresh URL.
    - `created_at` string, date-time, required
  - `failed` object[], required
    - `source` string, required: The URL or file name that failed.
    - `error` string, required
  - `project_photo_count` integer, required: Photos now in the project (max 100).

## Other responses

- `400`: The request is invalid. Codes: `validation_failed`.
- `401`: Missing, invalid or revoked credential. Codes: `unauthorized`.
- `403`: The credential can't do this. View-only connections get forbidden_scope on writes. Codes: `forbidden_scope`, `forbidden`.
- `404`: The resource doesn't exist in this workspace. Codes: `not_found`.
- `409`: The request conflicts with the current state. Codes: `conflict`, `idempotency_in_progress`.
- `422`: An Idempotency-Key was reused with a different body, or an upload was rejected. Codes: `idempotency_conflict`, `upload_rejected`.
- `429`: Too many requests for this connection. Codes: `rate_limited`.
- `500`: Something failed on our side. Retry later. Codes: `internal`.

---

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