Skip to content

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

ParameterTypeRequiredDescription
userstringYesEnd 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

FieldTypeDescription
dataarrayList of user's projects

Project Object

FieldTypeDescription
titlestringProject title (inferred from cover text, falls back to SKU)
previewstringPreview image URL (empty if not saved yet)
idstringProject ID (starts with p-)
statusobjectProject and photo status
status.photosstringPhoto status: none, uploading, analyzing, idle
status.projectstringProject status: created, building, editable, deleted
createdAtintegerUnix timestamp in milliseconds
updatedAtintegerUnix timestamp in milliseconds
expiresAtintegerUnix timestamp in seconds (when project will be deleted)
productobjectProduct details
product.pspstringPrinter name
product.skustringProduct SKU
product.marketIdintegerInternal market ID
product.marketIdAltstringAlternative market identifier
linkstringDirect link to project in PWA editor
orderedbooleanWhether project has been ordered

Photo Status Values

  • none - No photos uploaded
  • uploading - Photos currently uploading
  • analyzing - Photos being analyzed
  • idle - Photos ready (not uploading or analyzing)

Project Status Values

  • created - Project created, book data not yet generated
  • building - Book data being generated
  • editable - Ready for user editing
  • deleted - 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/delete

Request Parameters

json
{
  "user": "user-12345",
  "type": "async",
  "ids": [
    "p-abc123xyz",
    "p-def456uvw"
  ]
}
ParameterTypeRequiredDescription
userstringYesEnd user's identifier
typestringYesDeletion mode: async or sync
idsarrayYesProject 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/duplicate

Request Parameters

json
{
  "id": "p-abc123xyz",
  "callback": "https://your-app.com/api/order-callback"
}
ParameterTypeRequiredDescription
idstringYesProject ID to duplicate
callbackstringNoOverride 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/transfer

Request Parameters

json
{
  "id": "p-abc123xyz",
  "targetUser": "new-user-12345",
  "callback": "https://your-app.com/api/order-callback"
}
ParameterTypeRequiredDescription
idstringYesProject ID to transfer
targetUserstringYesTarget user identifier to transfer ownership to
callbackstringNoOverride 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

  1. Check Status - Use status.project to determine if project is ready for editing
  2. Handle Expiration - Display expiration warnings to users with upcoming expiresAt dates
  3. Async Deletion - Use async deletion type for better performance
  4. Batch Wisely - When deleting multiple projects, respect the 10-project limit

photobook.ai Developer Documentation