Subject types and variants
Describe what a customer brings and how it changes the price, then send both with an order.
Every order is for a subject, the thing the customer brings: a dog, a head of hair, a car. A business describes its subjects with a subject type (which attributes a subject has) and prices them with variants (a size, a length, a tier). The API does not assume one kind of business. Auto detailing is one preset; a dog grooming salon, used as the example below, defines its own.
Use a secret key. Reading needs catalog:read, writing needs catalog:write,
and every write needs an Idempotency-Key header. See Keys and auth.
Subject types
GET /subject-types lists the types the business accepts; a subject on an
order is checked against them. See
Read the subject types.
A business of industry GENERIC defines its own types with PUT /subject-types.
The call replaces the whole list. See
Replace the subject types.
curl -X PUT https://api.shyni.io/api/v1/subject-types \
-H "Authorization: Bearer $SHYNI_KEY" \
-H "Shyni-Business: $SHYNI_BUSINESS" \
-H "Idempotency-Key: dog-type-1" \
-H "Content-Type: application/json" \
-d '{
"types": [{
"code": "DOG",
"label": "Dog",
"attributes": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "breed", "label": "Breed", "type": "text" },
{ "key": "weightKg", "label": "Weight (kg)", "type": "number" }
],
"identityKeys": ["name"],
"summaryKeys": ["name", "breed"]
}]
}'codeis the value ofsubject.typeon an order. It usesA-Z,0-9and_, starts with a letter and has at most 32 characters.attributeshas up to 30 entries. Each has akey(starts with a lower-case letter), alabel, atype(text,number,choiceorbool) andrequired. Achoicealso lists itsoptions.identityKeysname the attributes that make one subject the same subject again, the same dog on the next visit. Empty means every order is a new subject.summaryKeysname the attributes joined into the one-linesummaryof a subject.
A wrong definition is 400 INVALID_INPUT with field naming the place, such as
types[0].attributes[1].key. Orders already stored keep what they recorded. The
answer is {"types": [...]}, the same shape GET /subject-types returns.
Variants
A variant is a code that changes the price and the time of a service: the size of
the dog, the length of the hair. GET /catalog/variants returns codes, the
variants the business offers, and defaults, the prices a new service is
prefilled with (null until the business sets them). See
Read the variants.
A business of industry GENERIC defines them with PUT /catalog/variants. See
Set the default variant prices.
curl -X PUT https://api.shyni.io/api/v1/catalog/variants \
-H "Authorization: Bearer $SHYNI_KEY" \
-H "Shyni-Business: $SHYNI_BUSINESS" \
-H "Idempotency-Key: dog-variants-1" \
-H "Content-Type: application/json" \
-d '{
"variants": [
{ "code": "SMALL", "label": "Small dog (up to 10 kg)", "priceMultiplier": 1.0 },
{ "code": "MEDIUM", "label": "Medium dog (10-25 kg)", "priceMultiplier": 1.2 },
{ "code": "LARGE", "label": "Large dog (over 25 kg)", "priceMultiplier": 1.5 }
]
}'- A list has 1 to 30 entries, kept in the order you send them.
codehas 1 to 16 characters (A-Z,0-9,_, starting with a letter).priceMultiplieris between 0.1 and 10.extraMinutes(0 to 600, default 0) lengthens the work.labelis shown to people and defaults to the code.customerPickabledefaults to true; false means staff only. At least one variant must be pickable.
The new codes are the ones POST /orders and a service's variants accept.
Existing services keep their own prices. The change is effective at once and is
not part of a publish.
Send them with an order
Read the codes from a service's variants (see Quickstart).
Pass the same variant to the start times, so the extra minutes are counted, and
to the order:
curl "https://api.shyni.io/api/v1/availability/starts?date=2026-11-03&serviceIds=$SERVICE_ID&variant=MEDIUM" \
-H "Authorization: Bearer $SHYNI_KEY" \
-H "Shyni-Business: $SHYNI_BUSINESS"curl -X POST https://api.shyni.io/api/v1/orders \
-H "Authorization: Bearer $SHYNI_KEY" \
-H "Shyni-Business: $SHYNI_BUSINESS" \
-H "Idempotency-Key: dog-booking-1" \
-H "Content-Type: application/json" \
-d '{
"serviceIds": ["'"$SERVICE_ID"'"],
"scheduledStart": "2026-11-03T08:00:00Z",
"scheduledEnd": "2026-11-03T09:00:00Z",
"customer": { "name": "Jane Doe", "email": "jane.doe@example.com", "phone": "+1 202 555 0104" },
"variant": "MEDIUM",
"subject": {
"type": "DOG",
"attributes": { "name": "Biscuit", "breed": "Beagle", "weightKg": 11 }
}
}'variant is required and must be one of the service's codes; an unknown code is
400 INVALID_INPUT with field: variant. subject is optional unless the business's
subject type has a required attribute; then 400 INVALID_SUBJECT names it in field.
A publishable key cannot read GET /subject-types, so the form's fields come from your server (secret key with catalog:read) or from your own configuration. The order returns the subject as
subject {id, type, attributes, summary}; here summary reads Biscuit · Beagle.
When the subject does not fit
subject.type is matched case-insensitively. Left out, it means the business's
only type; a business with several types must send it. subject.attributes is
checked against the type:
texttakes a string,numbera number or a numeric string,choiceone of itsoptions,booltrue or false.- A missing required attribute (
nameabove), a value of the wrong type, a choice outside its options or a key the type does not have is refused.
The refusal is 400 INVALID_SUBJECT. field names the attribute key, or
subject.type for an unknown type. Nothing is stored and the Idempotency-Key
is not used up, so fix the body and send it again with the same key.
{
"code": "INVALID_SUBJECT",
"message": "...",
"field": "name"
}Branch on code and field, never on message. See
Errors.
Auto detailing preset
A business of industry AUTO comes with the built-in subject type CAR
(attributes plate, make and model, all optional) and the vehicle-size
variants. It cannot change them: PUT /subject-types answers 409 CONFLICT, and
PUT /catalog/variants must list the existing codes once each. An order for a
car sends "subject": {"attributes": {"plate": "1AB 2345", "make": "Skoda", "model": "Octavia"}}
with the code of the vehicle size as variant.