{
  "openapi": "3.1.0",
  "info": {
    "title": "QRdx API",
    "version": "1.0.0",
    "description": "Create and manage dynamic QR codes, read scan analytics, report conversions and subscribe to events over HTTP.\n\n- **Base URL:** `https://api.qrdx.dev/v1`. The same routes answer at `https://qrdx.dev/api/v1`.\n- **Auth:** an OAuth 2.0 access token, `Authorization: Bearer <token>` ([how to get one](https://qrdx.dev/docs/developers/rest-api#get-an-access-token)). Tokens act on the token owner's personal workspace. API keys aren't available yet. The image API (`GET /qr`) needs no token.\n- **Errors:** `{ \"code\", \"message\", \"status\" }`, plus `docs` when a page explains the fix ([every code](https://qrdx.dev/docs/developers/errors)).\n- **Limits:** a daily quota per workspace (1,000 requests on Free, 10,000 on Pro, 100,000 on Business), reported in `X-RateLimit-*` headers, plus per-minute limits. Over a limit you get `429` with `Retry-After`.",
    "contact": {
      "name": "QRdx",
      "url": "https://qrdx.dev/docs/developers/quickstart"
    },
    "license": {
      "name": "AGPL-3.0",
      "url": "https://github.com/bucharitesh/qrdx/blob/main/LICENSE"
    }
  },
  "servers": [
    {
      "url": "https://api.qrdx.dev/v1",
      "description": "QRdx API"
    }
  ],
  "externalDocs": {
    "description": "Quickstart: your first request in 2 minutes",
    "url": "https://qrdx.dev/docs/developers/quickstart"
  },
  "tags": [
    {
      "name": "QR images",
      "description": "Keyless QR code images for `<img>` tags, emails and scripts."
    },
    {
      "name": "Codes",
      "description": "Dynamic QR codes: a short link you can point anywhere after printing."
    },
    {
      "name": "Scans",
      "description": "Scan analytics of a code."
    },
    {
      "name": "Conversions",
      "description": "Sales and sign-ups attributed to the scan behind them."
    },
    {
      "name": "Hooks",
      "description": "REST Hooks subscriptions for Zapier, Make and n8n."
    },
    {
      "name": "Themes",
      "description": "The user's saved QR designs."
    },
    {
      "name": "Profile",
      "description": "The user behind the token."
    }
  ],
  "security": [
    {
      "oauth2": []
    }
  ],
  "paths": {
    "/qr": {
      "get": {
        "operationId": "getQrImage",
        "summary": "Render a QR code image",
        "tags": ["QR images"],
        "security": [],
        "description": "A QR code image as SVG (default), PNG or EPS, with no token.\n\n**No token needed**, and it doesn't use your daily quota: put the URL straight into an `<img>` tag. The same query always returns the same bytes, so responses are cached for a year. Colors are hex without `#` (or with `#` encoded as `%23`). Limit: 300 requests a minute per IP address. More in the [Image API guide](https://qrdx.dev/docs/developers/image-api).",
        "parameters": [
          {
            "name": "data",
            "in": "query",
            "required": true,
            "description": "Text or URL to encode",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048
            },
            "example": "https://qrdx.dev"
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "description": "Width and height in pixels",
            "schema": {
              "default": 512,
              "type": "integer",
              "minimum": 64,
              "maximum": 2048
            }
          },
          {
            "name": "fg",
            "in": "query",
            "required": false,
            "description": "Foreground (module) color as hex",
            "schema": {
              "default": "#000000",
              "type": "string",
              "pattern": "^#?(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
            }
          },
          {
            "name": "bg",
            "in": "query",
            "required": false,
            "description": "Background color as hex",
            "schema": {
              "default": "#ffffff",
              "type": "string",
              "pattern": "^#?(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
            }
          },
          {
            "name": "eyeColor",
            "in": "query",
            "required": false,
            "description": "Color of the three corner eyes (defaults to fg)",
            "schema": {
              "type": "string",
              "pattern": "^#?(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
            }
          },
          {
            "name": "dotColor",
            "in": "query",
            "required": false,
            "description": "Color of the dot inside each corner eye (defaults to fg)",
            "schema": {
              "type": "string",
              "pattern": "^#?(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
            }
          },
          {
            "name": "body",
            "in": "query",
            "required": false,
            "description": "Shape of the data modules",
            "schema": {
              "default": "square",
              "type": "string",
              "enum": [
                "circle",
                "circle-large",
                "square",
                "diamond",
                "circle-mixed",
                "pacman",
                "rounded",
                "small-square",
                "vertical-line",
                "horizontal-line",
                "liquid",
                "liquid-horizontal",
                "liquid-vertical",
                "classy",
                "classy-rounded",
                "leaf",
                "plus",
                "hexagon",
                "star",
                "heart",
                "random-dots",
                "mosaic",
                "octagon"
              ]
            }
          },
          {
            "name": "eye",
            "in": "query",
            "required": false,
            "description": "Shape of the corner eye frames",
            "schema": {
              "default": "square",
              "type": "string",
              "enum": [
                "square",
                "rounded",
                "gear",
                "circle",
                "diya",
                "extra-rounded",
                "message",
                "pointy",
                "leaf",
                "drop",
                "octagon",
                "bevel",
                "squircle",
                "cushion",
                "shield",
                "petal",
                "leaf-reverse"
              ]
            }
          },
          {
            "name": "dot",
            "in": "query",
            "required": false,
            "description": "Shape of the dot inside each corner eye",
            "schema": {
              "default": "square",
              "type": "string",
              "enum": [
                "square",
                "rounded",
                "circle",
                "message",
                "message-reverse",
                "diya",
                "diya-reverse",
                "rounded-triangle",
                "leaf",
                "drop",
                "plus",
                "cross-rounded",
                "hexagon",
                "octagon",
                "flower",
                "heart",
                "squircle",
                "dots",
                "extra-rounded",
                "leaf-reverse",
                "drop-reverse"
              ]
            }
          },
          {
            "name": "level",
            "in": "query",
            "required": false,
            "description": "Error correction level: L 7%, M 15%, Q 25%, H 30%",
            "schema": {
              "default": "Q",
              "type": "string",
              "enum": ["L", "M", "Q", "H"]
            }
          },
          {
            "name": "margin",
            "in": "query",
            "required": false,
            "description": "Quiet zone around the code, in modules",
            "schema": {
              "default": 4,
              "type": "integer",
              "minimum": 0,
              "maximum": 16
            }
          },
          {
            "name": "template",
            "in": "query",
            "required": false,
            "description": "Decorative frame template",
            "schema": {
              "type": "string",
              "enum": [
                "default",
                "Arrow",
                "StandardBox",
                "SquareBorder",
                "StrikedBox",
                "Halloween"
              ]
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Image format",
            "schema": {
              "default": "svg",
              "type": "string",
              "enum": ["svg", "png", "eps"]
            },
            "example": "svg"
          }
        ],
        "responses": {
          "200": {
            "description": "The image. `Cache-Control: public, max-age=31536000, immutable`; CORS is open to every origin.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                },
                "example": "<svg height=\"512\" viewBox=\"0 0 512 512\" width=\"512\" xmlns=\"http://www.w3.org/2000/svg\"><rect fill=\"#ffffff\" height=\"512\" width=\"512\" x=\"0\" y=\"0\"/><g fill=\"#000000\">…</g></svg>"
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "image/png"
                }
              },
              "application/postscript": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "application/postscript"
                }
              }
            }
          },
          "400": {
            "description": "`VALIDATION_ERROR`: a query parameter is missing or invalid; `data.details` lists `{ param, message }`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "VALIDATION_ERROR",
                  "message": "Invalid query parameters",
                  "status": 400,
                  "data": {
                    "details": [
                      {
                        "param": "data",
                        "message": "Required: the text or URL to encode"
                      }
                    ]
                  }
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`: over 300 requests a minute from one IP address.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "RATE_LIMITED",
                  "message": "Rate limit exceeded. Please try again later.",
                  "status": 429
                }
              }
            }
          }
        }
      }
    },
    "/codes": {
      "get": {
        "operationId": "listCodes",
        "summary": "List codes",
        "tags": ["Codes"],
        "security": [
          {
            "oauth2": ["codes:read"]
          }
        ],
        "description": "The dynamic codes of the token owner's personal workspace, newest first.\n\nPage through them with `limit` and `offset`; `pagination.total` counts every match.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Which codes: `active` (default), `archived` or `all`",
            "schema": {
              "default": "active",
              "type": "string",
              "enum": ["active", "archived", "all"]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Search: titles, destinations, notes and tags contain it, or it is the exact back-half or short link",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Codes per page (1–100)",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Codes to skip, for the next page",
            "schema": {
              "default": 0,
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of codes",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Code"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991,
                          "description": "Codes matching the filters"
                        },
                        "limit": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        },
                        "offset": {
                          "type": "integer",
                          "minimum": -9007199254740991,
                          "maximum": 9007199254740991
                        }
                      },
                      "required": ["total", "limit", "offset"]
                    }
                  },
                  "required": ["data", "pagination"]
                },
                "examples": {
                  "page": {
                    "value": {
                      "data": [
                        {
                          "id": "cm5code0spring0menu00001",
                          "slug": "aB3dE5f",
                          "shortLink": "https://qrdx.dev/q/aB3dE5f",
                          "defaultShortLink": "https://qrdx.dev/q/aB3dE5f",
                          "domain": null,
                          "destinationUrl": "https://example.com/menu",
                          "title": "Spring menu",
                          "contentType": "url",
                          "status": "active",
                          "style": {
                            "fgColor": "#111827",
                            "bgColor": "#ffffff",
                            "bodyPattern": "circle",
                            "cornerEyePattern": "rounded",
                            "level": "Q"
                          },
                          "scanCount": 128,
                          "lastScannedAt": "2026-01-12T18:04:11.000Z",
                          "archivedAt": null,
                          "createdAt": "2026-01-05T09:30:00.000Z",
                          "updatedAt": "2026-01-05T09:30:00.000Z",
                          "organizationId": null
                        }
                      ],
                      "pagination": {
                        "total": 1,
                        "limit": 50,
                        "offset": 0
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_CodesRead"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      },
      "post": {
        "operationId": "createCodes",
        "summary": "Create codes",
        "tags": ["Codes"],
        "security": [
          {
            "oauth2": ["codes:write"]
          }
        ],
        "description": "Creates one dynamic code, or up to 100 at once.\n\nSend an object for one code, or an array for several (Pro). The response's `shortLink` is what the QR code encodes: point it somewhere else later with PATCH and the printed code keeps working. Render it with the image API, for example `https://api.qrdx.dev/v1/qr?data=<shortLink>`. Free workspaces have 10 active codes. Create requests are limited to 20 a minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CodeInput"
                  },
                  {
                    "minItems": 1,
                    "maxItems": 100,
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/CodeInput"
                    },
                    "description": "Up to 100 codes at once (Pro)"
                  }
                ]
              },
              "examples": {
                "one": {
                  "summary": "One code",
                  "value": {
                    "destinationUrl": "https://example.com/menu",
                    "title": "Spring menu"
                  }
                },
                "several": {
                  "summary": "Several codes (Pro)",
                  "value": [
                    {
                      "destinationUrl": "https://example.com/table/1",
                      "title": "Table 1"
                    },
                    {
                      "destinationUrl": "https://example.com/table/2",
                      "title": "Table 2"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created: `{ data: Code }` for an object, `{ data: Code[] }` for an array",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Code"
                        }
                      },
                      "required": ["data"]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Code"
                          },
                          "description": "The created codes, in request order"
                        }
                      },
                      "required": ["data"]
                    }
                  ]
                },
                "examples": {
                  "one": {
                    "value": {
                      "data": {
                        "id": "cm5code0spring0menu00001",
                        "slug": "aB3dE5f",
                        "shortLink": "https://qrdx.dev/q/aB3dE5f",
                        "defaultShortLink": "https://qrdx.dev/q/aB3dE5f",
                        "domain": null,
                        "destinationUrl": "https://example.com/menu",
                        "title": "Spring menu",
                        "contentType": "url",
                        "status": "active",
                        "style": {
                          "fgColor": "#111827",
                          "bgColor": "#ffffff",
                          "bodyPattern": "circle",
                          "cornerEyePattern": "rounded",
                          "level": "Q"
                        },
                        "scanCount": 128,
                        "lastScannedAt": "2026-01-12T18:04:11.000Z",
                        "archivedAt": null,
                        "createdAt": "2026-01-05T09:30:00.000Z",
                        "updatedAt": "2026-01-05T09:30:00.000Z",
                        "organizationId": null
                      }
                    }
                  },
                  "several": {
                    "value": {
                      "data": [
                        {
                          "id": "cm5code0spring0menu00001",
                          "slug": "aB3dE5f",
                          "shortLink": "https://qrdx.dev/q/aB3dE5f",
                          "defaultShortLink": "https://qrdx.dev/q/aB3dE5f",
                          "domain": null,
                          "destinationUrl": "https://example.com/menu",
                          "title": "Spring menu",
                          "contentType": "url",
                          "status": "active",
                          "style": {
                            "fgColor": "#111827",
                            "bgColor": "#ffffff",
                            "bodyPattern": "circle",
                            "cornerEyePattern": "rounded",
                            "level": "Q"
                          },
                          "scanCount": 128,
                          "lastScannedAt": "2026-01-12T18:04:11.000Z",
                          "archivedAt": null,
                          "createdAt": "2026-01-05T09:30:00.000Z",
                          "updatedAt": "2026-01-05T09:30:00.000Z",
                          "organizationId": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError_InvalidSlug_TooManyCodes_DomainNotVerified"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "402": {
            "$ref": "#/components/responses/Error402_PlanRequired_LimitReached"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_Forbidden_LimitReached_CodesWrite"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Error409_SlugTaken"
          },
          "413": {
            "$ref": "#/components/responses/Error413_PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/Error415_UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/Error422_UnsafeDestination"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          },
          "503": {
            "$ref": "#/components/responses/Error503_SlugUnavailable_Unavailable"
          }
        }
      }
    },
    "/codes/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The code's `id`",
          "schema": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "example": "cm5code0spring0menu00001"
        }
      ],
      "get": {
        "operationId": "getCode",
        "summary": "Get a code",
        "tags": ["Codes"],
        "security": [
          {
            "oauth2": ["codes:read"]
          }
        ],
        "description": "One dynamic code of the token owner's personal workspace.",
        "responses": {
          "200": {
            "description": "The code",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Code"
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "code": {
                    "value": {
                      "data": {
                        "id": "cm5code0spring0menu00001",
                        "slug": "aB3dE5f",
                        "shortLink": "https://qrdx.dev/q/aB3dE5f",
                        "defaultShortLink": "https://qrdx.dev/q/aB3dE5f",
                        "domain": null,
                        "destinationUrl": "https://example.com/menu",
                        "title": "Spring menu",
                        "contentType": "url",
                        "status": "active",
                        "style": {
                          "fgColor": "#111827",
                          "bgColor": "#ffffff",
                          "bodyPattern": "circle",
                          "cornerEyePattern": "rounded",
                          "level": "Q"
                        },
                        "scanCount": 128,
                        "lastScannedAt": "2026-01-12T18:04:11.000Z",
                        "archivedAt": null,
                        "createdAt": "2026-01-05T09:30:00.000Z",
                        "updatedAt": "2026-01-05T09:30:00.000Z",
                        "organizationId": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_CodesRead"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      },
      "patch": {
        "operationId": "updateCode",
        "summary": "Update a code",
        "tags": ["Codes"],
        "security": [
          {
            "oauth2": ["codes:write"]
          }
        ],
        "description": "Changes a code's destination, title, design, domain or back-half, or archives it.\n\nSend only the fields to change. `archived: true` stops redirects and frees a plan slot; `false` restores the code. The destination, title and design stay editable on every plan.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CodeUpdate"
              },
              "examples": {
                "destination": {
                  "summary": "Point the code somewhere else",
                  "value": {
                    "destinationUrl": "https://example.com/summer-menu"
                  }
                },
                "archive": {
                  "summary": "Archive",
                  "value": {
                    "archived": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated code",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Code"
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "code": {
                    "value": {
                      "data": {
                        "id": "cm5code0spring0menu00001",
                        "slug": "aB3dE5f",
                        "shortLink": "https://qrdx.dev/q/aB3dE5f",
                        "defaultShortLink": "https://qrdx.dev/q/aB3dE5f",
                        "domain": null,
                        "destinationUrl": "https://example.com/summer-menu",
                        "title": "Spring menu",
                        "contentType": "url",
                        "status": "active",
                        "style": {
                          "fgColor": "#111827",
                          "bgColor": "#ffffff",
                          "bodyPattern": "circle",
                          "cornerEyePattern": "rounded",
                          "level": "Q"
                        },
                        "scanCount": 128,
                        "lastScannedAt": "2026-01-12T18:04:11.000Z",
                        "archivedAt": null,
                        "createdAt": "2026-01-05T09:30:00.000Z",
                        "updatedAt": "2026-01-05T09:30:00.000Z",
                        "organizationId": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError_InvalidSlug_DomainNotVerified"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "402": {
            "$ref": "#/components/responses/Error402_PlanRequired_LimitReached"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_Forbidden_LimitReached_CodesWrite"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Error409_SlugTaken"
          },
          "413": {
            "$ref": "#/components/responses/Error413_PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/Error415_UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/Error422_UnsafeDestination"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          },
          "503": {
            "$ref": "#/components/responses/Error503_SlugUnavailable_Unavailable"
          }
        }
      },
      "delete": {
        "operationId": "deleteCode",
        "summary": "Delete a code",
        "tags": ["Codes"],
        "security": [
          {
            "oauth2": ["codes:write"]
          }
        ],
        "description": "Permanently deletes a code and its scan history.\n\nIts short link stops working, and this can't be undone. To pause a code and keep its scans, archive it with PATCH instead.",
        "responses": {
          "200": {
            "description": "Deleted",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean",
                          "const": true
                        }
                      },
                      "required": ["id", "deleted"]
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "deleted": {
                    "value": {
                      "data": {
                        "id": "cm5code0spring0menu00001",
                        "deleted": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_Forbidden_CodesWrite"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      }
    },
    "/codes/{id}/scans": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The code's `id`",
          "schema": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "example": "cm5code0spring0menu00001"
        }
      ],
      "get": {
        "operationId": "getCodeScans",
        "summary": "Get scan analytics",
        "tags": ["Scans"],
        "security": [
          {
            "oauth2": ["codes:read"]
          }
        ],
        "description": "Scan analytics of a code for a range of days.\n\nTotals, a daily series and the top countries, cities, devices, systems, browsers and referrers. The range is clamped to how long the plan keeps analytics. With `limit` above 0 the response also lists individual scans, newest first: pass the response's `nextBefore` as `before` for the next page.",
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "Days to cover; defaults to 30 days, or the longest range the plan keeps",
            "schema": {
              "type": "string",
              "enum": ["7d", "30d", "90d", "365d", "1095d"]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Individual scans to include (0–500; 0 = totals only)",
            "schema": {
              "default": 0,
              "type": "integer",
              "minimum": 0,
              "maximum": 500
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Only scans before this time: the previous `nextBefore`",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Analytics for the range",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ScanAnalytics"
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "analytics": {
                    "value": {
                      "data": {
                        "codeId": "cm5code0spring0menu00001",
                        "range": "7d",
                        "retentionDays": 30,
                        "from": "2026-01-06T00:00:00.000Z",
                        "days": 7,
                        "total": 42,
                        "unique": 37,
                        "series": [
                          {
                            "date": "2026-01-06",
                            "count": 3
                          },
                          {
                            "date": "2026-01-07",
                            "count": 9
                          }
                        ],
                        "countries": [
                          {
                            "value": "DE",
                            "count": 30
                          }
                        ],
                        "cities": [
                          {
                            "city": "Berlin",
                            "country": "DE",
                            "count": 21
                          }
                        ],
                        "devices": [
                          {
                            "value": "mobile",
                            "count": 39
                          }
                        ],
                        "os": [
                          {
                            "value": "iOS",
                            "count": 25
                          }
                        ],
                        "browsers": [
                          {
                            "value": "Safari",
                            "count": 24
                          }
                        ],
                        "referrers": [
                          {
                            "value": null,
                            "count": 42
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_CodesRead"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      }
    },
    "/conversions": {
      "post": {
        "operationId": "createConversion",
        "summary": "Report a conversion",
        "tags": ["Conversions"],
        "security": [
          {
            "oauth2": ["codes:write"]
          },
          {}
        ],
        "description": "Attributes a sale, sign-up or other conversion to the scan that brought the visitor (Business).\n\nFrom your server, send a token and the scan's `sid`, or the code id as `code`. From the browser, `conversions.js` sends `sid` with no token, for codes with conversion tracking turned on. An `id`, such as your order id, makes retries safe: a repeat returns 200 with `duplicate: true`. More in [Tracking and conversions](https://qrdx.dev/docs/tracking-and-conversions).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sid": {
                    "description": "The `qrdx_sid` the short link appended to your page URL",
                    "type": "string",
                    "maxLength": 200
                  },
                  "code": {
                    "description": "The code id, when no scan is known (token requests only)",
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{1,64}$"
                  },
                  "name": {
                    "default": "conversion",
                    "description": "What happened, e.g. `purchase` or `signup`",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "pattern": "^[\\p{L}\\p{N} _.:/-]+$"
                  },
                  "value": {
                    "description": "Amount, as a number or string with up to 2 decimals",
                    "type": ["number", "string"]
                  },
                  "currency": {
                    "description": "ISO 4217 code; `USD` when a value has none",
                    "type": "string",
                    "pattern": "^[A-Z]{3}$"
                  },
                  "id": {
                    "description": "Your id for it (order id, ...); repeats are ignored",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "metadata": {
                    "description": "Up to 20 extra keys",
                    "type": "object",
                    "propertyNames": {
                      "type": "string",
                      "pattern": "^[A-Za-z0-9_.-]{1,40}$"
                    },
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 200
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              },
              "example": {
                "sid": "eyJjIjoiY201Y29kZSIsInMiOiJjbTVzY2FuIn0.sig",
                "name": "purchase",
                "value": 49.9,
                "currency": "EUR",
                "id": "order_1042"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already recorded (same `id`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Conversion id (derived from `id` when you send one)"
                    },
                    "codeId": {
                      "type": "string"
                    },
                    "scanId": {
                      "description": "The attributed scan, or null when reported by code id",
                      "type": ["string", "null"]
                    },
                    "duplicate": {
                      "type": "boolean",
                      "description": "True when a conversion with the same `id` was already recorded (status 200)"
                    }
                  },
                  "required": ["id", "codeId", "scanId", "duplicate"]
                },
                "examples": {
                  "duplicate": {
                    "value": {
                      "id": "cv_3f1c2a9d0b7e4f6a8c5d1e2f3a4b5c6d",
                      "codeId": "cm5code0spring0menu00001",
                      "scanId": "cm5scan0berlin0000000001",
                      "duplicate": true
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Conversion id (derived from `id` when you send one)"
                    },
                    "codeId": {
                      "type": "string"
                    },
                    "scanId": {
                      "description": "The attributed scan, or null when reported by code id",
                      "type": ["string", "null"]
                    },
                    "duplicate": {
                      "type": "boolean",
                      "description": "True when a conversion with the same `id` was already recorded (status 200)"
                    }
                  },
                  "required": ["id", "codeId", "scanId", "duplicate"]
                },
                "examples": {
                  "recorded": {
                    "value": {
                      "id": "cv_3f1c2a9d0b7e4f6a8c5d1e2f3a4b5c6d",
                      "codeId": "cm5code0spring0menu00001",
                      "scanId": "cm5scan0berlin0000000001",
                      "duplicate": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError_SidRequired"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "402": {
            "$ref": "#/components/responses/Error402_PlanRequired"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_ConversionsOff_CodesWrite"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound_InvalidSid"
          },
          "413": {
            "$ref": "#/components/responses/Error413_PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      }
    },
    "/hooks": {
      "get": {
        "operationId": "listHooks",
        "summary": "List hooks",
        "tags": ["Hooks"],
        "security": [
          {
            "oauth2": ["codes:read"]
          }
        ],
        "description": "The workspace's hooks: REST Hooks subscriptions and webhook endpoints.\n\n`target_url` shows only the origin unless the token has `codes:write`. Listing works on every plan; subscribing needs Business.",
        "responses": {
          "200": {
            "description": "The hooks",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Hook"
                      }
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "hooks": {
                    "value": {
                      "data": [
                        {
                          "id": "cm5hook0zapier0scans00001",
                          "target_url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
                          "events": ["scan.created"],
                          "event": "scan.created",
                          "active": true,
                          "created_at": "2026-01-05T09:30:00.000Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_CodesRead"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      },
      "post": {
        "operationId": "createHook",
        "summary": "Subscribe to events",
        "tags": ["Hooks"],
        "security": [
          {
            "oauth2": ["codes:write"]
          }
        ],
        "description": "Subscribes a URL to events, the REST Hooks way Zapier, Make and n8n call it (Business).\n\nQRdx posts each event to the URL, signed with the `secret` in the response, which is shown only once ([verifying signatures](https://qrdx.dev/docs/webhooks)). Send the URL as `target_url` (or `targetUrl`, `hookUrl`, `hook_url` or `url`) and one `event` or several `events`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "target_url": {
                    "type": "string"
                  },
                  "targetUrl": {
                    "type": "string"
                  },
                  "hookUrl": {
                    "type": "string"
                  },
                  "hook_url": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "scan.created",
                      "scan.milestone",
                      "code.created",
                      "code.updated",
                      "form.submitted"
                    ]
                  },
                  "events": {
                    "minItems": 1,
                    "maxItems": 5,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "scan.created",
                        "scan.milestone",
                        "code.created",
                        "code.updated",
                        "form.submitted"
                      ]
                    }
                  }
                }
              },
              "example": {
                "target_url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
                "event": "scan.created"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscribed; `secret` is shown only here",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "target_url": {
                      "type": "string",
                      "description": "Where events are posted; only the origin unless the caller manages hooks (`codes:write`)"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "scan.created",
                          "scan.milestone",
                          "code.created",
                          "code.updated",
                          "form.submitted"
                        ]
                      }
                    },
                    "event": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "scan.created",
                            "scan.milestone",
                            "code.created",
                            "code.updated",
                            "form.submitted"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "The event, when the subscription has exactly one"
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Created (ISO 8601, UTC)"
                    },
                    "secret": {
                      "type": "string",
                      "description": "Signing secret for the `X-QRdx-Signature` header, shown only in this response"
                    }
                  },
                  "required": [
                    "id",
                    "target_url",
                    "events",
                    "event",
                    "active",
                    "created_at",
                    "secret"
                  ]
                },
                "examples": {
                  "created": {
                    "value": {
                      "id": "cm5hook0zapier0scans00001",
                      "target_url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
                      "events": ["scan.created"],
                      "event": "scan.created",
                      "active": true,
                      "created_at": "2026-01-05T09:30:00.000Z",
                      "secret": "whsec_9f2d7c1b8e4a6f3d0c5b2a7e1f8d4c6b"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "402": {
            "$ref": "#/components/responses/Error402_PlanRequired_LimitReached"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_Forbidden_LimitReached_CodesWrite"
          },
          "413": {
            "$ref": "#/components/responses/Error413_PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/Error415_UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      }
    },
    "/hooks/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The hook's `id`",
          "schema": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "example": "cm5hook0zapier0scans00001"
        }
      ],
      "get": {
        "operationId": "getHook",
        "summary": "Get a hook",
        "tags": ["Hooks"],
        "security": [
          {
            "oauth2": ["codes:read"]
          }
        ],
        "description": "One hook of the workspace.",
        "responses": {
          "200": {
            "description": "The hook",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Hook"
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "hook": {
                    "value": {
                      "data": {
                        "id": "cm5hook0zapier0scans00001",
                        "target_url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
                        "events": ["scan.created"],
                        "event": "scan.created",
                        "active": true,
                        "created_at": "2026-01-05T09:30:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_CodesRead"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteHook",
        "summary": "Unsubscribe",
        "tags": ["Hooks"],
        "security": [
          {
            "oauth2": ["codes:write"]
          }
        ],
        "description": "Unsubscribes a hook: events stop at once.\n\nThis is the REST Hooks unsubscribe Zapier calls when a Zap is turned off. It works on every plan.",
        "responses": {
          "204": {
            "description": "Unsubscribed (no body)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_Forbidden_CodesWrite"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      }
    },
    "/hooks/sample": {
      "get": {
        "operationId": "getHookSamples",
        "summary": "Get sample events",
        "tags": ["Hooks"],
        "security": [
          {
            "oauth2": ["codes:read"]
          }
        ],
        "description": "Sample payloads of an event, shaped exactly like real deliveries.\n\nZapier's “perform list”: up to three payloads built from the workspace's recent data, newest first, or a fixed example when it has none. Use it to map fields while building a Zap.",
        "parameters": [
          {
            "name": "event",
            "in": "query",
            "required": true,
            "description": "The event to sample",
            "schema": {
              "type": "string",
              "enum": [
                "scan.created",
                "scan.milestone",
                "code.created",
                "code.updated",
                "form.submitted"
              ]
            },
            "example": "scan.created"
          }
        ],
        "responses": {
          "200": {
            "description": "Sample payloads (a bare array)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  "description": "Payloads shaped exactly like real deliveries, newest first: recent data from the workspace, or a fixed example when it has none"
                },
                "examples": {
                  "scan.created": {
                    "value": [
                      {
                        "id": "evt_sample",
                        "type": "scan.created",
                        "created_at": "2026-01-05T09:30:00.000Z",
                        "data": {
                          "window_started_at": "2026-01-05T09:30:00.000Z",
                          "window_seconds": 60,
                          "count": 1,
                          "scans": [
                            {
                              "scan_id": "cm0sample0scan0000000000",
                              "code": {
                                "id": "cm0sample0code0000000000",
                                "slug": "aB3dE5f",
                                "title": "Spring menu"
                              },
                              "scanned_at": "2026-01-05T09:30:00.000Z",
                              "country": "DE",
                              "region": "BE",
                              "city": "Berlin",
                              "device_type": "mobile",
                              "os": "iOS",
                              "browser": "Safari",
                              "referer": null
                            }
                          ]
                        }
                      }
                    ]
                  },
                  "scan.milestone": {
                    "value": [
                      {
                        "id": "evt_sample",
                        "type": "scan.milestone",
                        "created_at": "2026-01-05T09:30:00.000Z",
                        "data": {
                          "milestone": 100,
                          "code": {
                            "id": "cm0sample0code0000000000",
                            "slug": "aB3dE5f",
                            "title": "Spring menu",
                            "scan_count": 100
                          },
                          "scan": {
                            "scan_id": "cm0sample0scan0000000000",
                            "code": {
                              "id": "cm0sample0code0000000000",
                              "slug": "aB3dE5f",
                              "title": "Spring menu"
                            },
                            "scanned_at": "2026-01-05T09:30:00.000Z",
                            "country": "DE",
                            "region": "BE",
                            "city": "Berlin",
                            "device_type": "mobile",
                            "os": "iOS",
                            "browser": "Safari",
                            "referer": null
                          }
                        }
                      }
                    ]
                  },
                  "code.created": {
                    "value": [
                      {
                        "id": "evt_sample",
                        "type": "code.created",
                        "created_at": "2026-01-05T09:30:00.000Z",
                        "data": {
                          "code": {
                            "id": "cm0sample0code0000000000",
                            "slug": "aB3dE5f",
                            "title": "Spring menu",
                            "short_link": "https://qrdx.dev/q/aB3dE5f",
                            "destination_url": "https://example.com/menu",
                            "status": "active",
                            "scan_count": 100,
                            "created_at": "2026-01-05T09:30:00.000Z",
                            "updated_at": "2026-01-05T09:30:00.000Z"
                          },
                          "actor_id": "user_sample"
                        }
                      }
                    ]
                  },
                  "code.updated": {
                    "value": [
                      {
                        "id": "evt_sample",
                        "type": "code.updated",
                        "created_at": "2026-01-05T09:30:00.000Z",
                        "data": {
                          "code": {
                            "id": "cm0sample0code0000000000",
                            "slug": "aB3dE5f",
                            "title": "Spring menu",
                            "short_link": "https://qrdx.dev/q/aB3dE5f",
                            "destination_url": "https://example.com/menu",
                            "status": "active",
                            "scan_count": 100,
                            "created_at": "2026-01-05T09:30:00.000Z",
                            "updated_at": "2026-01-05T09:30:00.000Z"
                          },
                          "changes": ["destination_url"],
                          "actor_id": "user_sample"
                        }
                      }
                    ]
                  },
                  "form.submitted": {
                    "value": [
                      {
                        "id": "evt_sample",
                        "type": "form.submitted",
                        "created_at": "2026-01-05T09:30:00.000Z",
                        "data": {
                          "response_id": "cm0sample0response000000",
                          "page": {
                            "id": "cm0sample0page0000000000",
                            "title": "Feedback"
                          },
                          "code": {
                            "id": "cm0sample0code0000000000",
                            "slug": "aB3dE5f"
                          },
                          "fields": [
                            {
                              "name": "email",
                              "label": "Email",
                              "value": "ada@example.com"
                            },
                            {
                              "name": "rating",
                              "label": "How was your visit?",
                              "value": "5"
                            }
                          ],
                          "submitted_at": "2026-01-05T09:30:00.000Z"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400_ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_CodesRead"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError"
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getProfile",
        "summary": "Get the token's user",
        "tags": ["Profile"],
        "security": [
          {
            "oauth2": ["profile:read"]
          }
        ],
        "description": "The id, name, email and avatar of the user who authorized the token.\n\nTokens only: a browser session gets `401`.",
        "responses": {
          "200": {
            "description": "The user",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Profile"
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "profile": {
                    "value": {
                      "data": {
                        "id": "user_8f3k2m1n0p9q",
                        "name": "Ada Lovelace",
                        "email": "ada@example.com",
                        "image": null
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken_OAuth"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_ProfileRead_OAuth"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited_OAuth"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError_OAuth"
          }
        }
      }
    },
    "/themes": {
      "get": {
        "operationId": "listThemes",
        "summary": "List themes",
        "tags": ["Themes"],
        "security": [
          {
            "oauth2": ["themes:read"]
          }
        ],
        "description": "The user's saved QR themes: each one's name and design.\n\nTokens only: a browser session gets `401`.",
        "responses": {
          "200": {
            "description": "The themes",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Theme"
                      }
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "themes": {
                    "value": {
                      "data": [
                        {
                          "id": "cm5theme0brand0blue00001",
                          "name": "Brand blue",
                          "style": {
                            "fgColor": "#155dfc",
                            "bodyPattern": "rounded",
                            "level": "M"
                          },
                          "createdAt": "2026-01-05T09:30:00.000Z",
                          "updatedAt": "2026-01-05T09:30:00.000Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken_OAuth"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_ThemesRead_OAuth"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited_OAuth"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError_OAuth"
          }
        }
      }
    },
    "/themes/{themeId}": {
      "parameters": [
        {
          "name": "themeId",
          "in": "path",
          "required": true,
          "description": "The theme's `id`",
          "schema": {
            "type": "string"
          },
          "example": "cm5theme0brand0blue00001"
        }
      ],
      "get": {
        "operationId": "getTheme",
        "summary": "Get a theme",
        "tags": ["Themes"],
        "security": [
          {
            "oauth2": ["themes:read"]
          }
        ],
        "description": "One of the user's saved themes.",
        "responses": {
          "200": {
            "description": "The theme",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Theme"
                    }
                  },
                  "required": ["data"]
                },
                "examples": {
                  "theme": {
                    "value": {
                      "data": {
                        "id": "cm5theme0brand0blue00001",
                        "name": "Brand blue",
                        "style": {
                          "fgColor": "#155dfc",
                          "bodyPattern": "rounded",
                          "level": "M"
                        },
                        "createdAt": "2026-01-05T09:30:00.000Z",
                        "updatedAt": "2026-01-05T09:30:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error401_Unauthorized_InvalidToken_OAuth"
          },
          "403": {
            "$ref": "#/components/responses/Error403_InsufficientScope_ThemesRead_OAuth"
          },
          "404": {
            "$ref": "#/components/responses/Error404_NotFound_OAuth"
          },
          "429": {
            "$ref": "#/components/responses/Error429_RateLimited_OAuth"
          },
          "500": {
            "$ref": "#/components/responses/Error500_InternalError_OAuth"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "An OAuth 2.0 access token, sent as `Authorization: Bearer <token>`. Register an app in [Settings → Developers](https://qrdx.dev/settings/developers), send the user through authorization (PKCE S256), then exchange the code for a token. Access tokens last 1 hour; refresh tokens 30 days. [Step by step](https://qrdx.dev/docs/developers/rest-api#get-an-access-token).",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://qrdx.dev/api/oauth/authorize",
            "tokenUrl": "https://qrdx.dev/api/oauth/token",
            "refreshUrl": "https://qrdx.dev/api/oauth/token",
            "scopes": {
              "themes:read": "Read your saved QR themes",
              "profile:read": "Read your profile (name, email and avatar)",
              "codes:read": "Read your dynamic QR codes and their scan analytics",
              "codes:write": "Create, edit and delete your dynamic QR codes, report conversions and manage webhook subscriptions"
            }
          }
        }
      }
    },
    "responses": {
      "Error400_ValidationError": {
        "description": "`VALIDATION_ERROR`: The body, query or path failed validation. `data.details` lists each problem as `{ path, message }` (`{ param, message }` on the image API).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "VALIDATION_ERROR": {
                "summary": "VALIDATION_ERROR",
                "value": {
                  "code": "VALIDATION_ERROR",
                  "message": "Invalid request body",
                  "status": 400,
                  "data": {
                    "details": [
                      {
                        "path": "destinationUrl",
                        "message": "Enter a full URL, starting with https://"
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "Error400_ValidationError_InvalidSlug_DomainNotVerified": {
        "description": "`VALIDATION_ERROR`: The body, query or path failed validation. `data.details` lists each problem as `{ path, message }` (`{ param, message }` on the image API).\n\n`INVALID_SLUG`: The custom back-half (`slug`) breaks a rule: a reserved word, or the same back-half twice in one request.\n\n`DOMAIN_NOT_VERIFIED`: `domainId` points to a custom domain that isn't verified yet.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "VALIDATION_ERROR": {
                "summary": "VALIDATION_ERROR",
                "value": {
                  "code": "VALIDATION_ERROR",
                  "message": "Invalid request body",
                  "status": 400,
                  "data": {
                    "details": [
                      {
                        "path": "destinationUrl",
                        "message": "Enter a full URL, starting with https://"
                      }
                    ]
                  }
                }
              },
              "INVALID_SLUG": {
                "summary": "INVALID_SLUG",
                "value": {
                  "code": "INVALID_SLUG",
                  "message": "The custom back-half (slug) breaks a rule: a reserved word, or the same back-half twice in one request.",
                  "status": 400
                }
              },
              "DOMAIN_NOT_VERIFIED": {
                "summary": "DOMAIN_NOT_VERIFIED",
                "value": {
                  "code": "DOMAIN_NOT_VERIFIED",
                  "message": "domainId points to a custom domain that isn't verified yet.",
                  "status": 400
                }
              }
            }
          }
        }
      },
      "Error400_ValidationError_InvalidSlug_TooManyCodes_DomainNotVerified": {
        "description": "`VALIDATION_ERROR`: The body, query or path failed validation. `data.details` lists each problem as `{ path, message }` (`{ param, message }` on the image API).\n\n`INVALID_SLUG`: The custom back-half (`slug`) breaks a rule: a reserved word, or the same back-half twice in one request.\n\n`TOO_MANY_CODES`: More than 100 codes in one request. The body schema normally rejects this first, as `VALIDATION_ERROR`.\n\n`DOMAIN_NOT_VERIFIED`: `domainId` points to a custom domain that isn't verified yet.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "VALIDATION_ERROR": {
                "summary": "VALIDATION_ERROR",
                "value": {
                  "code": "VALIDATION_ERROR",
                  "message": "Invalid request body",
                  "status": 400,
                  "data": {
                    "details": [
                      {
                        "path": "destinationUrl",
                        "message": "Enter a full URL, starting with https://"
                      }
                    ]
                  }
                }
              },
              "INVALID_SLUG": {
                "summary": "INVALID_SLUG",
                "value": {
                  "code": "INVALID_SLUG",
                  "message": "The custom back-half (slug) breaks a rule: a reserved word, or the same back-half twice in one request.",
                  "status": 400
                }
              },
              "TOO_MANY_CODES": {
                "summary": "TOO_MANY_CODES",
                "value": {
                  "code": "TOO_MANY_CODES",
                  "message": "More than 100 codes in one request. The body schema normally rejects this first, as VALIDATION_ERROR.",
                  "status": 400
                }
              },
              "DOMAIN_NOT_VERIFIED": {
                "summary": "DOMAIN_NOT_VERIFIED",
                "value": {
                  "code": "DOMAIN_NOT_VERIFIED",
                  "message": "domainId points to a custom domain that isn't verified yet.",
                  "status": 400
                }
              }
            }
          }
        }
      },
      "Error400_ValidationError_SidRequired": {
        "description": "`VALIDATION_ERROR`: The body, query or path failed validation. `data.details` lists each problem as `{ path, message }` (`{ param, message }` on the image API).\n\n`SID_REQUIRED`: A conversion without the `qrdx_sid` of the scan (from the browser), or without `sid` or `code` (from a server).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "VALIDATION_ERROR": {
                "summary": "VALIDATION_ERROR",
                "value": {
                  "code": "VALIDATION_ERROR",
                  "message": "Invalid request body",
                  "status": 400,
                  "data": {
                    "details": [
                      {
                        "path": "destinationUrl",
                        "message": "Enter a full URL, starting with https://"
                      }
                    ]
                  }
                }
              },
              "SID_REQUIRED": {
                "summary": "SID_REQUIRED",
                "value": {
                  "code": "SID_REQUIRED",
                  "message": "A conversion without the qrdx_sid of the scan (from the browser), or without sid or code (from a server).",
                  "status": 400
                }
              }
            }
          }
        }
      },
      "Error401_Unauthorized_InvalidToken": {
        "description": "`UNAUTHORIZED`: No credentials: the request has no `Authorization: Bearer` header (and no QRdx browser session).\n\n`INVALID_TOKEN`: The access token is unknown, expired (tokens last one hour) or revoked.",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Bearer realm=\"QRdx API\"`, with `error=\"invalid_token\"` for a bad token",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "UNAUTHORIZED": {
                "summary": "UNAUTHORIZED",
                "value": {
                  "code": "UNAUTHORIZED",
                  "message": "Missing access token. Send an OAuth access token as \"Authorization: Bearer <token>\". To get one, register an OAuth app at https://qrdx.dev/settings/developers and follow https://qrdx.dev/docs/developers/rest-api#get-an-access-token.",
                  "status": 401,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#get-an-access-token"
                }
              },
              "INVALID_TOKEN": {
                "summary": "INVALID_TOKEN",
                "value": {
                  "code": "INVALID_TOKEN",
                  "message": "Invalid or expired access token. Access tokens last one hour: get a new one with your refresh token, or send the user through authorization again.",
                  "status": 401,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#get-an-access-token"
                }
              }
            }
          }
        }
      },
      "Error401_Unauthorized_InvalidToken_OAuth": {
        "description": "`UNAUTHORIZED`: No credentials: the request has no `Authorization: Bearer` header (and no QRdx browser session).\n\n`INVALID_TOKEN`: The access token is unknown, expired (tokens last one hour) or revoked.",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Bearer realm=\"QRdx API\"`, with `error=\"invalid_token\"` for a bad token",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthCompatibleError"
            },
            "examples": {
              "UNAUTHORIZED": {
                "summary": "UNAUTHORIZED",
                "value": {
                  "error": "invalid_token",
                  "error_description": "Missing access token. Send an OAuth access token as \"Authorization: Bearer <token>\". To get one, register an OAuth app at https://qrdx.dev/settings/developers and follow https://qrdx.dev/docs/developers/rest-api#get-an-access-token.",
                  "code": "UNAUTHORIZED",
                  "message": "Missing access token. Send an OAuth access token as \"Authorization: Bearer <token>\". To get one, register an OAuth app at https://qrdx.dev/settings/developers and follow https://qrdx.dev/docs/developers/rest-api#get-an-access-token.",
                  "status": 401,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#get-an-access-token"
                }
              },
              "INVALID_TOKEN": {
                "summary": "INVALID_TOKEN",
                "value": {
                  "error": "invalid_token",
                  "error_description": "Invalid or expired access token. Access tokens last one hour: get a new one with your refresh token, or send the user through authorization again.",
                  "code": "INVALID_TOKEN",
                  "message": "Invalid or expired access token. Access tokens last one hour: get a new one with your refresh token, or send the user through authorization again.",
                  "status": 401,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#get-an-access-token"
                }
              }
            }
          }
        }
      },
      "Error402_PlanRequired": {
        "description": "`PLAN_REQUIRED`: The feature isn't on the workspace's plan: bulk creation, custom back-halves and custom domains need Pro; conversions and hooks need Business.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "PLAN_REQUIRED": {
                "summary": "PLAN_REQUIRED",
                "value": {
                  "code": "PLAN_REQUIRED",
                  "message": "The feature isn't on the workspace's plan: bulk creation, custom back-halves and custom domains need Pro; conversions and hooks need Business.",
                  "status": 402
                }
              }
            }
          }
        }
      },
      "Error402_PlanRequired_LimitReached": {
        "description": "`PLAN_REQUIRED`: The feature isn't on the workspace's plan: bulk creation, custom back-halves and custom domains need Pro; conversions and hooks need Business.\n\n`LIMIT_REACHED`: The workspace has as many active dynamic codes as its plan includes (10 on Free).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "PLAN_REQUIRED": {
                "summary": "PLAN_REQUIRED",
                "value": {
                  "code": "PLAN_REQUIRED",
                  "message": "The feature isn't on the workspace's plan: bulk creation, custom back-halves and custom domains need Pro; conversions and hooks need Business.",
                  "status": 402
                }
              },
              "LIMIT_REACHED": {
                "summary": "LIMIT_REACHED",
                "value": {
                  "code": "LIMIT_REACHED",
                  "message": "The workspace has as many active dynamic codes as its plan includes (10 on Free).",
                  "status": 402
                }
              }
            }
          }
        }
      },
      "Error403_InsufficientScope_CodesRead": {
        "description": "`INSUFFICIENT_SCOPE`: The token is valid but lacks the scope this endpoint needs.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "INSUFFICIENT_SCOPE": {
                "summary": "INSUFFICIENT_SCOPE",
                "value": {
                  "code": "INSUFFICIENT_SCOPE",
                  "message": "This token needs the 'codes:read' scope. Add it to your app and ask for it when the user authorizes.",
                  "status": 403,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
                }
              }
            }
          }
        }
      },
      "Error403_InsufficientScope_ConversionsOff_CodesWrite": {
        "description": "`INSUFFICIENT_SCOPE`: The token is valid but lacks the scope this endpoint needs.\n\n`CONVERSIONS_OFF`: A browser conversion for a code that doesn't have conversion tracking turned on.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "INSUFFICIENT_SCOPE": {
                "summary": "INSUFFICIENT_SCOPE",
                "value": {
                  "code": "INSUFFICIENT_SCOPE",
                  "message": "This token needs the 'codes:write' scope. Add it to your app and ask for it when the user authorizes.",
                  "status": 403,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
                }
              },
              "CONVERSIONS_OFF": {
                "summary": "CONVERSIONS_OFF",
                "value": {
                  "code": "CONVERSIONS_OFF",
                  "message": "A browser conversion for a code that doesn't have conversion tracking turned on.",
                  "status": 403
                }
              }
            }
          }
        }
      },
      "Error403_InsufficientScope_Forbidden_CodesWrite": {
        "description": "`INSUFFICIENT_SCOPE`: The token is valid but lacks the scope this endpoint needs.\n\n`FORBIDDEN`: The caller may not do this: a team member deleting a code someone else created, a team member managing hooks, or a browser-session write from another site.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "INSUFFICIENT_SCOPE": {
                "summary": "INSUFFICIENT_SCOPE",
                "value": {
                  "code": "INSUFFICIENT_SCOPE",
                  "message": "This token needs the 'codes:write' scope. Add it to your app and ask for it when the user authorizes.",
                  "status": 403,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
                }
              },
              "FORBIDDEN": {
                "summary": "FORBIDDEN",
                "value": {
                  "code": "FORBIDDEN",
                  "message": "The caller may not do this: a team member deleting a code someone else created, a team member managing hooks, or a browser-session write from another site.",
                  "status": 403
                }
              }
            }
          }
        }
      },
      "Error403_InsufficientScope_Forbidden_LimitReached_CodesWrite": {
        "description": "`INSUFFICIENT_SCOPE`: The token is valid but lacks the scope this endpoint needs.\n\n`FORBIDDEN`: The caller may not do this: a team member deleting a code someone else created, a team member managing hooks, or a browser-session write from another site.\n\n`LIMIT_REACHED`: The workspace already has 20 webhook endpoints and hooks.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "INSUFFICIENT_SCOPE": {
                "summary": "INSUFFICIENT_SCOPE",
                "value": {
                  "code": "INSUFFICIENT_SCOPE",
                  "message": "This token needs the 'codes:write' scope. Add it to your app and ask for it when the user authorizes.",
                  "status": 403,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
                }
              },
              "FORBIDDEN": {
                "summary": "FORBIDDEN",
                "value": {
                  "code": "FORBIDDEN",
                  "message": "The caller may not do this: a team member deleting a code someone else created, a team member managing hooks, or a browser-session write from another site.",
                  "status": 403
                }
              },
              "LIMIT_REACHED": {
                "summary": "LIMIT_REACHED",
                "value": {
                  "code": "LIMIT_REACHED",
                  "message": "The workspace already has 20 webhook endpoints and hooks.",
                  "status": 403
                }
              }
            }
          }
        }
      },
      "Error403_InsufficientScope_ProfileRead_OAuth": {
        "description": "`INSUFFICIENT_SCOPE`: The token is valid but lacks the scope this endpoint needs.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthCompatibleError"
            },
            "examples": {
              "INSUFFICIENT_SCOPE": {
                "summary": "INSUFFICIENT_SCOPE",
                "value": {
                  "error": "insufficient_scope",
                  "error_description": "This token needs the 'profile:read' scope. Add it to your app and ask for it when the user authorizes.",
                  "code": "INSUFFICIENT_SCOPE",
                  "message": "This token needs the 'profile:read' scope. Add it to your app and ask for it when the user authorizes.",
                  "status": 403,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
                }
              }
            }
          }
        }
      },
      "Error403_InsufficientScope_ThemesRead_OAuth": {
        "description": "`INSUFFICIENT_SCOPE`: The token is valid but lacks the scope this endpoint needs.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthCompatibleError"
            },
            "examples": {
              "INSUFFICIENT_SCOPE": {
                "summary": "INSUFFICIENT_SCOPE",
                "value": {
                  "error": "insufficient_scope",
                  "error_description": "This token needs the 'themes:read' scope. Add it to your app and ask for it when the user authorizes.",
                  "code": "INSUFFICIENT_SCOPE",
                  "message": "This token needs the 'themes:read' scope. Add it to your app and ask for it when the user authorizes.",
                  "status": 403,
                  "docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
                }
              }
            }
          }
        }
      },
      "Error404_NotFound": {
        "description": "`NOT_FOUND`: No such endpoint, or the code, hook, theme or custom domain doesn't exist in the token's workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "NOT_FOUND": {
                "summary": "NOT_FOUND",
                "value": {
                  "code": "NOT_FOUND",
                  "message": "QR code not found",
                  "status": 404
                }
              }
            }
          }
        }
      },
      "Error404_NotFound_InvalidSid": {
        "description": "`NOT_FOUND`: No such endpoint, or the code, hook, theme or custom domain doesn't exist in the token's workspace.\n\n`INVALID_SID`: The `qrdx_sid` is unknown or older than 30 days, the attribution window.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "NOT_FOUND": {
                "summary": "NOT_FOUND",
                "value": {
                  "code": "NOT_FOUND",
                  "message": "QR code not found",
                  "status": 404
                }
              },
              "INVALID_SID": {
                "summary": "INVALID_SID",
                "value": {
                  "code": "INVALID_SID",
                  "message": "The qrdx_sid is unknown or older than 30 days, the attribution window.",
                  "status": 404
                }
              }
            }
          }
        }
      },
      "Error404_NotFound_OAuth": {
        "description": "`NOT_FOUND`: No such endpoint, or the code, hook, theme or custom domain doesn't exist in the token's workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthCompatibleError"
            },
            "examples": {
              "NOT_FOUND": {
                "summary": "NOT_FOUND",
                "value": {
                  "error": "not_found",
                  "error_description": "QR code not found",
                  "code": "NOT_FOUND",
                  "message": "QR code not found",
                  "status": 404
                }
              }
            }
          }
        }
      },
      "Error409_SlugTaken": {
        "description": "`SLUG_TAKEN`: Another code already uses that back-half on that domain.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "SLUG_TAKEN": {
                "summary": "SLUG_TAKEN",
                "value": {
                  "code": "SLUG_TAKEN",
                  "message": "Another code already uses that back-half on that domain.",
                  "status": 409
                }
              }
            }
          }
        }
      },
      "Error413_PayloadTooLarge": {
        "description": "`PAYLOAD_TOO_LARGE`: The body is over the endpoint's limit: 4 MB for codes, 16 KB for hooks, 8 KB for conversions.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "PAYLOAD_TOO_LARGE": {
                "summary": "PAYLOAD_TOO_LARGE",
                "value": {
                  "code": "PAYLOAD_TOO_LARGE",
                  "message": "The body is over the endpoint's limit: 4 MB for codes, 16 KB for hooks, 8 KB for conversions.",
                  "status": 413
                }
              }
            }
          }
        }
      },
      "Error415_UnsupportedMediaType": {
        "description": "`UNSUPPORTED_MEDIA_TYPE`: A write without `Content-Type: application/json`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "UNSUPPORTED_MEDIA_TYPE": {
                "summary": "UNSUPPORTED_MEDIA_TYPE",
                "value": {
                  "code": "UNSUPPORTED_MEDIA_TYPE",
                  "message": "A write without Content-Type: application/json.",
                  "status": 415
                }
              }
            }
          }
        }
      },
      "Error422_UnsafeDestination": {
        "description": "`UNSAFE_DESTINATION`: Google Web Risk flags the destination URL (malware, phishing or unwanted software).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "UNSAFE_DESTINATION": {
                "summary": "UNSAFE_DESTINATION",
                "value": {
                  "code": "UNSAFE_DESTINATION",
                  "message": "Google Web Risk flags the destination URL (malware, phishing or unwanted software).",
                  "status": 422
                }
              }
            }
          }
        }
      },
      "Error429_RateLimited": {
        "description": "`RATE_LIMITED`: Too many requests: a per-minute limit (only `Retry-After` is set) or the workspace's daily quota (`Retry-After` plus `X-RateLimit-*`).",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "RATE_LIMITED": {
                "summary": "RATE_LIMITED",
                "value": {
                  "code": "RATE_LIMITED",
                  "message": "Daily API limit reached: the Free plan allows 1,000 requests a day. It resets at 00:00 UTC. Upgrade your plan for a higher limit.",
                  "status": 429
                }
              },
              "RATE_LIMITED_PER_MINUTE": {
                "summary": "RATE_LIMITED (per-minute limit)",
                "value": {
                  "code": "RATE_LIMITED",
                  "message": "Rate limit exceeded. Please try again later.",
                  "status": 429
                }
              }
            }
          }
        }
      },
      "Error429_RateLimited_OAuth": {
        "description": "`RATE_LIMITED`: Too many requests: a per-minute limit (only `Retry-After` is set) or the workspace's daily quota (`Retry-After` plus `X-RateLimit-*`).",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthCompatibleError"
            },
            "examples": {
              "RATE_LIMITED": {
                "summary": "RATE_LIMITED",
                "value": {
                  "error": "rate_limited",
                  "error_description": "Daily API limit reached: the Free plan allows 1,000 requests a day. It resets at 00:00 UTC. Upgrade your plan for a higher limit.",
                  "code": "RATE_LIMITED",
                  "message": "Daily API limit reached: the Free plan allows 1,000 requests a day. It resets at 00:00 UTC. Upgrade your plan for a higher limit.",
                  "status": 429
                }
              },
              "RATE_LIMITED_PER_MINUTE": {
                "summary": "RATE_LIMITED (per-minute limit)",
                "value": {
                  "error": "rate_limited",
                  "error_description": "Rate limit exceeded. Please try again later.",
                  "code": "RATE_LIMITED",
                  "message": "Rate limit exceeded. Please try again later.",
                  "status": 429
                }
              }
            }
          }
        }
      },
      "Error500_InternalError": {
        "description": "`INTERNAL_ERROR`: Something failed on QRdx's side. It has been logged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "INTERNAL_ERROR": {
                "summary": "INTERNAL_ERROR",
                "value": {
                  "code": "INTERNAL_ERROR",
                  "message": "Something failed on QRdx's side. It has been logged.",
                  "status": 500
                }
              }
            }
          }
        }
      },
      "Error500_InternalError_OAuth": {
        "description": "`INTERNAL_ERROR`: Something failed on QRdx's side. It has been logged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthCompatibleError"
            },
            "examples": {
              "INTERNAL_ERROR": {
                "summary": "INTERNAL_ERROR",
                "value": {
                  "error": "internal_error",
                  "error_description": "Something failed on QRdx's side. It has been logged.",
                  "code": "INTERNAL_ERROR",
                  "message": "Something failed on QRdx's side. It has been logged.",
                  "status": 500
                }
              }
            }
          }
        }
      },
      "Error503_SlugUnavailable_Unavailable": {
        "description": "`SLUG_UNAVAILABLE`: No free short-link slug could be reserved in time.\n\n`UNAVAILABLE`: Custom domains aren't set up on this deployment (a self-hosted instance without them).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "SLUG_UNAVAILABLE": {
                "summary": "SLUG_UNAVAILABLE",
                "value": {
                  "code": "SLUG_UNAVAILABLE",
                  "message": "No free short-link slug could be reserved in time.",
                  "status": 503
                }
              },
              "UNAVAILABLE": {
                "summary": "UNAVAILABLE",
                "value": {
                  "code": "UNAVAILABLE",
                  "message": "Custom domains aren't set up on this deployment (a self-hosted instance without them).",
                  "status": 503
                }
              }
            }
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests a day on the workspace's plan",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left today",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "When the daily count starts over (the next 00:00 UTC), in Unix seconds",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before trying again",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "Code": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Code id"
          },
          "slug": {
            "type": "string",
            "description": "Back-half of the short link"
          },
          "shortLink": {
            "type": "string",
            "format": "uri",
            "description": "The link the QR code encodes: on the code's custom domain once it is verified"
          },
          "defaultShortLink": {
            "anyOf": [
              {
                "type": "string",
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "description": "`https://qrdx.dev/q/<slug>`, which always works; null for a back-half chosen on a custom domain"
          },
          "domain": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "hostname": {
                    "type": "string"
                  },
                  "verified": {
                    "type": "boolean"
                  }
                },
                "required": ["id", "hostname", "verified"]
              },
              {
                "type": "null"
              }
            ],
            "description": "The custom domain the code lives on, or null"
          },
          "destinationUrl": {
            "type": "string",
            "format": "uri",
            "description": "Where a scan sends people; change it any time"
          },
          "title": {
            "description": "Name shown in the dashboard",
            "type": ["string", "null"]
          },
          "contentType": {
            "type": "string",
            "description": "What the code points to, e.g. `url`"
          },
          "status": {
            "type": "string",
            "enum": ["active", "archived", "disabled"],
            "description": "`archived` codes stop redirecting and free a plan slot; `disabled` codes were turned off by QRdx"
          },
          "style": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Style"
              },
              {
                "type": "null"
              }
            ]
          },
          "scanCount": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Scans so far"
          },
          "lastScannedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "Last scan (ISO 8601, UTC)"
              },
              {
                "type": "null"
              }
            ]
          },
          "archivedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "description": "When it was archived (ISO 8601, UTC)"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Created (ISO 8601, UTC)"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last changed (ISO 8601, UTC)"
          },
          "organizationId": {
            "description": "The team that owns the code; null in a personal workspace",
            "type": ["string", "null"]
          }
        },
        "required": [
          "id",
          "slug",
          "shortLink",
          "defaultShortLink",
          "domain",
          "destinationUrl",
          "title",
          "contentType",
          "status",
          "style",
          "scanCount",
          "lastScannedAt",
          "archivedAt",
          "createdAt",
          "updatedAt",
          "organizationId"
        ],
        "description": "A dynamic QR code"
      },
      "CodeInput": {
        "type": "object",
        "properties": {
          "destinationUrl": {
            "description": "Where a scan sends people: a full http(s) URL, up to 2,048 characters",
            "type": "string",
            "minLength": 1,
            "maxLength": 2048
          },
          "title": {
            "description": "Name shown in the dashboard, up to 100 characters",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ]
          },
          "style": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Style"
              },
              {
                "type": "null"
              }
            ]
          },
          "domainId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "description": "A verified custom domain of the workspace (Pro); null uses the default qrdx.dev short link"
              },
              {
                "type": "null"
              }
            ]
          },
          "slug": {
            "anyOf": [
              {
                "type": "string",
                "description": "A custom back-half for the short link, such as spring-menu (Pro and Business)"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": ["destinationUrl"],
        "description": "A new dynamic QR code"
      },
      "CodeUpdate": {
        "type": "object",
        "properties": {
          "destinationUrl": {
            "description": "Where a scan sends people: a full http(s) URL, up to 2,048 characters",
            "type": "string",
            "minLength": 1,
            "maxLength": 2048
          },
          "title": {
            "description": "Name shown in the dashboard, up to 100 characters",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ]
          },
          "style": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Style"
              },
              {
                "type": "null"
              }
            ]
          },
          "domainId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "description": "A verified custom domain of the workspace (Pro); null uses the default qrdx.dev short link"
              },
              {
                "type": "null"
              }
            ]
          },
          "slug": {
            "type": "string",
            "description": "A custom back-half for the short link, such as spring-menu (Pro and Business)"
          },
          "archived": {
            "type": "boolean"
          }
        },
        "description": "The fields to change; at least one"
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable, machine-readable error code; see the errors page"
          },
          "message": {
            "type": "string",
            "description": "What went wrong and how to fix it"
          },
          "status": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "The HTTP status, repeated"
          },
          "data": {
            "description": "Extra detail; for `VALIDATION_ERROR`, `{ details: [{ path, message }] }`"
          },
          "docs": {
            "description": "A docs page that explains the fix",
            "type": "string",
            "format": "uri"
          }
        },
        "required": ["code", "message", "status"]
      },
      "Hook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "target_url": {
            "type": "string",
            "description": "Where events are posted; only the origin unless the caller manages hooks (`codes:write`)"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "scan.created",
                "scan.milestone",
                "code.created",
                "code.updated",
                "form.submitted"
              ]
            }
          },
          "event": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "scan.created",
                  "scan.milestone",
                  "code.created",
                  "code.updated",
                  "form.submitted"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The event, when the subscription has exactly one"
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "Created (ISO 8601, UTC)"
          }
        },
        "required": [
          "id",
          "target_url",
          "events",
          "event",
          "active",
          "created_at"
        ]
      },
      "OAuthCompatibleError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable, machine-readable error code; see the errors page"
          },
          "message": {
            "type": "string",
            "description": "What went wrong and how to fix it"
          },
          "status": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "The HTTP status, repeated"
          },
          "data": {
            "description": "Extra detail; for `VALIDATION_ERROR`, `{ details: [{ path, message }] }`"
          },
          "docs": {
            "description": "A docs page that explains the fix",
            "type": "string",
            "format": "uri"
          },
          "error": {
            "type": "string",
            "description": "OAuth-style code these endpoints have always sent: `invalid_token`, `insufficient_scope`, `rate_limited` or `not_found`"
          },
          "error_description": {
            "type": "string",
            "description": "Same as `message`"
          }
        },
        "required": ["code", "message", "status", "error", "error_description"],
        "description": "The API error shape plus the OAuth fields /v1/me and /v1/themes have always sent"
      },
      "Profile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "image": {
            "description": "Avatar URL",
            "type": ["string", "null"]
          }
        },
        "required": ["id", "name", "email", "image"]
      },
      "ScanAnalytics": {
        "type": "object",
        "properties": {
          "codeId": {
            "type": "string"
          },
          "range": {
            "type": "string",
            "enum": ["7d", "30d", "90d", "365d", "1095d"],
            "description": "The range used: the requested one, or the longest the plan's retention allows"
          },
          "retentionDays": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "How far back the plan keeps analytics"
          },
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Start of the range (00:00 UTC) (ISO 8601, UTC)"
          },
          "days": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "total": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Scans in the range"
          },
          "unique": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Unique visitors in the range"
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["date", "count"]
            },
            "description": "Scans per UTC day, oldest first, one entry per day"
          },
          "countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": ["string", "null"]
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["value", "count"]
            },
            "description": "Top 10 countries"
          },
          "cities": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "city": {
                  "type": ["string", "null"]
                },
                "country": {
                  "type": ["string", "null"]
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["city", "country", "count"]
            },
            "description": "Top 10 cities"
          },
          "devices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": ["string", "null"]
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["value", "count"]
            }
          },
          "os": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": ["string", "null"]
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["value", "count"]
            }
          },
          "browsers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": ["string", "null"]
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["value", "count"]
            }
          },
          "referrers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": ["string", "null"]
                },
                "count": {
                  "type": "integer",
                  "minimum": -9007199254740991,
                  "maximum": 9007199254740991
                }
              },
              "required": ["value", "count"]
            }
          },
          "events": {
            "description": "Individual scans, newest first (only with `limit` > 0)",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "scannedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When the scan happened (ISO 8601, UTC)"
                },
                "country": {
                  "description": "ISO 3166-1 alpha-2 country",
                  "type": ["string", "null"]
                },
                "region": {
                  "type": ["string", "null"]
                },
                "city": {
                  "type": ["string", "null"]
                },
                "deviceType": {
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": ["mobile", "tablet", "desktop", "other"]
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "os": {
                  "type": ["string", "null"]
                },
                "browser": {
                  "type": ["string", "null"]
                },
                "referer": {
                  "type": ["string", "null"]
                }
              },
              "required": [
                "scannedAt",
                "country",
                "region",
                "city",
                "deviceType",
                "os",
                "browser",
                "referer"
              ]
            }
          },
          "nextBefore": {
            "description": "Pass as `before` for the next page of `events`; null on the last page",
            "type": ["string", "null"]
          }
        },
        "required": [
          "codeId",
          "range",
          "retentionDays",
          "from",
          "days",
          "total",
          "unique",
          "series",
          "countries",
          "cities",
          "devices",
          "os",
          "browsers",
          "referrers"
        ]
      },
      "Style": {
        "type": "object",
        "properties": {
          "size": {
            "description": "Size of the QR code in pixels",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "bgColor": {
            "description": "Background color of the QR code - supports solid colors and gradients",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "solid"
                  },
                  "color": {
                    "type": "string"
                  }
                },
                "required": ["type", "color"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "linear"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  },
                  "angle": {
                    "default": 0,
                    "type": "number"
                  }
                },
                "required": ["type", "stops"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "radial"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  }
                },
                "required": ["type", "stops"]
              }
            ]
          },
          "fgColor": {
            "description": "Foreground (main) color of the QR code - supports solid colors and gradients",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "solid"
                  },
                  "color": {
                    "type": "string"
                  }
                },
                "required": ["type", "color"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "linear"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  },
                  "angle": {
                    "default": 0,
                    "type": "number"
                  }
                },
                "required": ["type", "stops"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "radial"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  }
                },
                "required": ["type", "stops"]
              }
            ]
          },
          "eyeColor": {
            "description": "Color of the corner eye patterns - supports solid colors and gradients",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "solid"
                  },
                  "color": {
                    "type": "string"
                  }
                },
                "required": ["type", "color"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "linear"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  },
                  "angle": {
                    "default": 0,
                    "type": "number"
                  }
                },
                "required": ["type", "stops"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "radial"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  }
                },
                "required": ["type", "stops"]
              }
            ]
          },
          "dotColor": {
            "description": "Color of the corner eye dots - supports solid colors and gradients",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "solid"
                  },
                  "color": {
                    "type": "string"
                  }
                },
                "required": ["type", "color"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "linear"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  },
                  "angle": {
                    "default": 0,
                    "type": "number"
                  }
                },
                "required": ["type", "stops"]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "const": "radial"
                  },
                  "stops": {
                    "minItems": 2,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "color": {
                          "type": "string"
                        },
                        "offset": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100
                        }
                      },
                      "required": ["color", "offset"]
                    }
                  }
                },
                "required": ["type", "stops"]
              }
            ]
          },
          "bodyPattern": {
            "description": "Pattern style for the QR code body",
            "type": "string",
            "enum": [
              "circle",
              "circle-large",
              "square",
              "diamond",
              "circle-mixed",
              "pacman",
              "rounded",
              "small-square",
              "vertical-line",
              "horizontal-line",
              "liquid",
              "liquid-horizontal",
              "liquid-vertical",
              "classy",
              "classy-rounded",
              "leaf",
              "plus",
              "hexagon",
              "star",
              "heart",
              "random-dots",
              "mosaic",
              "octagon"
            ]
          },
          "cornerEyePattern": {
            "description": "Pattern style for the corner eyes",
            "type": "string",
            "enum": [
              "square",
              "rounded",
              "gear",
              "circle",
              "diya",
              "extra-rounded",
              "message",
              "pointy",
              "leaf",
              "drop",
              "octagon",
              "bevel",
              "squircle",
              "cushion",
              "shield",
              "petal",
              "leaf-reverse",
              "curly"
            ]
          },
          "cornerEyeDotPattern": {
            "description": "Pattern style for the corner eye dots",
            "type": "string",
            "enum": [
              "square",
              "rounded",
              "circle",
              "message",
              "message-reverse",
              "diya",
              "diya-reverse",
              "rounded-triangle",
              "leaf",
              "drop",
              "plus",
              "cross-rounded",
              "hexagon",
              "octagon",
              "flower",
              "heart",
              "squircle",
              "dots",
              "extra-rounded",
              "leaf-reverse",
              "drop-reverse",
              "diamond",
              "star",
              "banner"
            ]
          },
          "level": {
            "description": "Error correction level (L, M, Q, H)",
            "type": "string",
            "enum": ["L", "M", "Q", "H"]
          },
          "margin": {
            "description": "Margin (quiet zone) around the QR code, in modules",
            "type": "number",
            "minimum": 0
          },
          "templateId": {
            "description": "Template/frame ID to use",
            "type": "string"
          },
          "frame": {
            "description": "Call-to-action frame around the QR code, e.g. a 'Scan me' banner (text up to 24 characters)",
            "type": "object",
            "properties": {
              "style": {
                "type": "string",
                "enum": [
                  "none",
                  "banner-bottom",
                  "banner-top",
                  "border",
                  "rounded-border",
                  "speech-bubble",
                  "banner-top-bottom",
                  "speech-bubble-top",
                  "chat-bubble",
                  "circle",
                  "polaroid",
                  "phone",
                  "ticket",
                  "badge",
                  "envelope",
                  "tag",
                  "menu-card",
                  "sticker",
                  "gradient-border",
                  "focus"
                ]
              },
              "text": {
                "type": "string",
                "maxLength": 24
              },
              "color": {
                "type": "string"
              },
              "textColor": {
                "type": "string"
              },
              "secondaryText": {
                "type": "string",
                "maxLength": 24
              },
              "secondaryColor": {
                "type": "string"
              },
              "fontScale": {
                "type": "number",
                "minimum": 0.5,
                "maximum": 1.5
              }
            },
            "required": ["style"]
          },
          "showLogo": {
            "description": "Whether to show a logo in center",
            "type": "boolean"
          },
          "customLogo": {
            "description": "Custom logo image URL or data URI",
            "type": "string"
          },
          "art": {
            "description": "Logo QR or Image QR (halftone) art built from an image",
            "type": "object",
            "properties": {
              "mode": {
                "type": "string",
                "enum": ["logo", "halftone"]
              },
              "image": {
                "type": "object",
                "properties": {
                  "width": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 192
                  },
                  "height": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 192
                  },
                  "data": {
                    "type": "string",
                    "maxLength": 196608
                  }
                },
                "required": ["width", "height", "data"]
              },
              "intensity": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "variant": {
                "type": "string",
                "enum": ["blend", "badge", "color", "mono"]
              },
              "matchEyes": {
                "type": "boolean"
              }
            },
            "required": ["mode", "image"]
          }
        },
        "description": "A QR design: colors, shapes, logo and frame, the fields of the playground's design panel. Every field is optional; leave `style` out of a new code to use the workspace's default theme."
      },
      "Theme": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "style": {
            "$ref": "#/components/schemas/Style"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Created (ISO 8601, UTC)"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last changed (ISO 8601, UTC)"
          }
        },
        "required": ["id", "name", "style", "createdAt", "updatedAt"]
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique per event"
          },
          "type": {
            "type": "string",
            "enum": [
              "scan.created",
              "scan.milestone",
              "code.created",
              "code.updated",
              "form.submitted"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the event happened (ISO 8601, UTC)"
          },
          "data": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "The event's payload; see the webhooks guide for each type"
          }
        },
        "required": ["id", "type", "created_at", "data"]
      }
    }
  }
}
