Booking Links

Edit a pending booking link in place

PATCH
/api/v1/booking-links/{id}

Changes a PENDING link without issuing a new one: the id, token and url stay the same, so a link the customer already has keeps working. A field left out stays as it is; null clears a nullable one (scheduledStart: null turns the link into an open term and releases the hold). A changed term is held again; if the new slot is taken the answer is 409 and the link keeps its old hold. Every real change raises revision; a customer who opened the page before the edit is asked to look at the new offer before completing it. With notifyCustomer (default true) the customer is mailed the edited offer when something they see changed and the link has an e-mail address (429 when the link was mailed too often in the last hour). A completed, cancelled or expired link is 409 CONFLICT. agreedPrice, depositOverride and customLines need the PRO plan. Requires the orders:manage scope and an Idempotency-Key header.

Authorization

ApiKeyAuth orders:manage
headerAuthorizationBearer <shyni_sk_live_*>

An API key, issued once and never retrievable again: an organization key in the developer portal, or a studio key in the studio's admin (Moje studio → API). Send as Authorization: Bearer shyni_sk_live_....

Scope: orders:manage

Path Parameters

id*string

Header Parameters

Idempotency-Key*string

Caller-chosen idempotency token for this write. A retry under the SAME key with the SAME body replays the first response; the SAME key with a DIFFERENT body is 422 IDEMPOTENCY_KEY_MISMATCH. See Idempotency above.

Lengthlength <= 64

Request Body

application/json
  1. body

Every field is optional. A field left out stays as it is; null clears a nullable one. Lists replace the whole list.

serviceIds?array<string>

Service and/or package ids from GET /api/v1/services; replaces the list.

scheduledStart?|

The held start. null = open term: nothing is held and the customer picks the start.

Formatdate-time
customer?
subject?

The owner's guess of what the customer brings, a prefill for the customer's page. Checked as subject of POST /orders (400 INVALID_SUBJECT).

variant?|

Code of the variant (detailing - the vehicle size), a prefill.

adminNote?|

For the studio only, at most 500 characters.

customerMessage?|

Shown to the customer on the link's page, at most 500 characters.

validity?|

Left out, the expiry stays (an edit never extends it; it is only capped at a new, earlier start). Sent, the link stays open that long from now.

Value in"HOURS_24""HOURS_48""DAYS_7"null
agreedPrice?|

PRO. The price agreed for the whole job, minor units of the studio currency; null returns to the price list.

Formatint64
depositOverride?|

PRO. The deposit for this job, minor units; 0 = none; null returns to the studio default.

Formatint64
optionAnswers?array<>
customLines?array<>

PRO. Lines the owner types in outside the price list; replaces the list.

waiveDayAdjustment?boolean

The order made from this link does not pay the day-policy surcharge.

skipDeposit?boolean

The order made from this link requires no deposit.

notifyCustomer?boolean

Mail the customer the edited offer (same link) when something they see changed and the link has an e-mail.

Defaulttrue

Response Body

The edited link, with its new revision.

application/json
  1. response

A booking link as the studio sees it. url is the customer's page for the link and carries its token: hand it only to the customer. Money is minor units of currency.

id?string
status?string
Value in"PENDING""COMPLETED""EXPIRED""CANCELLED"
url?string
currency?string
serviceIds?array<string>
serviceNames?array<string>

Names against the published catalog, in the order of serviceIds.

optionAnswers?array<>
scheduledStart?|

Null = open term.

Formatdate-time
scheduledEnd?|
Formatdate-time
pickupAt?|
Formatdate-time
customer?
adminNote?string|null
customerMessage?string|null
variant?string|null
subject?|

The owner's prefill of what the customer brings; null when none was given.

agreedPrice?|
Formatint64
depositOverride?|
Formatint64
customLines?array<>
waiveDayAdjustment?boolean

The order made from this link does not pay the day-policy surcharge.

Defaultfalse
skipDeposit?boolean

The order made from this link requires no deposit.

