Price before booking
Quote a composition, show the same price on your booking form, price a list in one call and check a discount or voucher code.
A quote is the price a booking would have, computed by the same pricing as the business's own booking page. Use it instead of calculating prices on your side. Nothing is written or reserved.
You need a key with the orders:write scope. A publishable key (shyni_pk_...)
works too, so the form can quote straight from the browser.
BASE_URL="https://api.shyni.io"
API_KEY="shyni_sk_REPLACE_ME"1. Quote one composition
POST /api/v1/quotes takes the pricing fields of
Create an order: serviceIds and variant
are required, optionAnswers, voucherCode and scheduledStart are optional.
Unknown fields (the customer, for example) are 400 UNKNOWN_FIELD. There is no
Idempotency-Key: a quote is always computed fresh.
curl -sS -X POST "$BASE_URL/api/v1/quotes" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"serviceIds": ["3b7e9c52-81d4-4f6a-a0c3-2d9e5b8f1a47"],
"variant": "MEDIUM",
"scheduledStart": "2026-11-03T08:00:00Z",
"voucherCode": "SPRING10"
}'{
"available": true,
"scheduledStart": "2026-11-03T08:00:00Z",
"scheduledEnd": "2026-11-03T09:30:00Z",
"pickupAt": "2026-11-03T09:30:00Z",
"currency": "CZK",
"totalPrice": 75780,
"depositAmount": 50000,
"durationMinutes": 90,
"surcharges": [],
"dayPolicy": null,
"discountCode": {
"code": "SPRING10",
"type": "PERCENT",
"value": 10,
"discount": 8420,
"depositWaived": false
},
"giftVoucher": null,
"codeError": null,
"depositWaivedReason": null,
"depositHoldMinutes": 30
}Money is in minor units of currency (hellers for CZK), so 75780 is
757.80 CZK. depositHoldMinutes says how long an unpaid deposit holds the slot after
booking (null when no deposit is due). surcharges has one row per chosen
configurator option.
dayPolicy is the policy of the start date (name, kind such as VIP or
DISCOUNT, percent, and amount, the signed change in minor units against
the same selection without the policy), or null. depositWaivedReason says
why a deposit is waived; its only value is KNOWN_CAR, otherwise null.
2. Dated and undated prices
scheduledStart is optional. Take it from
free start times.
- With it the answer carries
available,scheduledEndandpickupAt, and any day policy of that date is priced in.scheduledEndisnullwhen the studio cannot work then (a closed day, lead time). - Without it you get an undated price: no day policy, no times, and
available: null. That is what you want for a price list.
Services the studio prices by hand (from-price, on request) cannot be quoted or
booked: 400 INVALID_INPUT with field: "serviceIds". A studio that paused
online bookings also answers 400 INVALID_INPUT, and an unpublished studio
409 STUDIO_NOT_PUBLISHED.
3. Show the same price on the booking form
Quote whenever the customer changes the service, variant, options, code or
start time, and render totalPrice and depositAmount from the answer. When
the customer submits, send the same fields to
Create an order. totalPrice and
depositAmount equal what POST /orders charges for the same serviceIds,
variant, optionAnswers, voucherCode and scheduledStart, so the price the
customer saw is the price they pay. See Booking on your site.
A server integration with a secret key can send waiveDayAdjustment: true (and
skipDeposit: true) on the quote, the order or a booking link. The day-policy
surcharge is then not charged, and the amount comes back as waivedDayAdjustment
(minor units, absent when nothing was waived) instead of a silent discount; a day
discount is never waived. skipDeposit makes the deposit NOT_REQUIRED. A
publishable key sending true for either gets 403 FORBIDDEN with field set
to the flag, and nothing is created.
4. A price list in one call
POST /api/v1/quotes/batch takes 1 to 50 quotes (400 INVALID_INPUT with
field: "quotes" otherwise): one per variant, one per day of a calendar,
one per option set.
curl -sS -X POST "$BASE_URL/api/v1/quotes/batch" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"quotes": [
{ "serviceIds": ["3b7e9c52-81d4-4f6a-a0c3-2d9e5b8f1a47"], "variant": "SMALL" },
{ "serviceIds": ["3b7e9c52-81d4-4f6a-a0c3-2d9e5b8f1a47"], "variant": "LARGE" },
{ "serviceIds": ["unknown"], "variant": "LARGE" }
]
}'Answers come back in input order, each { "quote": ... } or { "error": ... },
so one composition that cannot be priced does not fail the others:
{
"quotes": [
{ "quote": { "totalPrice": 64000, "depositAmount": 50000, "...": "..." } },
{ "quote": { "totalPrice": 92000, "depositAmount": 50000, "...": "..." } },
{ "error": { "code": "NOT_FOUND", "message": "Service not found.", "field": "serviceIds" } }
]
}Failures that concern the whole studio (paused bookings, unpublished studio, rate limit) answer the whole call, as on a single quote.
5. Check a discount or voucher code
Put the customer's code in voucherCode. A code that cannot be applied is not
an error: the answer is 200 with the undiscounted price and codeError.
"codeError": { "code": "EXPIRED", "message": "..." }codeError.code is INVALID, EXPIRED, EXHAUSTED or USED, and message is
an English sentence. Branch on code and write your own text. A valid code
fills one of two fields:
discountCode: a studio discount code. It lowerstotalPricebydiscount. ItstypeisPERCENT,FIXEDorDEPOSIT_EXEMPT; the last one has anullvalueand no price discount, anddepositWaivedtells whether the code waives the deposit.giftVoucher: the voucher paysappliedof the total;totalPricestays,depositAmountdrops toreducedDeposit, andremainderis what is left on the voucher.
Limits
A quote is rate limited like the studio's public price preview, per IP and per
studio. A batch spends one slot of those budgets regardless of its size. Over
the limit you get 429 RATE_LIMITED; see
Rate limits. A secret key calling on behalf of a
visitor can name the end customer's address in the Shyni-Customer-IP header so
the per-IP budget applies to that visitor.
Reference: Price a booking before it is made and Price up to 50 compositions in one call.