Palixo Sync API Specification

Version 1.1 | Last Updated: 2025-11-26

Overview

The Palixo Sync API provides a set of endpoints for synchronizing album and picture data between mobile clients and the server. The API supports bidirectional sync with conflict resolution, incremental updates, and efficient image upload/download via presigned URLs.

Key Features

Base URL

Environment URL
UAT https://uat.palixo.media/sync
Production https://palixo.media/sync

Authentication

All endpoints require authentication using JWT ID tokens from AWS Cognito User Pool.

Cognito Configuration

Setting UAT Environment Production Environment
Region ap-southeast-2 ap-southeast-2
User Pool ID Contact administrator Contact administrator
App Client ID Contact administrator Contact administrator

Using the ID Token

Include the ID Token (not access token) in all API requests:

Authorization: Bearer <JWT_ID_TOKEN>

Token Expiration

Note: Clients should proactively refresh tokens before expiration to avoid authentication failures during sync operations.

Common Headers

Header Required Description
Authorization Required JWT Bearer token from Cognito
Content-Type Required (POST) Must be application/json
X-Device-Id Optional Device identifier for conflict tracking
X-Idempotency-Key Optional Unique key for idempotent push operations

API Endpoints

GET /sync/pull

Pull Changes

Fetch changes from the server since a specific timestamp.

Query Parameters

Parameter Type Required Description
since string Optional ULID sequence identifier. Returns all changes if omitted
albumId string Optional Filter changes to a specific album
limit number Optional Max changes to return (default: 100, max: 1000)
continuationToken string Optional Base64-encoded pagination token

Response Example

{
  "changes": [
    {
      "id": "<ksuid>",
      "v": 1,
      "entityType": "Picture",
      "data": {
        "pictureId": "<ksuid>",
        "albumId": "<albumId>",
        "fileName": "Sunset.jpg",
        "created": "[ksuid]",
        "modified": "[ksuid]"
      }
    }
  ],
  "last_seq": "[ulid]",
  "albums": [
    {
      "albumId": "<albumId>",
      "name": "Vacation 2025",
      "accessLevel": "OWNER"
    }
  ],
  "hasMore": false
}
POST /sync/push

Push Mutations

Send create, update, upsert, or delete mutations to the server.

Request Example

{
  "albumId": "<albumId>",
  "mutations": [
    {
      "id": "<ksuid>",
      "op": "create",
      "data": {
        "entityType": "Picture",
        "fileName": "Beach.jpg",
        "timestamp": "[ksuid]",
        "created": "[ksuid]"
      }
    }
  ]
}

Mutation Operations

Operation Description
create Create a new entity. Pictures: use id with client-generated KSUID. Other entities: use temp_id
update Update an existing entity. Use server-assigned id
upsert Create if doesn't exist, update if exists
delete Delete an entity. Use server-assigned id

Auto-Tag Creation Feature

When creating a PrivatePictureTag that references a tag using a temp_id, the server will automatically create the PrivateTag entity if it doesn't exist. The response will include multiple results: one for the auto-created tag and one for the picture-tag relation.

POST /sync/get-upload-url/:pictureId

Get Upload URL

Get a presigned S3 URL for uploading an image.

Response Example

{
  "uploadUrl": "https://s3.amazonaws.com/bucket/<pictureId>?X-Amz-Algorithm=...",
  "expiresIn": 300
}
GET /sync/download-image/:pictureId

Download Image

Get a presigned S3 URL for downloading an image.

Response Example

{
  "downloadUrl": "https://s3.amazonaws.com/bucket/<pictureId>?X-Amz-Algorithm=...",
  "expiresIn": 300
}
POST /sync/download-images

Bulk Download Images

Get presigned S3 URLs for multiple images in a single request (max 100).

Request Example

{
  "pictureIds": [
    "<pictureId_1>",
    "<pictureId_2>",
    "<pictureId_3>"
  ]
}
GET /sync/health

Health Check

Check API health status.

Response Example

{
  "status": "healthy",
  "service": "sync",
  "timestamp": "2025-11-26T..."
}

Data Types

Entity Types

Entity Type Description
Album Photo album container
Picture Individual photo/image
PrivateTag User-created tag for organizing pictures
PrivatePictureTag Association between a picture and a tag
AlbumAccess User's access permissions to an album
User User account information

Picture Entity Fields

