Skip to Content

Events API

Events are reachable through two APIs:

Public API (/api/v1)Admin API (/api/admin)
AuthAPI token (Authorization: Bearer pp_live_…) created in Admin → Settings → API TokensAdmin session JWT
Event endpointsCreate, list, get, share link, list event typesUpdate, publish, archive, restore, delete, send the gallery email, and everything else

The public v1 API is intentionally narrow: it creates galleries, uploads photos and returns share links. Updating, publishing, archiving and deleting an event are admin API routes, which accept only an admin JWT — an API token sent to an /api/admin/… route is rejected.

For the OpenAPI spec of the public API, see /api/openapi.json on your instance — it is generated from the same route definitions.

Authentication

Authorization: Bearer <token>

See Authentication for API tokens, their scopes and the admin JWT.

Public API endpoints

Create event

curl -X POST "$API_URL/v1/events" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "event_type": "wedding", "event_name": "Emily & Jordan Celebration", "event_date": "2026-06-07", "customer_name": "Emily Carter", "customer_email": "[email protected]", "customer_phone": "+49 30 12345678", "admin_email": "[email protected]", "require_password": true, "password": "Shutter123", "expires_at": "2026-07-22" }'

Needs the admin scope and the events.create permission.

Response (201):

{ "id": 42, "slug": "wedding-emily-jordan-celebration-2026-06-07", "share_url": "https://picpeak.example.com/gallery/wedding-emily-jordan-celebration-2026-06-07/abc123", "share_token": "abc123" }

A gallery created through the v1 API is published immediately: is_draft is ignored and the gallery email goes out on creation, as when publishing from the admin UI.

Required fields:

FieldTypeNotes
event_typestringSlug of an active event type. Discover the live values with GET /v1/event-types.
event_namestringDisplay name.

The required-field switches in Event Defaults (event_require_event_date, event_require_customer_name, event_require_customer_email, event_require_admin_email, event_require_expiration) apply to the admin create form only. The v1 API does not enforce them.

Optional fields:

FieldTypeDefaultNotes
event_datestring(none)ISO date.
customer_namestring(none)
customer_emailstring(none)Receives the gallery email.
customer_phonestring(none)Persisted only when event_phone_field_enabled=true. Max 32 chars.
admin_emailstring(none)
welcome_messagestring(empty)Markdown supported.
require_passwordbooleanfrom event_default_require_passwordWhen false, gallery is accessible via the share-link token alone.
passwordstring(none)Required when require_password resolves to true. Min 6 chars, checked against the gallery password rules.
expires_atstring(none: never expires)See Expiry. expiration_days is ignored by the v1 API.
client_access_enabledbooleanfalseTurns on the client access link.
client_passwordstring(none)The client PIN. At least 6 characters on the beta channel from v3.142.1-beta.0; stable accepts shorter values.
photo_capinteger(unlimited)Max number of photos.
download_limitintegerfrom event_default_download_limitMax distinct photos the gallery may download. null = unlimited.
color_themestring(none)Preset name or JSON-encoded theme.
header_stylestringfrom themehero / standard / minimal / none
feedback_enabledbooleanfrom event_default_feedback_enabled
protection_levelstringfrom default_protection_levelbasic / standard / enhanced / maximum
image_qualityintegerfrom default_image_quality1–100. Stored for compatibility only; it does not change how images are encoded.
allow_user_uploadsbooleanfalseEnables guest uploads.
upload_category_idinteger(none)Required when allow_user_uploads=true.
default_photo_sortstringupload_date_descSort guests see first.

On the beta channel from v3.155.0-beta.0 the create call also takes:

FieldTypeDefaultNotes
custom_theme_enabledbooleansee notestrue = the gallery uses its own color_theme / css_template_id; false = it follows the global Branding theme. When omitted, sending a color_theme or css_template_id makes it true, as before the field existed.
customer_account_idsinteger[](none)Customer accounts to assign. Ignored while the customer portal is off. Assigned accounts are only mailed, and only stand in for a missing customer_email, when the token owner holds customers.events.
source_modestringmanagedmanaged = photos are uploaded to PicPeak; reference = the gallery links the photos in a folder under EXTERNAL_MEDIA_ROOT. See External Media.
external_pathstring(none)Required with source_mode: "reference". A folder relative to EXTERNAL_MEDIA_ROOT that must exist; the root itself ("", ".") is rejected with 400.
external_watchbooleanfalseWith reference: import new files that appear in the folder automatically. The token owner needs photos.upload (403 otherwise).
import_nowbooleanfalseWith reference: start the folder’s first import in the background right after the gallery is created. Needs photos.upload (403 otherwise). Progress: GET /api/admin/external-media/events/{id}/status.

Expiry

expires_at takes an ISO 8601 value:

  • A date-time with a zone or offset (2026-07-22T18:00:00+02:00, …Z) is stored as that instant.
  • A date-time without a zone (2026-07-22T18:00:00) is read as UTC.
  • A bare date (2026-07-22) means the end of that day (23:59:59.999) in the instance time zone — the Timezone (IANA) field of the business profile, or the server’s time zone when that is empty. On the beta channel from v3.174.0-beta.0. Stable builds store a bare date as midnight UTC at the start of that day.
  • Omitted or empty: the gallery never expires.

A value that is not a readable date returns 400.

