{
  "openapi": "3.0.3",
  "info": {
    "title": "RemyPass Public API",
    "version": "1.0.0",
    "description": "OpenAPI specification for the RemyPass public API. Authenticated endpoints require an API key in the X-API-Key header and the relevant permission on that key."
  },
  "servers": [
    {
      "url": "https://api.remypass.com/api/v1/public",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Members",
      "description": "Member management endpoints."
    },
    {
      "name": "Passes",
      "description": "Pass instance management endpoints."
    },
    {
      "name": "Pass Templates",
      "description": "Pass template discovery endpoints."
    },
    {
      "name": "Event Tickets",
      "description": "Event ticket issuing endpoints."
    }
  ],
  "paths": {
    "/members": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List members",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the members:read permission.",
        "x-code-examples": [
          {
            "title": "List members with pagination",
            "language": "bash",
            "code": "curl -X GET \"https://api.remypass.com/api/v1/public/members?limit=25&page=1&search=john&status=active\" \\\n  -H \"Authorization: Bearer YOUR_API_KEY\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Members retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Member"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Create a member",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the members:write permission.",
        "x-code-examples": [
          {
            "title": "Create a new member",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/members \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Dr. Sarah Johnson\",\n    \"email\": \"sarah.johnson@example.com\",\n    \"title\": \"Dr\",\n    \"memberId\": \"MEM001\",\n    \"type\": \"Member\",\n    \"status\": \"active\",\n    \"customFields\": {\n      \"department\": \"Engineering\",\n      \"membershipLevel\": \"Premium\"\n    }\n  }'"
          },
          {
            "title": "Create member with immediate pass issuance",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/members \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"title\": \"Ms\",\n    \"name\": \"Emily Chen\",\n    \"email\": \"emily.chen@example.com\",\n    \"memberId\": \"MEM002\",\n    \"issuePass\": true,\n    \"passTemplateId\": \"507f1f77bcf86cd799439012\",\n    \"expiryDate\": \"2024-12-31T23:59:59.000Z\"\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMemberRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Member created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/members/{id}": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "Get a member",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the members:read permission.",
        "x-code-examples": [
          {
            "title": "Get member by ID",
            "language": "bash",
            "code": "curl -X GET https://api.remypass.com/api/v1/public/members/507f1f77bcf86cd799439011 \\\n  -H \"Authorization: Bearer YOUR_API_KEY\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "Member retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "put": {
        "tags": [
          "Members"
        ],
        "summary": "Update a member",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the members:write permission.",
        "x-code-examples": [
          {
            "title": "Update member",
            "language": "bash",
            "code": "curl -X PUT https://api.remypass.com/api/v1/public/members/507f1f77bcf86cd799439011 \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"title\": \"Prof\",\n    \"name\": \"Prof. John Smith\",\n    \"memberId\": \"MEM001\",\n    \"status\": \"active\",\n    \"customFields\": {\n      \"department\": \"Research\",\n      \"position\": \"Senior Researcher\"\n    }\n  }'"
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMemberRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Member updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes": {
      "get": {
        "tags": [
          "Passes"
        ],
        "summary": "List pass instances",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the passes:read permission.",
        "x-code-examples": [
          {
            "title": "List pass instances with filtering",
            "language": "bash",
            "code": "curl -X GET \"https://api.remypass.com/api/v1/public/passes?limit=50&page=1&status=active&memberId=507f1f77bcf86cd799439011\" \\\n  -H \"Authorization: Bearer YOUR_API_KEY\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive",
                "expired",
                "void"
              ]
            }
          },
          {
            "name": "memberId",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-fA-F0-9]{24}$"
            }
          },
          {
            "name": "includeVoid",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pass instances retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PassInstance"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes/issue": {
      "post": {
        "tags": [
          "Passes"
        ],
        "summary": "Issue a pass",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Issues a pass to an existing member. Use this endpoint for standard member passes and for event ticket passes when the ticket recipient is already a member. Requires the passes:issue permission.",
        "x-banner": {
          "type": "info",
          "message": "Use this endpoint when the recipient is already a RemyPass member. For standard member passes, provide memberId and passTemplateId, plus optional expiryDate, customFields, and sendEmail. For event ticket passes, use a pre-created event ticket template and include eventTime, eventName, and seatInfo."
        },
        "x-code-examples": [
          {
            "title": "Issue a pass to a member",
            "description": "Use this for standard member pass templates.",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/passes/issue \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"memberId\": \"507f1f77bcf86cd799439011\",\n    \"passTemplateId\": \"507f1f77bcf86cd799439012\",\n    \"expiryDate\": \"2024-12-31T23:59:59.000Z\",\n    \"customFields\": {\n      \"membershipLevel\": \"Premium\",\n      \"accessLevel\": \"VIP\",\n      \"issueDate\": \"2024-01-15\"\n    },\n    \"sendEmail\": true\n  }'"
          },
          {
            "title": "Issue an event ticket pass to a member",
            "description": "Use this when the ticket recipient is already a member. Requires a pre-created event ticket template.",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/passes/issue \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"memberId\": \"507f1f77bcf86cd799439011\",\n    \"passTemplateId\": \"507f1f77bcf86cd799439012\",\n    \"eventTime\": \"2024-12-25T19:30:00.000Z\",\n    \"eventName\": \"The Matrix Reloaded\",\n    \"seatInfo\": {\n      \"seat\": \"A12\",\n      \"row\": \"A\",\n      \"section\": \"Screen 1\"\n    },\n    \"sendEmail\": true\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssuePassRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pass issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassInstanceResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes/{id}": {
      "get": {
        "tags": [
          "Passes"
        ],
        "summary": "Get a pass instance",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the passes:read permission.",
        "x-code-examples": [
          {
            "title": "Get pass instance by ID",
            "language": "bash",
            "code": "curl -X GET https://api.remypass.com/api/v1/public/passes/507f1f77bcf86cd799439013 \\\n  -H \"Authorization: Bearer YOUR_API_KEY\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "Pass instance retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassInstanceResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes/{id}/revoke": {
      "post": {
        "tags": [
          "Passes"
        ],
        "summary": "Revoke a pass",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the passes:revoke permission.",
        "x-code-examples": [
          {
            "title": "Revoke a pass permanently",
            "description": "Sets pass status to void. Voided passes cannot be reinstated; issue a new pass if needed.",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/passes/507f1f77bcf86cd799439013/revoke \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ],
        "x-banner": {
          "type": "warning",
          "message": "Important: This action is permanent and irreversible. Voided passes cannot be reactivated using the reinstate endpoint."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes/{id}/reinstate": {
      "post": {
        "tags": [
          "Passes"
        ],
        "summary": "Reinstate a pass",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the passes:reinstate permission.",
        "x-code-examples": [
          {
            "title": "Reinstate an inactive pass",
            "description": "Reactivates passes with inactive status. Cannot reinstate voided or expired passes.",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/passes/507f1f77bcf86cd799439013/reinstate \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ],
        "x-banner": {
          "type": "info",
          "message": "This endpoint only works for passes with inactive status. It cannot reinstate voided passes, which are permanently revoked; expired passes, which should use the Update pass expiry endpoint; or active passes, which are already active.\n\nUse cases:\n\n- Reactivating a member's passes when the member becomes active again\n- Reactivating a loyalty pass that was deactivated after reaching maximum stamps\n- Manually reactivating a pass that was previously set to inactive"
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes/{id}/expiry": {
      "put": {
        "tags": [
          "Passes"
        ],
        "summary": "Update pass expiry",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the passes:update_expiry permission. Send null to remove an expiry date.",
        "x-code-examples": [
          {
            "title": "Update pass expiry date",
            "description": "Automatically reactivates expired passes when setting a future expiry date.",
            "language": "bash",
            "code": "curl -X PUT https://api.remypass.com/api/v1/public/passes/507f1f77bcf86cd799439013/expiry \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"expiryDate\": \"2025-06-30T23:59:59.000Z\"\n  }'"
          }
        ],
        "x-banner": {
          "type": "info",
          "message": "- Setting a future expiry date automatically reactivates expired passes.\n- Expiry can only be updated for passes with active or expired status."
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePassExpiryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/passes/{id}/send-email": {
      "post": {
        "tags": [
          "Passes"
        ],
        "summary": "Send a pass email",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the passes:send_email permission.",
        "x-code-examples": [
          {
            "title": "Send pass email to member",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/passes/507f1f77bcf86cd799439013/send-email \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/pass-templates": {
      "get": {
        "tags": [
          "Pass Templates"
        ],
        "summary": "List pass templates",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the pass_templates:read permission.",
        "x-code-examples": [
          {
            "title": "List pass templates",
            "language": "bash",
            "code": "curl -X GET \"https://api.remypass.com/api/v1/public/pass-templates?limit=25&page=1&search=membership\" \\\n  -H \"Authorization: Bearer YOUR_API_KEY\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pass templates retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedResponse"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PassTemplate"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/pass-templates/{id}": {
      "get": {
        "tags": [
          "Pass Templates"
        ],
        "summary": "Get a pass template",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Requires the pass_templates:read permission.",
        "x-code-examples": [
          {
            "title": "Get pass template by ID",
            "language": "bash",
            "code": "curl -X GET https://api.remypass.com/api/v1/public/pass-templates/507f1f77bcf86cd799439012 \\\n  -H \"Authorization: Bearer YOUR_API_KEY\""
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Id"
          }
        ],
        "responses": {
          "200": {
            "description": "Pass template retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassTemplateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/event-tickets/create-from-booking": {
      "post": {
        "tags": [
          "Event Tickets"
        ],
        "summary": "Create an event ticket from a booking",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Creates an event ticket for a non-member recipient from cinema or booking-system data. Requires the passes:issue permission.",
        "x-code-examples": [
          {
            "title": "Create event ticket from booking for a non-member",
            "description": "Use this for cinema or booking-system integrations where the recipient is not a member. The API automatically creates templates and prevents duplicate bookings.",
            "language": "bash",
            "code": "curl -X POST https://api.remypass.com/api/v1/public/event-tickets/create-from-booking \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"bookingId\": \"BOOKING-12345\",\n    \"film\": \"The Matrix Reloaded\",\n    \"sendEmail\": true,\n    \"startTime\": \"2024-12-25T19:30:00.000Z\",\n    \"seat\": \"A12\",\n    \"row\": \"A\",\n    \"screen\": \"Screen 1\",\n    \"customer\": {\n      \"name\": \"John Doe\",\n      \"email\": \"john@example.com\"\n    }\n  }'"
          }
        ],
        "x-banner": {
          "type": "info",
          "message": "Use this endpoint when the ticket recipient is not a RemyPass member. The API creates or reuses an event ticket template for the film and start time, creates the ticket pass instance, and prevents duplicate bookings by rejecting requests with a bookingId that already exists."
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventTicketRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event ticket created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventTicketResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/event-tickets/issue": {
      "post": {
        "tags": [
          "Event Tickets"
        ],
        "summary": "Issue an event ticket",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Canonical alias for /event-tickets/create-from-booking. Requires the passes:issue permission.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventTicketRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event ticket created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventTicketResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "parameters": {
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-fA-F0-9]{24}$"
        }
      },
      "PassInstanceId": {
        "name": "passInstanceId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-fA-F0-9]{24}$"
        }
      },
      "QrCode": {
        "name": "qrCode",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9]{8}$"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 10
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      }
    },
    "responses": {
      "Success": {
        "description": "Successful operation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SuccessResponse"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Bad request or validation error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The caller is not allowed to access this resource.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ServerError": {
        "description": "Server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "SuccessResponse": {
        "type": "object",
        "required": [
          "success",
          "message"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string"
          },
          "data": {
            "nullable": true
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "success",
          "message"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "message": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "currentPage": {
            "type": "integer",
            "example": 1
          },
          "totalPages": {
            "type": "integer",
            "example": 5
          },
          "totalItems": {
            "type": "integer",
            "example": 47
          },
          "itemsPerPage": {
            "type": "integer",
            "example": 10
          },
          "hasNextPage": {
            "type": "boolean",
            "example": true
          },
          "hasPrevPage": {
            "type": "boolean",
            "example": false
          }
        }
      },
      "PaginatedResponse": {
        "type": "object",
        "required": [
          "success",
          "message",
          "data",
          "pagination"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "array",
            "items": {}
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Member": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439011"
          },
          "title": {
            "type": "string",
            "description": "Optional member title or honorific.",
            "enum": [
              "Mr",
              "Ms",
              "Mrs",
              "Miss",
              "Sir",
              "Madam",
              "Dr",
              "Rev"
            ]
          },
          "name": {
            "type": "string",
            "description": "Member full name.",
            "example": "John Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Member email address. Used for pass delivery emails when a pass is issued.",
            "example": "john@example.com"
          },
          "memberId": {
            "type": "string",
            "example": "MEM001"
          },
          "type": {
            "type": "string",
            "description": "Member category. Defaults to Member when omitted.",
            "enum": [
              "Member",
              "Visitor"
            ]
          },
          "status": {
            "type": "string",
            "description": "Member account status. Defaults to active when omitted.",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "customFields": {
            "type": "object",
            "description": "Organisation-defined custom field values keyed by custom field key.",
            "example": {
              "membershipLevel": "Gold",
              "favoriteLocation": "Main Street",
              "newsletterOptIn": true
            },
            "additionalProperties": true
          },
          "passes": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "additionalProperties": true
      },
      "CreateMemberRequest": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Optional member title or honorific.",
            "enum": [
              "Mr",
              "Ms",
              "Mrs",
              "Miss",
              "Sir",
              "Madam",
              "Dr",
              "Rev"
            ]
          },
          "name": {
            "type": "string",
            "description": "Member full name.",
            "example": "John Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Member email address. Used for pass delivery emails when a pass is issued.",
            "example": "john@example.com"
          },
          "memberId": {
            "type": "string",
            "description": "Unique member identifier. Must not contain spaces."
          },
          "type": {
            "type": "string",
            "description": "Member category. Defaults to Member when omitted.",
            "enum": [
              "Member",
              "Visitor"
            ]
          },
          "status": {
            "type": "string",
            "description": "Member account status. Defaults to active when omitted.",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "customFields": {
            "type": "object",
            "description": "Organisation-defined custom field values keyed by custom field key.",
            "example": {
              "membershipLevel": "Gold",
              "favoriteLocation": "Main Street",
              "newsletterOptIn": true
            },
            "additionalProperties": true
          },
          "issuePass": {
            "type": "boolean",
            "description": "Set to true to issue a pass immediately after creating the member."
          },
          "passTemplateId": {
            "type": "string",
            "description": "Required when issuePass is true. Use the id from the List pass templates endpoint.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "expiryDate": {
            "type": "string",
            "format": "date-time",
            "description": "Optional pass expiry date used when issuePass is true."
          }
        }
      },
      "UpdateMemberRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Optional member title or honorific.",
            "enum": [
              "Mr",
              "Ms",
              "Mrs",
              "Miss",
              "Sir",
              "Madam",
              "Dr",
              "Rev"
            ]
          },
          "name": {
            "type": "string",
            "description": "Updated member full name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Updated member email address."
          },
          "memberId": {
            "type": "string",
            "description": "Unique member identifier. Must not contain spaces."
          },
          "type": {
            "type": "string",
            "description": "Updated member category.",
            "enum": [
              "Member",
              "Visitor"
            ]
          },
          "status": {
            "type": "string",
            "description": "Updated member account status.",
            "enum": [
              "active",
              "inactive"
            ]
          },
          "customFields": {
            "type": "object",
            "description": "Updated organisation-defined custom field values keyed by custom field key.",
            "example": {
              "membershipLevel": "Gold",
              "favoriteLocation": "Main Street",
              "newsletterOptIn": true
            },
            "additionalProperties": true
          }
        }
      },
      "MemberResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessResponse"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Member"
              }
            }
          }
        ]
      },
      "PassInstance": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439013"
          },
          "serialNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive",
              "expired",
              "void"
            ]
          },
          "member": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "$ref": "#/components/schemas/Member"
              }
            ],
            "nullable": true
          },
          "pass": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "$ref": "#/components/schemas/PassTemplate"
              }
            ]
          },
          "recipientName": {
            "type": "string"
          },
          "recipientEmail": {
            "type": "string",
            "format": "email"
          },
          "expiryDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "walletUrls": {
            "$ref": "#/components/schemas/WalletUrls"
          }
        },
        "additionalProperties": true
      },
      "IssuePassRequest": {
        "type": "object",
        "required": [
          "memberId",
          "passTemplateId"
        ],
        "properties": {
          "memberId": {
            "type": "string",
            "description": "Member id to issue the pass to. Use the member _id returned by member endpoints, not your external memberId.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "passTemplateId": {
            "type": "string",
            "description": "Pass template id to issue. Use the id from the List pass templates endpoint.",
            "pattern": "^[a-fA-F0-9]{24}$"
          },
          "expiryDate": {
            "type": "string",
            "format": "date-time",
            "description": "Optional pass expiry date."
          },
          "customFields": {
            "type": "object",
            "description": "Custom field values to store on the issued pass.",
            "example": {
              "membershipLevel": "Gold",
              "favoriteLocation": "Main Street",
              "newsletterOptIn": true
            },
            "additionalProperties": true
          },
          "sendEmail": {
            "type": "boolean",
            "default": true,
            "description": "Defaults to true. Set to false to issue the pass without sending the pass email."
          },
          "eventTime": {
            "type": "string",
            "format": "date-time",
            "description": "Event start time for event ticket pass templates. Required when issuing an event ticket pass."
          },
          "eventName": {
            "type": "string",
            "description": "Event name displayed on event ticket passes. Required when issuing an event ticket pass."
          },
          "seatInfo": {
            "description": "Seat, row, and section details for event ticket passes. Required when issuing an event ticket pass.",
            "$ref": "#/components/schemas/SeatInfo"
          }
        }
      },
      "UpdatePassExpiryRequest": {
        "type": "object",
        "properties": {
          "expiryDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "New pass expiry date. Send null to remove the expiry date."
          }
        }
      },
      "PassInstanceResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessResponse"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/PassInstance"
              }
            }
          }
        ]
      },
      "PassTemplate": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string",
            "example": "507f1f77bcf86cd799439012"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "approved": {
            "type": "boolean"
          },
          "design": {
            "type": "object",
            "description": "Visual design settings used when rendering Apple Wallet and Google Wallet passes.",
            "properties": {
              "backgroundColor": {
                "type": "string",
                "description": "Primary pass background color.",
                "example": "#0F172A"
              },
              "foregroundColor": {
                "type": "string",
                "description": "Primary text color.",
                "example": "#FFFFFF"
              },
              "labelColor": {
                "type": "string",
                "description": "Label text color.",
                "example": "#CBD5E1"
              },
              "voidedBackgroundColor": {
                "type": "string",
                "description": "Background color shown for voided passes.",
                "example": "#64748B"
              },
              "logoText": {
                "type": "string",
                "description": "Text displayed alongside or instead of a logo where supported.",
                "example": "RemyPass"
              },
              "icon": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Icon image URL used by wallet platforms.",
                "example": "https://cdn.remypass.com/passes/icon.png"
              },
              "appleLogo": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Apple Wallet logo image URL.",
                "example": "https://cdn.remypass.com/passes/apple-logo.png"
              },
              "googleLogo": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Google Wallet logo image URL.",
                "example": "https://cdn.remypass.com/passes/google-logo.png"
              },
              "googleWideLogo": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Google Wallet wide logo image URL, commonly used for loyalty passes.",
                "example": "https://cdn.remypass.com/passes/google-wide-logo.png"
              },
              "heroImage": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Google Wallet hero image URL.",
                "example": "https://cdn.remypass.com/passes/hero.png"
              },
              "stripImage": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Apple Wallet strip image URL, commonly used for event tickets.",
                "example": "https://cdn.remypass.com/passes/strip.png"
              },
              "colorVariants": {
                "type": "array",
                "description": "Optional conditional color variants matched against member, pass, or custom field values.",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "Variant id.",
                      "example": "gold-members"
                    },
                    "name": {
                      "type": "string",
                      "description": "Variant display name.",
                      "example": "Gold members"
                    },
                    "field": {
                      "type": "string",
                      "description": "Field to match, such as a custom field or member attribute.",
                      "example": "custom_membershipLevel"
                    },
                    "value": {
                      "type": "string",
                      "description": "Value that activates the variant.",
                      "example": "Gold"
                    },
                    "colors": {
                      "type": "object",
                      "description": "Colors applied when the variant matches.",
                      "properties": {
                        "backgroundColor": {
                          "type": "string",
                          "description": "Variant background color.",
                          "example": "#F59E0B"
                        },
                        "foregroundColor": {
                          "type": "string",
                          "description": "Variant text color.",
                          "example": "#111827"
                        },
                        "labelColor": {
                          "type": "string",
                          "description": "Variant label color.",
                          "example": "#374151"
                        },
                        "voidedBackgroundColor": {
                          "type": "string",
                          "description": "Variant voided background color.",
                          "example": "#92400E"
                        }
                      }
                    }
                  }
                }
              }
            },
            "additionalProperties": true
          },
          "loyalty": {
            "type": "object",
            "description": "Loyalty pass configuration. Present for loyalty pass templates.",
            "properties": {
              "initialStamps": {
                "type": "integer",
                "minimum": 0,
                "default": 0,
                "description": "Number of stamps a loyalty pass starts with.",
                "example": 0
              },
              "maxStamps": {
                "type": "integer",
                "minimum": 1,
                "maximum": 40,
                "default": 10,
                "description": "Number of stamps required to complete the loyalty card.",
                "example": 10
              },
              "emptyStampIconBgColor": {
                "type": "string",
                "description": "Background color for empty stamp slots.",
                "example": "#FFFFFF"
              },
              "filledStampIcon": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Filled stamp icon image URL.",
                "example": "https://cdn.remypass.com/passes/filled-stamp.png"
              },
              "filledStampIconBgColor": {
                "type": "string",
                "description": "Background color for filled stamp slots.",
                "example": "#007BFF"
              },
              "rewardIcon": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Reward icon image URL.",
                "example": "https://cdn.remypass.com/passes/reward.png"
              },
              "stripBackgroundColor": {
                "type": "string",
                "description": "Background color for the loyalty stamp strip.",
                "example": "#F8F9FA"
              },
              "stripBackgroundImage": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Optional custom strip background image URL.",
                "example": "https://cdn.remypass.com/passes/loyalty-strip.png"
              },
              "stampShape": {
                "type": "string",
                "enum": [
                  "circle",
                  "rounded",
                  "square"
                ],
                "default": "circle",
                "description": "Shape used for stamp slots.",
                "example": "circle"
              },
              "issueRewards": {
                "type": "boolean",
                "default": true,
                "description": "Whether rewards are issued automatically when the card reaches maxStamps.",
                "example": true
              },
              "deactivateOnMaxStamps": {
                "type": "boolean",
                "default": false,
                "description": "Whether the loyalty pass is set inactive after reaching maxStamps.",
                "example": false
              },
              "rewardTitle": {
                "type": "string",
                "maxLength": 100,
                "default": "Reward Earned!",
                "description": "Title used for generated rewards.",
                "example": "Free Coffee"
              },
              "rewardDescription": {
                "type": "string",
                "maxLength": 500,
                "default": "Congratulations! Youve earned a reward.",
                "description": "Description used for generated rewards.",
                "example": "Show this reward to claim your free coffee."
              },
              "stampSingular": {
                "type": "string",
                "maxLength": 50,
                "description": "Singular label for a stamp.",
                "example": "Stamp"
              },
              "stampPlural": {
                "type": "string",
                "maxLength": 50,
                "description": "Plural label for stamps.",
                "example": "Stamps"
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "PassTemplateResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessResponse"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/PassTemplate"
              }
            }
          }
        ]
      },
      "EventTicketRequest": {
        "type": "object",
        "required": [
          "bookingId",
          "film",
          "startTime",
          "seat",
          "row",
          "screen",
          "customer"
        ],
        "properties": {
          "bookingId": {
            "type": "string",
            "description": "External booking or order identifier for the ticket. Must be unique; duplicate booking IDs are rejected.",
            "example": "BOOKING-12345"
          },
          "film": {
            "type": "string",
            "description": "Event or film name to display on the ticket.",
            "example": "The Matrix Reloaded"
          },
          "sendEmail": {
            "type": "boolean",
            "default": false,
            "description": "Defaults to false. Set to true to send the ticket email after creation."
          },
          "startTime": {
            "type": "string",
            "format": "date-time",
            "description": "Event start time in ISO 8601 format.",
            "example": "2024-12-25T19:30:00.000Z"
          },
          "seat": {
            "type": "string",
            "description": "Seat number or seat label.",
            "example": "A12"
          },
          "row": {
            "type": "string",
            "description": "Seat row.",
            "example": "A"
          },
          "screen": {
            "type": "string",
            "description": "Screen, room, section, or venue area.",
            "example": "Screen 1"
          },
          "customer": {
            "description": "Customer details for the ticket recipient.",
            "$ref": "#/components/schemas/EventTicketCustomer"
          }
        }
      },
      "EventTicketCustomer": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "John Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "john@example.com"
          }
        },
        "additionalProperties": true
      },
      "EventTicketResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessResponse"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "passInstance": {
                    "$ref": "#/components/schemas/PassInstance"
                  },
                  "walletUrls": {
                    "$ref": "#/components/schemas/WalletUrls"
                  },
                  "emailSent": {
                    "type": "boolean"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ]
      },
      "SeatInfo": {
        "type": "object",
        "properties": {
          "seat": {
            "type": "string"
          },
          "section": {
            "type": "string"
          },
          "row": {
            "type": "string"
          }
        }
      },
      "WalletUrls": {
        "type": "object",
        "properties": {
          "apple": {
            "type": "string",
            "format": "uri"
          },
          "google": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "enum": [
          "scan.created"
        ],
        "description": "Webhook event type. Currently scan.created is supported."
      },
      "WebhookHeaders": {
        "type": "object",
        "additionalProperties": {
          "type": "string"
        },
        "description": "Optional static headers RemyPass includes with each delivery to this subscription.",
        "example": {
          "X-Integration": "remypass"
        }
      },
      "CreateWebhookSubscriptionRequest": {
        "type": "object",
        "required": [
          "name",
          "url",
          "events"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Human-readable subscription name.",
            "example": "Scan Events Webhook"
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Optional internal description for this subscription.",
            "example": "Webhook for real-time scan events"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS endpoint that will receive webhook POST requests. The endpoint must be reachable by a HEAD request during validation.",
            "example": "https://your-app.com/webhooks/remypass"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Event types to deliver to this subscription."
          },
          "headers": {
            "$ref": "#/components/schemas/WebhookHeaders"
          }
        }
      },
      "UpdateWebhookSubscriptionRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Human-readable subscription name."
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Optional internal description for this subscription."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS endpoint that will receive webhook POST requests. If changed, the URL must pass validation."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Event types to deliver to this subscription."
          },
          "active": {
            "type": "boolean",
            "description": "Whether RemyPass should deliver events to this subscription.",
            "default": true
          },
          "headers": {
            "$ref": "#/components/schemas/WebhookHeaders"
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "required": [
          "_id",
          "name",
          "url",
          "events",
          "active",
          "secret",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "_id": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{24}$",
            "description": "Webhook subscription id.",
            "example": "507f1f77bcf86cd799439014"
          },
          "name": {
            "type": "string",
            "description": "Human-readable subscription name.",
            "example": "Scan Events Webhook"
          },
          "description": {
            "type": "string",
            "description": "Optional internal description.",
            "example": "Webhook for real-time scan events"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Destination HTTPS webhook URL.",
            "example": "https://your-app.com/webhooks/remypass"
          },
          "secret": {
            "type": "string",
            "description": "Signing secret. Creation responses include the generated secret; later reads return a masked value.",
            "example": "f3a9b120...d9e47c12"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "description": "Subscribed events."
          },
          "active": {
            "type": "boolean",
            "description": "Whether deliveries are enabled.",
            "example": true
          },
          "lastDeliveryAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp of the latest delivery attempt."
          },
          "lastSuccessfulDeliveryAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp of the latest successful delivery."
          },
          "failureCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Consecutive failed delivery count.",
            "example": 0
          },
          "maxRetries": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "default": 3,
            "description": "Number of retries after the first delivery attempt."
          },
          "retryDelaySeconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3600,
            "default": 60,
            "description": "Initial retry delay. Later retries use exponential backoff."
          },
          "timeoutSeconds": {
            "type": "integer",
            "minimum": 1,
            "maximum": 300,
            "default": 30,
            "description": "Delivery request timeout."
          },
          "headers": {
            "$ref": "#/components/schemas/WebhookHeaders"
          },
          "createdBy": {
            "type": "object",
            "properties": {
              "_id": {
                "type": "string",
                "nullable": true
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            },
            "description": "User or API key owner that created the subscription."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookSubscriptionResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessResponse"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/WebhookSubscription"
              }
            }
          }
        ]
      },
      "ScanCreatedWebhookPayload": {
        "type": "object",
        "required": [
          "id",
          "event",
          "timestamp",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique delivery id. Also sent in X-RemyPass-Delivery."
          },
          "event": {
            "type": "string",
            "enum": [
              "scan.created"
            ],
            "description": "Webhook event name. Also sent in X-RemyPass-Event."
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Time the webhook payload was created."
          },
          "data": {
            "type": "object",
            "required": [
              "scan"
            ],
            "properties": {
              "scan": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Scan record id."
                  },
                  "company": {
                    "type": "string",
                    "description": "Company id."
                  },
                  "source": {
                    "type": "string",
                    "description": "Scan source."
                  },
                  "member": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "description": "Member id."
                      },
                      "name": {
                        "type": "string",
                        "description": "Member name."
                      },
                      "email": {
                        "type": "string",
                        "format": "email",
                        "description": "Member email."
                      },
                      "memberId": {
                        "type": "string",
                        "description": "External member id."
                      }
                    }
                  },
                  "input": {
                    "type": "string",
                    "description": "Scanned input value."
                  },
                  "location": {
                    "type": "string",
                    "description": "Scan location, when provided."
                  },
                  "result": {
                    "type": "string",
                    "description": "Scan result."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Reason associated with the scan result."
                  },
                  "timestamp": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Scan creation time."
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "x-webhooks": {
    "scan.created": {
      "post": {
        "summary": "Scan created",
        "description": "Sent when a pass or member scan is recorded.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScanCreatedWebhookPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx status to acknowledge receipt."
          }
        }
      }
    }
  }
}