Field Type Required Description
pictureId string Required Client-generated KSUID (also serves as the S3 object key)
albumId string Required ID of the album containing this picture
fileName string Required Original file name (e.g., "IMG_1234.jpg")
timestamp string Required KSUID representing when the photo was captured
created string Required KSUID timestamp when entity was created
modified string Required KSUID timestamp when entity was last modified (server-managed)
nameOverride string Optional Display name that overrides fileName
description string Optional Description or caption for the picture
location string Optional GPS location or location description
imageVersion string Optional Version string for tracking edits/updates

KSUID vs ULID

Type Precision Usage
KSUID Second-level Entity IDs, created and modified timestamps
ULID Millisecond-level Sync sequence tracking only (last_seq, since)

Special Album ID: Use "global" as the albumId for user-scoped entities like PrivateTag that aren't associated with a specific album.

Common Workflows

Initial Sync

  1. Call GET /sync/pull without since parameter to fetch all data
  2. Process albums array to build local album list
  3. Process changes array to populate local database
  4. Store last_seq for incremental sync
  5. If hasMore is true, repeat with continuationToken

Incremental Sync

  1. Call GET /sync/pull?since={last_seq} using stored sequence
  2. Process changes array to update local database
  3. Update stored last_seq with response value
  4. Handle pagination if hasMore is true

Create Picture with Upload

  1. Generate unique KSUID for the picture ID (serves as both entity ID and S3 key)
  2. Call POST /sync/push to create picture entity
  3. Verify successful creation from result
  4. Call POST /sync/get-upload-url/:pictureId
  5. Upload image to S3 using presigned URL

Error Handling

HTTP Status Codes

Code Description
200 OK Request successful
400 Bad Request Invalid request parameters or body
401 Unauthorized Missing or invalid authentication token
403 Forbidden Authenticated but not authorized for resource
404 Not Found Resource not found
500 Internal Server Error Server-side error

Error Response Format

{
  "success": false,
  "message": "Error description",
  "error": "Detailed error message"
}

Client Implementation Notes

This section documents important patterns and considerations for implementing a robust sync client.

Document Processing Order

The client should process entity types in a specific order to satisfy foreign key dependencies:

  1. Album - Process first as other entities reference albums
  2. Picture - Process second as tags reference pictures
  3. PrivateTag - Process third as picture-tags reference tags
  4. PrivatePictureTag - Process last as it references both pictures and tags

Important: When processing changes from a pull response, entities should be applied in this dependency order to avoid foreign key constraint violations in your local database.

Duplicate Detection

Server pagination may occasionally return duplicate entities across pages. Clients should track processed entities using a composite key of entityType:id to detect and skip duplicates:

// Pseudocode
seenEntities = Set()
for each change in response.changes:
    key = change.entityType + ":" + change.id
    if key in seenEntities:
        skip  // Duplicate, already processed
    seenEntities.add(key)
    processChange(change)

Pagination Safety

Implement safety limits to prevent infinite loops in case of server pagination bugs:

Push Batching by Album

When pushing mutations, group them by albumId and send separate requests for each album. Push the global album first to ensure user-scoped entities (like tags) are created before album-specific entities that reference them.

Temp ID Replacement

When pushing multiple related mutations in sequence, maintain a mapping of temp IDs to server-assigned IDs. Before sending each batch, scan the mutation data and replace any temp ID references with their real IDs from previous responses:

// Pseudocode
tempIdToRealId = Map()

for each batch in pendingMutations:
    // Replace temp IDs in data fields
    for each mutation in batch:
        for field in ['pictureId', 'privateTagId', 'albumId']:
            if mutation.data[field] in tempIdToRealId:
                mutation.data[field] = tempIdToRealId[mutation.data[field]]

    // Send batch and process results
    response = push(batch)
    for result in response.results:
        if result.temp_id:
            tempIdToRealId[result.temp_id] = result.id

Best Practices

Authentication

  • Always include authentication headers
  • Implement token refresh before expiration
  • Store tokens securely

Idempotency

  • Always use X-Idempotency-Key for push requests
  • Generate unique keys per logical operation
  • Retry failed requests with same key

Performance

  • Use bulk operations (download-images) for multiple resources
  • Implement local caching of presigned URLs (respect expiresIn)
  • Batch mutations into single push requests when possible

Data Integrity

  • Validate entity data before pushing
  • Handle deleted entities in pull responses
  • Implement version-based conflict detection
  • Store last_seq persistently for crash recovery