API Reference
HTTP endpoints for photo uploads and integration with getpic.app.
1. Authentication
Pass your project API key in the HTTP request header:
x-api-key: YOUR_API_KEYGenerate keys in Dashboard → Project Settings → API Keys.
POST
/api/upload
Standard single-file upload via multipart/form-data.
| Parameter | Type | Required | Description |
|---|---|---|---|
| file | File | Yes | Binary photo data (JPEG, PNG, RAW) |
| destination | string | Yes | 'individual' (Photos 1:1) | 'gallery' | 'selection_preview' |
| slug | string | If gallery | Target gallery slug identifier |
Request Examples
curl -X POST "https://your-domain.com/api/upload" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@photo.jpg" \ -F "destination=gallery" \ -F "slug=wedding-2026"
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('destination', 'gallery'); // 'individual' | 'gallery' | 'selection_preview'
formData.append('slug', 'wedding-2026'); // required for 'gallery'
const res = await fetch('https://your-domain.com/api/upload', {
method: 'POST',
headers: { 'x-api-key': 'YOUR_API_KEY' },
body: formData,
});
const data = await res.json();
console.log(data);import requests
url = "https://your-domain.com/api/upload"
headers = {"x-api-key": "YOUR_API_KEY"}
files = {"file": open("photo.jpg", "rb")}
data = {"destination": "gallery", "slug": "wedding-2026"}
response = requests.post(url, headers=headers, files=files, data=data)
print(response.json())Response Example (200 OK)
{
"success": true,
"photoId": "550e8400-e29b-41d4-a716-446655440000",
"shortCode": "abc123xy",
"url": "https://your-domain.com/s/abc123xy",
"canonicalUrl": "https://your-domain.com/s/550e8400-e29b-41d4-a716-446655440000",
"filename": "photo.jpg",
"r2Key": "tenant-id/galleries/gallery-id/photo.jpg"
}RAW / Stream
Multipart Upload (/api/upload/multipart/*)
3-step chunked upload flow for large files or RAW camera formats:
1. INIT
POST /api/upload/multipart/init
Body:
{ destination, slug, filename }2. PART
POST /api/upload/multipart/part
Params:
rawR2Key, uploadId, partNumber3. COMPLETE
POST /api/upload/multipart/complete
Body:
{ photoId, parts: [{ partNumber, etag }] }// 1. Init session
const init = await fetch('/api/upload/multipart/init', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ destination: 'gallery', slug: 'wedding-2026', filename: file.name }),
}).then(r => r.json());
// 2. Upload chunk parts
const parts = [];
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
let partNumber = 1;
for (let start = 0; start < file.size; start += CHUNK_SIZE) {
const chunk = file.slice(start, start + CHUNK_SIZE);
const partUrl = `/api/upload/multipart/part?rawR2Key=${encodeURIComponent(init.rawR2Key)}&uploadId=${encodeURIComponent(init.uploadId)}&partNumber=${partNumber}`;
const { etag } = await fetch(partUrl, {
method: 'POST',
headers: { 'x-api-key': API_KEY },
body: chunk,
}).then(r => r.json());
parts.push({ partNumber, etag });
partNumber++;
}
// 3. Complete session
const result = await fetch('/api/upload/multipart/complete', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
photoId: init.photoId,
rawR2Key: init.rawR2Key,
uploadId: init.uploadId,
parts,
destination: 'gallery',
slug: 'wedding-2026',
filename: file.name,
size: file.size,
}),
}).then(r => r.json());4. Legacy Routes
| Legacy Endpoint | Method | Standard Equivalent |
|---|---|---|
| /api/upload/photo | POST | POST /api/upload (destination='individual') |
| /api/upload/selection-preview | POST | POST /api/upload (destination='selection_preview') |
| /api/upload/gallery/:slug | POST | POST /api/upload (destination='gallery' & slug) |