{
  "openapi": "3.1.0",
  "info": {
    "title": "QR Global API",
    "version": "1.0.0",
    "description": "Business API v1 for dynamic QR codes: QR codes, bulk creation, scan analytics, folders and tags, and webhook subscriptions (REST hooks).\n\nAuthenticate with an API key from the dashboard (API keys page) in the `X-Api-Key` header or as `Authorization: Bearer qrg_…`. Each operation lists the permission (scope) it needs; keys without scopes have full access. Requests count toward the per-minute limit of your plan."
  },
  "servers": [
    {
      "url": "https://qr-global.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "BearerToken": []
    }
  ],
  "tags": [
    {
      "name": "QR codes"
    },
    {
      "name": "Analytics"
    },
    {
      "name": "Folders and tags"
    },
    {
      "name": "Webhooks"
    }
  ],
  "paths": {
    "/qr-codes": {
      "get": {
        "summary": "List QR codes",
        "operationId": "listQrCodes",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:read"
            ]
          },
          {
            "BearerToken": [
              "qr:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Matches content or display name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "folder_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "description": "Tag slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "archived",
            "in": "query",
            "description": "true lists inactive codes instead of active ones.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated QR codes, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/QrCode"
                      }
                    },
                    "links": {
                      "$ref": "#/components/schemas/PaginationLinks"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "summary": "Create a QR code",
        "description": "Respects the limit of active QR codes of your plan (422 with code qr_limit_reached).",
        "operationId": "createQrCode",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:write"
            ]
          },
          {
            "BearerToken": [
              "qr:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QrCodeCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created; the image is generated on the server.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QrCode"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/qr-codes/bulk": {
      "post": {
        "summary": "Create up to 100 QR codes",
        "description": "Needs a plan with bulk creation (403 bulk_not_available otherwise) and respects the limit of active QR codes per item.",
        "operationId": "bulkCreateQrCodes",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:write"
            ]
          },
          {
            "BearerToken": [
              "qr:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkCreateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Every item is processed on its own; see `results`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BulkCreateResult"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/qr-codes/{id}": {
      "get": {
        "summary": "Get a QR code",
        "operationId": "getQrCode",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:read"
            ]
          },
          {
            "BearerToken": [
              "qr:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QR code id.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QrCode"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "summary": "Update a QR code",
        "operationId": "updateQrCode",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:write"
            ]
          },
          {
            "BearerToken": [
              "qr:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QR code id.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QrCodeUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QrCode"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "summary": "Delete a QR code",
        "operationId": "deleteQrCode",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:write"
            ]
          },
          {
            "BearerToken": [
              "qr:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QR code id.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/qr-codes/{id}/image": {
      "get": {
        "summary": "Render the QR code image",
        "operationId": "getQrCodeImage",
        "tags": [
          "QR codes"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:read"
            ]
          },
          {
            "BearerToken": [
              "qr:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QR code id.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "png",
                "svg"
              ],
              "default": "png"
            }
          },
          {
            "name": "size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 100,
              "maximum": 2000,
              "default": 300
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Image file.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/qr-codes/{id}/stats": {
      "get": {
        "summary": "Scan statistics",
        "operationId": "getQrCodeStats",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "analytics:read"
            ]
          },
          {
            "BearerToken": [
              "analytics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QR code id.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the period: a date (2026-10-01 = start of that day in `timezone`) or a full ISO 8601 timestamp. Clamped to your plan's history window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the period, inclusive: a date (end of that day in `timezone`) or a full timestamp. Must not be before `from`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "description": "IANA timezone for date-only bounds and daily buckets. Default UTC.",
            "schema": {
              "type": "string",
              "default": "UTC",
              "examples": [
                "Europe/Kyiv"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counts within the period and the history window of your plan.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/QrStats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/qr-codes/{id}/scans": {
      "get": {
        "summary": "Scan log",
        "operationId": "listQrCodeScans",
        "tags": [
          "Analytics"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "analytics:read"
            ]
          },
          {
            "BearerToken": [
              "analytics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "QR code id.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the period: a date (2026-10-01 = start of that day in `timezone`) or a full ISO 8601 timestamp. Clamped to your plan's history window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the period, inclusive: a date (end of that day in `timezone`) or a full timestamp. Must not be before `from`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "description": "IANA timezone for date-only bounds and daily buckets. Default UTC.",
            "schema": {
              "type": "string",
              "default": "UTC",
              "examples": [
                "Europe/Kyiv"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Scans, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/QrScan"
                      }
                    },
                    "links": {
                      "$ref": "#/components/schemas/PaginationLinks"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/folders": {
      "get": {
        "summary": "List folders",
        "operationId": "listFolders",
        "tags": [
          "Folders and tags"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:read"
            ]
          },
          {
            "BearerToken": [
              "qr:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Folders in dashboard order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Folder"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/tags": {
      "get": {
        "summary": "List tags",
        "operationId": "listTags",
        "tags": [
          "Folders and tags"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "qr:read"
            ]
          },
          {
            "BearerToken": [
              "qr:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Tags of the workspace; the default set is created on first use.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tag"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "summary": "List webhook subscriptions",
        "operationId": "listWebhooks",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "webhooks:manage"
            ]
          },
          {
            "BearerToken": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook endpoints of the workspace (without secrets).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "summary": "Subscribe to events",
        "description": "REST hook subscribe for Zapier, Make and similar tools. Needs a plan with webhooks (403 webhooks_not_available otherwise).",
        "operationId": "subscribeWebhook",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "webhooks:manage"
            ]
          },
          {
            "BearerToken": [
              "webhooks:manage"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookSubscribeRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscribed. `secret` is returned only here.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookSubscription"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "description": "Validation failed, `url_not_public`, or `webhook_limit_reached` (25 endpoints per workspace).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/Error"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "summary": "Unsubscribe",
        "description": "Works on any plan, so integrations can clean up after a downgrade.",
        "operationId": "unsubscribeWebhook",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "ApiKeyHeader": [
              "webhooks:manage"
            ]
          },
          {
            "BearerToken": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Webhook endpoint id.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Scopes: qr:read, qr:write, analytics:read, webhooks:manage."
      },
      "BearerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same API key (qrg_…) as a bearer token."
      }
    },
    "schemas": {
      "QrCode": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "short_code": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "examples": [
              "url",
              "wifi",
              "vcard"
            ]
          },
          "encoding": {
            "type": "string",
            "enum": [
              "tracking",
              "direct"
            ],
            "description": "`tracking` codes encode `scan_url` and count scans; Wi-Fi codes encode the credentials directly."
          },
          "content": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "structured_data": {
            "type": [
              "object",
              "array",
              "null"
            ]
          },
          "folder_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tag slugs (see GET /tags)."
          },
          "is_active": {
            "type": "boolean"
          },
          "scan_count": {
            "type": "integer"
          },
          "last_scanned_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "max_scans": {
            "type": [
              "integer",
              "null"
            ]
          },
          "is_password_protected": {
            "type": "boolean"
          },
          "scan_url": {
            "type": "string",
            "format": "uri"
          },
          "qr_image": {
            "type": "string",
            "description": "PNG data URI."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "QrCodeCreate": {
        "type": "object",
        "required": [
          "type",
          "content",
          "display_name"
        ],
        "properties": {
          "type": {
            "type": "string",
            "maxLength": 50,
            "examples": [
              "url"
            ]
          },
          "content": {
            "type": "string",
            "maxLength": 2000,
            "examples": [
              "https://example.com"
            ]
          },
          "display_name": {
            "type": "string",
            "maxLength": 255
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "structured_data": {
            "type": "object"
          },
          "folder_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Folder of your workspace; null takes the code out of its folder."
          },
          "tags": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "uniqueItems": true,
            "description": "Tag slugs of your workspace; replaces the whole set."
          }
        }
      },
      "QrCodeUpdate": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "content": {
            "type": "string",
            "maxLength": 2000
          },
          "display_name": {
            "type": "string",
            "maxLength": 255
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "structured_data": {
            "type": "object"
          },
          "is_active": {
            "type": "boolean"
          },
          "folder_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Folder of your workspace; null takes the code out of its folder."
          },
          "tags": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "uniqueItems": true,
            "description": "Tag slugs of your workspace; replaces the whole set."
          }
        }
      },
      "BulkCreateRequest": {
        "type": "object",
        "required": [
          "codes"
        ],
        "properties": {
          "codes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/QrCodeCreate"
            }
          }
        }
      },
      "BulkCreateResult": {
        "type": "object",
        "properties": {
          "requested": {
            "type": "integer"
          },
          "created": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "index",
                "status"
              ],
              "properties": {
                "index": {
                  "type": "integer",
                  "description": "Position of the item in `codes`."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "created",
                    "failed"
                  ]
                },
                "data": {
                  "$ref": "#/components/schemas/QrCode"
                },
                "error": {
                  "type": "string",
                  "enum": [
                    "validation_failed",
                    "qr_limit_reached",
                    "could_not_be_saved"
                  ]
                },
                "message": {
                  "type": "string"
                },
                "errors": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "QrScan": {
        "type": "object",
        "description": "Visitor IP address and user agent are never returned.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "qr_code_id": {
            "type": "integer"
          },
          "scanned_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "device_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "browser": {
            "type": [
              "string",
              "null"
            ]
          },
          "os": {
            "type": [
              "string",
              "null"
            ]
          },
          "language": {
            "type": [
              "string",
              "null"
            ]
          },
          "referer": {
            "type": [
              "string",
              "null"
            ]
          },
          "variant_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A/B variant that served the scan."
          },
          "matched_rule_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Smart-redirect rule that matched."
          }
        }
      },
      "QrStats": {
        "type": "object",
        "properties": {
          "qr_code_id": {
            "type": "integer"
          },
          "period": {
            "type": "object",
            "properties": {
              "from": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Effective lower bound after clamping to the history window."
              },
              "to": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "timezone": {
                "type": "string"
              }
            }
          },
          "retention_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Your plan's history window; null = unlimited."
          },
          "total_scans": {
            "type": "integer"
          },
          "today_scans": {
            "type": "integer"
          },
          "week_scans": {
            "type": "integer"
          },
          "month_scans": {
            "type": "integer"
          },
          "last_scanned_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "unique_visitors": {
            "type": "integer"
          },
          "top_countries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "country": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "device_types": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "device_type": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "browsers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "browser": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "daily": {
            "type": "array",
            "description": "Days with scans only, ascending, in `timezone`.",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "scans": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Folder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string",
            "examples": [
              "#6366F1"
            ]
          },
          "icon": {
            "type": [
              "string",
              "null"
            ]
          },
          "sort_order": {
            "type": "integer"
          },
          "qr_codes_count": {
            "type": "integer"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "Use this value in `tags`."
          },
          "color": {
            "type": "string"
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "qr.scan.created",
                "subscription.updated",
                "payment.succeeded",
                "lead.captured",
                "rsvp.received",
                "feedback.received",
                "poll.voted"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "disabled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "WebhookSubscribeRequest": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Public http(s) address; private and internal addresses are rejected."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "qr.scan.created",
                "subscription.updated",
                "payment.succeeded",
                "lead.captured",
                "rsvp.received",
                "feedback.received",
                "poll.voted"
              ]
            }
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "secret": {
            "type": "string",
            "minLength": 16,
            "maxLength": 255,
            "description": "Signing secret for X-Webhook-Signature. Generated when omitted."
          }
        }
      },
      "WebhookSubscription": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEndpoint"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "description": "Shown only in this response."
              }
            }
          }
        ]
      },
      "PaginationLinks": {
        "type": "object",
        "properties": {
          "first": {
            "type": [
              "string",
              "null"
            ]
          },
          "last": {
            "type": [
              "string",
              "null"
            ]
          },
          "prev": {
            "type": [
              "string",
              "null"
            ]
          },
          "next": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "current_page": {
            "type": "integer"
          },
          "from": {
            "type": [
              "integer",
              "null"
            ]
          },
          "last_page": {
            "type": "integer"
          },
          "path": {
            "type": "string"
          },
          "per_page": {
            "type": "integer"
          },
          "to": {
            "type": [
              "integer",
              "null"
            ]
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "examples": [
              "insufficient_scope",
              "not_found",
              "bulk_not_available"
            ]
          },
          "message": {
            "type": "string"
          },
          "required_scope": {
            "type": "string",
            "description": "Present when error = insufficient_scope."
          },
          "retry_after": {
            "type": "integer",
            "description": "Present when error = rate_limit_exceeded."
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, invalid or revoked API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key lacks the required scope (`insufficient_scope`) or the plan lacks the feature (`api_access_required`, `bulk_not_available`, `webhooks_not_available`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The resource does not exist or belongs to another workspace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "Validation failed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationError"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-minute limit of your plan reached. Retry after the number of seconds in the Retry-After header.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
