{
  "openapi": "3.1.0",
  "info": {
    "title": "ReGenerate Retailer API",
    "version": "1.0.0",
    "summary": "Buy digital codes at your negotiated price, from your own systems.",
    "description": "Everything on this API is scoped to the one account your key belongs to. There is no\nparameter anywhere that widens it, and there are no administrative endpoints: they are\nnot hidden behind a role, they are not part of this API.\n\nPrices are yours. The margin agreed for your account is applied server-side, so what the\ncatalog quotes is what the order costs.\n\nOrder sizes are not your problem. Our suppliers have batch limits; we split large orders\ninternally so that ordering three hundred cards behaves exactly like ordering three.",
    "contact": {
      "name": "ReGenerate integrations",
      "url": "https://api.regenerate.co.il"
    }
  },
  "servers": [
    {
      "url": "https://api.regenerate.co.il",
      "description": "Production — keys start rg_live_"
    },
    {
      "url": "https://api-sandbox.regenerate.co.il",
      "description": "Sandbox — keys start rg_test_; no real money"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "Trading a key for a short-lived token."
    },
    {
      "name": "Account",
      "description": "Your wallet."
    },
    {
      "name": "Catalog",
      "description": "What you can buy, and what it costs you."
    },
    {
      "name": "Orders",
      "description": "Placing orders and collecting codes."
    },
    {
      "name": "Webhooks",
      "description": "Being told when an order finishes."
    }
  ],
  "paths": {
    "/v1/auth/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "createToken",
        "summary": "Exchange an API key for a short-lived token",
        "description": "The only endpoint that accepts the API key itself. Everything else takes the token this\nreturns, so the long-lived secret sitting in your points of sale crosses the wire as\nseldom as possible.\n\nThe token expires within the hour. Fetch a new one when it does, or shortly before;\nthere is no refresh flow, because exchanging the key again is simpler than one.\n\nThe key may travel in the body or in an `X-API-Key` header, whichever suits your client.",
        "security": [],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "apiKey"
                ],
                "properties": {
                  "apiKey": {
                    "type": "string",
                    "example": "rg_live_4XK9ABCD_p7Qm2ZtVx8Ln..."
                  }
                }
              },
              "example": {
                "apiKey": "rg_live_4XK9ABCD_p7Qm2ZtVx8Ln4Rc9Wd3Yf6Hs"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "description": "The key, as an alternative to sending it in the body.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A token you may use until it expires.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                },
                "example": {
                  "accessToken": "eyJpc3MiOiJyZWdlbmVyYXRlIiwiYXVkIjoicmV0YWlsZXItYXBpIi4uLg.9Kx2...",
                  "tokenType": "Bearer",
                  "expiresIn": 3600
                }
              }
            }
          },
          "401": {
            "description": "The API key is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The API key is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getBalance",
        "summary": "Corporate wallet balance",
        "description": "What your prepaid wallet holds right now. Top-ups are arranged with your account manager and are not part of this API.",
        "responses": {
          "200": {
            "description": "The balance.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "balance": 1854000,
                  "currency": "ILS",
                  "asOf": "2026-09-14T08:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "listCatalog",
        "summary": "Products, denominations and your price",
        "description": "One entry per SKU, which is the unit you order. `price` is what this order will cost\nyour wallet, with your account’s margin already applied.\n\nA SKU is either fixed-value or open-value. A fixed one has a `faceValue` and you order\na quantity of it. An open one has `pricing.mode` of `range` and a band, and you send\n`requestedAmount` inside that band when you order.\n\nCache it for minutes rather than hours: prices move with the exchange rate.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Substring match on the product name.",
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "example": "console"
          }
        ],
        "responses": {
          "200": {
            "description": "The catalog as your account sees it.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items",
                    "generatedAt"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CatalogItem"
                      }
                    },
                    "generatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When this snapshot was taken. UTC, ISO 8601.",
                      "example": "2026-09-14T08:14:00.000Z"
                    }
                  }
                },
                "example": {
                  "items": [
                    {
                      "sku": "RG-7KQ4MX",
                      "productName": "PlayStation Store",
                      "category": "console",
                      "fulfillmentType": "code",
                      "faceValue": 10000,
                      "faceCurrency": "ILS",
                      "faceValueInIls": null,
                      "price": 9810,
                      "currency": "ILS",
                      "available": true,
                      "pricingMode": "fixed",
                      "minAmount": null,
                      "maxAmount": null,
                      "stepAmount": null
                    },
                    {
                      "sku": "RG-4HWN2C",
                      "productName": "Roblox Robux",
                      "category": "gaming",
                      "fulfillmentType": "code",
                      "faceValue": 0,
                      "faceCurrency": "ILS",
                      "faceValueInIls": null,
                      "price": 0,
                      "currency": "ILS",
                      "available": true,
                      "pricingMode": "range",
                      "minAmount": 5000,
                      "maxAmount": 50000,
                      "stepAmount": 1000
                    }
                  ],
                  "generatedAt": "2026-09-14T08:00:00.000Z"
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalog/{sku}/availability": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getAvailability",
        "summary": "Availability before ordering",
        "description": "A cheap pre-check: whether the SKU is sellable, and the largest quantity your account\nmay put in one order.\n\nIt is advisory. The binding check happens inside the order, in the same transaction that\nspends the money, so treat a `true` here as \"worth trying\" rather than as a reservation.",
        "parameters": [
          {
            "name": "sku",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "RG-7KQ4MX"
          }
        ],
        "responses": {
          "200": {
            "description": "Availability and your per-order ceiling.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                },
                "example": {
                  "sku": "RG-7KQ4MX",
                  "available": true,
                  "maxQuantityPerOrder": 500
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such SKU.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such SKU.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders": {
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "createOrder",
        "summary": "Place an order",
        "description": "`reference` is your own idempotency key and it is required. Send the same reference again\nand you get the same order back with status 200 instead of 201 — never a second charge.\nThis is what makes a till safe to retry after a timeout, which is the case that matters:\na retry without one is how a retailer buys three hundred cards twice.\n\nQuantity is bounded only by the per-order limit agreed for your account. Our suppliers\nhave batch limits of their own — one of them delivers five cards at a time — and we split\nlarge orders internally, so a three-hundred-card order behaves like a three-card one.\n\nThe response is immediate and the order comes back `created`, before the supplier has\nanswered. Wait for the callback, or poll while the status is `created` or `processing`,\nand fetch the codes once it is `delivered`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrder"
              },
              "examples": {
                "fixedValue": {
                  "summary": "A fixed-value SKU",
                  "value": {
                    "sku": "RG-7KQ4MX",
                    "quantity": 40,
                    "reference": "TILL-4471-20260914-0007"
                  }
                },
                "openValue": {
                  "summary": "An open-value SKU, with the amount inside the band",
                  "value": {
                    "sku": "RG-4HWN2C",
                    "quantity": 2,
                    "requestedAmount": 12000,
                    "reference": "TILL-4471-20260914-0008"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "This reference was used before. The original order is returned and nothing was charged again.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "201": {
            "description": "The order was created and your wallet was debited.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "example": {
                  "orderId": "0f8b4a2c-9d31-4e77-b0a5-6c1e2d3f4a5b",
                  "reference": "TILL-4471-20260914-0007",
                  "sku": "RG-7KQ4MX",
                  "productName": "PlayStation Store",
                  "quantity": 40,
                  "status": "created",
                  "unitPrice": 9810,
                  "totalPrice": 392400,
                  "requestedAmount": null,
                  "currency": "ILS",
                  "codesAvailable": false,
                  "createdAt": "2026-09-14T08:14:00.000Z",
                  "completedAt": null
                }
              }
            }
          },
          "400": {
            "description": "The request did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "The request did not validate.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such SKU.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such SKU.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The wallet does not hold enough. No order was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_balance",
                    "message": "The wallet does not hold enough. No order was created.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "listOrders",
        "summary": "List your orders",
        "description": "Newest first, paginated. Use it to reconcile a day across your estate rather than polling orders one by one.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "One-based page number."
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 25
            },
            "description": "Rows per page. The maximum is 50."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created",
                "processing",
                "delivered",
                "failed",
                "refunded"
              ]
            }
          },
          {
            "name": "reference",
            "in": "query",
            "description": "Your own reference, matched exactly.",
            "schema": {
              "type": "string",
              "maxLength": 50
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive calendar date.",
            "example": "2026-09-01"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Inclusive calendar date.",
            "example": "2026-09-14"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of orders.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderPage"
                },
                "example": {
                  "items": [
                    {
                      "orderId": "0f8b4a2c-9d31-4e77-b0a5-6c1e2d3f4a5b",
                      "reference": "TILL-4471-20260914-0007",
                      "sku": "RG-7KQ4MX",
                      "productName": "PlayStation Store",
                      "quantity": 40,
                      "status": "delivered",
                      "unitPrice": 9810,
                      "totalPrice": 392400,
                      "requestedAmount": null,
                      "currency": "ILS",
                      "codesAvailable": true,
                      "createdAt": "2026-09-14T08:14:00.000Z",
                      "completedAt": "2026-09-14T08:14:22.000Z"
                    }
                  ],
                  "page": 1,
                  "pageSize": 25,
                  "total": 137
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{id}": {
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getOrder",
        "summary": "Order status",
        "description": "`id` may be our order id or the `reference` you sent. Both work, deliberately: a till\nwhose request timed out knows only what it sent, and telling it to find our id first\nwould be telling it to do the impossible.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Our order id, or your own reference.",
            "example": "TILL-4471-20260914-0007"
          }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such order in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such order in your account.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{id}/codes": {
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getOrderCodes",
        "summary": "Retrieve delivered codes",
        "description": "Available once the order is `delivered`. Asking earlier returns 409, not an empty list,\nso a polling loop cannot mistake \"not yet\" for \"none\".\n\nEvery retrieval is recorded against the key that made it. Store what you receive: this\nis a retrieval endpoint, not a vault, and treat the response as you would a card number.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Our order id, or your own reference."
          }
        ],
        "responses": {
          "200": {
            "description": "One entry per card in the order.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderCodes"
                },
                "example": {
                  "orderId": "0f8b4a2c-9d31-4e77-b0a5-6c1e2d3f4a5b",
                  "codes": [
                    {
                      "cardNumber": "5312-8841-9027-4410",
                      "pin": "884120",
                      "claimUrl": null,
                      "expiresAt": "2027-09-14T00:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such order in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such order in your account.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The order has not been delivered yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "conflict",
                    "message": "The order has not been delivered yet.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "registerWebhook",
        "summary": "Register an order-status callback",
        "description": "The signing secret is returned once, here, and never again. Keep it: it is what lets you\ntell our calls from anyone else’s.\n\nThe URL must be https.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRegistration"
              },
              "example": {
                "url": "https://tills.example.co.il/hooks/regenerate",
                "events": [
                  "order.delivered",
                  "order.failed"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered. The signing secret is in this response only.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreated"
                },
                "example": {
                  "id": "7c2f1a9b-33de-4f0a-8b21-5d7e9c0a1b2c",
                  "url": "https://tills.example.co.il/hooks/regenerate",
                  "events": [
                    "order.delivered",
                    "order.failed"
                  ],
                  "status": "active",
                  "createdAt": "2026-09-14T08:00:00.000Z",
                  "signingSecret": "whsec_9Kx2Ld8Rm4Tn6Qp1Vs3Wy5Zb7Ce0Fg"
                }
              }
            }
          },
          "400": {
            "description": "The URL is not https, or no events were given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "The URL is not https, or no events were given.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List your callbacks",
        "description": "Without the signing secrets, which are not recoverable.",
        "responses": {
          "200": {
            "description": "Your active callbacks.",
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Requests allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests still allowed in the current window.",
                "schema": {
                  "type": "integer",
                  "example": 57
                }
              },
              "X-RateLimit-Reset": {
                "description": "Unix seconds at which the window resets.",
                "schema": {
                  "type": "integer",
                  "example": 1789000260
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "webhooks"
                  ],
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    }
                  }
                },
                "example": {
                  "webhooks": [
                    {
                      "id": "7c2f1a9b-33de-4f0a-8b21-5d7e9c0a1b2c",
                      "url": "https://tills.example.co.il/hooks/regenerate",
                      "events": [
                        "order.delivered",
                        "order.failed"
                      ],
                      "status": "active",
                      "createdAt": "2026-09-14T08:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Remove a callback",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed. No body."
          },
          "401": {
            "description": "The access token is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthenticated",
                    "message": "The access token is not valid.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such callback in your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No such callback in your account.",
                    "requestId": "9f1c2b6e-4a10-4b93-9a0e-2f5a1c3d7e88"
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The token from `POST /v1/auth/token`. Not the API key: that goes to the token endpoint only."
      }
    },
    "schemas": {
      "Token": {
        "type": "object",
        "required": [
          "accessToken",
          "tokenType",
          "expiresIn"
        ],
        "properties": {
          "accessToken": {
            "type": "string",
            "description": "Opaque. Send it back; do not parse it."
          },
          "tokenType": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expiresIn": {
            "type": "integer",
            "description": "Seconds until it expires.",
            "example": 3600
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "balance",
          "currency",
          "asOf"
        ],
        "properties": {
          "balance": {
            "type": "integer",
            "format": "int64",
            "description": "What the wallet holds. Minor units: 1234 is ₪12.34.",
            "example": 1854000
          },
          "currency": {
            "type": "string",
            "enum": [
              "ILS"
            ]
          },
          "asOf": {
            "type": "string",
            "format": "date-time",
            "description": "When the balance was read. UTC, ISO 8601.",
            "example": "2026-09-14T08:14:00.000Z"
          }
        }
      },
      "CatalogItem": {
        "type": "object",
        "required": [
          "sku",
          "productName",
          "category",
          "price",
          "currency",
          "available",
          "pricingMode"
        ],
        "properties": {
          "sku": {
            "type": "string",
            "description": "The identifier you order by.",
            "example": "RG-7KQ4MX"
          },
          "productName": {
            "type": "string",
            "example": "PlayStation Store"
          },
          "category": {
            "type": "string",
            "example": "console"
          },
          "fulfillmentType": {
            "type": "string",
            "enum": [
              "code"
            ],
            "description": "Version 1.0 sells codes only."
          },
          "faceValue": {
            "type": "integer",
            "format": "int64",
            "description": "The value of the voucher. Zero on an open-value SKU. Minor units: 1234 is ₪12.34.",
            "example": 10000
          },
          "faceCurrency": {
            "type": "string",
            "enum": [
              "ILS",
              "USD"
            ],
            "description": "The currency `faceValue` is in. `price` is always in shekels, whatever the card is denominated in.",
            "example": "ILS"
          },
          "faceValueInIls": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The face value in agorot, at the rate the price was computed with. Null on a shekel card and on an open-value SKU.",
            "example": null
          },
          "cardNote": {
            "type": [
              "string",
              "null"
            ],
            "description": "The note on the card, the same on every SKU of the product — what it is and where it works. Null when there is none.",
            "example": "Israel region"
          },
          "valueNote": {
            "type": [
              "string",
              "null"
            ],
            "description": "A note for this SKU only, shown beside `cardNote`. Null when there is none.",
            "example": null
          },
          "displayLabel": {
            "type": [
              "string",
              "null"
            ],
            "description": "What this value is called, where it is not its face value — \"1,000 Robux\" on a $10 card. Null: show the face value.",
            "example": null
          },
          "imageUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The card face, 250×350 WebP; the 500×700 rendition is the same address with `@2x` before `.webp`. Null when there is none.",
            "example": null
          },
          "price": {
            "type": "integer",
            "format": "int64",
            "description": "What it costs your wallet, your margin applied. Minor units: 1234 is ₪12.34.",
            "example": 9810
          },
          "currency": {
            "type": "string",
            "enum": [
              "ILS"
            ]
          },
          "available": {
            "type": "boolean"
          },
          "pricingMode": {
            "type": "string",
            "enum": [
              "fixed",
              "range"
            ],
            "description": "A fixed SKU is ordered by SKU alone. A range SKU is ordered with `requestedAmount` inside the band below."
          },
          "minAmount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Smallest `requestedAmount` allowed, in minor units. Null on a fixed SKU.",
            "example": 5000
          },
          "maxAmount": {
            "type": [
              "integer",
              "null"
            ],
            "example": 50000
          },
          "stepAmount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The amount must be a multiple of this. Without it you cannot tell that a band moving in thousands will refuse 12,345.",
            "example": 1000
          }
        }
      },
      "Availability": {
        "type": "object",
        "required": [
          "sku",
          "available",
          "maxQuantityPerOrder"
        ],
        "properties": {
          "sku": {
            "type": "string"
          },
          "available": {
            "type": "boolean"
          },
          "maxQuantityPerOrder": {
            "type": "integer",
            "description": "The per-order ceiling agreed for your account.",
            "example": 500
          }
        }
      },
      "CreateOrder": {
        "type": "object",
        "required": [
          "sku",
          "quantity",
          "reference"
        ],
        "properties": {
          "sku": {
            "type": "string",
            "example": "RG-7KQ4MX"
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "example": 40
          },
          "reference": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your idempotency key. Must be unique per order and stable across retries of that order.",
            "example": "TILL-4471-20260914-0007"
          },
          "requestedAmount": {
            "type": "integer",
            "description": "Required on an open-value SKU, refused on a fixed one. Minor units, inside the SKU band.",
            "example": 12000
          }
        }
      },
      "Order": {
        "type": "object",
        "required": [
          "orderId",
          "reference",
          "sku",
          "quantity",
          "status",
          "totalPrice",
          "currency"
        ],
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "The reference you sent."
          },
          "sku": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "processing",
              "delivered",
              "failed",
              "refunded"
            ],
            "description": "Non-terminal: `created`, which is what a new order comes back as, and `processing`. Terminal: `delivered`, `failed` and `refunded`, and the last two mean the wallet has already been put back. Poll while it is non-terminal."
          },
          "unitPrice": {
            "type": "integer",
            "format": "int64",
            "description": "Price for one card. Minor units: 1234 is ₪12.34.",
            "example": 9810
          },
          "totalPrice": {
            "type": "integer",
            "format": "int64",
            "description": "What was taken from the wallet. Minor units: 1234 is ₪12.34.",
            "example": 392400
          },
          "requestedAmount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "ILS"
            ]
          },
          "codesAvailable": {
            "type": "boolean",
            "description": "Whether `GET /v1/orders/{id}/codes` will return them now."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the order was accepted. UTC, ISO 8601.",
            "example": "2026-09-14T08:14:00.000Z"
          },
          "completedAt": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "When it reached a terminal state. UTC, ISO 8601.",
                "example": "2026-09-14T08:14:00.000Z"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "OrderPage": {
        "type": "object",
        "required": [
          "items",
          "page",
          "pageSize",
          "total"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            }
          },
          "page": {
            "type": "integer",
            "example": 1
          },
          "pageSize": {
            "type": "integer",
            "example": 25
          },
          "total": {
            "type": "integer",
            "description": "Rows matching the filter, not on this page."
          }
        }
      },
      "OrderCodes": {
        "type": "object",
        "required": [
          "orderId",
          "codes"
        ],
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "codes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Card"
            }
          }
        }
      },
      "Card": {
        "type": "object",
        "required": [
          "cardNumber"
        ],
        "properties": {
          "cardNumber": {
            "type": "string"
          },
          "pin": {
            "type": [
              "string",
              "null"
            ]
          },
          "claimUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Some products redeem through a link instead of a code."
          },
          "expiresAt": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "Voucher expiry, where the supplier gives one. UTC, ISO 8601.",
                "example": "2026-09-14T08:14:00.000Z"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "WebhookRegistration": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Must be https."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "enum": [
                "order.delivered",
                "order.failed",
                "order.refunded"
              ]
            }
          }
        }
      },
      "Webhook": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When it was registered. UTC, ISO 8601.",
            "example": "2026-09-14T08:14:00.000Z"
          }
        }
      },
      "WebhookCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "signingSecret": {
                "type": "string",
                "description": "Returned once, at registration. Not recoverable afterwards."
              }
            }
          }
        ]
      },
      "WebhookEvent": {
        "type": "object",
        "description": "The body we POST to your endpoint. It never contains a code.",
        "required": [
          "event",
          "orderId",
          "status",
          "occurredAt"
        ],
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "order.delivered",
              "order.failed",
              "order.refunded"
            ]
          },
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "codesAvailable": {
            "type": "boolean"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the order reached this state. UTC, ISO 8601.",
            "example": "2026-09-14T08:14:00.000Z"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "validation_failed",
                  "unauthenticated",
                  "forbidden",
                  "not_found",
                  "conflict",
                  "insufficient_balance",
                  "quantity_limit_exceeded",
                  "amount_out_of_range",
                  "product_unavailable",
                  "supplier_unavailable",
                  "supplier_rejected",
                  "rate_limited",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "For a person reading a log. Branch on `code`, never on this."
              },
              "details": {
                "type": "array",
                "description": "Field-level detail on a validation failure.",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              },
              "requestId": {
                "type": "string",
                "description": "Quote it when you contact us and we can find the request."
              }
            }
          }
        }
      }
    }
  }
}
