PHP examples
Snippets target PHP 8.1+. cURL works out of the box; for Guzzle:
composer require guzzlehttp/guzzle<?php
const PHOTOPICK_API = 'https://api.photopick.cz/api/v1';$token = getenv('PHOTOPICK_API_KEY'); // pp_live_xxxxxxxxxxxxxxxx1. Verify your key (/me)
Section titled “1. Verify your key (/me)”<?phpfunction pp_request(string $path, string $method = 'GET', ?array $body = null): array { $ch = curl_init(PHOTOPICK_API . '/' . ltrim($path, '/')); $headers = [ 'Authorization: Bearer ' . getenv('PHOTOPICK_API_KEY'), 'Accept: application/json', ]; if ($body !== null) { $headers[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); } curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, ]); $raw = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($status >= 400) { throw new RuntimeException("HTTP $status: $raw"); } return json_decode($raw, true);}
$me = pp_request('me');print_r($me['scopes']);print_r($me['rateLimit']);<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => PHOTOPICK_API . '/', 'headers' => [ 'Authorization' => 'Bearer ' . getenv('PHOTOPICK_API_KEY'), 'Accept' => 'application/json', ], 'timeout' => 10,]);
$me = json_decode($client->get('me')->getBody(), true);print_r($me['scopes']);print_r($me['rateLimit']);Required scope: customer:read.
2. List photos (paginated)
Section titled “2. List photos (paginated)”Generator yielding every photo across pages — constant memory:
<?phpuse GuzzleHttp\Client;
function iterate_photos(Client $client, int $pageSize = 100): Generator { $cursor = null; do { $query = ['limit' => $pageSize]; if ($cursor !== null) { $query['cursor'] = $cursor; } $body = json_decode( $client->get('photos', ['query' => $query])->getBody(), true, ); foreach ($body['data'] as $photo) { yield $photo; } $cursor = $body['pagination']['hasMore'] ? $body['pagination']['nextCursor'] : null; } while ($cursor !== null);}
foreach (iterate_photos($client) as $photo) { echo $photo['id'] . ' ' . $photo['filename'] . PHP_EOL;}Required scope: photos:read.
3. Fetch one photo + download original
Section titled “3. Fetch one photo + download original”Stream straight to disk so memory stays flat regardless of file size.
<?phpuse GuzzleHttp\Client;
function download_photo(Client $client, int $photoId, string $destination): array { $meta = json_decode($client->get("photos/$photoId")->getBody(), true);
// sink + stream avoids loading the whole file into memory $client->get("photos/$photoId/download", [ 'sink' => $destination, 'allow_redirects' => true, ]);
return $meta;}
$meta = download_photo($client, 1234, __DIR__ . '/photo-1234.jpg');echo "Saved {$meta['filename']} ({$meta['bytes']} bytes)" . PHP_EOL;Required scope: photos:read.
4. Tag CRUD
Section titled “4. Tag CRUD”<?phpuse GuzzleHttp\Client;
// Create$created = json_decode($client->post('tags', [ 'json' => ['name' => 'Selects', 'color' => '#e94b60'],])->getBody(), true);
// Attach (full replace — pass the complete tag list)$client->put("photos/1234/tags", [ 'json' => ['tagIds' => [$created['id']]],]);
// Rename$client->patch("tags/{$created['id']}", [ 'json' => ['name' => 'Final selects'],]);
// Delete$client->delete("tags/{$created['id']}");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)”Guzzle ships with a middleware for retry; the pattern below makes the per-call intent explicit.
<?phpuse GuzzleHttp\Client;use GuzzleHttp\Exception\RequestException;use Psr\Http\Message\ResponseInterface;
function with_retry(callable $call, int $maxAttempts = 5): ResponseInterface { for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) { try { $response = $call(); $status = $response->getStatusCode(); if ($status < 500 && $status !== 429) { return $response; } } catch (RequestException $e) { $response = $e->getResponse(); if ($response === null) { throw $e; // network error — bail } $status = $response->getStatusCode(); if ($status < 500 && $status !== 429) { throw $e; } } $retryAfter = (int) ($response->getHeaderLine('Retry-After') ?: $attempt); sleep(min($retryAfter, 2 ** $attempt)); } throw new RuntimeException('retry budget exhausted');}
$response = with_retry(fn() => $client->get('photos'));See Rate limits and Errors for the full vocabulary.