{
  "components" : {
    "schemas" : {
      "Brand" : {
        "properties" : {
          "id" : {
            "description" : "The brandId to name in a request",
            "format" : "uuid",
            "type" : "string"
          },
          "name" : {
            "example" : "Example Brand",
            "type" : "string"
          }
        },
        "required" : [ "id", "name" ],
        "type" : "object"
      },
      "Campaign" : {
        "properties" : {
          "brandIds" : {
            "description" : "The campaign's brands that the key can use. A personal link is for one of them. If there is more than one, name it as brandId",
            "items" : {
              "format" : "uuid",
              "type" : "string"
            },
            "type" : "array"
          },
          "expiresAt" : {
            "description" : "When the campaign completes on its own, and its open links expire with it; null when it never expires",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "id" : {
            "format" : "uuid",
            "type" : "string"
          },
          "name" : {
            "example" : "VIP birthdays",
            "type" : "string"
          },
          "status" : {
            "enum" : [ "ACTIVE", "COMPLETED" ],
            "type" : "string"
          }
        },
        "required" : [ "brandIds", "expiresAt", "id", "name", "status" ],
        "type" : "object"
      },
      "CampaignPage" : {
        "properties" : {
          "items" : {
            "items" : {
              "$ref" : "#/components/schemas/Campaign"
            },
            "type" : "array"
          },
          "nextCursor" : {
            "description" : "Pass as `after` for the next page; null on the last page",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "items", "nextCursor" ],
        "type" : "object"
      },
      "CancelRequest" : {
        "properties" : {
          "reason" : {
            "description" : "Why you cancel the link. Never shown to the player",
            "example" : "Player self-excluded",
            "maxLength" : 1000,
            "type" : "string"
          }
        },
        "required" : [ "reason" ],
        "type" : "object"
      },
      "Currency" : {
        "properties" : {
          "name" : {
            "type" : "string"
          },
          "symbol" : {
            "type" : "string"
          }
        },
        "required" : [ "name", "symbol" ],
        "type" : "object"
      },
      "CustomFieldAnswer" : {
        "properties" : {
          "checked" : {
            "description" : "The answer to a CHECKBOX field; null for any other type",
            "type" : [ "boolean", "null" ]
          },
          "fieldId" : {
            "description" : "The field's id: tenant.customFields[].id in GET /me",
            "format" : "uuid",
            "type" : "string"
          },
          "textValue" : {
            "description" : "The answer to a TEXT or DROPDOWN field; null for a CHECKBOX, or if unanswered",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "checked", "fieldId", "textValue" ],
        "type" : "object"
      },
      "Event" : {
        "description" : "One event: a PersonalLinkEvent for personal_link.*, a RewardStoreOrderEvent for order.*. Branch on `type`",
        "discriminator" : {
          "mapping" : {
            "order.approved" : "#/components/schemas/RewardStoreOrderEvent",
            "order.cancelled" : "#/components/schemas/RewardStoreOrderEvent",
            "order.completed" : "#/components/schemas/RewardStoreOrderEvent",
            "order.sent" : "#/components/schemas/RewardStoreOrderEvent",
            "personal_link.approved" : "#/components/schemas/PersonalLinkEvent",
            "personal_link.cancelled" : "#/components/schemas/PersonalLinkEvent",
            "personal_link.claimed" : "#/components/schemas/PersonalLinkEvent",
            "personal_link.created" : "#/components/schemas/PersonalLinkEvent",
            "personal_link.expired" : "#/components/schemas/PersonalLinkEvent"
          },
          "propertyName" : "type"
        },
        "oneOf" : [ {
          "$ref" : "#/components/schemas/PersonalLinkEvent"
        }, {
          "$ref" : "#/components/schemas/RewardStoreOrderEvent"
        } ]
      },
      "EventPage" : {
        "properties" : {
          "items" : {
            "items" : {
              "$ref" : "#/components/schemas/Event"
            },
            "type" : "array"
          },
          "nextCursor" : {
            "description" : "Pass as `after` to read on; returned on every page, the last one included. Null only when the feed is empty and no `after` was given",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "items", "nextCursor" ],
        "type" : "object"
      },
      "Image" : {
        "description" : "An image in every width it is available in. Each address is a complete URL that opens with no API key. Use it as given",
        "properties" : {
          "url" : {
            "description" : "The widest version",
            "type" : "string"
          },
          "variants" : {
            "description" : "Every width, narrowest first, for srcset",
            "items" : {
              "$ref" : "#/components/schemas/ImageVariant"
            },
            "type" : "array"
          }
        },
        "required" : [ "url", "variants" ],
        "type" : "object"
      },
      "ImageVariant" : {
        "properties" : {
          "url" : {
            "description" : "The image at that width",
            "type" : "string"
          },
          "width" : {
            "description" : "The width in pixels",
            "format" : "int32",
            "type" : "integer"
          }
        },
        "required" : [ "url", "width" ],
        "type" : "object"
      },
      "Key" : {
        "properties" : {
          "name" : {
            "description" : "The key's name, as your team sees it",
            "example" : "CRM journeys",
            "type" : "string"
          },
          "scopes" : {
            "description" : "What the key may do, at least one of: PERSONAL_LINKS lets it read campaigns and send personal links; REWARD_STORES lets it read rewards stores and place store orders",
            "items" : {
              "enum" : [ "PERSONAL_LINKS", "REWARD_STORES" ],
              "type" : "string"
            },
            "type" : "array"
          },
          "webhookUrl" : {
            "description" : "Where the key's events are sent; null when none is set",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "name", "scopes", "webhookUrl" ],
        "type" : "object"
      },
      "Me" : {
        "properties" : {
          "brands" : {
            "description" : "The brands the key may use. A key limited to some brands sees only those brands",
            "items" : {
              "$ref" : "#/components/schemas/Brand"
            },
            "type" : "array"
          },
          "environment" : {
            "description" : "The deployment that answered",
            "enum" : [ "production" ],
            "type" : "string"
          },
          "key" : {
            "$ref" : "#/components/schemas/Key"
          },
          "tenant" : {
            "$ref" : "#/components/schemas/Tenant"
          }
        },
        "required" : [ "brands", "environment", "key", "tenant" ],
        "type" : "object"
      },
      "PersonalLink" : {
        "properties" : {
          "approvedAt" : {
            "description" : "When your team approved a link that started REQUESTED; null for a link that started ACTIVE, because your account requires no approval",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "brandId" : {
            "description" : "The brand the claim page shows",
            "format" : "uuid",
            "type" : "string"
          },
          "campaignId" : {
            "description" : "The campaign whose gifts the player chooses from; null for a link your team sent with gifts picked from the catalog",
            "format" : "uuid",
            "type" : [ "string", "null" ]
          },
          "cancelledAt" : {
            "description" : "When the link was canceled; null unless CANCELLED",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "claimUrl" : {
            "description" : "The link to send to the player",
            "type" : "string"
          },
          "claimedAt" : {
            "description" : "When the player claimed it; null until CLAIMED",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "createdAt" : {
            "description" : "When the link was created",
            "format" : "date-time",
            "type" : "string"
          },
          "customFields" : {
            "description" : "The answers to your own order fields, one per field the link holds, whoever created it. To match a link to your player, use the field you fill with your own player ID",
            "items" : {
              "$ref" : "#/components/schemas/CustomFieldAnswer"
            },
            "type" : "array"
          },
          "expiresAt" : {
            "description" : "When the link expires with its campaign, unless it is claimed or canceled first; null if the campaign never expires",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "id" : {
            "description" : "Luxorr's ID of the link",
            "format" : "uuid",
            "type" : "string"
          },
          "selection" : {
            "description" : "CATALOG for a link your team sent with gifts picked from the catalog; null for a campaign link",
            "enum" : [ "CATALOG", null ],
            "type" : [ "string", "null" ]
          },
          "status" : {
            "description" : "Where the link is; see the personal links guide",
            "enum" : [ "REQUESTED", "ACTIVE", "CLAIMED", "EXPIRED", "CANCELLED" ],
            "type" : "string"
          },
          "updatedAt" : {
            "description" : "When the link last changed",
            "format" : "date-time",
            "type" : "string"
          }
        },
        "required" : [ "approvedAt", "brandId", "campaignId", "cancelledAt", "claimUrl", "claimedAt", "createdAt", "customFields", "expiresAt", "id", "selection", "status", "updatedAt" ],
        "type" : "object"
      },
      "PersonalLinkCreateRequest" : {
        "properties" : {
          "brandId" : {
            "description" : "The brand the link is for: one of the campaign's brandIds. The claim URL and page show this brand. Required if the key can use more than one of the campaign's brands; if it can use exactly one, that brand is used",
            "format" : "uuid",
            "type" : "string"
          },
          "campaignId" : {
            "description" : "The campaign whose gifts the player chooses from. GET /campaigns lists the campaigns that accept links",
            "format" : "uuid",
            "type" : "string"
          },
          "customFields" : {
            "description" : "Optional. Answers to your own order fields, which GET /me lists as tenant.customFields. A required field must be answered. An answer whose fieldId matches none of your fields is ignored",
            "items" : {
              "$ref" : "#/components/schemas/PersonalLinkCustomFieldAnswer"
            },
            "type" : "array"
          },
          "email" : {
            "description" : "Optional. The player's email. After the player claims the link, Luxorr sends the order's status updates to it",
            "example" : "alice@example.com",
            "format" : "email",
            "maxLength" : 255,
            "type" : "string"
          },
          "fullName" : {
            "description" : "The player's name. Your team sees the link and its order under this name, and the claim page greets the player with it",
            "example" : "Alice Smith",
            "maxLength" : 255,
            "type" : "string"
          },
          "locale" : {
            "description" : "Optional. The language of the claim page: one of tenant.locales in GET /me; en-US when absent",
            "example" : "de-DE",
            "type" : "string"
          },
          "message" : {
            "description" : "Optional. A personal note shown on the claim page",
            "example" : "Happy birthday!",
            "maxLength" : 255,
            "type" : "string"
          }
        },
        "required" : [ "campaignId", "fullName" ],
        "type" : "object"
      },
      "PersonalLinkCustomFieldAnswer" : {
        "properties" : {
          "checked" : {
            "description" : "The answer to a CHECKBOX field",
            "type" : "boolean"
          },
          "fieldId" : {
            "description" : "The field's id: tenant.customFields[].id in GET /me",
            "format" : "uuid",
            "type" : "string"
          },
          "textValue" : {
            "description" : "The answer to a TEXT field, or one of a DROPDOWN field's options",
            "example" : "player-1001",
            "maxLength" : 1000,
            "type" : "string"
          }
        },
        "required" : [ "fieldId" ],
        "type" : "object"
      },
      "PersonalLinkEvent" : {
        "description" : "A change to a personal link. Needs the PERSONAL_LINKS scope",
        "properties" : {
          "data" : {
            "$ref" : "#/components/schemas/PersonalLink",
            "description" : "The PersonalLink as the event left it"
          },
          "environment" : {
            "description" : "The deployment that sent the event",
            "enum" : [ "production" ],
            "type" : "string"
          },
          "id" : {
            "format" : "uuid",
            "type" : "string"
          },
          "occurredAt" : {
            "format" : "date-time",
            "type" : "string"
          },
          "type" : {
            "description" : "What happened",
            "enum" : [ "personal_link.created", "personal_link.approved", "personal_link.claimed", "personal_link.cancelled", "personal_link.expired" ],
            "type" : "string"
          }
        },
        "required" : [ "data", "environment", "id", "occurredAt", "type" ],
        "type" : "object"
      },
      "PersonalLinkPage" : {
        "properties" : {
          "items" : {
            "items" : {
              "$ref" : "#/components/schemas/PersonalLink"
            },
            "type" : "array"
          },
          "nextCursor" : {
            "description" : "Pass as `after` for the next page; null on the last page",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "items", "nextCursor" ],
        "type" : "object"
      },
      "ProblemDetail" : {
        "description" : "An `application/problem+json` error. Branch on `code`; `detail` is for people.",
        "properties" : {
          "code" : {
            "description" : "A stable code in snake_case. Branch on it",
            "example" : "not_cancellable",
            "type" : "string"
          },
          "coinPrice" : {
            "description" : "On `price_changed`: the gift's current price in the delivery country, as a decimal string; show it to the player and order again",
            "example" : "480",
            "type" : "string"
          },
          "currentStatus" : {
            "description" : "On `not_cancellable`: the link's status now",
            "enum" : [ "REQUESTED", "ACTIVE", "CLAIMED", "EXPIRED", "CANCELLED" ],
            "type" : "string"
          },
          "detail" : {
            "description" : "This occurrence, in English",
            "type" : "string"
          },
          "errors" : {
            "description" : "Validation failures, one per field",
            "items" : {
              "properties" : {
                "code" : {
                  "example" : "required",
                  "type" : "string"
                },
                "field" : {
                  "example" : "recipient.phone",
                  "type" : "string"
                },
                "message" : {
                  "type" : "string"
                }
              },
              "type" : "object"
            },
            "type" : "array"
          },
          "reason" : {
            "description" : "On `not_cancellable`: `created_elsewhere` if the link was created in your workspace or with another key. On `gift_not_available`: why the gift can't be ordered from this store, under this brand, to this country",
            "enum" : [ "created_elsewhere", "NOT_IN_STORE", "NOT_PUBLISHED", "NOT_USABLE_BY_BRAND", "NOT_SOLD_IN_COUNTRY", "NOT_AVAILABLE_NOW", "NO_SELECTABLE_COMBINATION" ],
            "type" : "string"
          },
          "status" : {
            "description" : "The HTTP status",
            "format" : "int32",
            "type" : "integer"
          },
          "title" : {
            "description" : "A short label for the code",
            "type" : "string"
          },
          "type" : {
            "description" : "Links to the code on the docs site",
            "format" : "uri",
            "type" : "string"
          }
        },
        "required" : [ "code", "status", "title", "type" ],
        "type" : "object"
      },
      "RewardStore" : {
        "properties" : {
          "banner" : {
            "anyOf" : [ {
              "$ref" : "#/components/schemas/Image"
            }, {
              "type" : "null"
            } ],
            "description" : "The store's banner; null if none is set"
          },
          "brand" : {
            "$ref" : "#/components/schemas/RewardStoreBrand",
            "description" : "The brand the store was read for"
          },
          "country" : {
            "description" : "The country the store was read for",
            "example" : "DE",
            "type" : "string"
          },
          "currency" : {
            "$ref" : "#/components/schemas/RewardStoreCurrency",
            "description" : "The brand's bonus currency in this store. Every coinPrice is in this currency"
          },
          "description" : {
            "description" : "The store's description, as your team wrote it. Plain text; keep its line breaks. Null if none is set",
            "type" : [ "string", "null" ]
          },
          "endsAt" : {
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "id" : {
            "description" : "The store's id",
            "format" : "uuid",
            "type" : "string"
          },
          "locale" : {
            "description" : "The language the store was read in",
            "example" : "de-DE",
            "type" : "string"
          },
          "name" : {
            "description" : "The store's name",
            "example" : "VIP shop",
            "type" : "string"
          },
          "phase" : {
            "enum" : [ "SCHEDULED", "LIVE" ],
            "type" : "string"
          },
          "sections" : {
            "description" : "The store's sections in display order, each with the gifts a player in this country can order now. A section with no such gift is left out",
            "items" : {
              "$ref" : "#/components/schemas/RewardStoreSection"
            },
            "type" : "array"
          },
          "startsAt" : {
            "format" : "date-time",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "banner", "brand", "country", "currency", "description", "endsAt", "id", "locale", "name", "phase", "sections", "startsAt" ],
        "type" : "object"
      },
      "RewardStoreBrand" : {
        "properties" : {
          "id" : {
            "format" : "uuid",
            "type" : "string"
          },
          "name" : {
            "example" : "Example Brand",
            "type" : "string"
          }
        },
        "required" : [ "id", "name" ],
        "type" : "object"
      },
      "RewardStoreCurrency" : {
        "properties" : {
          "name" : {
            "description" : "The currency's name, translated if the store has a translation",
            "example" : "Coins",
            "type" : "string"
          },
          "symbol" : {
            "description" : "The currency's symbol, translated if the store has a translation",
            "example" : "C",
            "type" : "string"
          }
        },
        "required" : [ "name", "symbol" ],
        "type" : "object"
      },
      "RewardStoreOrder" : {
        "properties" : {
          "brandId" : {
            "description" : "The brand it was ordered under",
            "format" : "uuid",
            "type" : "string"
          },
          "coinPrice" : {
            "description" : "The price the order was placed at, in the store's currency, as a decimal string. Refund this amount on order.cancelled",
            "type" : "string"
          },
          "createdAt" : {
            "description" : "When the order was placed",
            "format" : "date-time",
            "type" : "string"
          },
          "currency" : {
            "$ref" : "#/components/schemas/Currency",
            "description" : "The store's currency for the brand, as it stood when the order was placed"
          },
          "customFields" : {
            "description" : "The answers to your own order fields, one per field the order holds",
            "items" : {
              "$ref" : "#/components/schemas/CustomFieldAnswer"
            },
            "type" : "array"
          },
          "deliveryCountry" : {
            "description" : "The country it ships to, ISO 3166-1 alpha-2",
            "type" : "string"
          },
          "id" : {
            "description" : "Luxorr's ID of the order: the orderId to refund once on order.cancelled",
            "format" : "uuid",
            "type" : "string"
          },
          "productId" : {
            "description" : "The gift ordered",
            "format" : "uuid",
            "type" : "string"
          },
          "productName" : {
            "description" : "The gift's name in the order's locale, or in en-US where it has no translation",
            "type" : "string"
          },
          "reference" : {
            "description" : "The order's reference, as your team and Luxorr see it",
            "type" : "string"
          },
          "shipment" : {
            "anyOf" : [ {
              "$ref" : "#/components/schemas/Shipment"
            }, {
              "type" : "null"
            } ],
            "description" : "The carrier and tracking details, once the order ships; null until then"
          },
          "status" : {
            "description" : "Where the order is; see the rewards stores guide",
            "enum" : [ "REQUESTED", "PROCESSING", "SENT", "COMPLETED", "CANCELLED" ],
            "type" : "string"
          },
          "storeId" : {
            "description" : "The rewards store it was ordered from",
            "format" : "uuid",
            "type" : "string"
          },
          "updatedAt" : {
            "description" : "When the order last changed",
            "format" : "date-time",
            "type" : "string"
          },
          "variations" : {
            "additionalProperties" : {
              "type" : "string"
            },
            "description" : "The combination ordered, in canonical values; {} if the gift has none",
            "type" : "object"
          }
        },
        "required" : [ "brandId", "coinPrice", "createdAt", "currency", "customFields", "deliveryCountry", "id", "productId", "productName", "reference", "shipment", "status", "storeId", "updatedAt", "variations" ],
        "type" : "object"
      },
      "RewardStoreOrderAddress" : {
        "properties" : {
          "city" : {
            "example" : "Berlin",
            "maxLength" : 100,
            "type" : "string"
          },
          "country" : {
            "description" : "ISO 3166-1 alpha-2. The coin price and the gift's availability are checked for this country",
            "example" : "DE",
            "type" : "string"
          },
          "postalCode" : {
            "description" : "Required where the country uses postal codes",
            "example" : "10115",
            "maxLength" : 20,
            "type" : "string"
          },
          "street" : {
            "example" : "Example Street 1",
            "maxLength" : 255,
            "type" : "string"
          }
        },
        "required" : [ "city", "country", "street" ],
        "type" : "object"
      },
      "RewardStoreOrderCreateRequest" : {
        "properties" : {
          "additionalDetails" : {
            "description" : "Optional. A note about the gift for Luxorr",
            "example" : "Gift-wrap, please",
            "maxLength" : 2000,
            "type" : "string"
          },
          "brandId" : {
            "description" : "The player's brand: the brandId the store was read for",
            "format" : "uuid",
            "type" : "string"
          },
          "coinPrice" : {
            "description" : "The coinPrice the player saw, as a decimal string. If the gift now costs something else in the delivery country, the call returns price_changed",
            "example" : "450",
            "type" : "string"
          },
          "customFields" : {
            "description" : "Optional. Answers to your own order fields, which GET /me lists as tenant.customFields. A required field must be answered. An answer whose fieldId matches none of your fields is ignored",
            "items" : {
              "$ref" : "#/components/schemas/RewardStoreOrderCustomFieldAnswer"
            },
            "type" : "array"
          },
          "locale" : {
            "description" : "Optional. The player's language: one of tenant.locales in GET /me; en-US if absent. Luxorr writes its status emails to recipient.email in this language, and the order's productName uses it",
            "example" : "de-DE",
            "type" : "string"
          },
          "productId" : {
            "description" : "The gift: a productId from the store's sections",
            "format" : "uuid",
            "type" : "string"
          },
          "recipient" : {
            "$ref" : "#/components/schemas/RewardStoreOrderRecipient",
            "description" : "Who the gift is for and where it ships"
          },
          "storeId" : {
            "description" : "The store's id",
            "format" : "uuid",
            "type" : "string"
          },
          "variations" : {
            "additionalProperties" : {
              "type" : "string"
            },
            "description" : "The combination the player picked, one canonical value per dimension, exactly as in the gift's combinations; {} if the gift has none",
            "example" : {
              "Size" : "Large"
            },
            "type" : "object"
          }
        },
        "required" : [ "brandId", "coinPrice", "productId", "recipient", "storeId" ],
        "type" : "object"
      },
      "RewardStoreOrderCustomFieldAnswer" : {
        "properties" : {
          "checked" : {
            "description" : "The answer to a CHECKBOX field",
            "type" : "boolean"
          },
          "fieldId" : {
            "description" : "The field's id: tenant.customFields[].id in GET /me",
            "format" : "uuid",
            "type" : "string"
          },
          "textValue" : {
            "description" : "The answer to a TEXT field, or one of a DROPDOWN field's options",
            "example" : "player-1001",
            "maxLength" : 1000,
            "type" : "string"
          }
        },
        "required" : [ "fieldId" ],
        "type" : "object"
      },
      "RewardStoreOrderEvent" : {
        "description" : "A change to a store order this key placed. Needs the REWARD_STORES scope",
        "properties" : {
          "data" : {
            "$ref" : "#/components/schemas/RewardStoreOrder",
            "description" : "The RewardStoreOrder as the event left it"
          },
          "environment" : {
            "description" : "The deployment that sent the event",
            "enum" : [ "production" ],
            "type" : "string"
          },
          "id" : {
            "format" : "uuid",
            "type" : "string"
          },
          "occurredAt" : {
            "format" : "date-time",
            "type" : "string"
          },
          "type" : {
            "description" : "What happened",
            "enum" : [ "order.approved", "order.cancelled", "order.sent", "order.completed" ],
            "type" : "string"
          }
        },
        "required" : [ "data", "environment", "id", "occurredAt", "type" ],
        "type" : "object"
      },
      "RewardStoreOrderPage" : {
        "properties" : {
          "items" : {
            "items" : {
              "$ref" : "#/components/schemas/RewardStoreOrder"
            },
            "type" : "array"
          },
          "nextCursor" : {
            "description" : "Pass as `after` for the next page; null on the last page",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "items", "nextCursor" ],
        "type" : "object"
      },
      "RewardStoreOrderRecipient" : {
        "properties" : {
          "address" : {
            "$ref" : "#/components/schemas/RewardStoreOrderAddress"
          },
          "email" : {
            "description" : "Optional. Luxorr sends the order's status updates to it, in the order's locale",
            "example" : "alice@example.com",
            "format" : "email",
            "maxLength" : 255,
            "type" : "string"
          },
          "name" : {
            "example" : "Alice Smith",
            "maxLength" : 100,
            "type" : "string"
          },
          "phone" : {
            "description" : "An E.164 number, for the courier",
            "example" : "+4930123456",
            "type" : "string"
          }
        },
        "required" : [ "address", "name", "phone" ],
        "type" : "object"
      },
      "RewardStorePage" : {
        "properties" : {
          "items" : {
            "items" : {
              "$ref" : "#/components/schemas/RewardStoreSummary"
            },
            "type" : "array"
          },
          "nextCursor" : {
            "description" : "Pass as `after` for the next page; null on the last page",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "items", "nextCursor" ],
        "type" : "object"
      },
      "RewardStoreSection" : {
        "properties" : {
          "id" : {
            "format" : "uuid",
            "type" : "string"
          },
          "items" : {
            "items" : {
              "$ref" : "#/components/schemas/StoreGift"
            },
            "type" : "array"
          },
          "name" : {
            "example" : "Popular",
            "type" : "string"
          }
        },
        "required" : [ "id", "items", "name" ],
        "type" : "object"
      },
      "RewardStoreSummary" : {
        "properties" : {
          "brandIds" : {
            "description" : "The store's brands that this key can use. Read the store for one",
            "items" : {
              "format" : "uuid",
              "type" : "string"
            },
            "type" : "array"
          },
          "endsAt" : {
            "description" : "When the store closes; null if it has no end",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          },
          "id" : {
            "description" : "The store's id",
            "format" : "uuid",
            "type" : "string"
          },
          "name" : {
            "description" : "The store's name, as your team sees it",
            "example" : "VIP shop",
            "type" : "string"
          },
          "phase" : {
            "description" : "LIVE: takes orders now. SCHEDULED: opens at startsAt",
            "enum" : [ "SCHEDULED", "LIVE" ],
            "type" : "string"
          },
          "startsAt" : {
            "description" : "When the store opens; null if it is open from the start",
            "format" : "date-time",
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "brandIds", "endsAt", "id", "name", "phase", "startsAt" ],
        "type" : "object"
      },
      "Shipment" : {
        "properties" : {
          "carrier" : {
            "type" : [ "string", "null" ]
          },
          "trackingReference" : {
            "type" : [ "string", "null" ]
          },
          "trackingUrl" : {
            "type" : [ "string", "null" ]
          }
        },
        "required" : [ "carrier", "trackingReference", "trackingUrl" ],
        "type" : "object"
      },
      "StoreGift" : {
        "properties" : {
          "coinPrice" : {
            "description" : "The price in the store's currency for the store's brand and country, as a decimal string. One price covers every combination. Send it back as the order's coinPrice",
            "example" : "450",
            "type" : "string"
          },
          "combinations" : {
            "description" : "Every combination a player can order now, each at the gift's coinPrice. Empty if the gift has no options; order it with variations {}",
            "items" : {
              "$ref" : "#/components/schemas/StoreGiftCombination"
            },
            "type" : "array"
          },
          "description" : {
            "description" : "Plain text; keep its line breaks. Null if there is none",
            "type" : [ "string", "null" ]
          },
          "dimensions" : {
            "description" : "The options a player picks, such as size or color, with the values that some combination offers; empty if the gift has none",
            "items" : {
              "$ref" : "#/components/schemas/StoreGiftDimension"
            },
            "type" : "array"
          },
          "gallery" : {
            "description" : "The gift's further images, in display order; the thumbnail is not among them",
            "items" : {
              "$ref" : "#/components/schemas/Image"
            },
            "type" : "array"
          },
          "name" : {
            "example" : "Leather weekender",
            "type" : "string"
          },
          "productId" : {
            "description" : "The gift's id: the productId of an order",
            "format" : "uuid",
            "type" : "string"
          },
          "thumbnail" : {
            "anyOf" : [ {
              "$ref" : "#/components/schemas/Image"
            }, {
              "type" : "null"
            } ],
            "description" : "The gift's main image; null if it has none"
          }
        },
        "required" : [ "coinPrice", "combinations", "description", "dimensions", "gallery", "name", "productId", "thumbnail" ],
        "type" : "object"
      },
      "StoreGiftCombination" : {
        "properties" : {
          "variations" : {
            "additionalProperties" : {
              "type" : "string"
            },
            "description" : "One canonical value per dimension, as an order's variations takes them",
            "example" : {
              "Size" : "Large"
            },
            "type" : "object"
          }
        },
        "required" : [ "variations" ],
        "type" : "object"
      },
      "StoreGiftDimension" : {
        "properties" : {
          "label" : {
            "description" : "The name to show, translated if the gift has a translation",
            "example" : "Größe",
            "type" : "string"
          },
          "name" : {
            "description" : "The canonical name, the key in variations",
            "example" : "Size",
            "type" : "string"
          },
          "values" : {
            "items" : {
              "$ref" : "#/components/schemas/StoreGiftDimensionValue"
            },
            "type" : "array"
          }
        },
        "required" : [ "label", "name", "values" ],
        "type" : "object"
      },
      "StoreGiftDimensionValue" : {
        "properties" : {
          "label" : {
            "description" : "The value to show",
            "example" : "Groß",
            "type" : "string"
          },
          "value" : {
            "description" : "The canonical value, the value in variations",
            "example" : "Large",
            "type" : "string"
          }
        },
        "required" : [ "label", "value" ],
        "type" : "object"
      },
      "Tenant" : {
        "properties" : {
          "approvalRequired" : {
            "description" : "If true, new personal links and store orders start as REQUESTED and wait for your team's approval",
            "type" : "boolean"
          },
          "customFields" : {
            "description" : "Your own order fields, in display order. Personal links and store orders answer them",
            "items" : {
              "$ref" : "#/components/schemas/TenantCustomField"
            },
            "type" : "array"
          },
          "id" : {
            "description" : "Your account's ID. Quote it when you contact Luxorr",
            "format" : "uuid",
            "type" : "string"
          },
          "locales" : {
            "description" : "The languages that personal links, store reads, and store orders accept: en-US, and the languages enabled for your account",
            "example" : [ "en-US", "de-DE" ],
            "items" : {
              "type" : "string"
            },
            "type" : "array"
          }
        },
        "required" : [ "approvalRequired", "customFields", "id", "locales" ],
        "type" : "object"
      },
      "TenantCustomField" : {
        "properties" : {
          "id" : {
            "description" : "The fieldId to use in an answer",
            "format" : "uuid",
            "type" : "string"
          },
          "label" : {
            "type" : "string"
          },
          "options" : {
            "description" : "The values of a DROPDOWN field; empty for any other type",
            "items" : {
              "type" : "string"
            },
            "type" : "array"
          },
          "required" : {
            "description" : "If true, a personal link or a store order must answer this field",
            "type" : "boolean"
          },
          "type" : {
            "enum" : [ "TEXT", "CHECKBOX", "DROPDOWN" ],
            "type" : "string"
          }
        },
        "required" : [ "id", "label", "options", "required", "type" ],
        "type" : "object"
      },
      "WebhookTestResult" : {
        "properties" : {
          "error" : {
            "type" : [ "string", "null" ]
          },
          "responseStatus" : {
            "format" : "int32",
            "type" : [ "integer", "null" ]
          }
        },
        "required" : [ "error", "responseStatus" ],
        "type" : "object"
      }
    },
    "securitySchemes" : {
      "apiKey" : {
        "bearerFormat" : "lxr_live_<random>",
        "description" : "The API key, sent as `Authorization: Bearer <key>`. A missing, malformed, unknown, or revoked key returns 401 `invalid_api_key`. A key without the scope a call needs returns 403 `insufficient_scope`.",
        "scheme" : "bearer",
        "type" : "http"
      }
    }
  },
  "info" : {
    "description" : "Personal gift links, rewards stores, and signed events for operators, platforms, and CRMs. Authenticate every call with `Authorization: Bearer <key>`. A POST that creates something needs an `Idempotency-Key` header. Guides: https://docs.luxorr.io",
    "title" : "Luxorr API",
    "version" : "v1"
  },
  "openapi" : "3.1.0",
  "paths" : {
    "/campaigns" : {
      "get" : {
        "description" : "The key's campaigns that accept links (ACTIVE), paged by name: the campaignId a personal link is created for. A campaign past its expiresAt is left out, because it no longer accepts links. To look up the campaign of an older link, pass status=COMPLETED. Campaigns that are still being prepared are never listed.",
        "operationId" : "listCampaigns",
        "parameters" : [ {
          "description" : "Only this brand's campaigns",
          "in" : "query",
          "name" : "brandId",
          "required" : false,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "ACTIVE or COMPLETED; ACTIVE when absent. Only an ACTIVE campaign accepts new links",
          "in" : "query",
          "name" : "status",
          "required" : false,
          "schema" : {
            "enum" : [ "ACTIVE", "COMPLETED" ],
            "type" : "string"
          }
        }, {
          "description" : "The nextCursor of the previous page",
          "in" : "query",
          "name" : "after",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Page size, 1 to 100; 25 when absent",
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "default" : 25,
            "maximum" : 100,
            "minimum" : 1,
            "type" : "integer"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/CampaignPage"
                }
              }
            },
            "description" : "A page of campaigns"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: an unreadable brandId, status, limit or cursor; errors[].field names it"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the PERSONAL_LINKS scope"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "List campaigns",
        "tags" : [ "Campaigns" ]
      }
    },
    "/events" : {
      "get" : {
        "description" : "Returns your events in order: personal_link.* when a link is created, approved, claimed, canceled, or expired, for a key with the PERSONAL_LINKS scope; and order.* when a store order this key placed is approved, canceled, sent, or completed, for a key with the REWARD_STORES scope. Each item is the same Event a webhook delivers. The feed also holds the changes this key made itself, which are never sent to it as webhooks. Use the feed to sync changes; use GET /personal-links and GET /reward-store-orders to browse and backfill. To resume, pass the previous page's `nextCursor` as `after`: you get every later event, and none is skipped. `nextCursor` is returned on every page, the last one included, so you keep polling from it once you have caught up. It is null only if the feed is empty and you sent no `after`. The key's scopes and brands filter the feed, and `ping` events never appear. Events are kept for 30 days. If `limit` or `after` is invalid, the call returns 400 `invalid_request`.",
        "operationId" : "listEvents",
        "parameters" : [ {
          "description" : "The `nextCursor` of the previous page",
          "in" : "query",
          "name" : "after",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Page size, 1 to 100; 25 by default",
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "default" : 25,
            "maximum" : 100,
            "minimum" : 1,
            "type" : "integer"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "example" : {
                  "items" : [ {
                    "data" : {
                      "approvedAt" : null,
                      "brandId" : "2b7c9e10-4f3a-4b6d-9c8e-1d2e3f4a5b6c",
                      "campaignId" : "6a0d2f4e-3b1c-4d5e-8f70-9a1b2c3d4e5f",
                      "cancelledAt" : null,
                      "claimUrl" : "https://gifts.example/c/example-brand/Zm9vYmFy",
                      "claimedAt" : "2026-10-05T09:12:44Z",
                      "createdAt" : "2026-10-01T08:00:00Z",
                      "customFields" : [ {
                        "checked" : null,
                        "fieldId" : "7e3a1c55-2b9d-4e8f-a1c0-5d6e7f8a9b0c",
                        "textValue" : "player-123"
                      } ],
                      "expiresAt" : null,
                      "id" : "4c1f9a52-8d0e-4f4b-9a3e-1f2d3c4b5a69",
                      "selection" : null,
                      "status" : "CLAIMED",
                      "updatedAt" : "2026-10-05T09:12:44Z"
                    },
                    "environment" : "production",
                    "id" : "0b7e5f3c-2a1d-4c6e-9f8a-7b6c5d4e3f21",
                    "occurredAt" : "2026-10-05T09:12:44Z",
                    "type" : "personal_link.claimed"
                  } ],
                  "nextCursor" : "eyJ0cmFuc2FjdGlvbklkIjo3NDIsInNlcXVlbmNlIjoxOX0"
                },
                "schema" : {
                  "$ref" : "#/components/schemas/EventPage"
                }
              }
            },
            "description" : "A page of events"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "`invalid_request`: `limit` is outside 1 to 100, or `after` is not a cursor"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "List events",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "/me" : {
      "get" : {
        "description" : "Returns the calling key, your account's setup, the brands the key may use, and the environment. `tenant` holds the setup that links and store orders follow: the `locales` they accept, `approvalRequired`, and the `customFields` they answer. `key.webhookUrl` is null if no URL is set; the webhook secret is never returned. `key.scopes` lists what the key may do: PERSONAL_LINKS, REWARD_STORES, or both.",
        "operationId" : "getMe",
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "example" : {
                  "brands" : [ {
                    "id" : "0b9e8d7c-6b5a-4f3e-9d2c-1b0a9f8e7d6c",
                    "name" : "Example Brand"
                  } ],
                  "environment" : "production",
                  "key" : {
                    "name" : "CRM journeys",
                    "scopes" : [ "PERSONAL_LINKS", "REWARD_STORES" ],
                    "webhookUrl" : "https://hooks.operator.example/luxorr"
                  },
                  "tenant" : {
                    "approvalRequired" : false,
                    "customFields" : [ {
                      "id" : "3d2c1b0a-9f8e-4d7c-8b6a-5f4e3d2c1b0a",
                      "label" : "VIP tier",
                      "options" : [ "Gold", "Platinum" ],
                      "required" : true,
                      "type" : "DROPDOWN"
                    } ],
                    "id" : "6f1c2a0e-1d2b-4c3d-8e9f-0a1b2c3d4e5f",
                    "locales" : [ "en-US", "de-DE" ]
                  }
                },
                "schema" : {
                  "$ref" : "#/components/schemas/Me"
                }
              }
            },
            "description" : "The key"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Read the key",
        "tags" : [ "Keys" ]
      }
    },
    "/personal-links" : {
      "get" : {
        "description" : "Returns the links of the key's brands, however they were created, newest first. Use it to browse and to backfill, for example after more than 30 days without reading the event feed. To follow changes, read GET /events, which records every change in order.",
        "operationId" : "listPersonalLinks",
        "parameters" : [ {
          "description" : "Only this campaign's links",
          "in" : "query",
          "name" : "campaignId",
          "required" : false,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "Only this brand's links",
          "in" : "query",
          "name" : "brandId",
          "required" : false,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "Only links in this status",
          "in" : "query",
          "name" : "status",
          "required" : false,
          "schema" : {
            "enum" : [ "REQUESTED", "ACTIVE", "CLAIMED", "EXPIRED", "CANCELLED" ],
            "type" : "string"
          }
        }, {
          "description" : "The nextCursor of the previous page",
          "in" : "query",
          "name" : "after",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Page size, 1 to 100; 25 when absent",
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "default" : 25,
            "maximum" : 100,
            "minimum" : 1,
            "type" : "integer"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PersonalLinkPage"
                }
              }
            },
            "description" : "A page of personal links"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: a campaignId, brandId, status, limit, or cursor can't be read; errors[].field names it"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the PERSONAL_LINKS scope"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "List personal links",
        "tags" : [ "Personal links" ]
      },
      "post" : {
        "description" : "Creates one link for one player and returns it with its claimUrl. Send the claimUrl to the player through your own channel. The body asks for what your team enters when it creates a link by hand. If your account requires approval, the link starts REQUESTED: the player can't claim it until your team approves it, and personal_link.approved tells you when. Otherwise, it starts ACTIVE. The link lasts as long as its campaign. Build the Idempotency-Key from the occasion and the player, such as birthday-2026:player-123. If you send the same request again within 7 days, you get the first link back, so a retry after a timeout never creates a second link. The same key with a different body returns 422 idempotency_key_reused. Fields the body doesn't define are ignored. The link records a personal_link.created event, which the feed shows to this key but no webhook sends to it.",
        "operationId" : "createPersonalLink",
        "parameters" : [ {
          "description" : "A value unique to what the request does, and the same value for every retry of it. Build it from the occasion and the player, such as `birthday-2026:player-123`, so a resend never creates a second link. After a 2xx, a retry with the same key and the same JSON content returns the first answer with `Idempotent-Replayed: true`, whatever the key order or whitespace. The same key with a different value returns 422 `idempotency_key_reused`. A 4xx or 5xx isn't kept, so the same key runs again. A retry while the first request is still running returns 409 `idempotency_request_in_progress`. A key longer than 255 characters returns 400 `idempotency_key_invalid`. Keys are kept for 7 days.",
          "in" : "header",
          "name" : "Idempotency-Key",
          "required" : true,
          "schema" : {
            "maxLength" : 255,
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/PersonalLinkCreateRequest"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "201" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PersonalLink"
                }
              }
            },
            "description" : "The link, with its claimUrl"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: a field is missing or invalid, such as fullName, email, locale, customFields, campaignId, or brandId; errors[] names each field. idempotency_key_required: the Idempotency-Key header is missing"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the PERSONAL_LINKS scope"
          },
          "409" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "idempotency_request_in_progress: a request with the same Idempotency-Key is still running. Retry after it returns"
          },
          "422" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "Nothing was created. campaign_not_found: the key can't use this campaign, or the brandId isn't one of its brands. campaign_not_active: the campaign doesn't accept links, also if it is past its expiresAt. idempotency_key_reused: the key was used with a different request"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Create a personal link",
        "tags" : [ "Personal links" ]
      }
    },
    "/personal-links/{linkId}" : {
      "get" : {
        "description" : "Returns one link. If the key can't access the link, the call returns 404 not_found, the same as for an unknown link.",
        "operationId" : "getPersonalLink",
        "parameters" : [ {
          "description" : "The link's id",
          "in" : "path",
          "name" : "linkId",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PersonalLink"
                }
              }
            },
            "description" : "The personal link"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the PERSONAL_LINKS scope"
          },
          "404" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "not_found: no personal link with this id that the key can see"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Read a personal link",
        "tags" : [ "Personal links" ]
      }
    },
    "/personal-links/{linkId}:cancel" : {
      "post" : {
        "description" : "Cancels a link that this key created, while it is REQUESTED or ACTIVE. The player sees the link as expired. Your team sees the reason; the player never does. If the link is already CANCELLED, the call returns it as it is, with no change and no event, so a retry is safe with or without an Idempotency-Key.",
        "operationId" : "cancelPersonalLink",
        "parameters" : [ {
          "description" : "The link's id",
          "in" : "path",
          "name" : "linkId",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "Optional: a repeat of this call is safe without one. If you send one, a retry with the same key and request within 7 days returns the first 2xx answer with `Idempotent-Replayed: true`. The same key with a different request returns 422 `idempotency_key_reused`.",
          "in" : "header",
          "name" : "Idempotency-Key",
          "required" : false,
          "schema" : {
            "maxLength" : 255,
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CancelRequest"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PersonalLink"
                }
              }
            },
            "description" : "The canceled link, also if it was already canceled"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: reason is missing, blank, or longer than 1,000 characters. idempotency_key_invalid: the Idempotency-Key is longer than 255 characters"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the PERSONAL_LINKS scope"
          },
          "404" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "not_found: no personal link with this id that the key can see"
          },
          "409" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "not_cancellable: reason is created_elsewhere if the link was created in your workspace or with another key; otherwise, the link is CLAIMED or EXPIRED. currentStatus gives the link's status. idempotency_request_in_progress: a request with the same Idempotency-Key is still running"
          },
          "422" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "idempotency_key_reused: the Idempotency-Key was already used with a different request"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Cancel a personal link",
        "tags" : [ "Personal links" ]
      }
    },
    "/reward-store-orders" : {
      "get" : {
        "description" : "Returns the store orders this key placed, within its brands, newest first. Use it to browse and to backfill. To follow changes, read GET /events or receive the order.* webhooks.",
        "operationId" : "listRewardStoreOrders",
        "parameters" : [ {
          "description" : "Only this store's orders",
          "in" : "query",
          "name" : "storeId",
          "required" : false,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "Only this brand's orders",
          "in" : "query",
          "name" : "brandId",
          "required" : false,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "Only orders in this status",
          "in" : "query",
          "name" : "status",
          "required" : false,
          "schema" : {
            "enum" : [ "REQUESTED", "PROCESSING", "SENT", "COMPLETED", "CANCELLED" ],
            "type" : "string"
          }
        }, {
          "description" : "The nextCursor of the previous page",
          "in" : "query",
          "name" : "after",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Page size, 1 to 100; 25 when absent",
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "default" : 25,
            "maximum" : 100,
            "minimum" : 1,
            "type" : "integer"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/RewardStoreOrderPage"
                }
              }
            },
            "description" : "A page of orders"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: a storeId, brandId, status, limit, or cursor can't be read; errors[].field names it"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the REWARD_STORES scope"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "List rewards store orders",
        "tags" : [ "Rewards stores" ]
      },
      "post" : {
        "description" : "Places an order for one gift for one player, with the player's delivery address. Deduct the player's coins when the call returns 201. If order.cancelled arrives, refund them once for that orderId. If your account requires approval, the order starts REQUESTED; otherwise, it starts PROCESSING. A key never approves. The coin price and the gift's availability are checked for the delivery country. Build the Idempotency-Key from your own order id: if you send the same request again within 7 days, you get the first order back. A refused request creates no order and takes no stock. A key can't cancel an order. To cancel one, ask your team, and refund the coins when order.cancelled arrives.",
        "operationId" : "createRewardStoreOrder",
        "parameters" : [ {
          "description" : "A value unique to what the request does, and the same value for every retry of it. Build it from your own order id, such as `shop-order-1001`, so a resend never places a second order. After a 2xx, a retry with the same key and the same JSON content returns the first answer with `Idempotent-Replayed: true`, whatever the key order or whitespace. The same key with a different value returns 422 `idempotency_key_reused`. A 4xx or 5xx isn't kept, so the same key runs again. A retry while the first request is still running returns 409 `idempotency_request_in_progress`. A key longer than 255 characters returns 400 `idempotency_key_invalid`. Keys are kept for 7 days.",
          "in" : "header",
          "name" : "Idempotency-Key",
          "required" : true,
          "schema" : {
            "maxLength" : 255,
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/RewardStoreOrderCreateRequest"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "201" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/RewardStoreOrder"
                }
              }
            },
            "description" : "The order"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: a field is missing or invalid, such as recipient.phone, recipient.address.postalCode, variations, locale, or a required custom field; errors[] names each. idempotency_key_required: the Idempotency-Key header is missing"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the REWARD_STORES scope"
          },
          "409" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "price_changed: the gift costs something else in the delivery country; coinPrice gives the current price. out_of_stock: the combination is sold out. idempotency_request_in_progress: a request with the same Idempotency-Key is still running"
          },
          "422" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "Nothing was ordered. reward_store_not_found, brand_not_found, reward_store_not_live, gift_not_available (with a reason: NOT_IN_STORE, NOT_PUBLISHED, NOT_USABLE_BY_BRAND, NOT_SOLD_IN_COUNTRY, NOT_AVAILABLE_NOW, or NO_SELECTABLE_COMBINATION), or idempotency_key_reused"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Order a gift from a rewards store",
        "tags" : [ "Rewards stores" ]
      }
    },
    "/reward-store-orders/{orderId}" : {
      "get" : {
        "description" : "Returns a store order that this key placed. It contains no name, email, phone number, or address of the player. If the key can't access the order, the call returns 404 not_found, the same as for an unknown order.",
        "operationId" : "getRewardStoreOrder",
        "parameters" : [ {
          "description" : "The order's id",
          "in" : "path",
          "name" : "orderId",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/RewardStoreOrder"
                }
              }
            },
            "description" : "The order"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the REWARD_STORES scope"
          },
          "404" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "not_found: the key can't access a store order with this id"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Read a rewards store order",
        "tags" : [ "Rewards stores" ]
      }
    },
    "/reward-stores" : {
      "get" : {
        "description" : "Returns your stores that are SCHEDULED or LIVE and serve at least one of the key's brands, sorted by name. A store that isn't open or scheduled never appears.",
        "operationId" : "listRewardStores",
        "parameters" : [ {
          "description" : "Only the stores serving this brand",
          "in" : "query",
          "name" : "brandId",
          "required" : false,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "The nextCursor of the previous page",
          "in" : "query",
          "name" : "after",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Page size, 1 to 100; 25 when absent",
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "default" : 25,
            "maximum" : 100,
            "minimum" : 1,
            "type" : "integer"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/RewardStorePage"
                }
              }
            },
            "description" : "A page of stores"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: a brandId, limit, or cursor can't be read; errors[].field names it"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the REWARD_STORES scope"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "List rewards stores",
        "tags" : [ "Rewards stores" ]
      }
    },
    "/reward-stores/{storeId}" : {
      "get" : {
        "description" : "Returns the store as a player of one brand in one country sees it: its sections, each gift a player there can order now with the combinations they can pick, and each gift's coin price in the brand's currency. A gift that the player can't order there is left out, and so is a section with no gift left. Prices can change between two reads, so send the coinPrice the player saw with the order. If the key can't read the store for this brand, the call returns 404 not_found, the same as for an unknown store.",
        "operationId" : "getRewardStore",
        "parameters" : [ {
          "description" : "The store's id",
          "in" : "path",
          "name" : "storeId",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "The brand the player belongs to: one of the store's brandIds",
          "in" : "query",
          "name" : "brandId",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "The player's country, ISO 3166-1 alpha-2. The gifts, combinations, and coin prices are the ones sold there",
          "in" : "query",
          "name" : "country",
          "required" : true,
          "schema" : {
            "example" : "DE",
            "type" : "string"
          }
        }, {
          "description" : "Optional. The language of the gifts' names, descriptions, and option labels, the section names, and the currency: one of tenant.locales in GET /me; en-US if absent. A text with no translation falls back to en-US. The store's own name and description are shown as your team wrote them",
          "in" : "query",
          "name" : "locale",
          "required" : false,
          "schema" : {
            "example" : "de-DE",
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/RewardStore"
                }
              }
            },
            "description" : "The store"
          },
          "400" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_request: brandId or country is missing or can't be read, or the locale isn't enabled for your account; errors[] names each"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "403" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "insufficient_scope: the key lacks the REWARD_STORES scope"
          },
          "404" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "not_found: the key can't read a store with this id for this brand"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Read a rewards store",
        "tags" : [ "Rewards stores" ]
      }
    },
    "/webhooks:test" : {
      "post" : {
        "description" : "Sends a signed `ping` event to the key's webhook URL and returns your receiver's HTTP status in `responseStatus`. If your receiver answers 2xx, the result is `{\"responseStatus\": 200, \"error\": null}`. If it answers anything else, both are set: `responseStatus` and an `error` such as \"The receiver answered HTTP 500\". If your receiver can't be reached, `responseStatus` is null and `error` says why. If the key has no webhook URL, the call still returns 200, with `responseStatus` null and `error` \"This key has no webhook URL\". Every call sends a new ping and ignores an `Idempotency-Key` header, so a retry after you fix your receiver shows its new answer. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, as for every webhook. This call can take up to 15 seconds, so set your client's timeout above that.",
        "operationId" : "sendTestWebhook",
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "example" : {
                  "error" : null,
                  "responseStatus" : 200
                },
                "schema" : {
                  "$ref" : "#/components/schemas/WebhookTestResult"
                }
              }
            },
            "description" : "Your receiver's answer, or why there was none"
          },
          "401" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "invalid_api_key: the key is missing, malformed, unknown, or revoked"
          },
          "429" : {
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetail"
                }
              }
            },
            "description" : "rate_limited: more than 3,000 requests a minute from this key, across every path. See Retry-After"
          }
        },
        "summary" : "Send a test event",
        "tags" : [ "Events and webhooks" ]
      }
    }
  },
  "security" : [ {
    "apiKey" : [ ]
  } ],
  "servers" : [ {
    "description" : "Production",
    "url" : "https://api.luxorr.io/v1"
  } ],
  "tags" : [ {
    "description" : "Read your key, your account's setup, and the brands the key may use.",
    "name" : "Keys"
  }, {
    "description" : "List the campaigns Luxorr set up for you, to find the campaign a personal link is for.",
    "name" : "Campaigns"
  }, {
    "description" : "Create a gift link for one player, read and list links, and cancel a link.",
    "name" : "Personal links"
  }, {
    "description" : "Read a rewards store for a brand and a player's country, place a one-gift order and follow it.",
    "name" : "Rewards stores"
  }, {
    "description" : "The replayable event feed, the webhook test and the signed webhooks Luxorr sends.",
    "name" : "Events and webhooks"
  } ],
  "webhooks" : {
    "order.approved" : {
      "post" : {
        "description" : "`data` is the RewardStoreOrder, now `PROCESSING`: your team approved an order that started `REQUESTED`. Recorded at most once per order, and sent only to the key that placed it. It moves no coins. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_order_approved",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/RewardStoreOrder",
                    "description" : "`data` is the RewardStoreOrder, now `PROCESSING`: your team approved an order that started `REQUESTED`. Recorded at most once per order, and sent only to the key that placed it. It moves no coins."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "order.approved" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: order.approved",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "order.cancelled" : {
      "post" : {
        "description" : "`data` is the RewardStoreOrder, now `CANCELLED`: your team or Luxorr rejected or canceled it. Refund the player's coins once for this orderId. This event is recorded at most once per order, and sent only to the key that placed the order. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_order_cancelled",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/RewardStoreOrder",
                    "description" : "`data` is the RewardStoreOrder, now `CANCELLED`: your team or Luxorr rejected or canceled it. Refund the player's coins once for this orderId. This event is recorded at most once per order, and sent only to the key that placed the order."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "order.cancelled" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: order.cancelled",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "order.completed" : {
      "post" : {
        "description" : "`data` is the RewardStoreOrder, now `COMPLETED`: delivered. Recorded at most once per order, and sent only to the key that placed it. It moves no coins. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_order_completed",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/RewardStoreOrder",
                    "description" : "`data` is the RewardStoreOrder, now `COMPLETED`: delivered. Recorded at most once per order, and sent only to the key that placed it. It moves no coins."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "order.completed" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: order.completed",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "order.sent" : {
      "post" : {
        "description" : "`data` is the RewardStoreOrder, now `SENT`, with its shipment: carrier and tracking. Recorded at most once per order, and sent only to the key that placed it. It moves no coins. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_order_sent",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/RewardStoreOrder",
                    "description" : "`data` is the RewardStoreOrder, now `SENT`, with its shipment: carrier and tracking. Recorded at most once per order, and sent only to the key that placed it. It moves no coins."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "order.sent" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: order.sent",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "personal_link.approved" : {
      "post" : {
        "description" : "`data` is the PersonalLink, now `ACTIVE`. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_personal_link_approved",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/PersonalLink",
                    "description" : "`data` is the PersonalLink, now `ACTIVE`."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "personal_link.approved" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: personal_link.approved",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "personal_link.cancelled" : {
      "post" : {
        "description" : "`data` is the PersonalLink, now `CANCELLED`. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_personal_link_cancelled",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/PersonalLink",
                    "description" : "`data` is the PersonalLink, now `CANCELLED`."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "personal_link.cancelled" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: personal_link.cancelled",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "personal_link.claimed" : {
      "post" : {
        "description" : "`data` is the PersonalLink, now `CLAIMED`: the player chose a gift and Luxorr placed the order. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_personal_link_claimed",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/PersonalLink",
                    "description" : "`data` is the PersonalLink, now `CLAIMED`: the player chose a gift and Luxorr placed the order."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "personal_link.claimed" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: personal_link.claimed",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "personal_link.created" : {
      "post" : {
        "description" : "`data` is the PersonalLink as created: `REQUESTED` if your account requires approval, otherwise `ACTIVE`. One event per link, whoever created it. The key that created the link isn't sent this event. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_personal_link_created",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/PersonalLink",
                    "description" : "`data` is the PersonalLink as created: `REQUESTED` if your account requires approval, otherwise `ACTIVE`. One event per link, whoever created it. The key that created the link isn't sent this event."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "personal_link.created" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: personal_link.created",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "personal_link.expired" : {
      "post" : {
        "description" : "`data` is the PersonalLink, now `EXPIRED`: its campaign completed or passed its expiry. Sent to every key, including the key that created the link. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_personal_link_expired",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "$ref" : "#/components/schemas/PersonalLink",
                    "description" : "`data` is the PersonalLink, now `EXPIRED`: its campaign completed or passed its expiry. Sent to every key, including the key that created the link."
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "personal_link.expired" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: personal_link.expired",
        "tags" : [ "Events and webhooks" ]
      }
    },
    "ping" : {
      "post" : {
        "description" : "`data` is `{}`. Sent only by `POST /webhooks:test`, to the key that called it. Sent as a POST to the key's webhook URL, at least once and not always in order. Deduplicate by `id`, treat the resource's `updatedAt` as the truth, and read `GET /events` to reconcile. Any 2xx answer counts as delivered. Anything else is retried for 8 attempts over about 24 hours. Luxorr connects within 5 seconds and waits up to 10 seconds for the answer, so answer 2xx first and do the work after. A change made by a key's own request is never sent to that key; `GET /events` holds it.",
        "operationId" : "webhook_ping",
        "parameters" : [ {
          "description" : "The event's `id`, the same as in the body",
          "in" : "header",
          "name" : "Luxorr-Event-Id",
          "required" : true,
          "schema" : {
            "format" : "uuid",
            "type" : "string"
          }
        }, {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the key's webhook secret>`",
          "in" : "header",
          "name" : "Luxorr-Signature",
          "required" : true,
          "schema" : {
            "example" : "t=1759655564,v1=5f2b…",
            "type" : "string"
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "properties" : {
                  "data" : {
                    "description" : "`data` is `{}`. Sent only by `POST /webhooks:test`, to the key that called it.",
                    "type" : "object"
                  },
                  "environment" : {
                    "description" : "The deployment that sent the event",
                    "enum" : [ "production" ],
                    "type" : "string"
                  },
                  "id" : {
                    "format" : "uuid",
                    "type" : "string"
                  },
                  "occurredAt" : {
                    "format" : "date-time",
                    "type" : "string"
                  },
                  "type" : {
                    "enum" : [ "ping" ],
                    "type" : "string"
                  }
                },
                "required" : [ "data", "environment", "id", "occurredAt", "type" ],
                "type" : "object"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "2XX" : {
            "description" : "The event was received"
          }
        },
        "summary" : "Webhook: ping",
        "tags" : [ "Events and webhooks" ]
      }
    }
  }
}
