Orders

Bring a cancelled order back

POST
/api/v1/orders/{id}/revive

Revives a CANCELLED order to CONFIRMED, in its own box and at its own term unless the body says otherwise (placeId, scheduledStart). Only an order the studio or the customer cancelled can be revived; one that lapsed because its deposit was not paid answers 409 CONFLICT (send a new booking link instead), as does an order that is not cancelled. A deposit that had gone back to the customer is NOT_REQUIRED afterwards; a paid one stays PAID; a gift voucher drawn by the order is drawn again as far as its balance reaches. 409 SLOT_UNAVAILABLE: the box (or the chosen placeId) is no longer free at that time. 409 TERM_IN_PAST: the term has passed or starts too soon - send a new scheduledStart. With notifyCustomer true (the default) the customer gets the "your term is valid again" e-mail, unless the order's customer e-mails are suppressed. Emits order.revived. The whole body is optional. 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

The whole body is optional.

placeId?|

A different box (provozovna) when the order's own is not free; it must be free at the term, else 409 SLOT_UNAVAILABLE.

scheduledStart?|

A new start (ISO-8601, UTC) when the booked term has passed or should move; the original start when omitted.

Formatdate-time
notifyCustomer?|

Send the customer the "your term is valid again" e-mail. Defaults to true.

Response Body

The order, now CONFIRMED.

application/json
  1. response
id*string

Internal order id, use this for GET /api/v1/orders/{id}.

publicId*string

Display/reference id shown to the customer, a random 8-digit number without a leading zero (e.g. 48291375), unique per studio; it is also the payment variable symbol. Look an order up by it with GET /api/v1/orders/by-public-id/{publicId}.

state*OrderState
Value in"CREATED""CONFIRMED""IN_PROGRESS""DONE""CANCELLED"
kind?OrderKind

ORDER: an ordinary booking. INSPECTION: the studio's free inspection (price 0, no deposit), booked with inspection: true in POST /orders; finish it with POST /orders/{id}/inspection/complete.

Value in"ORDER""INSPECTION"
depositState*DepositState

PENDING: a deposit above zero is owed and unpaid on a live order. PAID: the deposit was received. REFUNDED: it was given back. NOT_REQUIRED: no deposit is owed, either because the order never had one (deposit 0, free inspection) or because the order was cancelled before the deposit was paid. A cancelled order whose deposit was already PAID or REFUNDED keeps that state, so depositState=PENDING lists only deposits that still have to be collected.

Value in"PENDING""PAID""REFUNDED""NOT_REQUIRED"
currency*string

ISO 4217 code of the studio currency every amount on this order (lines, payments, discount included) is denominated in.

depositAmount*integer

Amount in minor units of currency (hellers for CZK).

Formatint64
depositPaidAt?|
Formatdate-time
scheduledStart*string
Formatdate-time
scheduledEnd*string
Formatdate-time
pickupAt*string
Formatdate-time
createdAt*string
Formatdate-time
updatedAt*string

When the order last changed, state, time, price, lines, payments, assignment. Poll key for GET /api/v1/orders?updatedSince=; of two snapshots of the same order (two webhook deliveries, or a delivery and a read) the one with the later updatedAt is the newer.

Formatdate-time
externalRef*|

Your own id of the order (set by externalRef on POST /orders), unique per studio; find the order with GET /orders?externalRef=. Null when none.

readyAt?|
Formatdate-time
customer*

GDPR erasure sentinel values (e.g. redacted placeholders) pass through as-is post-erasure, v1 does not otherwise redact this data.

variant*string

Code of the variant the order was priced as (for detailing, the vehicle size, e.g. MEDIUM).

subject*|

The subject the order is for; null when the order carries no data about it.

serviceLines*array<>
totalPrice*integer

Amount in minor units of currency (hellers for CZK).

Formatint64
paidTotal*integer

Amount in minor units of currency (hellers for CZK).

Formatint64
balanceDue*integer

Amount in minor units of currency (hellers for CZK). Negative on overpayment.

Formatint64
payments*array<>
appliedDiscountCode?|null
note?string|null
cancellationReason?|

Free text given on cancel, or NO_SHOW when the order was cancelled through POST /orders/{id}/no-show.

sourceChannel*string

Where the order came from. STOREFRONT, EMBED and QR are claimed by the customer's browser; BOOKING_LINK, RETURN_REMINDER, ADMIN and API are decided by the server.

Value in"STOREFRONT""EMBED""QR""BOOKING_LINK""RETURN_REMINDER""ADMIN""API"
sourceLabel?|

Sanitized campaign or integration label ([a-z0-9-], max 40).

utmSource?string|null
utmMedium?string|null
utmCampaign?string|null
referrerDomain?|

Host name of the page that referred the customer, never a full URL.

attribution*

The attribution stored on the order. Every field is null when it was not sent (all orders before this field existed, and orders from the booking page for the click ids and utmTerm / utmContent).

metadata*

The flat string map sent as metadata when the order was created through POST /api/v1/orders; {} when none. Free-form keys, see the request schema for the caps.

