Python examples
Snippets target Python 3.10+. Install whichever HTTP client you prefer:
pip install requests # battle-tested, sync only# orpip install httpx # sync + async, HTTP/2import os
API = 'https://api.photopick.cz/api/v1'TOKEN = os.environ['PHOTOPICK_API_KEY'] # pp_live_xxxxxxxxxxxxxxxxAUTH = {'Authorization': f'Bearer {TOKEN}'}1. Verify your key (/me)
Section titled “1. Verify your key (/me)”import requests
res = requests.get(f'{API}/me', headers=AUTH, timeout=10)res.raise_for_status()me = res.json()print(me['scopes'], me['rateLimit'])import httpx
with httpx.Client(base_url=API, headers=AUTH, timeout=10) as c: me = c.get('/me').raise_for_status().json() print(me['scopes'], me['rateLimit'])import asyncio, httpx
async def main(): async with httpx.AsyncClient(base_url=API, headers=AUTH) as c: me = (await c.get('/me')).raise_for_status().json() print(me['scopes'], me['rateLimit'])
asyncio.run(main())Required scope: customer:read.
2. List photos (paginated)
Section titled “2. List photos (paginated)”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.
3. Fetch one photo + download original
Section titled “3. Fetch one photo + download original”import httpxfrom 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.
4. Tag CRUD
Section titled “4. Tag CRUD”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).
Retry pattern (rate limits + 5xx)
Section titled “Retry pattern (rate limits + 5xx)”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.