JavaScript examples
All snippets assume Node.js 22+ or any modern browser. fetch is built in. For axios, install with pnpm add axios.
// Recommended setupconst API = 'https://api.photopick.cz/api/v1';const TOKEN = process.env.PHOTOPICK_API_KEY; // pp_live_xxxxxxxxxxxxxxxxconst authHeaders = { Authorization: `Bearer ${TOKEN}` };1. Verify your key (/me)
Section titled “1. Verify your key (/me)”const res = await fetch(`${API}/me`, { headers: authHeaders });if (!res.ok) throw new Error(`HTTP ${res.status}`);const me = await res.json();console.log(me.scopes, me.rateLimit);import axios from 'axios';
const client = axios.create({ baseURL: API, headers: authHeaders,});
const { data } = await client.get('/me');console.log(data.scopes, data.rateLimit);Required scope: customer:read.
2. List photos (paginated)
Section titled “2. List photos (paginated)”Walk the full collection with cursor pagination:
async function* iteratePhotos({ pageSize = 100 } = {}) { let cursor = undefined; do { const url = new URL(`${API}/photos`); url.searchParams.set('limit', String(pageSize)); if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: authHeaders }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const body = await res.json();
for (const photo of body.data) yield photo; cursor = body.pagination.hasMore ? body.pagination.nextCursor : null; } while (cursor);}
// Usage — process every photo without holding the full list in memory.for await (const photo of iteratePhotos()) { console.log(photo.id, photo.filename);}Required scope: photos:read.
3. Fetch one photo + download original
Section titled “3. Fetch one photo + download original”async function downloadPhoto(photoId, outPath) { const meta = await fetch(`${API}/photos/${photoId}`, { headers: authHeaders }) .then((r) => r.json());
const res = await fetch(`${API}/photos/${photoId}/download`, { headers: authHeaders, redirect: 'follow', }); if (!res.ok) throw new Error(`HTTP ${res.status}`);
// Node.js stream-to-file const { writeFile } = await import('node:fs/promises'); await writeFile(outPath, Buffer.from(await res.arrayBuffer()));
return meta;}
const meta = await downloadPhoto(1234, './photo-1234.jpg');console.log(`Saved ${meta.filename} (${meta.bytes} bytes)`);Required scope: photos:read.
4. Tag CRUD
Section titled “4. Tag CRUD”// Createconst created = await fetch(`${API}/tags`, { method: 'POST', headers: { ...authHeaders, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Selects', color: '#e94b60' }),}).then((r) => r.json());
// Attach (full replace — pass the complete tag list)await fetch(`${API}/photos/1234/tags`, { method: 'PUT', headers: { ...authHeaders, 'Content-Type': 'application/json' }, body: JSON.stringify({ tagIds: [created.id] }),});
// Renameawait fetch(`${API}/tags/${created.id}`, { method: 'PATCH', headers: { ...authHeaders, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Final selects' }),});
// Deleteconst del = await fetch(`${API}/tags/${created.id}`, { method: 'DELETE', headers: authHeaders,});console.log(del.status); // 204Required scopes: tags:write (create/rename/delete), photos:write (attach), tags:read (list).
Retry pattern (rate limits + 5xx)
Section titled “Retry pattern (rate limits + 5xx)”Wrap any request that might hit 429 or transient 5xx:
async function withRetry(call, { maxAttempts = 5 } = {}) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { const res = await call(); if (res.ok || (res.status < 500 && res.status !== 429)) return res;
const retryAfter = Number(res.headers.get('retry-after') ?? attempt); const backoff = Math.min(retryAfter, 2 ** attempt); await new Promise((r) => setTimeout(r, backoff * 1000)); } throw new Error('retry budget exhausted');}
const res = await withRetry(() => fetch(`${API}/photos`, { headers: authHeaders }),);See Rate limits and Errors for the full vocabulary.