Gift vouchers

Issue a gift voucher

POST
/api/v1/gift-vouchers

Issue a gift voucher already paid for on the caller's own system. Lands ACTIVE immediately, carrying the caller's own paidAt, Shyni never re-checks payment for an API-issued voucher, it only records what the caller says already happened. Sends nothing to the buyer unless notifyBuyer: true, and never mails the studio owner (that notice is the admin surface's own "you made a sale" flow, not this one), with one exception: a voucher worth at least the key's alert threshold (default 5 000 CZK) mails the owner at once, naming the key. Each key may issue at most a daily total (default 20 000 CZK per studio calendar day, counted over every voucher it issued that day); above it the call is refused with 422 API_KEY_DAILY_VOUCHER_CAP_EXCEEDED. A replay of an answered Idempotency-Key returns the stored response and is not counted twice. Requires the vouchers:write scope, the studio's PRO tier, and an Idempotency-Key header (see Idempotency above).

Authorization

ApiKeyAuth vouchers:write
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: vouchers:write

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 has a default, even the ones that are conceptually required, so a missing field never produces a shapeless framework error. Instead it is refused as 400 INVALID_INPUT with a message naming the field: missing/blank buyer.email, value outside the allowed range, a missing or more-than-5-minutes- future paidAt, a validUntil already in the past, a billing block without company, or an unknown motif.

value?integer

Amount in minor units of the studio's currency (hellers for CZK), the voucher face value. Allowed range 10000..10000000 (100–100 000 CZK).

Formatint64
buyer?
billing?|null
motif?GiftVoucherMotif

PDF/print motif.

Value in"CLASSIC""INK""LINES""CLEAN"
dedication?|

Printed on the PDF as the buyer's message.

note?|

Internal studio-only note, never rendered.

paidAt?|

When the money already moved on the caller's own system. Required in practice, missing or more than 5 minutes in the future is 400 INVALID_INPUT.

Formatdate-time
validUntil?|

Overrides the default paidAt + 12 months validity. Must not be in the past (the studio's own day).

Formatdate
externalRef?|

The caller's own reference for this voucher, at most 64 characters, unique per studio. A repeat is 409 CONFLICT.

notifyBuyer?boolean

Send the buyer's delivery mail as part of this request. An API-issued voucher never mails anyone unless asked.

Defaultfalse

Response Body

The newly issued voucher.

application/json
  1. response
id*string

Internal voucher id. /refund, /resend and /pdf key on code, not this id.

code*string

The bearer code printed on the voucher and used to redeem it in a booking.

value*integer

Amount in minor units of currency (hellers for CZK), the voucher face value.

Formatint64
remaining*integer

Unspent balance in minor units of currency. An order draws only what it needs; the rest stays on the same code until validUntil. 0 once the voucher is fully used.

Formatint64
currency*string

ISO 4217 code of the studio currency the voucher is denominated in.

state*GiftVoucherState
Value in"CREATED""ACTIVE""USED""DEACTIVATED""PAYMENT_TIMEOUT""CANCELLED""REFUNDED"
issuedVia*GiftVoucherIssuedVia

How the voucher was issued. API-issued vouchers are the only ones this API can refund (see /refund). IMPORT = brought over from another system by the studio data import.

Value in"MANUAL""ONLINE""API""IMPORT"
paidAt?|
Formatdate-time
validUntil*string
Formatdate
buyer*
billing?|null
motif*GiftVoucherMotif

PDF/print motif.

Value in"CLASSIC""INK""LINES""CLEAN"
dedication?string|null
note?string|null
externalRef?string|null
redeemedOrderId?|

Set once the voucher has been applied to an order.

redeemedAt?|
Formatdate-time
redemptions*array<>

Every draw of this voucher that currently counts, oldest first. Draws reversed by a cancelled order are not listed, so the amounts always sum to value - remaining. Empty while nothing was drawn. redeemedOrderId/redeemedAt mirror the last item.

notification?|

The outcome of a delivery mail THIS request asked for, non-null only on an issue with notifyBuyer: true and on /resend. Every GET returns null: Shyni does not store a voucher's delivery state.

createdAt*string
Formatdate-time
Example Request
curl -X POST 'https://api.shyni.io/api/v1/gift-vouchers' \  -H 'Authorization: Bearer shyni_sk_test_YOUR_KEY' \  -H 'Idempotency-Key: 7f3c9a52-1d4e-4b86-a0c7-5e2b9d8f6a13' \  -H 'Content-Type: application/json' \  -d '{}'

Response 201

The newly issued voucher.

{  "id": "string",  "code": "string",  "value": 0,  "remaining": 0,  "currency": "string",  "state": "CREATED",  "issuedVia": "MANUAL",  "paidAt": "2019-08-24T14:15:22Z",  "validUntil": "2019-08-24",  "buyer": {    "name": "string",    "email": "user@example.com"  },  "billing": {    "company": "string",    "ico": "string",    "dic": "string",    "address": "string"  },  "motif": "CLASSIC",  "dedication": "string",  "note": "string",  "externalRef": "string",  "redeemedOrderId": "string",  "redeemedAt": "2019-08-24T14:15:22Z",  "redemptions": [    {      "orderId": "string",      "amount": 0,      "redeemedAt": "2019-08-24T14:15:22Z"    }  ],  "notification": "SENT",  "createdAt": "2019-08-24T14:15:22Z"}

Response 400

A required field is missing or a value is invalid. field names the offending input.

{  "code": "INVALID_INPUT",  "message": "paidAt is required when issuing a voucher through the API.",  "field": "paidAt"}

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 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

Two causes share this status, branch on code: API_KEY_DAILY_VOUCHER_CAP_EXCEEDED (this key's daily voucher cap is used up; nothing was issued) and IDEMPOTENCY_KEY_MISMATCH (the Idempotency-Key was already used for a different body, use a new key, see Idempotency above).

{  "code": "API_KEY_DAILY_VOUCHER_CAP_EXCEEDED",  "message": "This API key has issued vouchers up to its daily cap. Retry tomorrow or ask the studio owner to raise the cap."}
{  "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."}