★★★★★4.8·9,126 reviewsSee all →
Smiler
← Smiler for travel companies

Partner API reference

Version 1.0.0 · REST + JSON · webhooks

OpenAPI spec

Overview

Book professional local photoshoots for your guests in destinations around the world.

Authentication: send your API key as Authorization: Bearer <key>. Keys starting with sk_test_ work in test mode: bookings are not sent to photographers, are never invoiced and their status can be simulated with POST /bookings/{id}/simulate.

Times are always local wall-clock time at the shoot location (YYYY-MM-DDTHH:MM).

Idempotency: send an Idempotency-Key header with POST /bookings. Repeating the request with the same key returns the original response instead of creating a second booking.

Webhooks: we POST events to your webhook URL, signed with Smiler-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Respond with any 2xx status. Failed deliveries are retried for up to 2 days, so an event can arrive late or more than once: use the Smiler-Delivery id to ignore duplicates and booking.updated_at to keep the newest state.

Base URL: https://app-api.smiler.co/api/partner/v1

Errors: non-2xx responses return {"error": {"code", "message", "details"}}.

Rate limit: 120 requests per minute per API key.

Getting a key: contact us — you receive a test key first.

Account

GET/me

Your partner account and terms

Request

curl "https://app-api.smiler.co/api/partner/v1/me" \
  -H "Authorization: Bearer sk_test_…"

Response 200

{
  "partner": "Imagine Cruising",
  "mode": "test",
  "pricing_mode": "our_prices",
  "commission_pct": 20,
  "currency": "EUR",
  "free_cancellation_hours": 48,
  "min_notice_hours": 72,
  "guest_emails": true
}
  • 401 Missing or invalid API key

Catalogue

GET/products

Bookable photoshoot packages with your net price

ParameterInTypeDescription
countryquerystringISO 3166-1 alpha-2 country code
cityquerystringCity slug

Request

curl "https://app-api.smiler.co/api/partner/v1/products" \
  -H "Authorization: Bearer sk_test_…"

Response 200

{
  "data": [
    {
      "id": 1234,
      "name": "Photoshoot in Paris",
      "city": {
        "name": "Paris",
        "slug": "paris"
      },
      "country_code": "FR",
      "timezone": "Europe/Paris",
      "tier": "premium",
      "duration_minutes": 60,
      "photos_included": 30,
      "meeting_point": "Trocadéro fountains",
      "retail_price": 229,
      "commission_pct": 20,
      "net_price": 183.2,
      "currency": "EUR"
    }
  ]
}
GET/coverage

Check photographer availability for a city and date

ParameterInTypeDescription
city*querystring
date*querystring
timequerystring

Request

curl "https://app-api.smiler.co/api/partner/v1/coverage" \
  -H "Authorization: Bearer sk_test_…"

Response 200

{
  "city": "Paris",
  "date": "2026-11-03",
  "time": "10:00",
  "photographers_available": 6,
  "available": true
}
  • 404 Unknown city

Bookings

GET/bookings

List your bookings (newest first)

ParameterInTypeDescription
statusqueryStatus
updated_sincequerystring
limitqueryinteger
cursorquerystring

Request

curl "https://app-api.smiler.co/api/partner/v1/bookings" \
  -H "Authorization: Bearer sk_test_…"

Response 200