billing*|

The company billing details sent as billing when the order was created through POST /api/v1/orders; null when none were sent, and after the customer's data was erased.

cancelledBy?|

Who cancelled the order; HOLD_EXPIRY = the deposit hold elapsed. Null while the order is not cancelled.

Value in"STUDIO""CUSTOMER""HOLD_EXPIRY"null
assignedWorkerId?|

Id of the team member the order is assigned to; null when nobody is.

acceptedTermsVersion?integer

The studio terms version sent as acceptedTermsVersion when the order was created; absent when none was sent.

waivedDayAdjustment?integer

Day-policy surcharge the studio waived on this order (minor units, waiveDayAdjustment on create); absent when nothing was waived.

Formatint64
pendingReschedule?|

The customer's open request to move the order, null when there is none. Its id is the {requestId} of POST /api/v1/orders/{id}/reschedule-requests/{requestId}/approve and .../reject.

rejectedReschedule?|

The customer's newest reschedule request when the studio rejected it (the order never moved), null otherwise; a newer request, an approval or a cancellation replaces it. The window the customer asked for stays readable here, so an integration can tell the customer which time was declined (webhook order.reschedule_rejected).

inspection?|

The studio's verdict on a free inspection (kind INSPECTION); null for an ordinary order. Its reply stays null until the studio answers (POST /api/v1/orders/{id}/inspection/reply).

Example Request
curl -X POST 'https://api.shyni.io/api/v1/orders/string/revive' \  -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 order, now CONFIRMED.

{  "id": "string",  "publicId": "string",  "state": "CREATED",  "kind": "ORDER",  "depositState": "PENDING",  "currency": "string",  "depositAmount": 0,  "depositPaidAt": "2019-08-24T14:15:22Z",  "scheduledStart": "2019-08-24T14:15:22Z",  "scheduledEnd": "2019-08-24T14:15:22Z",  "pickupAt": "2019-08-24T14:15:22Z",  "createdAt": "2019-08-24T14:15:22Z",  "updatedAt": "2019-08-24T14:15:22Z",  "externalRef": "string",  "readyAt": "2019-08-24T14:15:22Z",  "customer": {    "id": "string",    "name": "string",    "email": "user@example.com",    "phone": "string",    "externalRef": "string"  },  "variant": "string",  "subject": {    "id": "string",    "type": "string",    "attributes": {      "property1": "string",      "property2": "string"    },    "summary": "string",    "externalRef": "string"  },  "serviceLines": [    {      "id": "string",      "name": "string",      "price": 0,      "durationMinutes": 0,      "packageHead": true,      "custom": true,      "serviceId": "string",      "packageId": "string",      "optionAnswers": [        {          "questionId": "string",          "optionId": "string"        }      ]    }  ],  "totalPrice": 0,  "paidTotal": 0,  "balanceDue": 0,  "payments": [    {      "id": "string",      "amount": 0,      "method": "string",      "recordedAt": "2019-08-24T14:15:22Z",      "reference": "string"    }  ],  "appliedDiscountCode": {    "code": "string",    "type": "string",    "discount": 0  },  "note": "string",  "cancellationReason": "string",  "sourceChannel": "STOREFRONT",  "sourceLabel": "string",  "utmSource": "string",  "utmMedium": "string",  "utmCampaign": "string",  "referrerDomain": "string",  "attribution": {    "utmSource": "string",    "utmMedium": "string",    "utmCampaign": "string",    "utmTerm": "string",    "utmContent": "string",    "referrer": "string",    "gclid": "string",    "gbraid": "string",    "wbraid": "string",    "fbclid": "string"  },  "metadata": {    "property1": "string",    "property2": "string"  },  "billing": {    "company": "string",    "ico": "string",    "dic": "string",    "address": "string"  },  "cancelledBy": "STUDIO",  "assignedWorkerId": "string",  "acceptedTermsVersion": 0,  "waivedDayAdjustment": 0,  "pendingReschedule": {    "id": "string",    "requestedStart": "2019-08-24T14:15:22Z",    "requestedEnd": "2019-08-24T14:15:22Z",    "createdAt": "2019-08-24T14:15:22Z"  },  "rejectedReschedule": {    "id": "string",    "requestedStart": "2019-08-24T14:15:22Z",    "requestedEnd": "2019-08-24T14:15:22Z",    "reason": "string",    "rejectedAt": "2019-08-24T14:15:22Z"  },  "inspection": {    "reply": "COME",    "text": "string",    "repliedAt": "2019-08-24T14:15:22Z"  }}

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

SLOT_UNAVAILABLE (the box is taken at that time), TERM_IN_PAST (the term is gone or too soon) or CONFLICT (the order is not cancelled, or lapsed on an unpaid deposit).

{  "code": "SLOT_UNAVAILABLE",  "message": "The requested time slot is no longer available."}
{  "code": "TERM_IN_PAST",  "message": "The booked term is in the past or too soon; pick a new scheduledStart."}
{  "code": "CONFLICT",  "message": "This order is not cancelled."}

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."}