Defaultfalse
inspectionOrderId?string|null
expiresAt?string
Formatdate-time
createdAt?string
Formatdate-time
firstOpenedAt?|
Formatdate-time
openCount?integer
delivery?|null
completedOrderId?|

The order the customer made out of the link.

completedAt?|
Formatdate-time
cancelledAt?|
Formatdate-time
revision?integer

Raised by every real edit (PATCH /booking-links/{id}); the customer's completion is refused when the page they saw is older.

Example Request
curl -X PATCH 'https://api.shyni.io/api/v1/booking-links/string' \  -H 'Authorization: Bearer shyni_sk_test_YOUR_KEY' \  -H 'Idempotency-Key: 7f3c9a52-1d4e-4b86-a0c7-5e2b9d8f6a13' \  -H 'Content-Type: application/json' \  -d '{}'

Response 200

The edited link, with its new revision.

{  "id": "string",  "status": "PENDING",  "url": "string",  "currency": "string",  "serviceIds": [    "string"  ],  "serviceNames": [    "string"  ],  "optionAnswers": [    {      "questionId": "string",      "optionId": "string"    }  ],  "scheduledStart": "2019-08-24T14:15:22Z",  "scheduledEnd": "2019-08-24T14:15:22Z",  "pickupAt": "2019-08-24T14:15:22Z",  "customer": {    "name": "string",    "email": "string",    "phone": "string"  },  "adminNote": "string",  "customerMessage": "string",  "variant": "string",  "subject": {    "type": "string",    "attributes": {      "property1": "string",      "property2": "string"    }  },  "agreedPrice": 0,  "depositOverride": 0,  "customLines": [    {      "name": "string",      "price": 0,      "durationMinutes": 0    }  ],  "waiveDayAdjustment": false,  "skipDeposit": false,  "inspectionOrderId": "string",  "expiresAt": "2019-08-24T14:15:22Z",  "createdAt": "2019-08-24T14:15:22Z",  "firstOpenedAt": "2019-08-24T14:15:22Z",  "openCount": 0,  "delivery": {    "channel": "EMAIL",    "at": "2019-08-24T14:15:22Z",    "to": "string"  },  "completedOrderId": "string",  "completedAt": "2019-08-24T14:15:22Z",  "cancelledAt": "2019-08-24T14:15:22Z",  "revision": 0}

Response 400

A parameter, header or request body is missing, malformed, or out of range. A request body property the endpoint does not define answers UNKNOWN_FIELD instead, with field naming it.

{  "code": "INVALID_INPUT",  "message": "Parameter perPage must be between 1 and 100."}

Response 401

Missing, malformed, unknown, or revoked API key (INVALID_API_KEY).

{  "code": "INVALID_API_KEY",  "message": "Missing or invalid API key."}

Response 403

The key is valid but the request is refused. Branch on code: INSUFFICIENT_SCOPE (the key lacks the permission this endpoint requires), PLAN_REQUIRED (the studio is not on a plan that includes API keys, Pro and above), ORIGIN_NOT_ALLOWED, SECRET_KEY_IN_BROWSER, or DEMO_STUDIO on a write to the demo studio. See Authentication and Scopes above.

{  "code": "INSUFFICIENT_SCOPE",  "message": "This API key does not have the permission this endpoint requires."}

Response 404

The resource does not exist, or belongs to a different studio.

{  "code": "NOT_FOUND",  "message": "Order not found."}

Response 409

The request is well-formed but the studio's current state forbids it.

{  "code": "CONFLICT",  "message": "The voucher cannot be deactivated because it is not active."}

Response 422

The same Idempotency-Key was already used for a request with a different body, use a new key (see Idempotency above).

{  "code": "IDEMPOTENCY_KEY_MISMATCH",  "message": "This Idempotency-Key was already used for a different request. Use a new key."}

Response 429

Burst rate limit exceeded.

{  "code": "RATE_LIMITED",  "message": "Too many requests. Retry after the time given in the Retry-After header."}