Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.shootbin.com/llms.txt

Use this file to discover all available pages before exploring further.

Photos are the core asset in Shootbin. The photos API covers the full lifecycle: uploading originals, listing albums with cursor-based pagination, fetching individual photo details, and toggling approval state. Uploads follow the same storage and web-variant generation pipeline as the Shootbin web app, including optional watermarking.
Uploading photos and updating approval state both require specific token scopes. Read operations (list, get) are available to any token that is a project member.

List photos in an album

Returns photos in an album using cursor-based pagination, ordered newest first.
GET /api/projects/{project}/albums/{album}/photos

Path parameters

project
integer
required
The project ID.
album
integer
required
The album ID.

Query parameters

per_page
integer
Number of photos to return per page. Default is 50. Maximum is 100.
cursor
string
Opaque cursor value from the next_cursor field of a previous response. Omit on the first request.

Example request

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  "https://your-shootbin-domain.com/api/projects/PROJECT_ID/albums/ALBUM_ID/photos?per_page=50"
To fetch the next page, pass the next_cursor value from the previous response:
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  "https://your-shootbin-domain.com/api/projects/PROJECT_ID/albums/ALBUM_ID/photos?per_page=50&cursor=eyJpZCI6MTIzfQ"

Example response

{
  "data": [
    {
      "id": 123,
      "album_id": 12,
      "title": "Frame 001",
      "description": null,
      "tags": [],
      "original_filename": "IMG_4821.CR3",
      "approved_at": null,
      "revision_uploaded_at": null,
      "has_annotations": false,
      "image_url": "https://your-shootbin-domain.com/photos/...",
      "revisions": [],
      "annotations": []
    }
  ],
  "photos": [...],
  "next_cursor": "eyJpZCI6MTIzfQ",
  "has_more": true,
  "meta": {
    "per_page": 50
  }
}
The photos key is a backward-compatible alias for data. New integrations should read from data. Both keys contain the same array.

Response fields

data
array
Array of photo objects for the current page.
next_cursor
string | null
Encoded cursor for the next page. Pass this as the cursor query parameter on your next request. null when there are no more pages.
has_more
boolean
true if there are additional pages of results.
meta.per_page
integer
The per_page value that was used for this response.

Upload a photo

Uploads a new photo to an album. The file goes through the same storage pipeline as the web UI, including web variant generation and watermarking if configured on the project owner’s account.
POST /api/projects/{project}/albums/{album}/photos
Requires the create (or post:create) token scope. If the project is locked for review, the upload will be rejected with 403.

Path parameters

project
integer
required
The project ID.
album
integer
required
The album ID.

Request body

Send as multipart/form-data:
photo
file
required
The image file to upload. Must be a valid image. Maximum size: 10 MB.
title
string
A display title for the photo. If omitted, the original filename (without extension) is used.
description
string
An optional description.
client_original_filename
string
Override the filename stored as original_filename. Useful when your HTTP client changes the filename during upload (e.g. temporary file paths). When omitted, the filename reported by the upload is used.

Example request

curl -X POST https://your-shootbin-domain.com/api/projects/PROJECT_ID/albums/ALBUM_ID/photos \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -F "photo=@/full/path/to/photo.jpg" \
  -F "title=Frame 001" \
  -F "description=Uploaded through the API"

Example response

201 Created
{
  "id": 124,
  "album_id": 12,
  "title": "Frame 001",
  "description": "Uploaded through the API",
  "tags": [],
  "original_filename": "photo.jpg",
  "approved_at": null,
  "revision_uploaded_at": null,
  "has_annotations": false,
  "image_url": "https://your-shootbin-domain.com/photos/...",
  "revisions": [],
  "annotations": []
}

Get a single photo

Returns the full payload for one photo, including its annotations and revision history.
GET /api/projects/{project}/albums/{album}/photos/{photo}

Path parameters

project
integer
required
The project ID.
album
integer
required
The album ID.
photo
integer
required
The photo ID.

Example request

curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  https://your-shootbin-domain.com/api/projects/PROJECT_ID/albums/ALBUM_ID/photos/PHOTO_ID

Example response

{
  "id": 124,
  "album_id": 12,
  "title": "Frame 001",
  "description": "Uploaded through the API",
  "tags": [],
  "original_filename": "IMG_4821.CR3",
  "approved_at": "2025-04-02T14:10:00.000000Z",
  "revision_uploaded_at": null,
  "has_annotations": true,
  "image_url": "https://your-shootbin-domain.com/photos/...",
  "revisions": [
    {
      "id": 5,
      "revision_number": 1,
      "original_filename": "IMG_4821_v1.CR3",
      "uploaded_at": "2025-04-01T10:00:00.000000Z",
      "archived_annotations_count": 2,
      "image_url": "https://your-shootbin-domain.com/photos/revisions/..."
    }
  ],
  "annotations": [
    {
      "id": 9,
      "comment": "Please retouch the hair on the left side.",
      "x_position": 42.5,
      "y_position": 31.2,
      "created_at": "2025-04-02T11:00:00.000000Z"
    }
  ]
}

Response fields

id
integer
The photo’s unique ID.
original_filename
string
The original filename of the uploaded file as it was named on disk. For example IMG_4821.CR3. This is preserved from upload and also tracked on each archived revision.
approved_at
string | null
ISO 8601 timestamp of when the photo was approved. null if not yet approved.
image_url
string
The URL of the web-optimized variant served by the Shootbin delivery layer. This URL is protected and may be time-limited depending on your storage configuration.
revisions
array
Previously uploaded versions of this photo, archived when a new revision was pushed.
annotations
array
Active annotations on the current version of the photo. Annotations from previous revisions are archived in revisions[].archived_annotations_count.

Update a photo

Updates mutable properties of a photo. The most common use case is setting the approval state.
PATCH /api/projects/{project}/albums/{album}/photos/{photo}
Requires the update (or post:update) token scope. Only the project owner can update title, description, and tags. Approvers can update the approved field.

Path parameters

project
integer
required
The project ID.
album
integer
required
The album ID.
photo
integer
required
The photo ID.

Request body

approved
boolean
Set to true to approve the photo, false to remove approval.
title
string
Update the photo title. Owner only.
description
string
Update the photo description. Owner only. Pass null to clear.
tags
array
Replace the photo’s tag list with a new array of strings. Owner only. Each tag is a string up to 100 characters.

Example: approve a photo

curl -X PATCH https://your-shootbin-domain.com/api/projects/PROJECT_ID/albums/ALBUM_ID/photos/PHOTO_ID \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"approved":true}'

Example: unapprove a photo

curl -X PATCH https://your-shootbin-domain.com/api/projects/PROJECT_ID/albums/ALBUM_ID/photos/PHOTO_ID \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"approved":false}'

Errors

StatusReason
403Token lacks update scope, or non-owner attempting to edit title/description/tags
422Approval rejected because the project’s photo selection limit has been reached
If the project has a photo selection package with a photo limit configured, approving a photo that would exceed that limit returns a 422 with an approved field error specifying the limit.