Getting Started with Pichr API

This guide will walk you through making your first API requests to Pichr, from authentication to uploading and managing images.

Prerequisites

Before you begin, you'll need:

  1. A Pichr account (sign up here)
  2. An API client (cURL, Postman, or your preferred programming language)
  3. Basic knowledge of REST APIs and HTTP requests

Base URL

All API requests should be made to:

https://api.pichr.io/api/v1

For local development:

http://localhost:8787/api/v1

Step 1: Create an Account

Sign up for a Pichr account via the API:

curl -X POST https://api.pichr.io/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "your@email.com",
"password": "your-secure-password",
"username": "your_username",
"firstName": "Your",
"lastName": "Name",
"ageConfirmed": true
}'

Response:

{
"user": {
"id": "usr_abc123",
"email": "your@email.com",
"username": "your_username",
"role": "user",
"plan": "free",
"ageConfirmed": true
},
"session": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "...",
"expires_in": 3600
}
}

Save the access_token - you'll need it for authenticated requests.

Step 2: Authenticate

For subsequent requests, you can log in with your credentials:

curl -X POST https://api.pichr.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "your@email.com",
"password": "your-secure-password"
}'

The response includes an access_token that you'll use for authenticated requests.

Step 3: Upload Your First Image

Option A: Direct Upload (Recommended for small files < 5MB)

# 1. Request a presigned upload URL
curl -X POST https://api.pichr.io/api/v1/upload/presign \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filename": "cat.jpg",
"mime": "image/jpeg",
"bytes": 524288,
"title": "My Cute Cat",
"description": "A photo of my adorable cat",
"visibility": "public"
}'

Response:

{
"uploadId": "upl_xyz789",
"uploadUrl": "https://api.pichr.io/api/v1/upload/direct/file_abc123",
"fileId": "file_abc123",
"r2Key": "uploads/2025-11-13/file_abc123/cat.jpg",
"message": "File record created. Use PUT request to upload file, then call /finalize"
}
# 2. Upload the file
curl -X PUT https://api.pichr.io/api/v1/upload/direct/file_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: image/jpeg" \
--data-binary "@/path/to/cat.jpg"

Response:

{
"fileId": "file_abc123",
"url": "https://i.pichr.io/file_abc123",
"mime": "image/jpeg",
"bytes": 524288,
"status": "ready"
}

Option B: Multipart Upload (For large files > 5MB)

# 1. Initialize multipart upload
curl -X POST https://api.pichr.io/api/v1/upload/multipart/init \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filename": "large-image.jpg",
"mime": "image/jpeg",
"totalBytes": 52428800,
"partSize": 5242880
}'
# 2. Upload parts (repeat for each part)
# 3. Complete upload
curl -X POST https://api.pichr.io/api/v1/upload/multipart/complete \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"uploadId": "upl_xyz789",
"fileId": "file_abc123",
"r2Key": "uploads/2025-11-13/file_abc123/large-image.jpg",
"parts": [
{ "partNumber": 1, "etag": "etag1" },
{ "partNumber": 2, "etag": "etag2" }
]
}'

Step 4: Retrieve Your Images

List all your uploaded images:

curl -X GET "https://api.pichr.io/api/v1/files?limit=10&offset=0" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Get a specific image's metadata:

curl -X GET https://api.pichr.io/api/v1/files/file_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
"id": "file_abc123",
"url": "https://i.pichr.io/file_abc123",
"cdnUrl": "https://cdn.pichr.io/uploads/2025-11-13/file_abc123/cat.jpg",
"title": "My Cute Cat",
"description": "A photo of my adorable cat",
"mime": "image/jpeg",
"bytes": 524288,
"width": 1920,
"height": 1080,
"visibility": "public",
"viewCount": 42,
"downloadCount": 5,
"nsfwScore": 0.02,
"ageRestricted": false,
"tags": ["cat", "pet"],
"createdAt": "2025-11-13T10:30:00Z",
"expiresAt": null
}

Step 5: Create an Album

Organize your images into albums:

curl -X POST https://api.pichr.io/api/v1/albums \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Cat Photos Collection",
"description": "All my favorite cat pictures",
"visibility": "public",
"fileIds": ["file_abc123", "file_def456"],
"tags": ["cats", "pets", "animals"]
}'

