{
  "openapi": "3.1.0",
  "info": {
    "title": "Nouq API for apps",
    "version": "0.2.0",
    "description": "Build apps and integrations for Nouq stores: sync products and stock, pull orders into an ERP or a\nwarehouse system, push shipments, create coupons, and get told the moment something happens.\n\nBase URL: `https://api.nouq.net` (locally `http://localhost:8090`). Every call needs an app access\ntoken: `Authorization: Bearer nouq_at_…`. A token belongs to one store and one app, and carries\nthe scopes the merchant granted. Money is always an integer in minor units (halalas for SAR).\n\n## Two ways to get a token\n\n**Private app: a merchant's own integration.** In the dashboard, *App store → My apps & API →\nA private app for your store*: give it a name, tick the scopes, copy the token. It works for that store only.\nRotate it from the same page if it leaks.\n\n**OAuth: an app any store can install.** Register the app in the\n[developer portal](https://app.nouq.net/developer) with its redirect URIs and the scopes it\nneeds. You get a `client_id` and a `client_secret` (shown once).\n\n1. Send the merchant to\n   `https://app.nouq.net/oauth/authorize?client_id=…&redirect_uri=…&scope=products:read%20orders:read&state=…`.\n   `redirect_uri` must match one you registered exactly. `scope` is optional and defaults to all\n   of the app's scopes. Use `state` to stop cross-site request forgery.\n2. The merchant reviews the scopes and approves. Nouq redirects to\n   `redirect_uri?code=…&state=…&store_id=…`.\n3. From your server, exchange the code within ten minutes:\n\n   ```sh\n   curl https://api.nouq.net/oauth/v1/token \\\n     -d grant_type=authorization_code -d code=$CODE -d redirect_uri=$REDIRECT_URI \\\n     -d client_id=$CLIENT_ID -d client_secret=$CLIENT_SECRET\n   # {\"access_token\":\"nouq_at_…\",\"token_type\":\"bearer\",\"scope\":\"products:read orders:read\",\"store_id\":\"…\"}\n   ```\n\nTokens don't expire. They stop working when the merchant uninstalls the app, or when a new code\nis exchanged for the same store (each exchange revokes the previous token). Keep them server-side:\nthey are never meant for a browser.\n\n**Installs from the Nouq app store.** Once your app is listed (developer portal → your app → Store\nlisting, then Nouq's review), merchants can install it from the app store. Those installs arrive\nat your redirect URI with `state=nouq_marketplace`, because no session of yours started them.\nAccept that value, then exchange the code as usual.\n\n## Scopes\n\n| Scope | Allows |\n| --- | --- |\n| `products:read` | Products, variants, stock, categories |\n| `products:write` | Create, edit and delete products and categories (includes read) |\n| `orders:read` | Orders, parcels and returns |\n| `orders:write` | Change order status, add shipments and notes (includes read) |\n| `customers:read` | Customers and their order history |\n| `discounts:read` | Coupons and automatic discounts |\n| `discounts:write` | Create, edit and delete them (includes read) |\n\n`GET /api/v1/token` tells you what your token can do. A call outside the token's scopes returns\n`403` with code `insufficient_scope` and names the scope it needs.\n\n## Charging for your app\n\nSet a monthly price (and an optional free trial) on your store listing. Nouq bills the merchant\neach month with a VAT invoice and pays you 80%. You don't handle cards or invoices. To check\nwhether this store pays for your app, call `GET /api/v1/subscription`:\n\n```json\n{ \"status\": \"active\", \"price\": 4900, \"trial_ends_at\": null, \"period_end\": \"2026-11-20T09:00:00Z\", \"payment_due\": false }\n```\n\n`status` is `free`, `trialing`, `active` or `past_due`. When `payment_due` is true the merchant\nhas an unpaid fee. After 7 days unpaid your token returns `401` until they pay, and then it works\nagain. Removing the app ends the subscription.\n\n## Rate limits\n\nEach installation has a bucket of 40 requests that refills at 4 per second. Every response carries\n`X-RateLimit-Limit` and `X-RateLimit-Remaining`. Over the limit, you get `429` with `Retry-After`\nin seconds. Back off and retry.\n\n## Errors\n\nErrors are [RFC 7807](https://www.rfc-editor.org/rfc/rfc7807) problem documents with a stable\n`code`. Validation errors are `422` with a `fields` map. The token endpoint uses OAuth's own error\nformat (`{\"error\":\"invalid_grant\"}`).\n\n## Webhooks\n\nSubscribe with `POST /api/v1/webhooks` (`{\"url\":\"https://…\",\"topics\":[\"order.created\"]}`). The\nanswer includes the signing `secret` (`whsec_…`) once; keep it.\n\n| Topic | When | `data` | Scope |\n| --- | --- | --- | --- |\n| `order.created` | An order is placed | the order | `orders:read` |\n| `order.updated` | An order's status changes | the order | `orders:read` |\n| `order.refunded` | A return is refunded | `{order, refund}` | `orders:read` |\n| `product.created` / `product.updated` | A product is saved | the product | `products:read` |\n| `product.deleted` | A product is deleted | `{id}` | `products:read` |\n| `app.uninstalled` | The merchant removes your app | `{installation_id}` | — |\n\nEach delivery is a `POST` with this body:\n\n```json\n{ \"id\": \"…event id…\", \"topic\": \"order.created\", \"store_id\": \"…\", \"created_at\": \"2026-10-20T09:00:00Z\", \"data\": { } }\n```\n\nHeaders: `Nouq-Topic`, `Nouq-Event-Id`, `Nouq-Delivery-Id`, `Nouq-Store-Id` and `Nouq-Signature`.\n\n**Verify every delivery.** `Nouq-Signature` is `t=<unix seconds>,v1=<hex>`, where `v1` is\nHMAC-SHA256 with your secret over `<t>.<raw body>`. Compute it over the exact bytes you received,\nbefore parsing the JSON. Compare in constant time, and reject a `t` more than five minutes old.\n\n```js\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\nfunction verify(secret, header, rawBody) {\n  const { t, v1 } = Object.fromEntries(header.split(\",\").map((p) => p.split(\"=\")));\n  const want = createHmac(\"sha256\", secret).update(`${t}.${rawBody}`).digest(\"hex\");\n  return Math.abs(Date.now() / 1000 - Number(t)) < 300 && timingSafeEqual(Buffer.from(want), Buffer.from(v1));\n}\n```\n\nAnswer any `2xx` within ten seconds; do slow work after you answer. Anything else is retried after\n1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours (8 attempts). A subscription whose\ndeliveries run out of retries five times in a row is disabled; update it to turn it back on.\nDeliveries can arrive more than once or out of order: de-duplicate on the event `id`, and re-read\nthe resource if order matters.\n\n**Event log and replay.** `GET /api/v1/webhook-deliveries` lists the last 30 days with each\nattempt's status code, error and response. `POST /api/v1/webhook-deliveries/{id}/replay` sends\nthe same payload again now. `POST /api/v1/webhooks/{id}/test` sends a `webhook.test` event. The\ndeveloper portal has the same console across every store that installed your app.\n"
  },
  "servers": [
    {
      "url": "http://localhost:8090"
    }
  ],
  "tags": [
    {
      "name": "Store"
    },
    {
      "name": "Products"
    },
    {
      "name": "Orders"
    },
    {
      "name": "Customers"
    },
    {
      "name": "Discounts"
    },
    {
      "name": "Webhooks"
    },
    {
      "name": "oauth"
    }
  ],
  "paths": {
    "/api/v1/categories": {
      "parameters": [],
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "merchantCategories",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Category"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `products:read`"
      },
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "createCategory",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CategoryInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:write`"
      }
    },
    "/api/v1/categories/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "put": {
        "tags": [
          "Products"
        ],
        "operationId": "updateCategory",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CategoryInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:write`"
      },
      "delete": {
        "tags": [
          "Products"
        ],
        "operationId": "deleteCategory",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:write`"
      }
    },
    "/api/v1/customers": {
      "parameters": [],
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "merchantCustomers",
        "security": [
          {
            "appToken": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Q"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Customers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `customers:read`"
      }
    },
    "/api/v1/customers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "operationId": "getCustomer",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The customer and their latest orders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "customer",
                    "orders"
                  ],
                  "properties": {
                    "customer": {
                      "$ref": "#/components/schemas/CustomerSummary"
                    },
                    "orders": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OrderSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `customers:read`"
      }
    },
    "/api/v1/discounts": {
      "parameters": [],
      "get": {
        "tags": [
          "Discounts"
        ],
        "operationId": "merchantDiscounts",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Discounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Discount"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `discounts:read`"
      },
      "post": {
        "tags": [
          "Discounts"
        ],
        "operationId": "createDiscount",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DiscountInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Discount"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `discounts:write`"
      }
    },
    "/api/v1/discounts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "put": {
        "tags": [
          "Discounts"
        ],
        "operationId": "updateDiscount",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DiscountInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Discount"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `discounts:write`"
      },
      "delete": {
        "tags": [
          "Discounts"
        ],
        "operationId": "deleteDiscount",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `discounts:write`"
      }
    },
    "/api/v1/orders": {
      "parameters": [],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "merchantOrders",
        "security": [
          {
            "appToken": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "schema": {
              "$ref": "#/components/schemas/OrderStatus"
            }
          },
          {
            "$ref": "#/components/parameters/Q"
          },
          {
            "in": "query",
            "name": "from",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Placed on or after this day (the store's time zone)"
          },
          {
            "in": "query",
            "name": "to",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Placed on or before this day (the store's time zone)"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Orders",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/orders/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "merchantOrder",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/orders/{id}/notes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "addOrderNote",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteInput"
              }
            }
          }
        },
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `orders:write`"
      }
    },
    "/api/v1/orders/{id}/parcels": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "orderParcels",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Parcels",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Parcel"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/orders/{id}/returns": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "merchantOrderReturns",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Returns of this order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Return"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/orders/{id}/shipments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "addShipment",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShipmentInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Shipped",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `orders:write`"
      }
    },
    "/api/v1/orders/{id}/status": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "changeOrderStatus",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StatusChange"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `orders:write`"
      }
    },
    "/api/v1/parcels/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getParcel",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Parcel with label data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Parcel"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/products": {
      "parameters": [],
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "merchantProducts",
        "security": [
          {
            "appToken": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "active",
                "archived"
              ]
            }
          },
          {
            "in": "query",
            "name": "category",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Q"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Products",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `products:read`"
      },
      "post": {
        "tags": [
          "Products"
        ],
        "operationId": "createProduct",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:write`"
      }
    },
    "/api/v1/products/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "merchantProduct",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Product",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:read`"
      },
      "put": {
        "tags": [
          "Products"
        ],
        "operationId": "updateProduct",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProductInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:write`"
      },
      "delete": {
        "tags": [
          "Products"
        ],
        "operationId": "deleteProduct",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `products:write`"
      }
    },
    "/api/v1/returns": {
      "parameters": [],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "merchantReturns",
        "security": [
          {
            "appToken": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "status",
            "schema": {
              "$ref": "#/components/schemas/ReturnStatus"
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Returns",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReturnPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/returns/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "merchantReturn",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Return",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Return"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** `orders:read`"
      }
    },
    "/api/v1/store": {
      "parameters": [],
      "get": {
        "tags": [
          "Store"
        ],
        "operationId": "getMyStore",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Store",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Store"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/subscription": {
      "get": {
        "tags": [
          "Store"
        ],
        "operationId": "apiAppSubscription",
        "security": [
          {
            "appToken": []
          }
        ],
        "summary": "Whether this store pays for your app",
        "description": "**Scope:** any app token\n\nFree apps get {status: free}. When payment_due is true the merchant has an unpaid fee; after a 7-day grace your token stops working until it is paid.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppBilling"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/token": {
      "get": {
        "tags": [
          "Store"
        ],
        "operationId": "apiTokenInfo",
        "summary": "What this token can do",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenInfo"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhook-deliveries": {
      "parameters": [],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookDeliveries",
        "security": [
          {
            "appToken": []
          }
        ],
        "parameters": [
          {
            "in": "query",
            "name": "subscription_id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "topic",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "failed"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhook-deliveries/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhookDelivery",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhook-deliveries/{id}/replay": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "replayWebhookDelivery",
        "summary": "Send a delivery's payload again, now",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhooks": {
      "parameters": [],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhooks",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookSubscription"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookSubscriptionInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "webhook",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhook",
        "security": [
          {
            "appToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookSubscriptionInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhooks/{id}/rotate-secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "rotateWebhookSecret",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/api/v1/webhooks/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdPath"
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "testWebhook",
        "summary": "Send a webhook.test event now",
        "security": [
          {
            "appToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked token"
          },
          "403": {
            "description": "The token lacks the scope"
          },
          "429": {
            "description": "Rate limited; see Retry-After"
          },
          "default": {
            "$ref": "#/components/responses/Problem"
          }
        },
        "description": "**Scope:** any app token"
      }
    },
    "/oauth/v1/token": {
      "post": {
        "tags": [
          "oauth"
        ],
        "operationId": "oauthToken",
        "summary": "Exchange an authorisation code for an access token",
        "description": "RFC 6749 §4.1.3. Send the body form-encoded or as JSON. Codes are single use and expire after ten minutes.\nA new exchange revokes the installation's previous token.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OAuthTokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthGrant"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Bad client credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "appToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "An app access token (nouq_at_…) from OAuth or a private app"
      }
    },
    "parameters": {
      "Page": {
        "in": "query",
        "name": "page",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "PerPage": {
        "in": "query",
        "name": "per_page",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      },
      "Q": {
        "in": "query",
        "name": "q",
        "schema": {
          "type": "string"
        }
      },
      "IdPath": {
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "Problem": {
        "description": "Error (RFC 7807)",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NoContent": {
        "description": "Done"
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Locale": {
        "type": "string",
        "enum": [
          "ar",
          "en"
        ]
      },
      "Money": {
        "type": "integer",
        "format": "int64",
        "description": "Minor units (halalas)"
      },
      "Domain": {
        "type": "object",
        "required": [
          "host",
          "is_primary",
          "verified"
        ],
        "properties": {
          "host": {
            "type": "string"
          },
          "is_primary": {
            "type": "boolean"
          },
          "verified": {
            "type": "boolean"
          }
        }
      },
      "Store": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "name",
          "description",
          "logo_url",
          "whatsapp_phone",
          "currency",
          "locale",
          "status",
          "cod_enabled",
          "cod_fee",
          "vat_enabled",
          "vat_rate_bp",
          "min_order_total",
          "recovery_enabled",
          "recovery_delay_minutes",
          "recovery_discount_code",
          "domains",
          "created_at",
          "phone_messages"
        ],
        "properties": {
          "phone_messages": {
            "type": "boolean",
            "description": "Nouq can deliver WhatsApp and SMS (PHONE_CODES). False: shoppers hear by email only; don't offer or promise phone messages"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "logo_url": {
            "type": "string"
          },
          "whatsapp_phone": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "locale": {
            "$ref": "#/components/schemas/Locale"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "closed"
            ]
          },
          "cod_enabled": {
            "type": "boolean"
          },
          "cod_fee": {
            "$ref": "#/components/schemas/Money"
          },
          "vat_enabled": {
            "type": "boolean"
          },
          "vat_rate_bp": {
            "type": "integer"
          },
          "min_order_total": {
            "$ref": "#/components/schemas/Money",
            "description": "The least the store takes an order for: products after offers and discounts, before rewards and shipping (0: no minimum)"
          },
          "recovery_enabled": {
            "type": "boolean"
          },
          "recovery_delay_minutes": {
            "type": "integer"
          },
          "recovery_discount_code": {
            "type": "string"
          },
          "domains": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Domain"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SearchSnippet": {
        "type": "object",
        "description": "What a product or category shows in Google. Empty fields fall back to the title and description.",
        "required": [
          "title",
          "description"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 70
          },
          "description": {
            "type": "string",
            "maxLength": 170
          }
        }
      },
      "Category": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "name_ar",
          "name_en",
          "position",
          "product_count",
          "description_ar",
          "description_en",
          "seo",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "name_ar": {
            "type": "string"
          },
          "name_en": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          },
          "product_count": {
            "type": "integer"
          },
          "description_ar": {
            "type": "string",
            "description": "Intro text shown above the category's products"
          },
          "description_en": {
            "type": "string"
          },
          "seo": {
            "$ref": "#/components/schemas/SearchSnippet"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CategoryInput": {
        "type": "object",
        "required": [
          "name_ar"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name_ar": {
            "type": "string"
          },
          "name_en": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          },
          "description_ar": {
            "type": "string",
            "maxLength": 2000,
            "description": "Omit to keep the stored text"
          },
          "description_en": {
            "type": "string",
            "maxLength": 2000
          },
          "seo": {
            "$ref": "#/components/schemas/SearchSnippet",
            "description": "Omit to keep the stored snippet"
          }
        }
      },
      "Option": {
        "type": "object",
        "required": [
          "name",
          "values"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Variant": {
        "type": "object",
        "required": [
          "id",
          "sku",
          "options",
          "title",
          "price",
          "compare_at_price",
          "stock",
          "track_inventory",
          "in_stock"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sku": {
            "type": "string"
          },
          "options": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "title": {
            "type": "string"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "compare_at_price": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ]
          },
          "stock": {
            "type": "integer"
          },
          "track_inventory": {
            "type": "boolean"
          },
          "in_stock": {
            "type": "boolean"
          },
          "weight_grams": {
            "type": "integer",
            "description": "Shipped weight; 0 = the store's default parcel weight"
          },
          "cost": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What the merchant pays per unit (minor units; null = not entered). Only in the merchant's own product responses: never to apps or the storefront."
          }
        }
      },
      "VariantInput": {
        "type": "object",
        "required": [
          "options",
          "price",
          "stock",
          "track_inventory"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "sku": {
            "type": "string"
          },
          "options": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "compare_at_price": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ]
          },
          "stock": {
            "type": "integer"
          },
          "track_inventory": {
            "type": "boolean"
          },
          "weight_grams": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "cost": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Cost per unit (minor units). null clears it; leaving it out keeps the stored cost. Ignored from apps."
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "title_ar",
          "title_en",
          "description_ar",
          "description_en",
          "status",
          "featured",
          "options",
          "variants",
          "images",
          "category_ids",
          "seo",
          "free_shipping",
          "page",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "title_ar": {
            "type": "string"
          },
          "title_en": {
            "type": "string"
          },
          "description_ar": {
            "type": "string"
          },
          "description_en": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "archived"
            ]
          },
          "featured": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Option"
            }
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Variant"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "category_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "seo": {
            "$ref": "#/components/schemas/SearchSnippet"
          },
          "free_shipping": {
            "type": "boolean",
            "description": "This product ships free. An order ships free when every line in it does"
          },
          "page": {
            "$ref": "#/components/schemas/ProductPageSettings"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProductPageSettings": {
        "type": "object",
        "description": "How this product's page sells, for products sold from an ad. All off: the store's normal product page.",
        "required": [
          "buy_now",
          "button_label",
          "order_form",
          "hide_chrome",
          "hide_related",
          "sticky_bar",
          "hide_reviews"
        ],
        "properties": {
          "buy_now": {
            "type": "boolean",
            "description": "The button skips the cart and goes straight to checkout"
          },
          "button_label": {
            "type": "string",
            "maxLength": 30,
            "description": "The button's words (empty: the store's)"
          },
          "order_form": {
            "type": "string",
            "enum": [
              "",
              "before_description",
              "after_description"
            ],
            "description": "The whole order form on the page, in place of the buy box"
          },
          "hide_chrome": {
            "type": "boolean",
            "description": "A focused header and footer instead of the store's"
          },
          "hide_related": {
            "type": "boolean",
            "description": "No other products: similar ones, bought together, bundles"
          },
          "sticky_bar": {
            "type": "string",
            "enum": [
              "",
              "on",
              "off"
            ],
            "description": "The mobile buy bar (empty: as the store's theme has it)"
          },
          "hide_reviews": {
            "type": "boolean",
            "description": "No stars and no reviews section"
          }
        }
      },
      "ProductInput": {
        "type": "object",
        "required": [
          "title_ar",
          "status",
          "options",
          "variants",
          "images",
          "category_ids"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title_ar": {
            "type": "string"
          },
          "title_en": {
            "type": "string"
          },
          "description_ar": {
            "type": "string"
          },
          "description_en": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "archived"
            ]
          },
          "featured": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Option"
            }
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantInput"
            }
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "category_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "seo": {
            "$ref": "#/components/schemas/SearchSnippet",
            "description": "Omit to keep the stored snippet"
          },
          "free_shipping": {
            "type": "boolean",
            "description": "Omit to keep what's stored"
          },
          "page": {
            "$ref": "#/components/schemas/ProductPageSettings",
            "description": "Omit to keep what's stored"
          }
        }
      },
      "ProductSummary": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "title_ar",
          "title_en",
          "status",
          "featured",
          "price",
          "compare_at_price",
          "in_stock",
          "total_stock",
          "variant_count",
          "image_url",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "title_ar": {
            "type": "string"
          },
          "title_en": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "archived"
            ]
          },
          "featured": {
            "type": "boolean"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "compare_at_price": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ]
          },
          "in_stock": {
            "type": "boolean"
          },
          "total_stock": {
            "type": "integer"
          },
          "variant_count": {
            "type": "integer"
          },
          "image_url": {
            "type": "string"
          },
          "hover_image_url": {
            "type": "string",
            "description": "The second photo (empty when there is one); storefront cards fade to it on hover"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProductPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "per_page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "CarrierAddress": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "district": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "building_number": {
            "type": "string"
          },
          "postal_code": {
            "type": "string"
          },
          "short_address": {
            "type": "string"
          }
        }
      },
      "CarrierMode": {
        "type": "string",
        "enum": [
          "own",
          "nouq"
        ],
        "description": "own: the merchant's carrier contract; nouq: Nouq Shipping (Nouq's contract, paid from the shipping balance, ADR 0020)"
      },
      "ParcelEvent": {
        "type": "object",
        "required": [
          "at",
          "status",
          "description",
          "location"
        ],
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "$ref": "#/components/schemas/ParcelStatus"
          },
          "description": {
            "type": "string"
          },
          "location": {
            "type": "string"
          }
        }
      },
      "ParcelStatus": {
        "type": "string",
        "enum": [
          "label_created",
          "picked_up",
          "in_transit",
          "out_for_delivery",
          "delivered",
          "failed_attempt",
          "returned",
          "cancelled"
        ]
      },
      "ParcelLabel": {
        "type": "object",
        "required": [
          "format",
          "barcode",
          "service",
          "route",
          "from",
          "to",
          "pieces",
          "reference",
          "contents"
        ],
        "properties": {
          "format": {
            "type": "string"
          },
          "barcode": {
            "type": "string"
          },
          "service": {
            "type": "string"
          },
          "route": {
            "type": "string"
          },
          "from": {
            "$ref": "#/components/schemas/CarrierAddress"
          },
          "to": {
            "$ref": "#/components/schemas/CarrierAddress"
          },
          "pieces": {
            "type": "integer"
          },
          "reference": {
            "type": "string"
          },
          "contents": {
            "type": "string"
          }
        }
      },
      "Parcel": {
        "type": "object",
        "required": [
          "id",
          "order_id",
          "carrier",
          "carrier_name",
          "sandbox",
          "tracking_number",
          "track_url",
          "status",
          "weight_grams",
          "cost",
          "cod_amount",
          "mode",
          "currency",
          "retail",
          "cod_fee",
          "pickup_ref",
          "pickup_at",
          "events",
          "delivered_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "carrier": {
            "type": "string"
          },
          "carrier_name": {
            "type": "string"
          },
          "sandbox": {
            "type": "boolean"
          },
          "tracking_number": {
            "type": "string"
          },
          "track_url": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/ParcelStatus"
          },
          "weight_grams": {
            "type": "integer"
          },
          "cost": {
            "$ref": "#/components/schemas/Money"
          },
          "cod_amount": {
            "$ref": "#/components/schemas/Money"
          },
          "mode": {
            "$ref": "#/components/schemas/CarrierMode"
          },
          "currency": {
            "type": "string"
          },
          "retail": {
            "$ref": "#/components/schemas/Money",
            "description": "Walk-in rate the Nouq price compares with (nouq mode)"
          },
          "cod_fee": {
            "$ref": "#/components/schemas/Money",
            "description": "Charged for collecting the cash (nouq mode)"
          },
          "label": {
            "$ref": "#/components/schemas/ParcelLabel"
          },
          "pickup_ref": {
            "type": "string"
          },
          "pickup_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParcelEvent"
            }
          },
          "delivered_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DiscountKind": {
        "type": "string",
        "enum": [
          "code",
          "automatic"
        ]
      },
      "DiscountType": {
        "type": "string",
        "enum": [
          "percent",
          "fixed",
          "free_shipping",
          "buy_x_get_y"
        ],
        "description": "buy_x_get_y: for every buy_quantity + get_quantity covered pieces, the cheapest get_quantity come value% off (100: free); it repeats"
      },
      "Discount": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "code",
          "title",
          "type",
          "value",
          "min_subtotal",
          "starts_at",
          "ends_at",
          "usage_limit",
          "usage_count",
          "per_customer_limit",
          "once_per_customer",
          "active",
          "applies_to",
          "target_ids",
          "min_quantity",
          "buy_quantity",
          "get_quantity",
          "max_discount",
          "class",
          "combines_with",
          "total_discounted",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "$ref": "#/components/schemas/DiscountKind"
          },
          "code": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/DiscountType"
          },
          "value": {
            "type": "integer",
            "format": "int64",
            "description": "Whole percent for percent and buy_x_get_y; minor units for fixed"
          },
          "min_subtotal": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "usage_limit": {
            "type": [
              "integer",
              "null"
            ]
          },
          "usage_count": {
            "type": "integer"
          },
          "per_customer_limit": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Orders one customer (phone number) can use it on; null: no limit"
          },
          "once_per_customer": {
            "type": "boolean",
            "description": "per_customer_limit is 1 (kept for older clients)"
          },
          "active": {
            "type": "boolean"
          },
          "applies_to": {
            "$ref": "#/components/schemas/DiscountScope"
          },
          "target_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Category ids (applies_to=categories) or product ids (applies_to=products)"
          },
          "min_quantity": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pieces of the covered products the discount needs (not for buy_x_get_y)"
          },
          "buy_quantity": {
            "type": [
              "integer",
              "null"
            ],
            "description": "buy_x_get_y: pieces the shopper pays for in each group"
          },
          "get_quantity": {
            "type": [
              "integer",
              "null"
            ],
            "description": "buy_x_get_y: pieces in each group that come value% off"
          },
          "max_discount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "percent and buy_x_get_y: the most it takes off"
          },
          "class": {
            "$ref": "#/components/schemas/DiscountClass"
          },
          "combines_with": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiscountClass"
            },
            "description": "The other classes it stacks with; two discounts stack only when each allows the other's class"
          },
          "total_discounted": {
            "$ref": "#/components/schemas/Money"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DiscountInput": {
        "type": "object",
        "required": [
          "kind",
          "title",
          "type",
          "value",
          "active"
        ],
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/DiscountKind"
          },
          "code": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/DiscountType"
          },
          "value": {
            "type": "integer",
            "format": "int64"
          },
          "min_subtotal": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ]
          },
          "starts_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "usage_limit": {
            "type": [
              "integer",
              "null"
            ]
          },
          "per_customer_limit": {
            "type": [
              "integer",
              "null"
            ],
            "description": "1-1000, at most usage_limit"
          },
          "once_per_customer": {
            "type": "boolean",
            "description": "Same as per_customer_limit 1, when per_customer_limit is not given"
          },
          "active": {
            "type": "boolean"
          },
          "applies_to": {
            "$ref": "#/components/schemas/DiscountScope"
          },
          "target_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "min_quantity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "buy_quantity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "get_quantity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_discount": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "percent and buy_x_get_y only; ignored otherwise"
          },
          "combines_with": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DiscountClass"
            },
            "description": "Its own class is ignored. Leaving it out means it never stacks."
          }
        }
      },
      "DiscountClass": {
        "type": "string",
        "enum": [
          "product",
          "order",
          "shipping"
        ],
        "description": "product: covers some products or categories, or buy_x_get_y. order: percent or fixed off the whole cart. shipping: free shipping. A cart gets at most one of each, product first, then order on what is left, then shipping; each one's minimums are checked after the earlier ones came off."
      },
      "DiscountScope": {
        "type": "string",
        "enum": [
          "all",
          "categories",
          "products"
        ],
        "description": "What the discount covers. Percent and fixed amounts come off the matching products only; free shipping is always for the whole order."
      },
      "Address": {
        "type": "object",
        "required": [
          "city"
        ],
        "properties": {
          "city": {
            "type": "string"
          },
          "district": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "building_number": {
            "type": "string"
          },
          "additional_number": {
            "type": "string"
          },
          "postal_code": {
            "type": "string"
          },
          "short_address": {
            "type": "string",
            "description": "Saudi National Address short code, e.g. RRRD2929 (Saudi stores only)"
          },
          "floor": {
            "type": "string",
            "description": "Egyptian stores: floor (الدور)"
          },
          "apartment": {
            "type": "string",
            "description": "Egyptian stores: apartment (شقة)"
          },
          "landmark": {
            "type": "string",
            "maxLength": 120,
            "description": "Egyptian stores: a well-known place near the door (علامة مميزة)"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "name",
          "phone"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        }
      },
      "FieldAnswer": {
        "type": "object",
        "required": [
          "key",
          "label_ar",
          "label_en",
          "value"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "label_ar": {
            "type": "string"
          },
          "label_en": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "PaymentMethod": {
        "type": "string",
        "enum": [
          "cod",
          "card",
          "stc_pay",
          "tabby",
          "tamara",
          "bank_transfer",
          "net_terms",
          "exchange"
        ],
        "description": "exchange = zero-cost replacement order from a return; net_terms = a business account's credit terms (pay later)"
      },
      "OrderStatus": {
        "type": "string",
        "enum": [
          "pending_confirmation",
          "pending_payment",
          "confirmed",
          "processing",
          "shipped",
          "delivered",
          "cancelled"
        ]
      },
      "OrderItem": {
        "type": "object",
        "required": [
          "id",
          "product_id",
          "variant_id",
          "title",
          "variant_title",
          "sku",
          "image_url",
          "unit_price",
          "quantity",
          "total"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "product_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "variant_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "variant_title": {
            "type": "string"
          },
          "sku": {
            "type": "string"
          },
          "image_url": {
            "type": "string"
          },
          "unit_price": {
            "$ref": "#/components/schemas/Money"
          },
          "quantity": {
            "type": "integer"
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "OrderEvent": {
        "type": "object",
        "required": [
          "type",
          "message",
          "actor",
          "created_at"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "actor": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Shipment": {
        "type": "object",
        "required": [
          "id",
          "carrier",
          "tracking_number",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "carrier": {
            "type": "string"
          },
          "tracking_number": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Order": {
        "type": "object",
        "required": [
          "id",
          "number",
          "token",
          "status",
          "payment_method",
          "payment_status",
          "currency",
          "subtotal",
          "shipping_total",
          "cod_fee",
          "discount_total",
          "discount_code",
          "recovered_from_cart",
          "vat_total",
          "total",
          "refunded_total",
          "deposit_amount",
          "via_passport",
          "loyalty_points",
          "loyalty_amount",
          "referral_amount",
          "offer_total",
          "upsell_total",
          "landing_page_id",
          "phone_whatsapp",
          "parent_order_id",
          "customer",
          "address",
          "shipping_method",
          "note",
          "custom_fields",
          "payment_discount",
          "bank_offer",
          "card_prefix",
          "paid_card_prefix",
          "bank_offer_mismatch",
          "cod_confirmed_at",
          "cod_reminders",
          "cod_expires_at",
          "address_confirmed_at",
          "delivery_window",
          "items",
          "shipments",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "integer"
          },
          "token": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "payment_method": {
            "$ref": "#/components/schemas/PaymentMethod"
          },
          "payment_status": {
            "type": "string",
            "enum": [
              "unpaid",
              "deposit_paid",
              "paid",
              "refunded"
            ]
          },
          "currency": {
            "type": "string"
          },
          "subtotal": {
            "$ref": "#/components/schemas/Money"
          },
          "shipping_total": {
            "$ref": "#/components/schemas/Money"
          },
          "cod_fee": {
            "$ref": "#/components/schemas/Money"
          },
          "discount_total": {
            "$ref": "#/components/schemas/Money"
          },
          "discount_code": {
            "type": "string"
          },
          "recovered_from_cart": {
            "type": "boolean"
          },
          "vat_total": {
            "$ref": "#/components/schemas/Money"
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "refunded_total": {
            "$ref": "#/components/schemas/Money"
          },
          "deposit_amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Partial COD: paid online before shipping; the rest on delivery"
          },
          "via_passport": {
            "type": "boolean",
            "description": "Placed with a Nouq Passport (verified phone)"
          },
          "loyalty_points": {
            "type": "integer",
            "description": "Points spent on the order"
          },
          "loyalty_amount": {
            "$ref": "#/components/schemas/Money",
            "description": "What the points took off (also in discount_total)"
          },
          "referral_amount": {
            "$ref": "#/components/schemas/Money",
            "description": "Friend-referral discount (also in discount_total)"
          },
          "offer_total": {
            "$ref": "#/components/schemas/Money",
            "description": "A landing page's quantity offer (also in discount_total)"
          },
          "upsell_total": {
            "$ref": "#/components/schemas/Money",
            "description": "What cross-sell and post-purchase offers took off (also in discount_total)"
          },
          "landing_page_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The landing page the order came from"
          },
          "phone_whatsapp": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the shopper said their number is on WhatsApp (null: not asked)"
          },
          "parent_order_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The order this one ships with: an after-order offer accepted on a paid card order becomes its own small order, paid on its own"
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "address": {
            "$ref": "#/components/schemas/Address"
          },
          "shipping_method": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "custom_fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldAnswer"
            },
            "description": "Answers to the store's checkout questions"
          },
          "payment_discount": {
            "$ref": "#/components/schemas/Money",
            "description": "What paying this way took off (also in discount_total)"
          },
          "bank_offer": {
            "type": "string",
            "description": "The bank whose card offer the order took (its amount is in payment_discount)"
          },
          "card_prefix": {
            "type": "string",
            "description": "First 6 digits the shopper gave for that offer"
          },
          "paid_card_prefix": {
            "type": "string",
            "description": "First 6 digits of the card the gateway reports paid (empty: not reported yet)"
          },
          "bank_offer_mismatch": {
            "type": "boolean",
            "description": "The card that paid isn't in the offer"
          },
          "cod_confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cod_reminders": {
            "type": "integer",
            "description": "Confirmation reminders sent (COD)"
          },
          "cod_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When an unconfirmed COD order cancels itself"
          },
          "address_confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "delivery_window": {
            "type": "string",
            "enum": [
              "",
              "morning",
              "afternoon",
              "evening"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderItem"
            }
          },
          "shipments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Shipment"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderEvent"
            },
            "description": "Merchant view only"
          },
          "next_actions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderStatus"
            },
            "description": "Merchant view only"
          }
        }
      },
      "OrderSummary": {
        "type": "object",
        "required": [
          "id",
          "number",
          "status",
          "payment_method",
          "payment_status",
          "total",
          "currency",
          "customer_name",
          "customer_phone",
          "item_count",
          "created_at",
          "cod_reminders",
          "address_confirmed"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "payment_method": {
            "$ref": "#/components/schemas/PaymentMethod"
          },
          "payment_status": {
            "type": "string"
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "currency": {
            "type": "string"
          },
          "customer_name": {
            "type": "string"
          },
          "customer_phone": {
            "type": "string"
          },
          "item_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "cod_reminders": {
            "type": "integer"
          },
          "address_confirmed": {
            "type": "boolean"
          }
        }
      },
      "OrderPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "per_page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "StatusChange": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "ShipmentInput": {
        "type": "object",
        "required": [
          "carrier"
        ],
        "properties": {
          "carrier": {
            "type": "string"
          },
          "tracking_number": {
            "type": "string"
          }
        }
      },
      "NoteInput": {
        "type": "object",
        "required": [
          "note"
        ],
        "properties": {
          "note": {
            "type": "string"
          }
        }
      },
      "CustomerSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "phone",
          "email",
          "orders_count",
          "total_spent",
          "last_order_at",
          "created_at",
          "marketing_consent",
          "anonymized"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "marketing_consent": {
            "type": "boolean"
          },
          "anonymized": {
            "type": "boolean",
            "description": "Erased under PDPL"
          },
          "orders_count": {
            "type": "integer"
          },
          "total_spent": {
            "$ref": "#/components/schemas/Money"
          },
          "last_order_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CustomerPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "per_page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "ReturnStatus": {
        "type": "string",
        "enum": [
          "requested",
          "approved",
          "rejected",
          "received",
          "refunded",
          "exchanged"
        ]
      },
      "ReturnReason": {
        "type": "string",
        "enum": [
          "wrong_size",
          "damaged",
          "not_as_described",
          "wrong_item",
          "changed_mind",
          "other"
        ]
      },
      "ReturnResolution": {
        "type": "string",
        "enum": [
          "refund",
          "exchange"
        ]
      },
      "ReturnItem": {
        "type": "object",
        "required": [
          "order_item_id",
          "title",
          "variant_title",
          "image_url",
          "unit_price",
          "quantity",
          "exchange_variant_id",
          "exchange_title"
        ],
        "properties": {
          "order_item_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "variant_title": {
            "type": "string"
          },
          "image_url": {
            "type": "string"
          },
          "unit_price": {
            "type": "integer",
            "format": "int64"
          },
          "quantity": {
            "type": "integer"
          },
          "exchange_variant_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "exchange_title": {
            "type": "string"
          },
          "exchange_price": {
            "type": "integer",
            "format": "int64"
          }
        }
      },
      "Return": {
        "type": "object",
        "required": [
          "id",
          "number",
          "order_id",
          "order_number",
          "status",
          "resolution",
          "reason",
          "note",
          "photos",
          "merchant_note",
          "restocked",
          "refund_amount",
          "refund_method",
          "suggested_refund",
          "max_refund",
          "exchange_order_id",
          "payment_method",
          "customer_name",
          "customer_phone",
          "currency",
          "items",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "integer"
          },
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "order_number": {
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/ReturnStatus"
          },
          "resolution": {
            "$ref": "#/components/schemas/ReturnResolution"
          },
          "reason": {
            "$ref": "#/components/schemas/ReturnReason"
          },
          "note": {
            "type": "string"
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "merchant_note": {
            "type": "string"
          },
          "restocked": {
            "type": "boolean"
          },
          "refund_amount": {
            "type": "integer",
            "format": "int64"
          },
          "refund_method": {
            "type": "string",
            "description": "card | manual"
          },
          "suggested_refund": {
            "type": "integer",
            "format": "int64",
            "description": "Items' value after the order's discount"
          },
          "max_refund": {
            "type": "integer",
            "format": "int64",
            "description": "What's left to refund on the order"
          },
          "exchange_order_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "price_difference": {
            "type": "integer",
            "format": "int64",
            "description": "Exchange only: positive = the shopper pays it, negative = refunded to them"
          },
          "payment_method": {
            "type": "string"
          },
          "customer_name": {
            "type": "string"
          },
          "customer_phone": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReturnItem"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReturnSummary": {
        "type": "object",
        "required": [
          "id",
          "number",
          "order_id",
          "order_number",
          "status",
          "resolution",
          "reason",
          "customer_name",
          "customer_phone",
          "refund_amount",
          "currency",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "integer"
          },
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "order_number": {
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/ReturnStatus"
          },
          "resolution": {
            "$ref": "#/components/schemas/ReturnResolution"
          },
          "reason": {
            "$ref": "#/components/schemas/ReturnReason"
          },
          "customer_name": {
            "type": "string"
          },
          "customer_phone": {
            "type": "string"
          },
          "refund_amount": {
            "type": "integer",
            "format": "int64"
          },
          "currency": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReturnPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "per_page",
          "open"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReturnSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "open": {
            "type": "integer",
            "description": "Returns waiting for the merchant"
          }
        }
      },
      "OAuthTokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "code",
          "redirect_uri",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code"
            ]
          },
          "code": {
            "type": "string"
          },
          "redirect_uri": {
            "type": "string",
            "description": "The redirect_uri the authorisation used"
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "description": "Or send client_id/client_secret with HTTP Basic auth"
          }
        }
      },
      "OAuthGrant": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "scope",
          "store_id"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "example": "nouq_at_…"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "bearer"
            ]
          },
          "scope": {
            "type": "string",
            "example": "products:read orders:read"
          },
          "store_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unsupported_grant_type"
            ]
          },
          "error_description": {
            "type": "string"
          }
        }
      },
      "TokenInfo": {
        "type": "object",
        "required": [
          "store_id",
          "installation_id",
          "app_id",
          "scopes"
        ],
        "properties": {
          "store_id": {
            "type": "string",
            "format": "uuid"
          },
          "installation_id": {
            "type": "string",
            "format": "uuid"
          },
          "app_id": {
            "type": "string",
            "format": "uuid"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "required": [
          "id",
          "url",
          "topics",
          "status",
          "disabled_reason",
          "consecutive_failures",
          "installation_id",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "topics": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "order.created",
                "order.updated",
                "order.refunded",
                "product.created",
                "product.updated",
                "product.deleted",
                "app.uninstalled"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "disabled_reason": {
            "type": "string",
            "enum": [
              "",
              "failing",
              "uninstalled"
            ],
            "description": "failing: five deliveries in a row ran out of retries. Saving the subscription re-enables it."
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "installation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The app that made it; null when the merchant did"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret (whsec_…), only right after creation or rotation"
          }
        }
      },
      "WebhookSubscriptionInput": {
        "type": "object",
        "required": [
          "url",
          "topics"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "https only"
          },
          "topics": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "subscription_id",
          "event_id",
          "topic",
          "status",
          "attempts",
          "next_attempt_at",
          "last_status",
          "last_error",
          "last_duration_ms",
          "replay_of",
          "created_at",
          "delivered_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "subscription_id": {
            "type": "string",
            "format": "uuid"
          },
          "event_id": {
            "type": "string",
            "format": "uuid",
            "description": "Same for every delivery and replay of one event: de-duplicate on it"
          },
          "topic": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "next_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The receiver's HTTP status"
          },
          "last_error": {
            "type": "string"
          },
          "last_response": {
            "type": "string",
            "description": "First 2 KB of the receiver's answer (detail only)"
          },
          "last_duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "replay_of": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payload": {
            "description": "The exact body sent (detail only)"
          },
          "store_id": {
            "type": "string",
            "format": "uuid",
            "description": "Developer console only"
          },
          "store_name": {
            "type": "string",
            "description": "Developer console only"
          },
          "url": {
            "type": "string",
            "description": "Developer console only"
          }
        }
      },
      "WebhookDeliveryPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "per_page"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "AppBilling": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "free",
              "trialing",
              "active",
              "past_due"
            ]
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "trial_ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "period_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payment_due": {
            "type": "boolean",
            "description": "The merchant has an unpaid fee for this app"
          }
        }
      }
    }
  }
}