Server integration

Use a secret key from your backend to read orders, create bookings and watch usage.

A secret key (shyni_sk_...) belongs on your server. It carries the scopes you chose when you created it. Keep it in an environment variable and never ship it to a browser; that is refused with 403 SECRET_KEY_IN_BROWSER anyway.

export SHYNI_KEY="shyni_sk_live_..."

Check the key

GET /me needs only a valid secret key, whatever its scopes, so use it first.

curl https://api.shyni.io/api/v1/me -H "Authorization: Bearer $SHYNI_KEY"

It returns tenantId, keyName, scopes, mode, monthlyQuota, usedThisMonth and remaining. See Who am I.

Read orders

orders:read lists orders. Filters: state, depositState, fromDate, toDate, search, customerEmail, sourceChannel, sourceLabel, and sort (asc or desc on scheduled start).

curl "https://api.shyni.io/api/v1/orders?state=CONFIRMED&fromDate=2026-11-01&perPage=50" \
  -H "Authorization: Bearer $SHYNI_KEY"

The answer is {"data": [...], "meta": {"page": 1, "perPage": 50, "total": 137}}. Walk it with page; see Pagination. One order's full detail is GET /orders/{id} with the id from the list, not the publicId. See List orders and Get an order.

Orders carry customer personal data. Handle it as the studio's data processor.

An order's assignedWorkerId is resolved with GET /workers (orders:read, secret key), which returns {"data": [{"id", "name"}]}. See List workers. GET /me also returns the studio's industry (AUTO or GENERIC); tolerate new values.

Read customers

customers:read lists the studio's customers with page, perPage and search, and GET /customers/{id} returns one. A customer carries contacts, subjects (what the customer brought, such as dogs with a name and breed, or for auto detailing cars with plate, make and model), orderCount, firstOrderAt and lastOrderAt. An order's customer.id is the same id.

curl "https://api.shyni.io/api/v1/customers?search=doe&perPage=50" \
  -H "Authorization: Bearer $SHYNI_KEY"

An anonymized customer is returned without personal data and has anonymizedAt set. To keep a CRM copy in step, subscribe to the customer.anonymized webhook and erase your copy of data.id. See List customers and Get a customer.

Create bookings from your server

POST /orders needs orders:write and an Idempotency-Key. The booking page limits apply to it: the per-IP budget is shared with the page, the per-studio budget is separate. From a server, pass the end customer's address in the Shyni-Customer-IP header (IPv4 or IPv6, as your server saw it), and the per-IP and per-studio budgets are applied to that address. Without the header a secret key is limited by its per-key burst limit only.

curl -X POST https://api.shyni.io/api/v1/orders \
  -H "Authorization: Bearer $SHYNI_KEY" \
  -H "Idempotency-Key: crm-4411-booking" \
  -H "Shyni-Customer-IP: 203.0.113.7" \
  -H "Content-Type: application/json" \
  -d '{"serviceIds":["3b7e9c52-81d4-4f6a-a0c3-2d9e5b8f1a47"],
       "scheduledStart":"2026-11-03T08:00:00Z","scheduledEnd":"2026-11-03T09:30:00Z",
       "customer":{"name":"Jane Doe","email":"jane.doe@example.com","phone":"+1 202 555 0104"},
       "variant":"MEDIUM",
       "sourceLabel":"partner-web",
       "attribution":{"utmSource":"newsletter","utmCampaign":"autumn"},
       "metadata":{"crmDealId":"4411"}}'

attribution and metadata are optional and come back on the order and in its webhooks; see Booking on your site. Every field of the body must be one the endpoint defines: an unknown one is 400 UNKNOWN_FIELD. variant is a code from the service's variants; answers to the service's questions go in optionAnswers. sourceLabel names your integration on the order (lower-case letters, digits and hyphens, 40 characters at most). The order's channel is always API. The studio owner can switch off the customer e-mails per key; then you mail the customer yourself. An unpaid order with a deposit holds the slot until holdUntil in the response: 30 minutes when the studio takes card payments (paymentUrl present), 3 hours for bank transfer. At an unpublished studio the call is 409 STUDIO_NOT_PUBLISHED. See Create an order.

Read the studio

GET /studio returns the studio's settings with a secret key (catalog:read) and PATCH /studio (settings:write) changes them. PATCH refuses the fields only the admin may change (bank account, currency, allowed domains and the like) with 400 UNKNOWN_FIELD instead of ignoring them silently. See Get the studio settings and Update the studio settings.

Watch your usage

Secret keys have a monthly quota, counted on successful calls only. Every response carries X-Quota-Limit, X-Quota-Used and X-Quota-Remaining. On 429 wait the Retry-After seconds. See Rate limits and quota.

Stay in sync without polling

Poll lists on a schedule if you must, but webhooks push an event when an order or voucher changes.

If you keep a copy of the orders, compare webhook snapshots by updatedAt and keep the newer one. After an outage, catch up with GET /orders?updatedSince=<last updatedAt you hold>&sortBy=updatedAt. Store your own id in externalRef on the order, customer or subject, and look it up with ?externalRef=.

On this page