Keys and auth
Organization and studio keys, scopes, the business header and allowed domains.
There is no anonymous access. Every request carries a key as a bearer token:
Authorization: Bearer shyni_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA missing, malformed, unknown or revoked key is one uniform answer,
401 INVALID_API_KEY.
Creating a key
You create an organization key in the portal under API keys. Give it a name of 1 to 100 characters, choose secret or publishable, test or live, and for a secret key at least one scope. A key is shown once; Shyni keeps only a hash, so a lost key cannot be recovered: revoke it and issue a new one. An organization has at most 20 active keys of each kind.
What a member may create depends on the role:
| Role | Test keys | Live keys |
|---|---|---|
| Owner, admin | Yes | Yes, once live mode is on |
| Developer | Yes | No |
A live key needs live mode turned on for the organization; see Sandbox. A test key acts only on sandbox businesses, a live key only on live ones.
Choosing the business
An organization key works across all businesses of the organization. Name the
one a request is for in the Shyni-Business header; GET /businesses lists
the ids.
Shyni-Business: 5c1d8e0a-3f2b-4a76-9b0e-7d4c2a1f6e83The refusals:
| Code | Status | Meaning |
|---|---|---|
BUSINESS_REQUIRED | 400 | A business endpoint was called without the header. Only the organization-level endpoints (/businesses, /studios, /webhook-endpoints, and /me for the organization itself) work without it. |
BUSINESS_HEADER_NOT_ALLOWED | 400 | A studio key sent the header. Studio keys never choose a business. |
BUSINESS_NOT_FOUND | 404 | The business does not exist, belongs to another organization, is archived, or its mode differs from the key's. The answer is the same in every case. |
BILLING_SUSPENDED | 403 | A live key of an organization whose live mode was suspended for an unpaid monthly charge. Test keys keep working. |
Secret and publishable
| Prefix | Kind | Where it runs |
|---|---|---|
shyni_sk_live_ | secret | Your server only. Carries scopes. |
shyni_pk_live_ | publishable | A browser on the business's own website. Fixed allowance, no scopes. |
shyni_sk_test_ | secret | As sk_live, acts on sandbox businesses. |
shyni_pk_test_ | publishable | As pk_live, acts on sandbox businesses. |
live or test is the mode of the key, set when it is created. A key works on a
business only when its mode equals the business's mode. GET /me reports it as
mode.
Scopes
A secret key carries one or more scopes, chosen when it is created.
| Scope | Grants |
|---|---|
catalog:read | GET /services, GET /availability/starts, GET /availability/calendar, and the secret-key reads of drafts and settings: GET /catalog/draft/services, /catalog/draft/packages, /publish-state, /service-categories, /service-groups, /places, /discount-codes, /availability/blocks, /availability/day-policies, /availability/hours, /studio, /storefront, /images |
orders:read | GET /orders, GET /orders/{id} |
customers:read | GET /customers, GET /customers/{id} |
orders:write | POST /orders, creating a booking, and POST /quotes, /quotes/batch, the price of a booking before it is made. It changes no existing order |
orders:manage | Managing orders that already exist: confirm, start, complete, cancel, no-show, ready, reschedule and its requests, work intervals, inspection, PATCH /orders/{id}, custom lines, booking links, review replies and reports |
payments:write | Money on an existing order: the deposit refund and the paid and refunded marks, POST /orders/{id}/payments and DELETE /orders/{id}/payments/{paymentId} |
vouchers:read | GET /gift-vouchers, GET /gift-vouchers/{code}, GET /gift-vouchers/{code}/pdf |
vouchers:write | POST /gift-vouchers, /refund, /resend, and the deactivate, confirm-payment and cancel actions |
catalog:write | Services, categories, configurator groups, questions, packages, places, discount codes, variant defaults, and POST /catalog/publish, DELETE /catalog/draft |
availability:write | Availability blocks, day policies and opening hours |
storefront:write | The studio page (description, FAQ, layout, theme, photos), POST /storefront/publish, DELETE /storefront/draft, and the photo library (/images) |
settings:write | PATCH /studio and the studio's operational settings under /studio/... |
businesses:read | GET /businesses, GET /businesses/{id}, GET /studios, GET /studios/{id}. Organization-level: no Shyni-Business header |
businesses:manage | POST /businesses, PATCH /businesses/{id}, …/archive, …/reset, POST /studios and …/owner-invite. Organization-level |
workers:write | POST /workers, PATCH /workers/{id} and …/archive |
payments:read | GET /payments/gateway |
payments:manage | PUT and DELETE /payments/gateway |
webhooks:read | GET /webhook-endpoints and GET /webhook-endpoints/{id}. Organization-level |
webhooks:manage | POST, PATCH and DELETE /webhook-endpoints. Organization-level |
POST /publish publishes the catalog and the studio page together and needs
both catalog:write and storefront:write.
Scopes are independent: no scope implies another. orders:write does not
include orders:read, orders:manage or payments:write, and a *:write
scope does not include the matching read;
the list reads of drafts and settings need catalog:read. A key without the
scope an endpoint needs gets 403 INSUFFICIENT_SCOPE. See
Writing through the API for what a write does.
A key for a partner website that only takes bookings needs orders:write
alone. It cannot cancel an order or refund a deposit.
GET /me is exempt for secret keys. It works with any valid secret key and
tells you which scopes the key carries and its quota use. See
Who am I.
Publishable keys in the browser
A publishable key has no scopes and its allowance cannot be widened:
GET /services, GET /availability/starts, GET /availability/calendar,
GET /studio, POST /quotes, POST /quotes/batch, POST /orders and
POST /gift-vouchers/purchases. It never writes anything else. Every other
endpoint, including /me, the other gift-voucher endpoints and every catalog,
availability, studio and storefront write, answers 403 INSUFFICIENT_SCOPE. The list is exact: other
methods on those paths (HEAD, say) and variants such as a trailing slash are
refused the same way.
A publishable key is used from a browser on the business's own website, so the allowed websites must be on a list. There are two lists, and a request is accepted if its origin is on either:
- the organization's list, in the portal under Settings → Allowed websites;
- the list of the selected business, set with
allowedDomainsonPATCH /businesses/{id}; the portal shows it on the business page.
Each list holds up to 10 hostnames such as www.mypawsalon.com. A browser
request carries an Origin header, and with a publishable key it is accepted
only when the origin is https://<host> with <host> on a list.
http://localhost works only when a list holds localhost; the
organization's list takes no localhost, so list it on a sandbox business. Any
other origin gets 403 ORIGIN_NOT_ALLOWED. An organization can issue
publishable keys like secret ones; each key still names its business with
Shyni-Business, and the key's mode must match the business.
An accepted request is answered with Access-Control-Allow-Origin set to that
exact origin (never *) and Vary: Origin. Cookies are never allowed. The
response exposes X-Quota-Limit, X-Quota-Used, X-Quota-Remaining,
Retry-After and Idempotency-Key to your code.
The preflight (OPTIONS with an Origin header, on any /api/v1 path) needs
no key. It answers 204 with Access-Control-Allow-Methods: GET, POST,
Access-Control-Allow-Headers: Authorization, Content-Type, Idempotency-Key, Shyni-Business
and Access-Control-Max-Age: 600. A request without an Origin header, such
as from a server or curl, is not affected by the list.
Never a secret key in a browser
A request that carries an Origin header with a secret key is refused with
403 SECRET_KEY_IN_BROWSER. Use a publishable key in
the browser and keep secret keys on your server. A key that has been exposed
should be revoked and re-issued.
Personal data
An orders:read or customers:read key can read customer name, e-mail, phone and subject details (such as a dog's name and breed, or for auto detailing the car's plate, make and model).
Whoever holds the key acts as a data processor for that data under the terms
of the business it belongs to. The API redacts no field in v1. Keep keys in an
environment variable or a secret manager, never in source control or logs.
Studio keys
A studio, a business with a Shyni admin of its own, issues its own keys in that
admin. Such a key is bound to the studio, never sends Shyni-Business (the
header is refused with BUSINESS_HEADER_NOT_ALLOWED) and needs the studio to be
on a Pro subscription (403 PLAN_REQUIRED below it). Building for a studio
covers working with a studio that uses Shyni and creating one for a client.
See Errors for every refusal and what to do about it.