Response:

{
"id": "alb_xyz789",
"title": "Cat Photos Collection",
"description": "All my favorite cat pictures",
"visibility": "public",
"tags": ["cats", "pets", "animals"],
"createdAt": "2025-11-13T10:35:00Z",
"url": "https://pichr.io/a/alb_xyz789"
}

Code Examples

JavaScript/TypeScript

// Install dependencies: npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
);
// 1. Sign up
const { data: authData, error: authError } = await supabase.auth.signUp({
email: 'your@email.com',
password: 'your-secure-password',
});
// 2. Upload image
async function uploadImage(file: File, token: string) {
// Request presign
const presignRes = await fetch('https://api.pichr.io/api/v1/upload/presign', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: file.name,
mime: file.type,
bytes: file.size,
visibility: 'public',
}),
});
const { uploadUrl, fileId } = await presignRes.json();
// Upload file
await fetch(uploadUrl, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': file.type,
},
body: file,
});
return fileId;
}
// 3. List images
async function listImages(token: string) {
const res = await fetch('https://api.pichr.io/api/v1/files', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
return res.json();
}

Python

# Install dependencies: pip install requests
import requests
API_BASE = 'https://api.pichr.io/api/v1'
# 1. Sign up
def signup(email, password, username):
response = requests.post(f'{API_BASE}/auth/signup', json={
'email': email,
'password': password,
'username': username,
'firstName': 'Your',
'lastName': 'Name',
'ageConfirmed': True
})
return response.json()
# 2. Upload image
def upload_image(file_path, token):
# Get file info
import os
file_size = os.path.getsize(file_path)
file_name = os.path.basename(file_path)
# Request presign
presign_res = requests.post(
f'{API_BASE}/upload/presign',
headers={'Authorization': f'Bearer {token}'},
json={
'filename': file_name,
'mime': 'image/jpeg',
'bytes': file_size,
'visibility': 'public'
}
)
presign_data = presign_res.json()
upload_url = presign_data['uploadUrl']
file_id = presign_data['fileId']
# Upload file
with open(file_path, 'rb') as f:
upload_res = requests.put(
upload_url,
headers={'Authorization': f'Bearer {token}'},
data=f
)
return file_id
# 3. List images
def list_images(token):
response = requests.get(
f'{API_BASE}/files',
headers={'Authorization': f'Bearer {token}'}
)
return response.json()

PHP

<?php
// Install dependencies: composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'https://api.pichr.io/api/v1/']);
// 1. Sign up
function signup($email, $password, $username) {
global $client;
$response = $client->post('auth/signup', [
'json' => [
'email' => $email,
'password' => $password,
'username' => $username,
'firstName' => 'Your',
'lastName' => 'Name',
'ageConfirmed' => true
]
]);
return json_decode($response->getBody(), true);
}
// 2. Upload image
function uploadImage($filePath, $token) {
global $client;
// Request presign
$presignRes = $client->post('upload/presign', [
'headers' => ['Authorization' => "Bearer $token"],
'json' => [
'filename' => basename($filePath),
'mime' => mime_content_type($filePath),
'bytes' => filesize($filePath),
'visibility' => 'public'
]
]);
$presignData = json_decode($presignRes->getBody(), true);
$uploadUrl = $presignData['uploadUrl'];
$fileId = $presignData['fileId'];
// Upload file
$client->put($uploadUrl, [
'headers' => ['Authorization' => "Bearer $token"],
'body' => fopen($filePath, 'r')
]);
return $fileId;
}
// 3. List images
function listImages($token) {
global $client;
$response = $client->get('files', [
'headers' => ['Authorization' => "Bearer $token"]
]);
return json_decode($response->getBody(), true);
}
?>

Error Handling

The API uses conventional HTTP status codes:

CodeMeaning
200Success
201Created
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing token
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
413Payload Too Large - File size exceeds limit
429Too Many Requests - Rate limit exceeded
500Internal Server Error
503Service Unavailable

Error response format:

{
"error": "Rate limit exceeded",
"retryAfter": 1700000000000,
"remaining": 0
}

Rate Limit Headers

All responses include rate limit information:

X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 995 X-RateLimit-Reset: 1700000000

Next Steps

Need Help?

Last updated: 13 November 2025