{
  "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 a member, or an event ticket to a non-member recipient. Requires the passes:issue permission.",
        "x-banner": {
          "type": "info",
          "message": "Use this endpoint for all pass issuing. For standard member passes, provide memberId and passTemplateId, plus optional expiryDate, customFields, and sendEmail. For non-member event tickets, omit memberId and provide recipientName and recipientEmail instead. Non-member recipients are only supported for event ticket templates.\n\nFor event tickets, include eventName, eventTime, and any seatInfo required by the selected template. eventTime is optional only when the selected template is configured as redeemable any time. Section, row, and seat are required when visible; hidden seat fields can be omitted. Gate is optional for backwards compatibility, even when it is visible on the template. If the template only uses custom fields, omit seatInfo and provide those values in customFields.\n\nSet maxUses to issue a multi-use event ticket. If omitted, event tickets default to 1 use. Each successful ticket scan consumes one use and the ticket is automatically inactivated when no uses remain."
        },
        "x-docs-extra": "## Event Ticket Uses\n\nEvent ticket passes support a top-level `maxUses` field. Use this for tickets that can be scanned more than once, such as family ride tickets.\n\nIf `maxUses` is omitted, the ticket is issued with 1 use. Each successful ticket scan atomically consumes 1 use, records a redemption, updates `usesRemaining` and `redeemedCount`, and automatically inactivates the ticket when `usesRemaining` reaches 0.\n\nRepeat scans of the same ticket within the company's configured scan-ignore window are treated as duplicates: no use is consumed and the response indicates the scan was ignored.\n\n`maxUses` must be sent as a top-level field, not inside `customFields`.\n\n## Event Ticket Custom Fields\n\nFor event ticket templates, `customFields` can populate company-level custom fields configured on the pass design with a `custom_` dynamic field.\n\nUse a configured company custom field key in the API payload without the `custom_` prefix. For example, a pass design field with `dataField: \"custom_guardianSpaces\"` is populated by `customFields.guardianSpaces` when `guardianSpaces` exists in the company's custom field definitions.\n\nBuilt-in event ticket fields are supplied separately:\n\n| Pass design dynamic field | API source |\n| --- | --- |\n| `company_name` | The company name |\n| `event_name` | `eventName` |\n| `event_time` | `eventTime` |\n| `gate` | `seatInfo.gate` |\n| `screen` | `seatInfo.section` |\n| `row` | `seatInfo.row` |\n| `seat` | `seatInfo.seat` |\n| `ticket_max_uses` | `maxUses` |\n| `ticket_uses_remaining` | Remaining ticket uses after scans |\n| `ticket_redeemed_count` | Number of redeemed ticket uses |\n| `ticket_uses_progress` | Redeemed/maximum uses, for example `1/4` |\n| `custom_<key>` | `customFields.<key>` for a configured company custom field |\n\nGoogle Wallet stores row and seat as separate values (`seatInfo.row` and `seatInfo.seat`). Google may display them together as `Row / Seat`, but API payloads should continue to send them as separate `row` and `seat` fields. Do not send a combined `row_seat` field.\n\nIf a pass template hides one of the seat fields, omit the matching value from `seatInfo`. For example, when Gate is hidden, omit `seatInfo.gate`. When Row is hidden, omit `seatInfo.row`. If a template only uses custom fields in its information row, omit `seatInfo` entirely.",
        "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    \"maxUses\": 4,\n    \"seatInfo\": {\n      \"seat\": \"A12\",\n      \"row\": \"A\",\n      \"section\": \"Screen 1\"\n    },\n    \"customFields\": {\n      \"guardianSpaces\": \"2\",\n      \"siblingSpaces\": \"1\",\n      \"under2s\": \"0\"\n    },\n    \"sendEmail\": true\n  }'"
          },
          {
            "title": "Issue a custom-field event ticket without seats",
            "description": "Use this when the selected event ticket template does not expose built-in Gate, Section, Row, or Seat fields.",
            "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\": \"69fb31b35a479408bfa08e62\",\n    \"eventTime\": \"2026-06-14T10:00:00.000Z\",\n    \"eventName\": \"Summer Splash Day\",\n    \"expiryDate\": \"2026-06-14T23:59:59.000Z\",\n    \"customFields\": {\n      \"guardianSpaces\": \"2\",\n      \"siblingSpaces\": \"1\",\n      \"under2s\": \"0\"\n    },\n    \"sendEmail\": true\n  }'"
          },
          {
            "title": "Issue an event ticket to a non-member",
            "description": "Use this when the ticket recipient is not a RemyPass 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    \"passTemplateId\": \"507f1f77bcf86cd799439012\",\n    \"recipientName\": \"John Doe\",\n    \"recipientEmail\": \"john@example.com\",\n    \"eventTime\": \"2024-12-25T19:30:00.000Z\",\n    \"eventName\": \"The Matrix Reloaded\",\n    \"maxUses\": 1,\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/IssuePassResponse"
                }
              }
            }
          },
          "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"
          }
        }
      }
    }
  },
  "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"
          },
          "maxUses": {
            "type": "integer",
            "minimum": 1,
            "description": "Maximum number of successful scans allowed for an event ticket. Defaults to 1 for tickets issued before multi-use support or when omitted."
          },
          "usesRemaining": {
            "type": "integer",
            "minimum": 0,
            "description": "Remaining successful scans available on an event ticket."
          },
          "redeemedCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of successful event ticket redemptions."
          },
          "lastRedeemedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of the latest successful event ticket redemption."
          }
        },
        "additionalProperties": true
      },
      "IssuePassRequest": {
        "type": "object",
        "required": [
          "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. Required for standard member passes.",
            "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": "Company-level custom field values to store on the issued pass. Unknown custom field keys are rejected.",
            "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."
          },
          "maxUses": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "description": "Event tickets only. Maximum number of successful scans allowed before the ticket is automatically inactivated. Send as a top-level field, not inside customFields."
          },
          "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 details for event ticket passes. For event ticket templates, include values for visible built-in section, row, and seat fields. Gate is optional. Hidden fields can be omitted. Custom-only event ticket templates can omit seatInfo.",
            "$ref": "#/components/schemas/SeatInfo"
          },
          "recipientName": {
            "type": "string",
            "description": "Non-member event ticket recipient name. Use with recipientEmail instead of memberId.",
            "example": "John Doe"
          },
          "recipientEmail": {
            "type": "string",
            "format": "email",
            "description": "Non-member event ticket recipient email. Use with recipientName instead of memberId.",
            "example": "john@example.com"
          }
        },
        "oneOf": [
          {
            "required": [
              "memberId"
            ]
          },
          {
            "required": [
              "recipientName",
              "recipientEmail"
            ]
          }
        ],
        "example": {
          "memberId": "507f1f77bcf86cd799439011",
          "passTemplateId": "507f1f77bcf86cd799439012",
          "expiryDate": "2024-12-31T23:59:59.000Z",
          "customFields": {
            "membershipLevel": "Premium"
          },
          "sendEmail": true
        }
      },
      "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"
              }
            }
          }
        ]
      },
      "IssuePassResponse": {
        "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"
                  }
                }
              }
            }
          }
        ]
      },
      "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"
              }
            }
          }
        ]
      },
      "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."
          }
        }
      }
    }
  }
}
