Uploading photos
Four ways to get listing photos into a project.
Every photo belongs to a project. Create one first (create_project, or POST /v1/projects).
All routes accept JPEG, PNG, WebP, HEIC and HEIF files up to 25 MB each, and a project holds up to 100 photos. HEIC and HEIF are converted to JPEG, and large photos are resized to a 2048px long edge, as in the web app.
| Method | Use it when | MCP | REST |
|---|---|---|---|
| Image URLs | The photos are online (CDN, listing site, Dropbox) | ✓ | ✓ |
| ChatGPT files | The user attached photos in ChatGPT | ✓ | |
| Upload link | The photos are on someone's phone or computer | ✓ | ✓ |
| Signed upload | Your script has the files locally | ✓ |
Image URLs
Pass up to 25 public https URLs to add_photos (POST /v1/projects/{project_id}/photos):
{
"image_urls": ["https://example.com/front.jpg", "https://www.dropbox.com/s/abc/kitchen.jpg?dl=0"]
}Public Dropbox and Google Drive share links are converted to direct downloads. URLs must be reachable without signing in; private and internal addresses are refused. The response lists the new photos and a failed entry, with a reason, for each URL that couldn't be used.
ChatGPT files
In ChatGPT, photos attached to a message are passed to add_photos as files, and PropertyPixel downloads them. If ChatGPT can't hand over a file, PropertyPixel shows its upload widget instead.
Upload links
create_upload_link (POST /v1/projects/{project_id}/upload-links) returns a private url and a QR code (qr_svg). Open the link on a phone or computer to add photos without signing in.
- A link works for 2 hours and only for that project.
- Photos appear in the project as they upload.
get_projectshows the latest link'supload_link.status(waiting,receiving,doneorexpired) andphotos_received.- When the person finishes, PropertyPixel sends the
upload.completedwebhook.
Anyone with the link can add photos to that project until it expires, so share it only with the person taking the photos.
Signed upload
For files on disk, use the three-step signed upload (REST only).
1. Reserve uploads with each file's name, type and size:
curl -s -X POST "$PP_API/uploads" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id": "'"$PROJECT_ID"'",
"files": [{"filename": "kitchen.heic", "content_type": "image/heic", "size": 3481234}]}'The response has an upload id and, per file, an upload_url, the method, the headers to send and a storage_path.
2. Upload each file's bytes to its upload_url with exactly those headers:
curl -s -X PUT "$UPLOAD_URL" -H "Content-Type: image/heic" --data-binary @kitchen.heic3. Complete the upload, listing every file that uploaded:
curl -s -X POST "$PP_API/uploads/$UPLOAD_ID/complete" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id": "'"$PROJECT_ID"'",
"files": [{"storage_path": "…", "filename": "kitchen.heic", "size": 3481234, "content_type": "image/heic"}]}'PropertyPixel checks each file's size, type and contents, then adds it to the project. Files you leave out are discarded. Rejected files are listed in failed with a reason.