{
  "data": [
    {
      "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
      "reference": "KD4BQHZR",
      "partner_reference": "IC-48213",
      "mode": "live",
      "status": "received",
      "product": {
        "id": 1234,
        "name": "Photoshoot in Paris",
        "duration_minutes": 60,
        "photos_included": 30
      },
      "city": "Paris",
      "country_code": "FR",
      "timezone": "Europe/Paris",
      "start_local": "2026-11-03T10:00",
      "end_local": "2026-11-03T11:00",
      "start_utc": "2026-11-03T09:00:00.000Z",
      "latest_end_local": "2026-11-03T13:30",
      "ship_name": "Sky Princess",
      "port": "Le Havre",
      "meeting_point": "Trocadéro fountains",
      "notes": "Anniversary",
      "guest": {
        "name": "Jane Smith",
        "email": "jane@example.com",
        "phone": "+44 7700 900123",
        "group_size": 2
      },
      "photographer": {
        "first_name": "Camille"
      },
      "guest_page_url": "https://smiler.co/b/KD4BQHZR",
      "gallery": {
        "url": "https://smiler.co/gallery/ab12cd34",
        "delivered_at": "2026-11-05T16:20:00.000Z"
      },
      "price": {
        "currency": "EUR",
        "retail": 229,
        "commission_pct": 20,
        "commission": 45.8,
        "net": 183.2
      },
      "cancellation": {
        "free_until": "2026-11-01T09:00:00.000Z",
        "cancelled_at": null,
        "reason": null,
        "charged": null
      },
      "created_at": "2026-10-07T14:55:00.000Z",
      "updated_at": "2026-10-08T09:12:00.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "string"
}
POST/bookings

Create a booking

ParameterInTypeDescription
Idempotency-Keyheaderstring

Request

curl -X POST "https://app-api.smiler.co/api/partner/v1/bookings" \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "partner_reference": "IC-48213",
    "product_id": 1234,
    "start_local": "2026-11-03T10:00",
    "latest_end_local": "2026-11-03T13:30",
    "guest": {
      "name": "Jane Smith",
      "email": "jane@example.com",
      "phone": "+44 7700 900123"
    },
    "group_size": 2,
    "meeting_point": "Trocadéro fountains",
    "notes": "Anniversary, would love sunset light",
    "ship_name": "Sky Princess",
    "port": "Le Havre"
  }'

Response 201

{
  "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
  "reference": "KD4BQHZR",
  "partner_reference": "IC-48213",
  "mode": "live",
  "status": "received",
  "product": {
    "id": 1234,
    "name": "Photoshoot in Paris",
    "duration_minutes": 60,
    "photos_included": 30
  },
  "city": "Paris",
  "country_code": "FR",
  "timezone": "Europe/Paris",
  "start_local": "2026-11-03T10:00",
  "end_local": "2026-11-03T11:00",
  "start_utc": "2026-11-03T09:00:00.000Z",
  "latest_end_local": "2026-11-03T13:30",
  "ship_name": "Sky Princess",
  "port": "Le Havre",
  "meeting_point": "Trocadéro fountains",
  "notes": "Anniversary",
  "guest": {
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+44 7700 900123",
    "group_size": 2
  },
  "photographer": {
    "first_name": "Camille"
  },
  "guest_page_url": "https://smiler.co/b/KD4BQHZR",
  "gallery": {
    "url": "https://smiler.co/gallery/ab12cd34",
    "delivered_at": "2026-11-05T16:20:00.000Z"
  },
  "price": {
    "currency": "EUR",
    "retail": 229,
    "commission_pct": 20,
    "commission": 45.8,
    "net": 183.2
  },
  "cancellation": {
    "free_until": "2026-11-01T09:00:00.000Z",
    "cancelled_at": null,
    "reason": null,
    "charged": null
  },
  "created_at": "2026-10-07T14:55:00.000Z",
  "updated_at": "2026-10-08T09:12:00.000Z"
}
  • 404 Product not found
  • 409 Duplicate partner_reference or Idempotency-Key in progress
  • 422 Validation error, too short notice or the shoot does not fit the time window
GET/bookings/{id}

Get a booking

ParameterInTypeDescription
id*pathstringSmiler booking id or your partner_reference

Request

curl "https://app-api.smiler.co/api/partner/v1/bookings/IC-48213" \
  -H "Authorization: Bearer sk_test_…"

Response 200

{
  "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
  "reference": "KD4BQHZR",
  "partner_reference": "IC-48213",
  "mode": "live",
  "status": "received",
  "product": {
    "id": 1234,
    "name": "Photoshoot in Paris",
    "duration_minutes": 60,
    "photos_included": 30
  },
  "city": "Paris",
  "country_code": "FR",
  "timezone": "Europe/Paris",
  "start_local": "2026-11-03T10:00",
  "end_local": "2026-11-03T11:00",
  "start_utc": "2026-11-03T09:00:00.000Z",
  "latest_end_local": "2026-11-03T13:30",
  "ship_name": "Sky Princess",
  "port": "Le Havre",
  "meeting_point": "Trocadéro fountains",
  "notes": "Anniversary",
  "guest": {
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+44 7700 900123",
    "group_size": 2
  },
  "photographer": {
    "first_name": "Camille"
  },
  "guest_page_url": "https://smiler.co/b/KD4BQHZR",
  "gallery": {
    "url": "https://smiler.co/gallery/ab12cd34",
    "delivered_at": "2026-11-05T16:20:00.000Z"
  },
  "price": {
    "currency": "EUR",
    "retail": 229,
    "commission_pct": 20,
    "commission": 45.8,
    "net": 183.2
  },
  "cancellation": {
    "free_until": "2026-11-01T09:00:00.000Z",
    "cancelled_at": null,
    "reason": null,
    "charged": null
  },
  "created_at": "2026-10-07T14:55:00.000Z",
  "updated_at": "2026-10-08T09:12:00.000Z"
}
  • 404 Not found
PATCH/bookings/{id}

Update guest details, notes or reschedule

Date and time can be changed until the free cancellation deadline. All other fields can be changed until the shoot.

ParameterInTypeDescription
id*pathstringSmiler booking id or your partner_reference

Request

curl -X PATCH "https://app-api.smiler.co/api/partner/v1/bookings/IC-48213" \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "start_local": "string",
    "latest_end_local": "string",
    "guest": {
      "name": "string",
      "email": "string",
      "phone": "string"
    },
    "group_size": 1,
    "meeting_point": "string",
    "notes": "string",
    "ship_name": "string",
    "port": "string"
  }'

