Appearance
Project Management
API Type
PWA Backend API - Uses access keys in X-Albumstory header. See Authentication.
Manage user projects: list, delete, duplicate, and transfer ownership.
Environment URLs:
- Development:
https://pwa-api-dev.photobook.ai/v2/* - Production:
https://pwa-api.photobook.ai/v2/*
List User Projects
Retrieve all projects for a specific user.
Endpoint
GET https://pwa-api{-env}.photobook.ai/v2/project/list?user={userId}Authentication
See Authentication - PWA Backend
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
user | string | Yes | End user's identifier (userId or email used during creation) |
Response
json
{
"data": [
{
"title": "Summer Memories 2024",
"preview": "https://cdn.photobook.ai/previews/abc123.jpg",
"id": "p-abc123xyz",
"status": {
"photos": "idle",
"project": "editable"
},
"createdAt": 1713190200000,
"updatedAt": 1713276600000,
"expiresAt": 1715868600,
"product": {
"psp": "PrinterName",
"sku": "PB001",
"marketId": 0,
"marketIdAlt": "custom-market"
},
"link": "https://{frontend}.photobook.ai?id=p-abc123xyz",
"ordered": false
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
data | array | List of user's projects |
Project Object
| Field | Type | Description |
|---|---|---|
title | string | Project title (inferred from cover text, falls back to SKU) |
preview | string | Preview image URL (empty if not saved yet) |
id | string | Project ID (starts with p-) |
status | object | Project and photo status |
status.photos | string | Photo status: none, uploading, analyzing, idle |
status.project | string | Project status: created, building, editable, deleted |
createdAt | integer | Unix timestamp in milliseconds |
updatedAt | integer | Unix timestamp in milliseconds |
expiresAt | integer | Unix timestamp in seconds (when project will be deleted) |
product | object | Product details |
product.psp | string | Printer name |
product.sku | string | Product SKU |
product.marketId | integer | Internal market ID |
product.marketIdAlt | string | Alternative market identifier |
link | string | Direct link to project in PWA editor |
ordered | boolean | Whether project has been ordered |
Photo Status Values
none- No photos uploadeduploading- Photos currently uploadinganalyzing- Photos being analyzedidle- Photos ready (not uploading or analyzing)
Project Status Values
created- Project created, book data not yet generatedbuilding- Book data being generatededitable- Ready for user editingdeleted- Marked for deletion (data expunged after 2 days)
Example
javascript
async function getUserProjects(userId) {
const response = await fetch(
`https://pwa-api-dev.photobook.ai/v2/project/list?user=${encodeURIComponent(userId)}`,
{
method: 'GET',
headers: {
'X-Albumstory': JSON.stringify({
accessKey: process.env.PWA_ACCESS_KEY,
accessSecret: process.env.PWA_ACCESS_SECRET
})
}
}
);
const data = await response.json();
return data.data;
}Delete Projects
Delete one or more projects for a user.
Endpoint
POST https://pwa-api{-env}.photobook.ai/v2/project/deleteRequest Parameters
json
{
"user": "user-12345",
"type": "async",
"ids": [
"p-abc123xyz",
"p-def456uvw"
]
}| Parameter | Type | Required | Description |
|---|---|---|---|
user | string | Yes | End user's identifier |
type | string | Yes | Deletion mode: async or sync |
ids | array | Yes | Project IDs to delete (max 10 per request) |
Deletion Types
async- Initiates deletion and returns immediately (recommended)sync- Waits for deletion to complete before responding
Max Projects
Maximum of 10 project IDs per API call.
Response
json
{
"message": "scheduled to delete p-abc123xyz, p-def456uvw"
}Example
javascript
async function deleteProjects(userId, projectIds) {
const response = await fetch('https://pwa-api-dev.photobook.ai/v2/project/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Albumstory': JSON.stringify({
accessKey: process.env.PWA_ACCESS_KEY,
accessSecret: process.env.PWA_ACCESS_SECRET
})
},
body: JSON.stringify({
user: userId,
type: 'async',
ids: projectIds.slice(0, 10) // Ensure max 10
})
});
return await response.json();
}Duplicate Project
Create a copy of an existing project for the same user.
Endpoint
POST https://pwa-api{-env}.photobook.ai/v2/project/duplicateRequest Parameters
json
{
"id": "p-abc123xyz",
"callback": "https://your-app.com/api/order-callback"
}| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID to duplicate |
callback | string | No | Override callback URL for the duplicated project |
Response
json
{
"title": "Summer Memories 2024",
"preview": "https://cdn.photobook.ai/previews/new123.jpg",
"id": "p-new123xyz",
"endUserId": "user-12345",
"createdAt": 1713190200000,
"updatedAt": 1713190200000,
"expiresAt": 1715868600,
"product": {
"sku": "PB001",
"psp": "PrinterName",
"marketId": 0,
"marketIdAlt": "custom-market"
},
"link": "https://{frontend}.photobook.ai?id=p-new123xyz"
}Example
javascript
async function duplicateProject(projectId, newCallback = null) {
const requestData = { id: projectId };
if (newCallback) {
requestData.callback = newCallback;
}
const response = await fetch('https://pwa-api-dev.photobook.ai/v2/project/duplicate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Albumstory': JSON.stringify({
accessKey: process.env.PWA_ACCESS_KEY,
accessSecret: process.env.PWA_ACCESS_SECRET
})
},
body: JSON.stringify(requestData)
});
const data = await response.json();
return data.id; // Return new project ID
}Transfer Project
Transfer project ownership to another user.
Endpoint
POST https://pwa-api{-env}.photobook.ai/v2/project/transferRequest Parameters
json
{
"id": "p-abc123xyz",
"targetUser": "new-user-12345",
"callback": "https://your-app.com/api/order-callback"
}| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Project ID to transfer |
targetUser | string | Yes | Target user identifier to transfer ownership to |
callback | string | No | Override callback URL for the transferred project |
Response
json
{
"title": "Summer Memories 2024",
"preview": "https://cdn.photobook.ai/previews/abc123.jpg",
"id": "p-abc123xyz",
"endUserId": "new-user-12345",
"createdAt": 1713190200000,
"updatedAt": 1713276600000,
"expiresAt": 1715868600,
"product": {
"sku": "PB001",
"psp": "PrinterName",
"marketId": 0,
"marketIdAlt": "custom-market"
},
"link": "https://{frontend}.photobook.ai?id=p-abc123xyz"
}⚠️
After transfer, the endUserId reflects the new owner. The original user will no longer see this project in their list.
Example
javascript
async function transferProject(projectId, newUserId) {
const response = await fetch('https://pwa-api-dev.photobook.ai/v2/project/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Albumstory': JSON.stringify({
accessKey: process.env.PWA_ACCESS_KEY,
accessSecret: process.env.PWA_ACCESS_SECRET
})
},
body: JSON.stringify({
id: projectId,
targetUser: newUserId
})
});
return await response.json();
}Use Cases
Display User's Projects
javascript
async function displayProjects(userId) {
const projects = await getUserProjects(userId);
// Filter by status
const editableProjects = projects.filter(p =>
p.status.project === 'editable' && !p.ordered
);
// Sort by recently updated
editableProjects.sort((a, b) => b.updatedAt - a.updatedAt);
return editableProjects;
}Cleanup Old Projects
javascript
async function cleanupExpiredProjects(userId) {
const projects = await getUserProjects(userId);
const now = Date.now() / 1000; // Convert to seconds
// Find projects expiring soon (within 7 days)
const expiringSoon = projects.filter(p =>
p.expiresAt - now < 7 * 24 * 60 * 60
);
if (expiringSoon.length > 0) {
console.log(`${expiringSoon.length} projects expiring soon`);
}
}Batch Delete
javascript
async function batchDeleteProjects(userId, projectIds) {
// Delete in batches of 10
const batches = [];
for (let i = 0; i < projectIds.length; i += 10) {
batches.push(projectIds.slice(i, i + 10));
}
for (const batch of batches) {
await deleteProjects(userId, batch);
}
}Suggested Practices
- Check Status - Use
status.projectto determine if project is ready for editing - Handle Expiration - Display expiration warnings to users with upcoming
expiresAtdates - Async Deletion - Use
asyncdeletion type for better performance - Batch Wisely - When deleting multiple projects, respect the 10-project limit