Quickstart: REST
Enhance a listing from the command line with curl.
This walks through one listing: create a project, add photos by URL, enhance them with a quote, wait for the results, and download them.
1. Create an API key
In PropertyPixel, open Settings → Developers and select Create key. Pick the workspace and choose View and edit access. Copy the key (it starts with pp_live_); it is shown only once.
export PP_API_KEY="pp_live_..."
export PP_API="https://api.propertypixel.app/v1"Check that it works:
curl -s "$PP_API/account" -H "Authorization: Bearer $PP_API_KEY"The response shows the workspace, your access level and credits.available.
2. Create a project
curl -s -X POST "$PP_API/projects" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "14 Harbour Street", "address": "14 Harbour Street, Sydney"}'Note the id in the response:
export PROJECT_ID="..."3. Add photos by URL
curl -s -X POST "$PP_API/projects/$PROJECT_ID/photos" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"image_urls": [
"https://example.com/photos/living-room.jpg",
"https://example.com/photos/front.jpg"
]}'The response lists the new photos with their IDs, and a failed list for any URL that couldn't be used. To upload files from disk instead, see Uploading photos.
4. Get a quote
Spending operations price the request before running it. Call POST /enhancements without a quote_id:
curl -s -X POST "$PP_API/enhancements" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id": "'"$PROJECT_ID"'", "all_photos": true,
"enhancement_types": ["improve_lighting", "blue_skies"]}'Nothing is spent. The response is 409 with code quote_required, and a quote:
{
"type": "https://docs.propertypixel.app/errors#quote_required",
"title": "Quote required",
"status": 409,
"code": "quote_required",
"detail": "This request spends credits. Confirm the quote by sending quote_id, or send auto_confirm: true.",
"quote": {
"object": "quote",
"id": "5d1c…",
"operation": "enhance_photos",
"credits": 200,
"balance": { "available": 500, "after": 300 },
"sufficient": true,
"watermarked": true,
"expires_at": "2026-10-04T10:10:00Z"
}
}5. Confirm and run
Send the same body again with the quote's id:
curl -s -X POST "$PP_API/enhancements" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"project_id": "'"$PROJECT_ID"'", "all_photos": true,
"enhancement_types": ["improve_lighting", "blue_skies"],
"quote_id": "5d1c…"}'The response (201) is a job batch: one job per photo, credits_spent, and poll_after_ms.
Scripts that don't need a quote
Send "auto_confirm": true instead of quote_id to spend in one call. Only do this in code you
control; see Credits and quotes.
6. Wait for the results
curl -s "$PP_API/jobs?ids=JOB_ID_1,JOB_ID_2" -H "Authorization: Bearer $PP_API_KEY"Repeat after poll_after_ms until all_done is true. Each completed job has result.url, signed for one hour. Or skip polling and subscribe to the job.completed webhook.
7. Download
One result is a single signed link. Several start a zip:
curl -s -X POST "$PP_API/downloads" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"job_ids": ["JOB_ID_1", "JOB_ID_2"]}'If status is processing, poll GET /downloads/{id} until it returns a url.