For developers
The public package catalog
When a studio turns on Website catalog in the app, its packages are published as JSON that any website can read. Prices are calculated by the same engine as the app and update on their own when the studio changes a package, an item or a setting.
GET https://photoboothpricing.com/catalog/{slug}.json
Fields (schemaVersion 1)
| Field | Type | Meaning |
|---|---|---|
| schemaVersion | 1 | Changes only when the format breaks. |
| studioName | string | The studio's display name. |
| currency | string | The studio's base currency, for example USD. |
| updatedAt | ISO 8601 | When the catalog content last changed. |
| boothTypes[].name | string | Booth type, for example "Open-Air Booth". |
| boothTypes[].tiers[].name | string | Tier, in the studio's tier order. |
| …tiers[].packages[] | object | Packages in the tier, cheapest first. |
| id, name | string | Stable package id and display name. |
| clientDescription | string | null | Customer-facing description. |
| boothType, tier | string | null | The package's own grouping, null when ungrouped. |
| durationHours | number | Event length in hours. |
| price, currency | number, string | Final client price, rounded to cents. |
| imageUrl | string | null | An https image URL, or null. |
| includedItems | string[] | Names of the items in the package. |
Sample response
{
"schemaVersion": 1,
"studioName": "Golden Hour Booths",
"currency": "USD",
"updatedAt": "2026-10-20T14:05:12.000Z",
"boothTypes": [
{
"name": "Open-Air Booth",
"tiers": [
{
"name": "Gold",
"packages": [
{
"id": "pkg_wedding_gold",
"name": "Wedding Gold",
"clientDescription": "Perfect for weddings. Unlimited prints, custom template, on-site attendant.",
"boothType": "Open-Air Booth",
"tier": "Gold",
"durationHours": 4,
"price": 1395,
"currency": "USD",
"imageUrl": "https://example.com/images/wedding-gold.jpg",
"includedItems": ["Open-air booth", "Unlimited 4x6 prints", "Custom print template", "On-site attendant"]
}
]
}
]
},
{
"name": "Other",
"tiers": [
{
"name": "",
"packages": [
{
"id": "pkg_audio_guestbook",
"name": "Audio Guest Book",
"clientDescription": null,
"boothType": null,
"tier": null,
"durationHours": 3,
"price": 299.5,
"currency": "USD",
"imageUrl": null,
"includedItems": ["Audio guest book phone"]
}
]
}
]
}
]
}
Fetch it
const res = await fetch('https://photoboothpricing.com/catalog/golden-hour-booths.json');
if (!res.ok) throw new Error('Catalog is not published');
const catalog = await res.json();
const money = new Intl.NumberFormat('en-US', { style: 'currency', currency: catalog.currency });
for (const boothType of catalog.boothTypes) {
for (const tier of boothType.tiers) {
for (const pkg of tier.packages) {
console.log(boothType.name, tier.name, pkg.name, money.format(pkg.price));
}
}
}
Good to know
- Changes usually show up within 5 minutes. Responses are cached for 60 seconds in browsers and 5 minutes on the CDN, and carry an
ETagforIf-None-Matchrequests. - Static sites can add up to 3 deploy hooks in the app. They are called about 5 minutes after the last change, once the CDN copy has expired, so the rebuild gets current prices.
- Every response sends
Access-Control-Allow-Origin: *, so browsers can fetch it from any site. - Packages without a booth type are listed under the booth type
"Other"in one tier named"". - Packages hidden from the website, and packages whose price can't be calculated, are left out.
- An unpublished or unknown slug returns
404, which may be cached for up to 60 seconds. - Costs, markups, margins, internal notes, overhead and team data are never in the payload.