Skip to content

Python examples

Snippets target Python 3.10+. Install whichever HTTP client you prefer:

Terminal window
pip install requests # battle-tested, sync only
# or
pip install httpx # sync + async, HTTP/2
import os
API = 'https://api.photopick.cz/api/v1'
TOKEN = os.environ['PHOTOPICK_API_KEY'] # pp_live_xxxxxxxxxxxxxxxx
AUTH = {'Authorization': f'Bearer {TOKEN}'}
import requests
res = requests.get(f'{API}/me', headers=AUTH, timeout=10)
res.raise_for_status()
me = res.json()
print(me['scopes'], me['rateLimit'])

Required scope: customer:read.

Generator yielding every photo across pages — uses constant memory:

import httpx
def iterate_photos(client: httpx.Client, page_size: int = 100):
cursor = None
while True:
params = {'limit': page_size}
if cursor:
params['cursor'] = cursor
body = client.get('/photos', params=params).raise_for_status().json()
yield from body['data']
if not body['pagination']['hasMore']:
return
cursor = body['pagination']['nextCursor']
with httpx.Client(base_url=API, headers=AUTH, timeout=30) as c:
for photo in iterate_photos(c):
print(photo['id'], photo['filename'])

Required scope: photos:read.

import httpx
from pathlib import Path
def download_photo(client: httpx.Client, photo_id: int, out: Path) -> dict:
meta = client.get(f'/photos/{photo_id}').raise_for_status().json()
with client.stream('GET', f'/photos/{photo_id}/download',
follow_redirects=True) as res:
res.raise_for_status()
with out.open('wb') as fh:
for chunk in res.iter_bytes():
fh.write(chunk)
return meta
with httpx.Client(base_url=API, headers=AUTH, timeout=60) as c:
meta = download_photo(c, 1234, Path('photo-1234.jpg'))
print(f"Saved {meta['filename']} ({meta['bytes']} bytes)")

Required scope: photos:read.

import httpx
with httpx.Client(base_url=API, headers=AUTH, timeout=10) as c:
# Create
created = c.post('/tags', json={
'name': 'Selects',
'color': '#e94b60',
}).raise_for_status().json()
# Attach (full replace — pass the complete tag list)
c.put(f'/photos/1234/tags', json={
'tagIds': [created['id']],
}).raise_for_status()
# Rename
c.patch(f'/tags/{created["id"]}', json={
'name': 'Final selects',
}).raise_for_status()
# Delete
c.delete(f'/tags/{created["id"]}').raise_for_status()

Required scopes: tags:write (create/rename/delete), photos:write (attach), tags:read (list).

import time, httpx
def with_retry(call, max_attempts: int = 5) -> httpx.Response:
for attempt in range(1, max_attempts + 1):
res = call()
if res.is_success or (res.status_code < 500 and res.status_code != 429):
return res
retry_after = int(res.headers.get('retry-after', attempt))
time.sleep(min(retry_after, 2 ** attempt))
raise RuntimeError('retry budget exhausted')
with httpx.Client(base_url=API, headers=AUTH) as c:
res = with_retry(lambda: c.get('/photos'))
res.raise_for_status()

See Rate limits and Errors for the full vocabulary.