Customer Photos
The customer photos API enables merchants to collect and moderate user-generated content (UGC) photos from customers. Photos can be submitted via the storefront (publishable key) or uploaded directly by admins (secret key). All submissions start in a pending state and require moderation before appearing on the storefront.
Use cases:
- Product page photo galleries showing real customer photos
- Social proof and trust signals on product pages
- User-generated content for marketing campaigns
- Customer submissions from Instagram, reviews, or email
Base URL: https://api.hydrajs.dev
Endpoints
| Method |
Path |
Auth |
Description |
GET |
/v1/customer-photos |
Secret |
List customer photos |
POST |
/v1/customer-photos |
Publishable |
Upload customer photo |
GET |
/v1/customer-photos/{id} |
Secret |
Get customer photo |
PATCH |
/v1/customer-photos/{id} |
Secret |
Update customer photo |
POST |
/v1/customer-photos/{id}/approve |
Secret |
Approve customer photo |
POST |
/v1/customer-photos/{id}/reject |
Secret |
Reject customer photo |
POST |
/v1/customer-photos/{id}/replace-image |
Secret |
Replace customer photo image |
DELETE |
/v1/customer-photos/{id} |
Secret |
Delete customer photo |
GET |
/v1/products/{id}/customer-photos |
Publishable |
Get approved product photos |
List customer photos
GET /v1/customer-photos
Returns a cursor-paginated list of customer photos. Supports filtering by product and moderation status. Requires a secret key.
Query parameters
| Parameter |
Type |
Description |
limit |
integer |
Results per page (1–100, default 25) |
cursor |
string |
Pagination cursor from a previous response |
product_id |
string |
Filter by product ID |
status |
string |
Filter by status: pending, approved, rejected |
sort |
string |
Sort field: created_at (default), updated_at |
order |
string |
Sort direction: asc, desc (default) |
Request
curl "https://api.hydrajs.dev/v1/customer-photos?status=pending&limit=10" \
-H "Authorization: Bearer sk_YOUR_KEY"
Response 200
{
"data": [
{
"id": "cp_abc123",
"product_id": "prod_xyz789",
"image_url": "https://cdn.hydrajs.dev/store_abc/ugc/a1b2c3d4.jpg",
"bucket_path": "store_abc/ugc/a1b2c3d4.jpg",
"width": 1200,
"height": 1500,
"aspect_ratio": "4:5",
"file_size": 245678,
"credit": "@janedoe",
"link_url": "https://instagram.com/janedoe",
"link_text": "View on Instagram",
"customer_id": null,
"customer_email": "jane@example.com",
"alt_text": "Customer wearing navy organic cotton t-shirt",
"show_in_gallery": true,
"status": "pending",
"moderated_at": null,
"moderated_by": null,
"moderation_notes": null,
"created_at": "2026-09-15T14:30:00.000Z",
"updated_at": "2026-09-15T14:30:00.000Z"
}
],
"pagination": {
"cursor": "eyJ0IjoiMjAyNi...",
"has_more": false,
"total": 1
}
}
Upload customer photo
POST /v1/customer-photos
Upload a customer photo for a product. Accepts multipart/form-data with an image file and metadata. Publishable or secret keys work — publishable-key uploads start in pending status, while admin uploads (secret key) are auto-approved.
Request body (multipart/form-data)
| Field |
Type |
Required |
Description |
image |
file |
Yes |
Image file (JPEG, PNG, WebP, GIF, AVIF, HEIC, max 10MB) |
product_id |
string |
Yes |
Product ID to attach photo to |
credit |
string |
No |
Photo credit (name or @handle, max 100 chars) |
link_url |
string |
No |
External link URL (e.g. Instagram profile) |
link_text |
string |
No |
Link text display (max 50 chars) |
alt_text |
string |
No |
Image alt text for accessibility (max 500 chars) |
customer_email |
string |
No |
Customer email (for guest submissions) |
Request
curl -X POST "https://api.hydrajs.dev/v1/customer-photos" \
-H "Authorization: Bearer pk_YOUR_KEY" \
-F "image=@customer-photo.jpg" \
-F "product_id=prod_xyz789" \
-F "credit=@janedoe" \
-F "link_url=https://instagram.com/janedoe" \
-F "customer_email=jane@example.com"
Response 201
{
"data": {
"id": "cp_abc123",
"product_id": "prod_xyz789",
"image_url": "https://cdn.hydrajs.dev/store_abc/ugc/a1b2c3d4.jpg",
"status": "pending",
"credit": "@janedoe",
"created_at": "2026-09-15T14:30:00.000Z"
}
}
Get customer photo
GET /v1/customer-photos/{id}
Get a single customer photo by ID. Returns full photo details including moderation status and metadata.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Customer photo ID |
Request
curl "https://api.hydrajs.dev/v1/customer-photos/cp_abc123" \
-H "Authorization: Bearer sk_YOUR_KEY"
Response 200
{
"data": {
"id": "cp_abc123",
"product_id": "prod_xyz789",
"image_url": "https://cdn.hydrajs.dev/store_abc/ugc/a1b2c3d4.jpg",
"bucket_path": "store_abc/ugc/a1b2c3d4.jpg",
"width": 1200,
"height": 1500,
"aspect_ratio": "4:5",
"file_size": 245678,
"credit": "@janedoe",
"link_url": "https://instagram.com/janedoe",
"link_text": "View on Instagram",
"customer_id": null,
"customer_email": "jane@example.com",
"alt_text": "Customer wearing navy organic cotton t-shirt",
"show_in_gallery": true,
"status": "approved",
"moderated_at": "2026-09-15T15:00:00.000Z",
"moderated_by": "admin@store.com",
"moderation_notes": null,
"created_at": "2026-09-15T14:30:00.000Z",
"updated_at": "2026-09-15T15:00:00.000Z"
}
}
Update customer photo
PATCH /v1/customer-photos/{id}
Update customer photo metadata. Only include the fields you want to change. Status changes should use the dedicated approve/reject endpoints.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Customer photo ID |
Request body
| Field |
Type |
Description |
credit |
string |
Photo credit (name or @handle, max 100 chars) |
link_url |
string |
External link URL |
link_text |
string |
Link text display (max 50 chars) |
alt_text |
string |
Image alt text for accessibility (max 200 chars) |
show_in_gallery |
boolean |
Whether to show in product gallery |
Request
curl -X PATCH "https://api.hydrajs.dev/v1/customer-photos/cp_abc123" \
-H "Authorization: Bearer sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"alt_text": "Customer wearing navy organic cotton t-shirt outdoors",
"show_in_gallery": true
}'
Response 200
{
"data": {
"id": "cp_abc123",
"product_id": "prod_xyz789",
"alt_text": "Customer wearing navy organic cotton t-shirt outdoors",
"show_in_gallery": true,
"updated_at": "2026-09-15T16:00:00.000Z"
}
}
Approve customer photo
POST /v1/customer-photos/{id}/approve
Approve a pending customer photo. Sets status to approved and makes the photo visible on the storefront. Records moderation timestamp and moderator email.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Customer photo ID |
Request
curl -X POST "https://api.hydrajs.dev/v1/customer-photos/cp_abc123/approve" \
-H "Authorization: Bearer sk_YOUR_KEY"
Response 200
{
"data": {
"id": "cp_abc123",
"status": "approved",
"moderated_at": "2026-09-15T15:00:00.000Z",
"moderated_by": "admin@store.com",
"updated_at": "2026-09-15T15:00:00.000Z"
}
}
Reject customer photo
POST /v1/customer-photos/{id}/reject
Reject a pending customer photo. Sets status to rejected and hides the photo from the storefront. Optionally include rejection notes for internal reference.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Customer photo ID |
Request body
| Field |
Type |
Description |
notes |
string |
Rejection notes (e.g. “Product not visible in photo”) |
Request
curl -X POST "https://api.hydrajs.dev/v1/customer-photos/cp_abc123/reject" \
-H "Authorization: Bearer sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"notes": "Product not clearly visible in the photo"
}'
Response 200
{
"data": {
"id": "cp_abc123",
"status": "rejected",
"moderated_at": "2026-09-15T15:00:00.000Z",
"moderated_by": "admin@store.com",
"moderation_notes": "Product not clearly visible in the photo",
"updated_at": "2026-09-15T15:00:00.000Z"
}
}
Replace customer photo image
POST /v1/customer-photos/{id}/replace-image
Replace the image file on an existing customer photo while preserving all metadata, approval status, and moderation history. Useful when a merchant wants to retouch, crop, or color-correct an image without losing the existing record.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Customer photo ID |
Request body (multipart/form-data)
| Field |
Type |
Required |
Description |
image |
file |
Yes |
New image file (JPEG, PNG, WebP, GIF, AVIF, max 10MB) |
alt_text |
string |
No |
Updated alt text for the new image (max 500 chars) |
Request
curl -X POST "https://api.hydrajs.dev/v1/customer-photos/cp_abc123/replace-image" \
-H "Authorization: Bearer sk_YOUR_KEY" \
-F "image=@retouched-photo.jpg"
Response 200
{
"data": {
"id": "cp_abc123",
"product_id": "prod_xyz789",
"image_url": "https://cdn.hydrajs.dev/store_abc/ugc/e5f6g7h8.webp",
"width": 1200,
"height": 1500,
"aspect_ratio": "4:5",
"file_size": 198432,
"credit": "@janedoe",
"status": "approved",
"updated_at": "2026-09-30T12:00:00.000Z"
}
}
Delete customer photo
DELETE /v1/customer-photos/{id}
Soft delete a customer photo. The photo is marked as deleted but the image file remains in storage. Deleted photos are hidden from all listings and the storefront.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Customer photo ID |
Request
curl -X DELETE "https://api.hydrajs.dev/v1/customer-photos/cp_abc123" \
-H "Authorization: Bearer sk_YOUR_KEY"
Response 204
No response body.
Get approved product photos
GET /v1/products/{id}/customer-photos
Get approved customer photos for a product (storefront-safe endpoint). Returns up to 24 approved photos for display on product pages. Works with publishable or secret keys.
Path parameters
| Parameter |
Type |
Description |
id |
string |
Product ID |
Request
curl "https://api.hydrajs.dev/v1/products/prod_xyz789/customer-photos" \
-H "Authorization: Bearer pk_YOUR_KEY"
Response 200
{
"data": [
{
"id": "cp_abc123",
"product_id": "prod_xyz789",
"image_url": "https://cdn.hydrajs.dev/store_abc/ugc/a1b2c3d4.jpg",
"width": 1200,
"height": 1500,
"aspect_ratio": "4:5",
"credit": "@janedoe",
"link_url": "https://instagram.com/janedoe",
"link_text": "View on Instagram",
"alt_text": "Customer wearing navy organic cotton t-shirt",
"show_in_gallery": true,
"created_at": "2026-09-15T14:30:00.000Z"
}
]
}
The customer photo object
| Field |
Type |
Description |
id |
string |
Unique ID (prefix: cp_) |
product_id |
string |
Product ID the photo is attached to |
image_url |
string |
CDN URL of the uploaded image |
bucket_path |
string |
R2 storage path (internal) |
width |
integer |
Image width in pixels |
height |
integer |
Image height in pixels |
aspect_ratio |
string |
Auto-detected aspect ratio: 1:1 or 4:5 |
file_size |
integer |
File size in bytes |
credit |
string|null |
Photo credit (name or @handle) |
link_url |
string|null |
External link URL (e.g. Instagram profile) |
link_text |
string|null |
Link text display |
customer_id |
string|null |
Customer ID if submitted by registered customer |
customer_email |
string|null |
Customer email (for guest submissions) |
alt_text |
string|null |
Image alt text for accessibility |
show_in_gallery |
boolean |
Whether to show in product gallery (default true) |
status |
string |
Moderation status: pending, approved, rejected |
moderated_at |
string|null |
ISO 8601 timestamp when moderated |
moderated_by |
string|null |
Email of admin who moderated the photo |
moderation_notes |
string|null |
Internal rejection notes |
created_at |
string |
ISO 8601 timestamp when photo was uploaded |
updated_at |
string |
ISO 8601 timestamp of last update |