Response 200

{
  "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
  "reference": "KD4BQHZR",
  "partner_reference": "IC-48213",
  "mode": "live",
  "status": "received",
  "product": {
    "id": 1234,
    "name": "Photoshoot in Paris",
    "duration_minutes": 60,
    "photos_included": 30
  },
  "city": "Paris",
  "country_code": "FR",
  "timezone": "Europe/Paris",
  "start_local": "2026-11-03T10:00",
  "end_local": "2026-11-03T11:00",
  "start_utc": "2026-11-03T09:00:00.000Z",
  "latest_end_local": "2026-11-03T13:30",
  "ship_name": "Sky Princess",
  "port": "Le Havre",
  "meeting_point": "Trocadéro fountains",
  "notes": "Anniversary",
  "guest": {
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+44 7700 900123",
    "group_size": 2
  },
  "photographer": {
    "first_name": "Camille"
  },
  "guest_page_url": "https://smiler.co/b/KD4BQHZR",
  "gallery": {
    "url": "https://smiler.co/gallery/ab12cd34",
    "delivered_at": "2026-11-05T16:20:00.000Z"
  },
  "price": {
    "currency": "EUR",
    "retail": 229,
    "commission_pct": 20,
    "commission": 45.8,
    "net": 183.2
  },
  "cancellation": {
    "free_until": "2026-11-01T09:00:00.000Z",
    "cancelled_at": null,
    "reason": null,
    "charged": null
  },
  "created_at": "2026-10-07T14:55:00.000Z",
  "updated_at": "2026-10-08T09:12:00.000Z"
}
  • 409 Booking closed or too late to reschedule
  • 422 Validation error
POST/bookings/{id}/cancel

Cancel a booking

Free until cancellation.free_until. Weather, a skipped port or an itinerary change are always free. Later cancellations for other reasons are charged in full. The response tells you whether it was charged.

ParameterInTypeDescription
id*pathstring

Request

curl -X POST "https://app-api.smiler.co/api/partner/v1/bookings/IC-48213/cancel" \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "guest_request",
    "note": "string"
  }'

Response 200