The admin create form, which works with a number of days instead, sets the expiry to the event date (or today, when there is none) plus that many days, ending at the end of that day in the same instance time zone (beta channel from v3.174.0-beta.0). The admin GET /api/admin/events/:id response carries the zone it uses as expiry_time_zone.

List events

curl "$API_URL/v1/events?limit=20&page=1" \ -H "Authorization: Bearer $TOKEN"

Needs the read scope and events.view. Lists only the events the token’s owner may see, newest first.

Query parameters:

ParamDefaultNotes
page11-based page number.
limit25Max 100.

There is no offset parameter (it is ignored) and no server-side filter: filter on is_archived, is_draft or event_type in the response.

Response:

{ "events": [ { "id": 42, "slug": "wedding-emily-jordan-celebration-2026-06-07", "event_name": "Emily & Jordan Celebration", "event_type": "wedding", "event_date": "2026-06-07", "expires_at": "2026-07-22T21:59:59.999Z", "is_active": true, "is_archived": false, "is_draft": false, "created_at": "2026-06-01T09:12:00.000Z" } ], "pagination": { "page": 1, "limit": 20, "total": 57 } }

Get event

curl "$API_URL/v1/events/$EVENT_ID" \ -H "Authorization: Bearer $TOKEN"

Returns the event row with its password hashes and stored password copies removed. 404 for an unknown id.

curl "$API_URL/v1/events/$EVENT_ID/share-link" \ -H "Authorization: Bearer $TOKEN"
{ "slug": "wedding-emily-jordan-celebration-2026-06-07", "share_token": "abc123", "share_url": "https://picpeak.example.com/gallery/…" }

List event types

curl "$API_URL/v1/event-types" \ -H "Authorization: Bearer $TOKEN"

Returns { "eventTypes": [{ "slug_prefix": "wedding", "name": "Wedding", "emoji": "…" }, …] }. The catalog is customisable under Settings → Event Types, so read the values from here instead of hardcoding them.

Admin API endpoints

Everything below needs an admin JWT (see Authentication), not an API token.

Update event

curl -X PUT "$API_URL/admin/events/$EVENT_ID" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "expires_at": "2026-09-30", "welcome_message": "Updated copy." }'

Permission: events.edit. Send only the fields you want to change. expires_at follows the expiry rules above; send null or "" to remove the expiry. expiration_days is not accepted here.

customer_name and customer_email accept "" or null to clear them, unless the field is required by the event defaults; customer_email must be a string or null. A value for expires_at that is not a readable date-time answers 400. Sending customer_account_ids needs the customers.events permission when it changes the assignment.

On the beta channel, some fields are reserved to the gallery’s owner — the admin who created it, a super admin, or a role that manages all galleries; another admin who reaches the gallery as a team member gets 403 EVENT_OWNER_REQUIRED when the request would change them:

  • the gallery password, the client PIN, regenerate_client_token and switching require_password (from v3.167.0-beta.0);
  • who receives the gallery’s emails: customer_email, admin_email and customer_account_ids (from v3.175.1-beta.0). Sending back the stored values unchanged is allowed.

See Multi-Admin for the gallery team.

Publish (draft → live)

curl -X POST "$API_URL/admin/events/$EVENT_ID/publish" \ -H "Authorization: Bearer $ADMIN_JWT"

Permission: events.edit. Flips is_draft=false and fires the event.published webhook.

# First send of a published gallery's email curl -X POST "$API_URL/admin/events/$EVENT_ID/send-gallery-email" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "password": "Shutter123" }' # Re-send the gallery-created email curl -X POST "$API_URL/admin/events/$EVENT_ID/resend-email" \ -H "Authorization: Bearer $ADMIN_JWT" \ -H "Content-Type: application/json" \ -d '{ "password": "Shutter123" }'

Omit password to send the variant without credentials (or the stored password, when password storage is on). resend-email answers 400 when the gallery has nobody to send to.

On the beta channel from v3.165.0-beta.0 the gallery email reaches the inline customer email and every assigned customer account that can sign in to the portal (the accounts only when the caller holds customers.events). Publish, send-gallery-email and resend-email then answer with what was actually queued:

{ "recipients": { "email": "[email protected]", "account_count": 2, "accounts": [{ "id": 7, "name": "Emily Carter", "email": "[email protected]" }] } }

accounts lists names and addresses only for callers holding customers.view; everyone else gets the count and an empty list.

Archive

curl -X POST "$API_URL/admin/events/$EVENT_ID/archive" \ -H "Authorization: Bearer $ADMIN_JWT"

Permission: events.archive; owner only on the beta channel. See Archiving Events for what this does.

Restore from archive

curl -X POST "$API_URL/admin/archives/$EVENT_ID/restore" \ -H "Authorization: Bearer $ADMIN_JWT"

Permission: archives.restore. The id is the event id. 409 ARCHIVE_IN_PROGRESS while the gallery is still being archived.

Delete

curl -X DELETE "$API_URL/admin/events/$EVENT_ID" \ -H "Authorization: Bearer $ADMIN_JWT"

Permission: events.delete; owner only on the beta channel. Hard delete: removes the event row, all associated photos, thumbnails, and feedback. Not recoverable.

Field naming notes

The API uses customer_name, customer_email, customer_phone. Older deployments may have legacy host_name / host_email columns — migrations copy them on upgrade. Both names are accepted on input; output always uses the customer_* form.

Last updated on