CrenoSync

Developers · Version 1.0.0

Reference

Every endpoint, field and error code, generated from the OpenAPI contract. The guide explains how they fit together.

← Back to home

Addresses

  • Productionhttps://{business}.crenosync.ca/api/v1
  • Staginghttps://{business}.staging.crenosync.ca/api/v1

About the examples: their IDs, dates, keys and tokens are illustrations; use your business's own, and dates from today on. The example answers are in English, as asked by Accept-Language: en; with fr, names, texts and messages come in French.

Site

The business itself and its policies.

GET/site

The business

The business's public identity, languages, currency, time zone and what its bookings can involve.

Credentials publishable key or secret key

Parameters

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPGET /api/v1/site
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
Accept-Language: en

Answers

  • 200

    Site

    The business.

    Example A business with a deposit paid online.

    JSON
    JSON{
        "name": "Évasion Alpha",
        "logo_url": "https://alpha.crenosync.ca/media/logo.png",
        "address": "1234 Saint-Denis St., Montréal, QC H2X 3K1",
        "phone": "514 555-0100",
        "website_url": "https://www.example.ca",
        "booking_url": "https://alpha.crenosync.ca/en/book",
        "languages": [
            "fr",
            "en"
        ],
        "default_language": "fr",
        "currency": "CAD",
        "timezone": "America/Toronto",
        "capabilities": {
            "online_payment": "deposit",
            "email_verification": false,
            "promo_codes": true,
            "shared_sessions": false
        }
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

GET/policies

The policies in force

The privacy policy and terms and conditions in force, in the requested language, with the version IDs a checkout must send back to show which texts the visitor accepted. Show both texts (or link to them) next to the acceptance boxes.

Credentials publishable key or secret key

Parameters

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPGET /api/v1/policies
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
Accept-Language: en

Answers

  • 200

    Policies

    The policies.

    Example Both texts, shortened here.

    JSON
    JSON{
        "privacy": {
            "version_id": 3,
            "title": "Privacy policy",
            "html": "<h2>1. Introduction</h2><p>Évasion Alpha collects…</p>",
            "url": "https://alpha.crenosync.ca/en/privacy-policy",
            "published_at": "2026-10-19T15:56:55-04:00"
        },
        "terms": {
            "version_id": 2,
            "title": "Terms and conditions",
            "html": "<p>Arrive 15 minutes before your game…</p>",
            "url": null,
            "published_at": "2026-10-19T15:56:55-04:00"
        }
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

Activities

What can be booked, and when.

GET/activities

List activities

The activities open for booking, in the business's display order.

Credentials publishable key or secret key

Parameters

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPGET /api/v1/activities
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
Accept-Language: en

Answers

  • 200

    object

    The activities.

    data array of Activity required
    has_more boolean required

    Always false in v1; reserved for pagination.

    Example The business's activities, one shown here.

    JSON
    JSON{
        "data": [
            {
                "id": "act_7Kq2mX9pLr",
                "slug": "labo-perdu",
                "name": "The Lost Lab",
                "description": "A lab abandoned overnight, an experiment still running. Find the antidote before the doors seal.",
                "theme": "Science",
                "highlights": null,
                "rules": "Arrive 15 minutes early.",
                "image_url": "https://alpha.crenosync.ca/media/rooms/labo-perdu.jpg",
                "display_order": 1,
                "duration_minutes": 60,
                "group": {
                    "min": 2,
                    "max": 6,
                    "recommended": "3-5"
                },
                "mode": "private",
                "shared": null,
                "difficulty": {
                    "levels": [
                        {
                            "level": 0,
                            "name": "Easy",
                            "success_rate": null
                        },
                        {
                            "level": 1,
                            "name": "Classic",
                            "success_rate": 62.5
                        }
                    ]
                },
                "success_rate": 62.5,
                "minimum_age": 8,
                "price_categories": [
                    {
                        "id": "cat_Rt6yU1oP3aSd",
                        "name": "Adults",
                        "age_min": 16,
                        "age_max": null,
                        "price": {
                            "amount": 3000,
                            "currency": "CAD"
                        },
                        "counts_toward_group": true,
                        "must_be_accompanied": false,
                        "can_accompany": true,
                        "min_per_booking": null,
                        "max_per_booking": null
                    },
                    {
                        "id": "cat_Wq3eR5tY7uIo",
                        "name": "Youths",
                        "age_min": 8,
                        "age_max": 15,
                        "price": {
                            "amount": 2200,
                            "currency": "CAD"
                        },
                        "counts_toward_group": true,
                        "must_be_accompanied": true,
                        "can_accompany": false,
                        "min_per_booking": null,
                        "max_per_booking": null
                    }
                ],
                "from_price": {
                    "amount": 2200,
                    "currency": "CAD"
                },
                "online_payment": "deposit",
                "deposit": {
                    "amount": {
                        "amount": 2000,
                        "currency": "CAD"
                    },
                    "per_person": false
                },
                "no_show_fee": null,
                "resources": [
                    {
                        "id": "res_Zp4xN7cB2qLe",
                        "type": "room",
                        "name": "The Lost Lab"
                    }
                ],
                "booking_url": "https://alpha.crenosync.ca/en/book?roomId=labo-perdu"
            }
        ],
        "has_more": false
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

GET/activities/{activity_id}

Get an activity

Credentials publishable key or secret key

Parameters

activity_id string required path

Example act_7Kq2mX9pLr

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPGET /api/v1/activities/act_7Kq2mX9pLr
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
Accept-Language: en

Answers

  • 200

    Activity

    The activity.

    Example A private room with two difficulty levels and a deposit.

    JSON
    JSON{
        "id": "act_7Kq2mX9pLr",
        "slug": "labo-perdu",
        "name": "The Lost Lab",
        "description": "A lab abandoned overnight, an experiment still running. Find the antidote before the doors seal.",
        "theme": "Science",
        "highlights": null,
        "rules": "Arrive 15 minutes early.",
        "image_url": "https://alpha.crenosync.ca/media/rooms/labo-perdu.jpg",
        "display_order": 1,
        "duration_minutes": 60,
        "group": {
            "min": 2,
            "max": 6,
            "recommended": "3-5"
        },
        "mode": "private",
        "shared": null,
        "difficulty": {
            "levels": [
                {
                    "level": 0,
                    "name": "Easy",
                    "success_rate": null
                },
                {
                    "level": 1,
                    "name": "Classic",
                    "success_rate": 62.5
                }
            ]
        },
        "success_rate": 62.5,
        "minimum_age": 8,
        "price_categories": [
            {
                "id": "cat_Rt6yU1oP3aSd",
                "name": "Adults",
                "age_min": 16,
                "age_max": null,
                "price": {
                    "amount": 3000,
                    "currency": "CAD"
                },
                "counts_toward_group": true,
                "must_be_accompanied": false,
                "can_accompany": true,
                "min_per_booking": null,
                "max_per_booking": null
            },
            {
                "id": "cat_Wq3eR5tY7uIo",
                "name": "Youths",
                "age_min": 8,
                "age_max": 15,
                "price": {
                    "amount": 2200,
                    "currency": "CAD"
                },
                "counts_toward_group": true,
                "must_be_accompanied": true,
                "can_accompany": false,
                "min_per_booking": null,
                "max_per_booking": null
            }
        ],
        "from_price": {
            "amount": 2200,
            "currency": "CAD"
        },
        "online_payment": "deposit",
        "deposit": {
            "amount": {
                "amount": 2000,
                "currency": "CAD"
            },
            "per_person": false
        },
        "no_show_fee": null,
        "resources": [
            {
                "id": "res_Zp4xN7cB2qLe",
                "type": "room",
                "name": "The Lost Lab"
            }
        ],
        "booking_url": "https://alpha.crenosync.ca/en/book?roomId=labo-perdu"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

GET/activities/{activity_id}/availability

Open times

The days and times a group can book, between two dates (at most 31 days, from today on). A time is listed only if it is in the future, not booked or held, and, for a shared activity, has enough places left for the group.

Fetch it when the visitor opens the calendar or changes the date or group size, not on a timer.

Credentials publishable key or secret key

Parameters

activity_id string required path

Example act_7Kq2mX9pLr

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

from string (date) required query

First day, local date. Today or later.

Example 2026-11-14

to string (date) required query

Last day, local date, included. At most 30 days after from.

Example 2026-11-15

group_size integer query

People who count toward the group size (see PriceCategory.counts_toward_group). By default, the activity's group.min. It must be within the activity's group.min and group.max; otherwise the answer is 422 validation_failed with fields.group_size, since no time could be booked for that group.

from 1 to 1000

Example 4

Example request

HTTP
HTTPGET /api/v1/activities/act_7Kq2mX9pLr/availability?from=2026-11-14&to=2026-11-15&group_size=4
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
Accept-Language: en

Answers

  • 200

    Availability

    The open days and times.

    Example Two days of open times for a group of 4.

    JSON
    JSON{
        "activity_id": "act_7Kq2mX9pLr",
        "from": "2026-11-14",
        "to": "2026-11-15",
        "group_size": 4,
        "timezone": "America/Toronto",
        "days": [
            {
                "date": "2026-11-14",
                "times": [
                    {
                        "time": "17:00",
                        "starts_at": "2026-11-14T17:00:00-05:00",
                        "places_left": null,
                        "still_needed": null,
                        "booking_url": "https://alpha.crenosync.ca/en/booking/widget?room=labo-perdu&date=2026-11-14&time=17%3A00&players=4"
                    },
                    {
                        "time": "19:30",
                        "starts_at": "2026-11-14T19:30:00-05:00",
                        "places_left": null,
                        "still_needed": null,
                        "booking_url": "https://alpha.crenosync.ca/en/booking/widget?room=labo-perdu&date=2026-11-14&time=19%3A30&players=4"
                    }
                ]
            },
            {
                "date": "2026-11-15",
                "times": [
                    {
                        "time": "13:00",
                        "starts_at": "2026-11-15T13:00:00-05:00",
                        "places_left": null,
                        "still_needed": null,
                        "booking_url": "https://alpha.crenosync.ca/en/booking/widget?room=labo-perdu&date=2026-11-15&time=13%3A00&players=4"
                    }
                ]
            }
        ]
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 422

    Error

    validation_failed, with each field's messages in error.fields; or a booking rule: group_invalid, difficulty_required, category_not_offered, promo_code_invalid, quote_hold_mismatch, return_url_not_allowed, verification_failed (with error.attempts_left).

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

Booking

Booking sessions, quotes, holds and checkouts.

POST/booking-sessions

Start a booking session

A guest token for one visitor's booking attempt. Create it when the visitor starts booking (not on page load), keep it for that visitor only, and send it as CrenoSync-Guest-Token with every quote, hold and checkout. It lasts 60 minutes. A server integration creates one per visitor too: the limits on held times apply per guest token, and to all of the business's keys together (a second key adds no allowance).

Credentials publishable key or secret key

Example request

HTTP
HTTPPOST /api/v1/booking-sessions
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…

Answers

  • 201

    BookingSession

    The session.

    Example A guest token for 60 minutes.

    JSON
    JSON{
        "token": "cs_gt_4fG7hJ2kL9mN3pQ6rS8tV1wX",
        "expires_at": "2026-11-10T15:01:55-05:00",
        "limits": {
            "holds": 3
        }
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

POST/quotes

Price a booking

The exact price CrenoSync will charge for a time, a group and an optional promotion code: each price category, the discount, the taxes, the total, and what is due now. Valid 15 minutes. A quote reserves nothing; hold the time next.

The group is checked against the activity's rules (size, each category's limits, accompaniment, difficulty level). Availability is not guaranteed by a quote.

Credentials publishable key + guest token or secret key + guest token

Parameters

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Request body

QuoteRequest

Example request

HTTP
HTTPPOST /api/v1/quotes
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Accept-Language: en
Content-Type: application/json

{
    "activity_id": "act_7Kq2mX9pLr",
    "date": "2026-11-14",
    "time": "19:30",
    "participants": [
        {
            "category_id": "cat_Rt6yU1oP3aSd",
            "quantity": 4
        }
    ],
    "difficulty_level": 1,
    "promo_code": null
}

Answers

  • 201

    Quote

    The quote.

    Example The price, with a deposit due online.

    JSON
    JSON{
        "id": "quo_2Jf9TzQk4mVw",
        "expires_at": "2026-11-10T14:17:11-05:00",
        "activity_id": "act_7Kq2mX9pLr",
        "date": "2026-11-14",
        "time": "19:30",
        "starts_at": "2026-11-14T19:30:00-05:00",
        "group_size": 4,
        "difficulty": {
            "level": 1,
            "name": "Classic"
        },
        "lines": [
            {
                "category_id": "cat_Rt6yU1oP3aSd",
                "name": "Adults",
                "quantity": 4,
                "unit_price": {
                    "amount": 3000,
                    "currency": "CAD"
                },
                "amount": {
                    "amount": 12000,
                    "currency": "CAD"
                }
            }
        ],
        "subtotal": {
            "amount": 12000,
            "currency": "CAD"
        },
        "discount": null,
        "taxes": [
            {
                "name": "GST",
                "rate": 5,
                "amount": {
                    "amount": 600,
                    "currency": "CAD"
                }
            },
            {
                "name": "QST",
                "rate": 9.9749999999999996447286321199499070644378662109375,
                "amount": {
                    "amount": 1197,
                    "currency": "CAD"
                }
            }
        ],
        "total": {
            "amount": 13797,
            "currency": "CAD"
        },
        "due_now": {
            "amount": 2000,
            "currency": "CAD"
        },
        "due_later": {
            "amount": 11797,
            "currency": "CAD"
        },
        "payment": "deposit"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 422

    Error

    validation_failed, with each field's messages in error.fields; or a booking rule: group_invalid, difficulty_required, category_not_offered, promo_code_invalid, quote_hold_mismatch, return_url_not_allowed, verification_failed (with error.attempts_left).

    See its examples

  • 413

    Error

    payload_too_large: the request body is larger than the server accepts.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

POST/holds

Hold a time

Reserves the quoted time (or, for a shared activity, the group's places) for this guest while the visitor fills in their details. The hold lasts the business's hold time (10 minutes by default; see expires_at) and is not renewed. Starting a checkout that needs a payment extends it for the length of the payment window, plus 5 minutes for the payment to be confirmed. The payment window lasts 30 minutes, cut short to end 15 minutes before the activity starts, but never lasts less than 10 minutes: for a checkout opened in the last 25 minutes before the start, the 10 minutes win, and its window can end after the start. A time abandoned near its start is offered again in time for a last-minute visitor, and a checkout opened just before the start can still be paid. A checkout paid or confirmed by code before its hold ends is booked, even once the activity has just started; a checkout can't be opened after the start. When the payment provider's page outlives the window, it is closed at the window's end, and the time is offered again once the provider confirms the page is closed. A quote_changed answer to a checkout leaves the hold as it was.

Each guest token may hold a few times at once (BookingSession.limits); holding a new time while at that limit is refused. Release a hold the visitor gives up.

Credentials publishable key + guest token or secret key + guest token

Parameters

Idempotency-Key string required header

A unique value per operation you mean to perform once, e.g. a UUID. Kept 24 hours.

8 to 255 characters

Example 6f1c2a4e-8b3d-4e9a-9c7f-2d5b1a0e3c84

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Request body

quote_id string required

A quote of this guest, not expired.

Example quo_2Jf9TzQk4mVw

Example request

HTTP
HTTPPOST /api/v1/holds
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Idempotency-Key: 6f1c2a4e-8b3d-4e9a-9c7f-2d5b1a0e3c84
Accept-Language: en
Content-Type: application/json

{
    "quote_id": "quo_2Jf9TzQk4mVw"
}

Answers

  • 201

    Hold

    The hold.

    Headers Idempotent-Replayed

    Example The time held for 10 minutes.

    JSON
    JSON{
        "id": "hld_Vb3nP8sQw1Zx",
        "quote_id": "quo_2Jf9TzQk4mVw",
        "activity_id": "act_7Kq2mX9pLr",
        "date": "2026-11-14",
        "time": "19:30",
        "starts_at": "2026-11-14T19:30:00-05:00",
        "places": 4,
        "status": "active",
        "expires_at": "2026-11-10T14:12:30-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 422

    Error

    validation_failed, with each field's messages in error.fields; or a booking rule: group_invalid, difficulty_required, category_not_offered, promo_code_invalid, quote_hold_mismatch, return_url_not_allowed, verification_failed (with error.attempts_left).

    See its examples

  • 413

    Error

    payload_too_large: the request body is larger than the server accepts.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

DELETE/holds/{hold_id}

Release a hold

Gives the time back. Safe to repeat: a hold already released or expired answers 204 too. A hold whose checkout is being started or waiting for a payment cannot be released (409 hold_in_checkout); cancel the checkout instead.

Credentials publishable key + guest token or secret key + guest token

Parameters

hold_id string required path

Example hld_Vb3nP8sQw1Zx

Example request

HTTP
HTTPDELETE /api/v1/holds/hld_Vb3nP8sQw1Zx
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…

Answers

  • 204

    Released.

  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

POST/checkouts

Check out

Books the held time with the visitor's details and their acceptance of the policies. quote_id is the quote the visitor saw; it must be for the hold's exact choice (activity, date, time, participants, difficulty level), or the answer is 422 quote_hold_mismatch.

When the price changed. The price is recomputed. If it differs from the quote's (a price, a tax rate or the promotion changed, or the quote expired and its price no longer holds), the answer is 409 quote_changed with a new quote in error.quote, and nothing is charged or reserved:

  • the hold is kept as it was: same time, same places, same expires_at;
  • the new quote is for the hold's choice, with the same promotion code, so it can be used with the same hold;
  • no promotion use was taken (a use is only taken when a checkout starts);
  • show the new price, then send the checkout again with the new quote_id, the same hold_id and a new Idempotency-Key (the body changed).

If the promotion code no longer applies at all, the answer is 422 promo_code_invalid: get a new quote without the code (the hold is kept). If the hold ended meanwhile, the answer is 409 hold_expired: start again from the availability. If a policy changed, the answer is 409 policy_changed with the texts now in force; show them and ask again.

One checkout per hold. Sent again for the same hold (a retry with a new Idempotency-Key, a second tab), the answer is the checkout already open, when nothing changed; a checkout with other details replaces it. While another request is still opening a checkout of the same hold, the answer is 409 checkout_in_progress with Retry-After: send the request again after it, and the checkout it opened comes back. Never two payment pages for one hold.

The answer's status says what happens next: confirmed, awaiting_verification (email code) or awaiting_payment (send the visitor to next_action.url).

Credentials publishable key + guest token or secret key + guest token

Parameters

Idempotency-Key string required header

A unique value per operation you mean to perform once, e.g. a UUID. Kept 24 hours.

8 to 255 characters

Example 6f1c2a4e-8b3d-4e9a-9c7f-2d5b1a0e3c84

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Request body

CheckoutRequest

Example request

HTTP
HTTPPOST /api/v1/checkouts
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Idempotency-Key: 6f1c2a4e-8b3d-4e9a-9c7f-2d5b1a0e3c84
Accept-Language: en
Content-Type: application/json

{
    "hold_id": "hld_Vb3nP8sQw1Zx",
    "quote_id": "quo_2Jf9TzQk4mVw",
    "customer": {
        "name": "Camille Tremblay",
        "email": "camille@example.com",
        "phone": "514 555-0199",
        "language": "en"
    },
    "consent": {
        "privacy_version_id": 3,
        "privacy_accepted": true,
        "terms_version_id": 2,
        "terms_accepted": true
    },
    "return_url": "https://www.example.ca/booking/thanks",
    "attribution": {
        "source": "newsletter",
        "medium": "email",
        "campaign": "fall-2026"
    },
    "external_reference": "order-1042"
}

Answers

  • 201

    Checkout

    The checkout.

    Headers Idempotent-Replayed

    Example A deposit is due: send the visitor to the payment page.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "awaiting_payment",
        "next_action": {
            "type": "redirect",
            "url": "https://alpha.crenosync.ca/en/checkout/chk_Hd5rL2kTy9Mc?expires=1794339340&signature=3f9c0e…",
            "expires_at": "2026-11-10T14:35:40-05:00"
        },
        "amount_due_now": {
            "amount": 2000,
            "currency": "CAD"
        },
        "expires_at": "2026-11-10T14:35:40-05:00",
        "booking": null,
        "created_at": "2026-11-10T14:05:40-05:00"
    }

    Example Nothing to pay online, the business asks for the emailed code.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "awaiting_verification",
        "next_action": {
            "type": "verify_email",
            "url": "https://alpha.crenosync.ca/en/checkout/chk_Hd5rL2kTy9Mc?expires=1794337950&signature=8b21d4…",
            "expires_at": "2026-11-10T14:12:30-05:00",
            "email_hint": "c•••••@example.com",
            "attempts_left": 5
        },
        "amount_due_now": {
            "amount": 0,
            "currency": "CAD"
        },
        "expires_at": "2026-11-10T14:12:30-05:00",
        "booking": null,
        "created_at": "2026-11-10T14:05:40-05:00"
    }

    Example Nothing to pay online and no code: booked at once.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "confirmed",
        "next_action": null,
        "amount_due_now": {
            "amount": 0,
            "currency": "CAD"
        },
        "expires_at": null,
        "booking": {
            "reference": "PB7K2QXA-20261114"
        },
        "created_at": "2026-11-10T14:05:40-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 422

    Error

    validation_failed, with each field's messages in error.fields; or a booking rule: group_invalid, difficulty_required, category_not_offered, promo_code_invalid, quote_hold_mismatch, return_url_not_allowed, verification_failed (with error.attempts_left).

    See its examples

  • 413

    Error

    payload_too_large: the request body is larger than the server accepts.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

GET/checkouts/{checkout_id}

Get a checkout

A checkout's current state. After a CrenoSync page (payment or email code), read it with the return token from your return_url. While the status is processing, ask again after a few seconds, waiting longer each time (2, 4, 8 seconds…), for at most a few minutes: the payment is being confirmed with the provider. If it is still processing after that, show the visitor that the booking is being confirmed and that an email will follow; never ask them to pay again.

Credentials publishable key + guest token or secret key + guest token or publishable key + return token or secret key

Parameters

checkout_id string required path

Example chk_Hd5rL2kTy9Mc

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPGET /api/v1/checkouts/chk_Hd5rL2kTy9Mc
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Accept-Language: en

Answers

  • 200

    Checkout

    The checkout.

    Example Booked after the payment.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "confirmed",
        "next_action": null,
        "amount_due_now": {
            "amount": 2000,
            "currency": "CAD"
        },
        "expires_at": null,
        "booking": {
            "reference": "PB7K2QXA-20261114"
        },
        "created_at": "2026-11-10T14:05:40-05:00"
    }

    Example The payment is being confirmed: read it again in a few seconds.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "processing",
        "next_action": null,
        "amount_due_now": {
            "amount": 2000,
            "currency": "CAD"
        },
        "expires_at": "2026-11-10T14:35:40-05:00",
        "booking": null,
        "created_at": "2026-11-10T14:05:40-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

POST/checkouts/{checkout_id}/verify-email

Confirm with the email code

For a checkout awaiting_verification: the 6-digit code CrenoSync emailed to the visitor. The right code confirms the booking. Five wrong codes cancel the checkout and release the time (409 verification_locked). The code works until the hold ends.

Credentials publishable key + guest token or secret key + guest token

Parameters

checkout_id string required path

Example chk_Hd5rL2kTy9Mc

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Request body

code string required

pattern ^[0-9]{6}$

Example 482913

Example request

HTTP
HTTPPOST /api/v1/checkouts/chk_Hd5rL2kTy9Mc/verify-email
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Accept-Language: en
Content-Type: application/json

{
    "code": "482913"
}

Answers

  • 200

    Checkout

    The booking is confirmed.

    Example The right code: booked.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "confirmed",
        "next_action": null,
        "amount_due_now": {
            "amount": 0,
            "currency": "CAD"
        },
        "expires_at": null,
        "booking": {
            "reference": "PB7K2QXA-20261114"
        },
        "created_at": "2026-11-10T14:05:40-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 422

    Error

    validation_failed, with each field's messages in error.fields; or a booking rule: group_invalid, difficulty_required, category_not_offered, promo_code_invalid, quote_hold_mismatch, return_url_not_allowed, verification_failed (with error.attempts_left).

    See its examples

  • 413

    Error

    payload_too_large: the request body is larger than the server accepts.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

POST/checkouts/{checkout_id}/resend-code

Send the email code again

At most three codes per checkout, one a minute.

Credentials publishable key + guest token or secret key + guest token

Parameters

checkout_id string required path

Example chk_Hd5rL2kTy9Mc

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPPOST /api/v1/checkouts/chk_Hd5rL2kTy9Mc/resend-code
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Accept-Language: en

Answers

  • 202

    Checkout

    A new code was sent.

    Example A new code is on its way.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "awaiting_verification",
        "next_action": {
            "type": "verify_email",
            "url": "https://alpha.crenosync.ca/en/checkout/chk_Hd5rL2kTy9Mc?expires=1794337950&signature=8b21d4…",
            "expires_at": "2026-11-10T14:12:30-05:00",
            "email_hint": "c•••••@example.com",
            "attempts_left": 5
        },
        "amount_due_now": {
            "amount": 0,
            "currency": "CAD"
        },
        "expires_at": "2026-11-10T14:12:30-05:00",
        "booking": null,
        "created_at": "2026-11-10T14:05:40-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

  • 503

    Error

    business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

    Headers Retry-After

    See its examples

POST/checkouts/{checkout_id}/cancel

Cancel a checkout

The visitor gave up before the booking was confirmed: the checkout ends and its time and promotion use are released. Safe to repeat. A checkout whose payment may already be under way cannot be cancelled this way (409 checkout_processing): CrenoSync finishes checking it with the payment provider first.

Credentials publishable key + guest token or secret key + guest token

Parameters

checkout_id string required path

Example chk_Hd5rL2kTy9Mc

Example request

HTTP
HTTPPOST /api/v1/checkouts/chk_Hd5rL2kTy9Mc/cancel
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…

Answers

  • 200

    Checkout

    The cancelled checkout.

    Example Given up: the time is free again.

    JSON
    JSON{
        "id": "chk_Hd5rL2kTy9Mc",
        "status": "cancelled",
        "next_action": null,
        "amount_due_now": {
            "amount": 0,
            "currency": "CAD"
        },
        "expires_at": null,
        "booking": null,
        "created_at": "2026-11-10T14:05:40-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 409

    Error

    The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

    Headers Retry-After

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

Bookings

Confirmed bookings.

GET/bookings/{reference}

Get a booking

A confirmed booking made through this API. A guest token or return token reads only the booking of its own checkout; a secret key reads only bookings made through that key. There is no search by name, email or phone.

Credentials publishable key + guest token or secret key + guest token or publishable key + return token or secret key

Parameters

reference string required path

The booking number.

Example PB7K2QXA-20261114

Accept-Language string header

fr or en. Default: the business's default language.

Values fr en

Example request

HTTP
HTTPGET /api/v1/bookings/PB7K2QXA-20261114
Host: alpha.crenosync.ca
Authorization: Bearer cs_pk_…
CrenoSync-Guest-Token: cs_gt_…
Accept-Language: en

Answers

  • 200

    Booking

    The booking.

    Example A booking with its deposit paid.

    JSON
    JSON{
        "reference": "PB7K2QXA-20261114",
        "status": "confirmed",
        "activity": {
            "id": "act_7Kq2mX9pLr",
            "name": "The Lost Lab"
        },
        "date": "2026-11-14",
        "time": "19:30",
        "starts_at": "2026-11-14T19:30:00-05:00",
        "group_size": 4,
        "difficulty": {
            "level": 1,
            "name": "Classic"
        },
        "lines": [
            {
                "name": "Adults",
                "quantity": 4,
                "amount": {
                    "amount": 12000,
                    "currency": "CAD"
                }
            }
        ],
        "price": {
            "subtotal": {
                "amount": 12000,
                "currency": "CAD"
            },
            "discount": null,
            "taxes": [
                {
                    "name": "GST",
                    "amount": {
                        "amount": 600,
                        "currency": "CAD"
                    }
                },
                {
                    "name": "QST",
                    "amount": {
                        "amount": 1197,
                        "currency": "CAD"
                    }
                }
            ],
            "total": {
                "amount": 13797,
                "currency": "CAD"
            }
        },
        "paid": {
            "amount": 2000,
            "currency": "CAD"
        },
        "balance": {
            "amount": 11797,
            "currency": "CAD"
        },
        "customer": {
            "name": "Camille Tremblay",
            "email": "camille@example.com",
            "phone": "514 555-0199",
            "language": "en"
        },
        "confirmation_url": "https://alpha.crenosync.ca/en/booking/confirmation?booking_number=PB7K2QXA-20261114&token=Gl22GGQx…",
        "channel": {
            "type": "api",
            "key_name": "Our website"
        },
        "attribution": {
            "source": "newsletter",
            "medium": "email",
            "campaign": "fall-2026"
        },
        "external_reference": "order-1042",
        "created_at": "2026-11-10T14:05:40-05:00"
    }
  • 401

    Error

    missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

    See its examples

  • 403

    Error

    not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

    See its examples

  • 404

    Error

    not_found: no such record for this credential. Records of another guest, key or business answer the same.

    See its examples

  • 429

    Error

    rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

    Headers Retry-After

    See its examples

  • 500

    Error

    internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

    See its examples

Error answers

The answers several endpoints share, mostly refusals, each with examples. Every error has the same shape: test its code, never its message.

401 Unauthenticated

missing_key, invalid_key (also a key of another business: keys exist only on their own business's host), key_revoked, missing_guest_token, invalid_guest_token (unknown, or made with another key), guest_token_expired, invalid_return_token (unknown, made with another key, replaced by a newer one, or past its 30 minutes). Too many refused keys from one address in a minute answers 429 rate_limited for a while, even with a valid key.

Example A key this business doesn't know.

JSON
JSON{
    "error": {
        "code": "invalid_key",
        "message": "This API key is not valid for this business.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

403 Forbidden

not_in_plan (the business's plan doesn't include the API), integration_disabled, origin_not_allowed, secret_key_in_browser, not_allowed (this credential can't do that).

Example A web page on a website not registered with the key.

JSON
JSON{
    "error": {
        "code": "origin_not_allowed",
        "message": "This website is not registered with this API key.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

404 NotFound

not_found: no such record for this credential. Records of another guest, key or business answer the same.

Example Nothing with that ID for this key or token.

JSON
JSON{
    "error": {
        "code": "not_found",
        "message": "Not found.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

409 Conflict

The request is valid but the situation doesn't allow it: time_unavailable, activity_unavailable, quote_expired, quote_changed (with error.quote), policy_changed (with error.policies), hold_expired, hold_in_checkout, hold_already_used, checkout_in_progress, checkout_processing, checkout_closed, verification_locked, idempotency_key_reused, idempotency_in_progress. The two …_in_progress answers carry Retry-After: send the same request again after it.

Example The time was taken meanwhile: choose another one.

JSON
JSON{
    "error": {
        "code": "time_unavailable",
        "message": "This time slot is no longer available.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

Example The price changed (checkout): show the new quote, then check out with it.

JSON
JSON{
    "error": {
        "code": "quote_changed",
        "message": "The price changed since this quote. Show the new quote, then check out with it.",
        "request_id": "req_01JBQ7Z5K3W8",
        "quote": {
            "id": "quo_8Nc3Vx6Bm1Qa",
            "expires_at": "2026-11-10T14:20:40-05:00",
            "activity_id": "act_7Kq2mX9pLr",
            "date": "2026-11-14",
            "time": "19:30",
            "starts_at": "2026-11-14T19:30:00-05:00",
            "group_size": 4,
            "difficulty": {
                "level": 1,
                "name": "Classic"
            },
            "lines": [
                {
                    "category_id": "cat_Rt6yU1oP3aSd",
                    "name": "Adults",
                    "quantity": 4,
                    "unit_price": {
                        "amount": 3000,
                        "currency": "CAD"
                    },
                    "amount": {
                        "amount": 12000,
                        "currency": "CAD"
                    }
                }
            ],
            "subtotal": {
                "amount": 12000,
                "currency": "CAD"
            },
            "discount": null,
            "taxes": [
                {
                    "name": "GST",
                    "rate": 5,
                    "amount": {
                        "amount": 600,
                        "currency": "CAD"
                    }
                },
                {
                    "name": "QST",
                    "rate": 9.9749999999999996447286321199499070644378662109375,
                    "amount": {
                        "amount": 1197,
                        "currency": "CAD"
                    }
                }
            ],
            "total": {
                "amount": 13797,
                "currency": "CAD"
            },
            "due_now": {
                "amount": 2000,
                "currency": "CAD"
            },
            "due_later": {
                "amount": 11797,
                "currency": "CAD"
            },
            "payment": "deposit"
        }
    }
}

Example The same hold's checkout is being started: wait for Retry-After, then send it again.

JSON
JSON{
    "error": {
        "code": "checkout_in_progress",
        "message": "A checkout for this time is already being started. Wait a moment, then try again.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

422 ValidationFailed

validation_failed, with each field's messages in error.fields; or a booking rule: group_invalid, difficulty_required, category_not_offered, promo_code_invalid, quote_hold_mismatch, return_url_not_allowed, verification_failed (with error.attempts_left).

Example Fields to correct, each with its messages.

JSON
JSON{
    "error": {
        "code": "validation_failed",
        "message": "The data sent is not valid.",
        "request_id": "req_01JBQ7Z5K3W8",
        "fields": {
            "customer.email": [
                "The email address is not valid."
            ],
            "time": [
                "The time field is required."
            ]
        }
    }
}

Example A wrong code, when checking the emailed code.

JSON
JSON{
    "error": {
        "code": "verification_failed",
        "message": "This code is not right. Check the latest email we sent and try again.",
        "request_id": "req_01JBQ7Z5K3W8",
        "attempts_left": 4
    }
}

429 RateLimited

rate_limited, too_many_holds, business_busy, too_many_codes. Wait for Retry-After. A page can read these answers from any website. An address over its limit also gets 429 on browser preflights, which browsers report as a network error: wait before trying again.

Example Too many requests: wait for Retry-After.

JSON
JSON{
    "error": {
        "code": "rate_limited",
        "message": "Too many requests. Please try again later.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

503 Unavailable

business_unavailable (suspended, under maintenance or still being set up), payment_unavailable, email_unavailable.

Example The business is under maintenance.

JSON
JSON{
    "error": {
        "code": "business_unavailable",
        "message": "This site is under maintenance. Please try again in a few minutes.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

413 PayloadTooLarge

payload_too_large: the request body is larger than the server accepts.

Example A body over the limit.

JSON
JSON{
    "error": {
        "code": "payload_too_large",
        "message": "The request is too large.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

500 InternalError

internal_error: a failure on CrenoSync's side. Quote request_id when contacting support.

Example A failure on CrenoSync's side: quote the request_id.

JSON
JSON{
    "error": {
        "code": "internal_error",
        "message": "Something went wrong on our side. Please try again later.",
        "request_id": "req_01JBQ7Z5K3W8"
    }
}

Objects

Error object

error object required
error.code string required

Stable, machine-readable. New codes may be added.

Example time_unavailable

error.message string required

For people, in the request's language. May change; never test it.

Example This time slot is no longer available.

error.request_id string required
error.fields object

For validation_failed, each invalid field (dotted path) and its messages.

Example {"customer.email":["The email address is not valid."]}

error.attempts_left integer

For verification_failed.

error.quote Quote

For quote_changed, the new quote, for the same hold's choice and promotion code.

error.policies Policies

For policy_changed, the texts now in force.

Money object

amount integer required

In the currency's smallest unit (cents).

Example 4250

currency string required

ISO 4217.

Example CAD

LocalizedText string | null

Plain text in the request's language (line breaks kept, no HTML), or null when empty.

Site object

name string required

Example Évasion Alpha

logo_url string | null (uri)
address string | null
phone string | null
website_url string (uri)

The business's hosted CrenoSync site.

booking_url string (uri) required

The hosted booking page, as a fallback link.

languages array of string required

Open list.

Values · open list fr en

default_language string required

Open list.

Values · open list fr en

currency string required

Example CAD

timezone string required

IANA time zone of every local date and time.

Example America/Toronto

capabilities object required
capabilities.online_payment string required

none: everything is paid at the venue. deposit: activities may ask for a deposit online. full: activities ask for the full price online. guarantee: activities save the visitor's card as a no-show guarantee, with nothing paid online. mixed: it depends on the activity (its online_payment).

Values · open list none deposit full guarantee mixed

capabilities.email_verification boolean required

Bookings with nothing to pay online are confirmed with an emailed code.

capabilities.promo_codes boolean required
capabilities.shared_sessions boolean required

Some activities let several groups share a session.

Policies object

privacy Policy | null required

Null only while the business has published none.

terms Policy | null required

Null when the business has no terms and conditions.

Policy object

version_id integer required

Example 3

title string required
html string required

Sanitized HTML (headings, paragraphs, lists, links, emphasis).

url string | null (uri)

The same text on the hosted site, when it has its own page (the privacy policy does; the terms are shown at booking).

published_at string (date-time) required

Activity object

id string required

Example act_7Kq2mX9pLr

slug string required

Human-readable, for your own URLs. May change; store id.

Example labo-perdu

name string required

Example Le Labo Perdu

description LocalizedText
highlights LocalizedText
image_url string | null (uri)
display_order integer
duration_minutes integer required

How long the activity lasts.

Example 60

group object required

Counts only people in categories that count toward the group.

group.min integer required

Example 2

group.max integer | null required

Example 6

group.recommended string | null

Example 3-5

mode string required

private: one group per time. shared: several groups share a session, up to its places. Open list.

Values · open list private shared

shared object | null

For a shared activity.

shared.places_per_session integer

Example 12

shared.min_to_play integer | null

People the session needs to take place; below it the business may cancel the session.

difficulty object | null

Present when the visitor must choose a level.

difficulty.levels array of object required
difficulty.levels[].level integer required

Example 0

difficulty.levels[].name string required

Example Facile

difficulty.levels[].success_rate number | null

Example 62.5

success_rate number | null

The activity's single success rate, when it has no levels.

minimum_age integer | null
price_categories array of PriceCategory required
from_price Money | null

The lowest non-zero price per person, before taxes.

online_payment string required

What booking it asks to pay online: none, deposit (see deposit), full, the whole price with taxes once a promotion comes off, or guarantee: nothing paid, the visitor's card saved for the no-show fee (see no_show_fee).

Values · open list none deposit full guarantee

deposit object | null

The deposit due online, before any promotion; null when no deposit is paid online (nothing, or the full price).

deposit.amount Money
deposit.per_person boolean
no_show_fee object | null

The no-show fee of an activity that saves the visitor's card (online_payment: guarantee), at most amount (per person when per_person), charged only if no one comes or the booking is canceled less than free_cancellation_hours before its start; null otherwise.

no_show_fee.amount Money
no_show_fee.per_person boolean
no_show_fee.free_cancellation_hours integer
resources array of Resource required

What is booked for the activity. In v1, one room. Later, people such as trainers.

booking_url string (uri) required

This activity on the hosted booking page.

PriceCategory object

id string required

Example cat_Rt6yU1oP3aSd

name string required

Example Adulte

age_min integer | null
age_max integer | null
price Money required

Per person, before taxes.

counts_toward_group boolean required

Whether these people count toward the group size and the places (an infant may not).

must_be_accompanied boolean required
can_accompany boolean required
min_per_booking integer | null
max_per_booking integer | null

Resource object

id string required

Example res_Zp4xN7cB2qLe

type string required

Open list. room in v1; new types such as person will be added. Show a type you don't know by its name.

Values · open list room

name string required

Availability object

activity_id string required
from string (date) required
to string (date) required
group_size integer required
timezone string required

Example America/Toronto

days array of object required

Only days with at least one open time.

days[].date string (date) required

Example 2026-11-14

days[].times array of OpenTime required

OpenTime object

time string required

Example 19:30

starts_at string (date-time) required

Example 2026-11-14T19:30:00-05:00

places_left integer | null

For a shared session, the places still free; null for a private time.

still_needed integer | null

For a shared session with a minimum, the people it still needs to take place.

booking_url string (uri) required

This time on the hosted booking page, for sites that hand the booking over to CrenoSync.

BookingSession object

token string required

Example cs_gt_4fG7hJ2kL9mN3pQ6rS8tV1wX

expires_at string (date-time) required
limits object required
limits.holds integer required

Times this session may hold at once.

Example 3

ParticipantInput object

category_id string required
quantity integer required

from 0 to 1000

QuoteRequest object

activity_id string required
date string (date) required
time string required

pattern ^([01][0-9]|2[0-3]):[0-5][0-9]$

Example 19:30

participants array of ParticipantInput required

1 to 50 items

difficulty_level integer | null

Required when the activity has difficulty.

promo_code string | null

at most 64 characters

Quote object

id string required

Example quo_2Jf9TzQk4mVw

expires_at string (date-time) required
activity_id string required
date string (date) required
time string required
starts_at string (date-time) required
group_size integer required
difficulty object | null
difficulty.level integer
difficulty.name string
lines array of object required
lines[].category_id string required
lines[].name string required
lines[].quantity integer required
lines[].unit_price Money required
lines[].amount Money required
subtotal Money required
discount object | null
discount.code string
discount.label string
discount.amount Money
taxes array of object required
taxes[].name string required

Example TPS

taxes[].rate number required

Percent.

Example 5

taxes[].amount Money required
total Money required
due_now Money required

Paid online at checkout (the deposit, or the full price), possibly 0.

due_later Money required

Paid at the venue.

payment string required

What the checkout asks to pay online: none, deposit, full, the whole price, or guarantee, nothing paid but the visitor's card saved for the no-show fee. Anything but none sends the visitor to the payment page.

Values · open list none deposit full guarantee

Hold object

id string required

Example hld_Vb3nP8sQw1Zx

quote_id string required
activity_id string required
date string (date) required
time string required
starts_at string (date-time) required
places integer required

For a shared session, the places held; for a private time, the group size.

status string required

Open list.

Values · open list active in_checkout used released expired

expires_at string (date-time) required

Customer object

name string required

at most 255 characters

Example Camille Tremblay

email string (email) required

at most 255 characters

phone string required

at most 30 characters

Example 514 555-0199

language string

The language of the emails sent to the customer; by default, the request's language.

Values fr en

CheckoutRequest object

hold_id string required
quote_id string required

The quote shown to the visitor, for the hold's exact choice. After quote_changed, the new quote's ID.

customer Customer required
notes string | null

at most 5000 characters

attribution Attribution | null

Where the visitor came from, for the business's reports. Optional.

external_reference string | null

Your own reference for this booking (an order number in your system), returned on the booking. Not checked for uniqueness. Never put personal data in it.

at most 100 characters

pattern ^[A-Za-z0-9._:/#-]{1,100}$

consent object required

The versions shown to the visitor (from GET /policies) and their explicit acceptance.

consent.privacy_version_id integer required
consent.privacy_accepted boolean required

Values true

consent.terms_version_id integer | null

Required when the business has terms.

consent.terms_accepted boolean | null

Required (true) when the business has terms.

consent.no_show_fee_accepted boolean | null

Required (true) when the quote's payment is guarantee: the visitor accepted the activity's no-show fee (no_show_fee).

return_url string | null (uri)

Required when the quote's payment is not none; optional otherwise. Where the visitor comes back after a CrenoSync page of next_action.url (payment, email code); CrenoSync adds checkout_id and return_token after # (https://www.example.ca/thanks#checkout_id=chk_…&return_token=cs_rt_…), its own query string kept. Without one, that page ends on the hosted confirmation.

The address is parsed, never compared as text, and must match one of the return addresses registered with the key (422 return_url_not_allowed otherwise):

  • https only; http is accepted only for a local development host (localhost, 127.0.0.1, *.localhost, *.test), and never in production;
  • the scheme, host and port equal the registered address's, exactly (no wildcard, no subdomain of it);
  • the path equals the registered path, or continues below it after a / (registered /booking allows /booking/done, never /booking-done);
  • no user name or password, and no fragment (#…).

Browsers never send the part after # to your server, its logs or another site. Read it in the page's script, then take it out of the address (history.replaceState) before any analytics or other outside script runs.

at most 2048 characters

Checkout object

id string required

Example chk_Hd5rL2kTy9Mc

status string required

Open list: treat a status you don't know as not confirmed yet, and read it again later.

  • awaiting_verification: waiting for the emailed code.
  • awaiting_payment: waiting for the visitor on the payment page.
  • processing: the payment is being confirmed with the provider.
  • confirmed: booked; see booking.
  • expired: the time ran out before the code or the payment.
  • cancelled: given up, or too many wrong codes.
  • attention_required: paid, but the booking could not be made automatically. The business was alerted and will contact the customer. Never ask them to pay again.

Values · open list awaiting_verification awaiting_payment processing confirmed expired cancelled attention_required

next_action NextAction | null

What the visitor must do next for this checkout, or null.

amount_due_now Money required
expires_at string | null (date-time)

When an awaiting checkout ends: the end of the payment window (30 minutes, cut short to end 15 minutes before the start, but never less than 10 minutes, so it can end after the start for a checkout opened in the last 25 minutes), or of the hold for an email code. Paid or confirmed by then, it is booked.

booking BookingSummary | null

Once confirmed.

created_at string (date-time) required

NextAction object

A step the visitor must complete for this checkout. Every action, whatever its type, has a url: CrenoSync's page where the visitor completes that step for this same checkout. The page ends on your return_url, or on the hosted confirmation when the checkout has none.

Handle the types you know your own way. For a type you don't know, send the visitor to url. Never send them to booking_url or start a new quote or hold: this checkout already holds the time, and may already have a payment under way.

type string required

Open list.

  • verify_email: ask for the 6-digit code emailed to the visitor and send it with POST /checkouts/{id}/verify-email, or send the visitor to url to type it on CrenoSync's page.
  • redirect: send the visitor to url, CrenoSync's payment page.

Values · open list verify_email redirect

url string (uri) required

CrenoSync's page for this step, on the business's own host. Open it in the whole window, never in a frame. It is a signed link for this checkout only, valid until expires_at: don't store, share or log it. The first browser that opens it and loads the page keeps the checkout and completes the step there (it can reload, and comes back there from the payment); the address loses its signature at once. Another browser opening the same link is then refused, so send it only to the visitor's own browser.

Example https://alpha.crenosync.ca/fr/checkout/chk_Hd5rL2kTy9Mc?expires=1794339340&signature=3f9c…

expires_at string (date-time) required

When the step can no longer be completed (the end of the hold or of the payment window; see Checkout.expires_at).

Example 2026-11-10T14:35:40-05:00

email_hint string

For verify_email, the address the code went to, partly hidden.

Example c•••••@gmail.com

attempts_left integer

For verify_email, the wrong codes still allowed.

Example 5

BookingSummary object

reference string required

Example PB7K2QXA-20261114

Booking object

reference string required

Example PB7K2QXA-20261114

status string required

Open list.

Values · open list confirmed cancelled

activity object required
activity.id string required
activity.name string required
date string (date) required
time string required
starts_at string (date-time) required
group_size integer required
difficulty object | null
difficulty.level integer
difficulty.name string
lines array of object required
lines[].name string required
lines[].quantity integer required
lines[].amount Money required
price object required
price.subtotal Money required
price.discount Money | null
price.taxes array of object required
price.taxes[].name string required
price.taxes[].amount Money required
price.total Money required
paid Money required
balance Money required

Left to pay at the venue.

customer object

With a guest token or the secret key: name, email, phone and language. With a return token: name, email_hint and language only.

customer.name string
customer.email string
customer.email_hint string

The email, masked, e.g. c••••••@example.com (return token reads).

customer.phone string
customer.language string

Open list.

Values · open list fr en

confirmation_url string | null (uri)

The customer's own confirmation page on the hosted site (private link). Null on a read with the return token.

channel object

How the booking was made, recorded by CrenoSync.

channel.type string required

api for every booking this endpoint returns. Open list (see Conventions).

Values · open list api

channel.key_name string | null

The name the owner gave the key in Settings › API.

attribution Attribution | null
external_reference string | null
created_at string (date-time) required

Attribution object

Campaign information, as in utm_source, utm_medium and utm_campaign. Each value is at most 100 characters of letters, digits, spaces and ._-+:/. Never put personal data in them.

source string | null

at most 100 characters

pattern ^[\p{L}\p{N} ._\-+:/]{1,100}$

Example facebook

medium string | null

at most 100 characters

pattern ^[\p{L}\p{N} ._\-+:/]{1,100}$

Example social

campaign string | null

at most 100 characters

pattern ^[\p{L}\p{N} ._\-+:/]{1,100}$

Example halloween-2026