{
  "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
  "reference": "KD4BQHZR",
  "partner_reference": "IC-48213",
  "mode": "live",
  "status": "received",
  "product": {
    "id": 1234,
    "name": "Photoshoot in Paris",
    "duration_minutes": 60,
    "photos_included": 30
  },
  "city": "Paris",
  "country_code": "FR",
  "timezone": "Europe/Paris",
  "start_local": "2026-11-03T10:00",
  "end_local": "2026-11-03T11:00",
  "start_utc": "2026-11-03T09:00:00.000Z",
  "latest_end_local": "2026-11-03T13:30",
  "ship_name": "Sky Princess",
  "port": "Le Havre",
  "meeting_point": "Trocadéro fountains",
  "notes": "Anniversary",
  "guest": {
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+44 7700 900123",
    "group_size": 2
  },
  "photographer": {
    "first_name": "Camille"
  },
  "guest_page_url": "https://smiler.co/b/KD4BQHZR",
  "gallery": {
    "url": "https://smiler.co/gallery/ab12cd34",
    "delivered_at": "2026-11-05T16:20:00.000Z"
  },
  "price": {
    "currency": "EUR",
    "retail": 229,
    "commission_pct": 20,
    "commission": 45.8,
    "net": 183.2
  },
  "cancellation": {
    "free_until": "2026-11-01T09:00:00.000Z",
    "cancelled_at": null,
    "reason": null,
    "charged": null
  },
  "created_at": "2026-10-07T14:55:00.000Z",
  "updated_at": "2026-10-08T09:12:00.000Z"
}
  • 409 Booking already completed
POST/bookings/{id}/simulate

Test mode only: simulate what happens next

ParameterInTypeDescription
id*pathstring

Request

curl -X POST "https://app-api.smiler.co/api/partner/v1/bookings/IC-48213/simulate" \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "photographer_assigned"
  }'

Response 200

{
  "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
  "reference": "KD4BQHZR",
  "partner_reference": "IC-48213",
  "mode": "live",
  "status": "received",
  "product": {
    "id": 1234,
    "name": "Photoshoot in Paris",
    "duration_minutes": 60,
    "photos_included": 30
  },
  "city": "Paris",
  "country_code": "FR",
  "timezone": "Europe/Paris",
  "start_local": "2026-11-03T10:00",
  "end_local": "2026-11-03T11:00",
  "start_utc": "2026-11-03T09:00:00.000Z",
  "latest_end_local": "2026-11-03T13:30",
  "ship_name": "Sky Princess",
  "port": "Le Havre",
  "meeting_point": "Trocadéro fountains",
  "notes": "Anniversary",
  "guest": {
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+44 7700 900123",
    "group_size": 2
  },
  "photographer": {
    "first_name": "Camille"
  },
  "guest_page_url": "https://smiler.co/b/KD4BQHZR",
  "gallery": {
    "url": "https://smiler.co/gallery/ab12cd34",
    "delivered_at": "2026-11-05T16:20:00.000Z"
  },
  "price": {
    "currency": "EUR",
    "retail": 229,
    "commission_pct": 20,
    "commission": 45.8,
    "net": 183.2
  },
  "cancellation": {
    "free_until": "2026-11-01T09:00:00.000Z",
    "cancelled_at": null,
    "reason": null,
    "charged": null
  },
  "created_at": "2026-10-07T14:55:00.000Z",
  "updated_at": "2026-10-08T09:12:00.000Z"
}
  • 403 Live API key

Webhooks

POST/webhooks/test

Send a test `ping` event to your webhook URL

Request

curl -X POST "https://app-api.smiler.co/api/partner/v1/webhooks/test" \
  -H "Authorization: Bearer sk_test_…"

Response 200

{
  "delivered": true,
  "status": 1,
  "error": "string"
}

Webhooks

We send a POST to your webhook URL whenever a booking changes, whoever made the change. Each request carries Smiler-Event, Smiler-Delivery (unique id, use it to ignore duplicates) and Smiler-Signature. Respond with any 2xx status within 10 seconds; failed deliveries are retried after 1, 5, 15 minutes, then 1, 3, 6, 12 and 24 hours.

Event types:

  • booking.created — booking received
  • booking.confirmed — a photographer is assigned (first name and meeting point included)
  • booking.photographer_changed — a different photographer took over
  • booking.rescheduled — date or time changed
  • booking.cancelled — cancelled (by you or by Smiler support)
  • booking.completed — the shoot took place
  • booking.no_show — the guest did not show up
  • gallery.delivered — the online gallery is ready (booking.gallery.url)
  • ping — test event

Event payload

{
  "id": "b1e7c2a0-3f4d-4e5a-9b8c-7d6e5f4a3b2c",
  "type": "booking.confirmed",
  "mode": "live",
  "created_at": "2026-10-08T09:12:00.000Z",
  "data": {
    "booking": {
      "id": "5f2b8c1e-7a4d-4c1b-9e0f-2d6a3b8c9e10",
      "reference": "KD4BQHZR",
      "partner_reference": "IC-48213",
      "mode": "live",
      "status": "received",
      "product": {
        "id": 1234,
        "name": "Photoshoot in Paris",
        "duration_minutes": 60,
        "photos_included": 30
      },
      "city": "Paris",
      "country_code": "FR",
      "timezone": "Europe/Paris",
      "start_local": "2026-11-03T10:00",
      "end_local": "2026-11-03T11:00",
      "start_utc": "2026-11-03T09:00:00.000Z",
      "latest_end_local": "2026-11-03T13:30",
      "ship_name": "Sky Princess",
      "port": "Le Havre",
      "meeting_point": "Trocadéro fountains",
      "notes": "Anniversary",
      "guest": {
        "name": "Jane Smith",
        "email": "jane@example.com",
        "phone": "+44 7700 900123",
        "group_size": 2
      },
      "photographer": {
        "first_name": "Camille"
      },
      "guest_page_url": "https://smiler.co/b/KD4BQHZR",
      "gallery": {
        "url": "https://smiler.co/gallery/ab12cd34",
        "delivered_at": "2026-11-05T16:20:00.000Z"
      },
      "price": {
        "currency": "EUR",
        "retail": 229,
        "commission_pct": 20,
        "commission": 45.8,
        "net": 183.2
      },
      "cancellation": {
        "free_until": "2026-11-01T09:00:00.000Z",
        "cancelled_at": null,
        "reason": null,
        "charged": null
      },
      "created_at": "2026-10-07T14:55:00.000Z",
      "updated_at": "2026-10-08T09:12:00.000Z"
    }
  }
}

Verify the signature (Node.js)

import crypto from 'node:crypto';

// Express: use the raw body, e.g. app.post('/smiler-webhook', express.raw({ type: 'application/json' }), handler)
function verifySmilerSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false;          // reject old events (replay)
  const expected = crypto.createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex'));
}

Objects

Booking

FieldTypeDescription
idstring
referencestringSmiler booking reference
partner_referencestring
mode"live" | "test"
statusStatus
productobject
product.idinteger
product.namestring
product.duration_minutesinteger (nullable)
product.photos_includedinteger (nullable)
citystring
country_codestring
timezonestring
start_localstring
end_localstring (nullable)
start_utcstring
latest_end_localstring (nullable)
ship_namestring (nullable)
portstring (nullable)
meeting_pointstring (nullable)
notesstring (nullable)
guestobject
guest.namestring
guest.emailstring (nullable)
guest.phonestring (nullable)
guest.group_sizeinteger
photographerobject (nullable)
photographer.first_namestring
guest_page_urlstring (nullable)Page for the guest: meeting point, chat with the photographer, live location on the day
galleryobject (nullable)
gallery.urlstring (nullable)
gallery.delivered_atstring
priceobject
price.currencystring
price.retailnumber
price.commission_pctnumber
price.commissionnumber
price.netnumber
cancellationobject
cancellation.free_untilstring
cancellation.cancelled_atstring (nullable)
cancellation.reasonstring (nullable)
cancellation.chargedboolean (nullable)
created_atstring
updated_atstring (nullable)

Product

FieldTypeDescription
idinteger
namestring
cityobject
city.namestring
city.slugstring
country_codestring
timezonestring
tierstring (nullable)
duration_minutesinteger (nullable)
photos_includedinteger (nullable)
meeting_pointstring (nullable)
retail_pricenumber
commission_pctnumber
net_pricenumberWhat you pay us: retail price minus your commission
currencystring

CreateBooking

FieldTypeDescription
partner_reference*stringYour booking reference, unique per account
product_id*integer
start_local*stringLocal time at the shoot location
latest_end_localstringOptional: the guest must be done by then (e.g. all-aboard time minus travel)
guest*object
guest.name*string
guest.emailstring
guest.phonestring
group_sizeinteger
meeting_pointstring
notesstring
ship_namestring
portstring

Error

FieldTypeDescription
errorobject
error.codestring
error.messagestring
error.detailsobject