{
  "openapi": "3.0.3",
  "info": {
    "title": "EMS Keluarga Allah — Check-in API",
    "description": "REST API for the check-in application. All endpoints require a valid API token issued from the admin panel (`/backend/api-tokens`).\n\n## Authentication\nPass the token in every request header:\n```\nAuthorization: Bearer <your_token>\n```\n\n## Token Scoping\n- A token may be **global** (all churches, all events) or **scoped** to a specific event and/or set of churches.\n- Attempting to access a resource outside your token's scope returns `403 Forbidden`.\n\n## Base URL\n`http://localhost:8080/api/v1` (local dev)",
    "version": "1.0.0",
    "contact": {
      "name": "EMS — Keluarga Allah"
    }
  },
  "servers": [
    {
      "url": "http://localhost:8080/api/v1",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "BearerToken": []
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token issued from /backend/api-tokens. Pass as `Authorization: Bearer <token>`."
      }
    },
    "schemas": {
      "SuccessEnvelope": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "message": {
            "type": "string",
            "example": "Participant not found."
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "example": 250
          },
          "page": {
            "type": "integer",
            "example": 1
          },
          "per_page": {
            "type": "integer",
            "example": 50
          },
          "pages": {
            "type": "integer",
            "example": 5
          }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1
          },
          "name": {
            "type": "string",
            "example": "Unlimited Conference 2026"
          },
          "event_code": {
            "type": "string",
            "example": "UC2026"
          },
          "slug": {
            "type": "string",
            "example": "unlimited-conference-2026"
          },
          "logo_url": {
            "type": "string",
            "nullable": true,
            "example": "http://localhost:8080/uploads/logos/event-logo.png"
          },
          "thumbnail_url": {
            "type": "string",
            "nullable": true,
            "example": "http://localhost:8080/uploads/thumbnails/event-thumb.jpg"
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "example": "2026-07-10"
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "example": "2026-07-12"
          },
          "quota": {
            "type": "integer",
            "example": 1000
          },
          "used_quota": {
            "type": "integer",
            "example": 342
          }
        }
      },
      "ParticipantSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 42
          },
          "name": {
            "type": "string",
            "example": "John Doe"
          },
          "qr_number": {
            "type": "string",
            "example": "AB120042CD56"
          },
          "church": {
            "type": "string",
            "nullable": true,
            "example": "GBI Keluarga Allah"
          },
          "package": {
            "type": "string",
            "example": "Full Package"
          },
          "payment_status": {
            "type": "string",
            "enum": ["paid", "pending", "canceled", "rejected"],
            "example": "paid"
          },
          "invoice_number": {
            "type": "string",
            "example": "INV/2026/00042"
          },
          "is_checked_in": {
            "type": "boolean",
            "example": false
          },
          "first_checked_in_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": null
          },
          "last_checked_in_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": null
          },
          "checkin_count": {
            "type": "integer",
            "example": 0
          }
        }
      },
      "ParticipantListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 42
          },
          "name": {
            "type": "string",
            "example": "John Doe"
          },
          "qr_number": {
            "type": "string",
            "example": "AB120042CD56"
          },
          "church_name": {
            "type": "string",
            "nullable": true,
            "description": "Free-text church name if not linked to a church record",
            "example": null
          },
          "church_db_name": {
            "type": "string",
            "nullable": true,
            "description": "Church name from the churches table",
            "example": "GBI Keluarga Allah"
          },
          "package_name": {
            "type": "string",
            "example": "Full Package"
          },
          "is_checked_in": {
            "type": "integer",
            "enum": [0, 1],
            "example": 0
          },
          "first_checked_in_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": null
          },
          "last_checked_in_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": null
          }
        }
      },
      "CheckinLog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1
          },
          "checked_in_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-07-10 09:15:00"
          },
          "checked_in_by": {
            "type": "string",
            "description": "Name of the API token that recorded this scan",
            "example": "Main Gate Scanner"
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "ip_address": {
            "type": "string",
            "example": "192.168.1.10"
          }
        }
      },
      "EventCheckinLogItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1
          },
          "checked_in_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-07-10 09:15:00"
          },
          "checked_in_by": {
            "type": "string",
            "example": "Main Gate Scanner"
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "ip_address": {
            "type": "string",
            "example": "192.168.1.10"
          },
          "participant_name": {
            "type": "string",
            "example": "John Doe"
          },
          "qr_number": {
            "type": "string",
            "example": "AB120042CD56"
          },
          "church": {
            "type": "string",
            "nullable": true,
            "example": "GBI Keluarga Allah"
          }
        }
      },
      "TicketTemplate": {
        "type": "object",
        "properties": {
          "sort_order": {
            "type": "integer",
            "example": 1
          },
          "ticket_master_id": {
            "type": "integer",
            "example": 3
          },
          "name": {
            "type": "string",
            "example": "Main Ticket"
          },
          "ticket_code": {
            "type": "string",
            "example": "TKT-MAIN"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "General admission ticket"
          },
          "template_html": {
            "type": "string",
            "description": "Raw HTML template with {{slug}} placeholders",
            "example": "<div>{{participant_name}} — {{qr_number}}</div>"
          },
          "template_logo_url": {
            "type": "string",
            "nullable": true,
            "example": "http://localhost:8080/uploads/ticket_logos/logo.png"
          }
        }
      },
      "PackageWithTickets": {
        "type": "object",
        "properties": {
          "package_id": {
            "type": "integer",
            "example": 2
          },
          "package_name": {
            "type": "string",
            "example": "Full Package"
          },
          "tickets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TicketTemplate"
            }
          }
        }
      },
      "Certificate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1
          },
          "event_id": {
            "type": "integer",
            "example": 1
          },
          "name": {
            "type": "string",
            "example": "Certificate of Participation"
          },
          "orientation": {
            "type": "string",
            "enum": ["portrait", "landscape"],
            "example": "landscape"
          },
          "template_html": {
            "type": "string",
            "description": "Raw HTML template with {{slug}} placeholders (unparsed)",
            "example": "<div>{{participant_name}} has participated in {{event_name}}</div>"
          }
        }
      },
      "ScanTicketItem": {
        "type": "object",
        "description": "A ticket template with all {{slugs}} already replaced with participant data",
        "properties": {
          "sort_order": {
            "type": "integer",
            "example": 1
          },
          "ticket_master_id": {
            "type": "integer",
            "example": 3
          },
          "name": {
            "type": "string",
            "example": "Main Ticket"
          },
          "ticket_code": {
            "type": "string",
            "example": "TKT-MAIN"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "template_html": {
            "type": "string",
            "description": "Fully rendered HTML — all {{slugs}} replaced, images optionally base64 embedded",
            "example": "<div>John Doe — AB120042CD56 <img src=\"data:image/png;base64,...\"></div>"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid Bearer token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "message": "Unauthorized. Provide a valid Bearer token."
            }
          }
        }
      },
      "Forbidden": {
        "description": "Token does not have access to this resource (event or church scope mismatch).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "message": "Forbidden. This token does not have access to this resource."
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "message": "Participant not found."
            }
          }
        }
      }
    },
    "parameters": {
      "QrNumber": {
        "name": "qr",
        "in": "path",
        "required": true,
        "description": "Participant QR number (case-insensitive, auto-uppercased server-side)",
        "schema": {
          "type": "string",
          "example": "AB120042CD56"
        }
      },
      "EventId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Event ID",
        "schema": {
          "type": "integer",
          "example": 1
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 1,
          "minimum": 1
        }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 50,
          "minimum": 1,
          "maximum": 200
        }
      },
      "OrderDir": {
        "name": "order_dir",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": ["ASC", "DESC"],
          "default": "ASC"
        }
      }
    }
  },
  "tags": [
    {
      "name": "Events",
      "description": "Event listing"
    },
    {
      "name": "Check-in",
      "description": "Participant lookup and check-in scanning"
    },
    {
      "name": "Participants",
      "description": "Participant list and history"
    },
    {
      "name": "Tickets",
      "description": "Ticket templates per package"
    },
    {
      "name": "Certificates",
      "description": "Certificate template and PDF generation"
    }
  ],
  "paths": {
    "/events": {
      "get": {
        "tags": ["Events"],
        "summary": "List active events",
        "description": "Returns all active events accessible by this token. If the token is scoped to a specific event, only that event is returned.",
        "operationId": "listEvents",
        "responses": {
          "200": {
            "description": "List of events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/checkin/participant/{qr}": {
      "get": {
        "tags": ["Check-in"],
        "summary": "Look up participant by QR number",
        "description": "Returns participant details without recording a check-in. Use this for pre-scan preview.",
        "operationId": "getParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/QrNumber"
          }
        ],
        "responses": {
          "200": {
            "description": "Participant found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ParticipantSummary"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/checkin/participant/{qr}/logs": {
      "get": {
        "tags": ["Check-in"],
        "summary": "Get check-in history for a participant",
        "description": "Returns the full check-in log for a single participant (all scan events).",
        "operationId": "participantLogs",
        "parameters": [
          {
            "$ref": "#/components/parameters/QrNumber"
          }
        ],
        "responses": {
          "200": {
            "description": "Check-in history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "participant_id": {
                      "type": "integer",
                      "example": 42
                    },
                    "qr_number": {
                      "type": "string",
                      "example": "AB120042CD56"
                    },
                    "name": {
                      "type": "string",
                      "example": "John Doe"
                    },
                    "checkin_count": {
                      "type": "integer",
                      "example": 2
                    },
                    "logs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CheckinLog"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/checkin/scan": {
      "post": {
        "tags": ["Check-in"],
        "summary": "Record a check-in",
        "description": "Records a check-in for the participant with the given QR number. The transaction must have `payment_status = paid`. Returns the participant details and all pre-rendered ticket templates for the participant's package.\n\nIf the participant has already been checked in, the response includes `checkin_count > 1` and the message indicates a repeat scan.",
        "operationId": "scan",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["qr_number"],
                "properties": {
                  "qr_number": {
                    "type": "string",
                    "description": "Participant QR number (case-insensitive)",
                    "example": "AB120042CD56"
                  },
                  "notes": {
                    "type": "string",
                    "nullable": true,
                    "description": "Optional notes for this check-in scan",
                    "example": "Gate 1"
                  },
                  "embed_images": {
                    "type": "boolean",
                    "default": false,
                    "description": "When true, images in `template_html` are embedded as base64 data URIs instead of public URLs. Use this when printing offline."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Check-in recorded successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "message": {
                      "type": "string",
                      "example": "Check-in successful."
                    },
                    "checkin_count": {
                      "type": "integer",
                      "description": "Total number of times this participant has been scanned",
                      "example": 1
                    },
                    "participant": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "example": 42
                        },
                        "name": {
                          "type": "string",
                          "example": "John Doe"
                        },
                        "qr_number": {
                          "type": "string",
                          "example": "AB120042CD56"
                        },
                        "event_id": {
                          "type": "integer",
                          "example": 1
                        },
                        "event_name": {
                          "type": "string",
                          "example": "Unlimited Conference 2026"
                        },
                        "package_id": {
                          "type": "integer",
                          "example": 2
                        },
                        "package_name": {
                          "type": "string",
                          "example": "Full Package"
                        }
                      }
                    },
                    "tickets": {
                      "type": "array",
                      "description": "Pre-rendered ticket templates. All {{slug}} placeholders replaced with real participant data.",
                      "items": {
                        "$ref": "#/components/schemas/ScanTicketItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Participant not found or transaction not paid",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "success": false,
                  "message": "Participant not found or transaction not paid."
                }
              }
            }
          },
          "422": {
            "description": "Missing required field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "success": false,
                  "message": "qr_number is required."
                }
              }
            }
          }
        }
      }
    },
    "/event/{id}/stats": {
      "get": {
        "tags": ["Events"],
        "summary": "Get live check-in statistics for an event",
        "description": "Returns total participants, checked-in count, and not-checked count. Scoped to accessible churches if the token has church restrictions.",
        "operationId": "eventStats",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventId"
          }
        ],
        "responses": {
          "200": {
            "description": "Statistics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "event_id": {
                          "type": "integer",
                          "example": 1
                        },
                        "event_name": {
                          "type": "string",
                          "example": "Unlimited Conference 2026"
                        },
                        "total": {
                          "type": "integer",
                          "example": 342
                        },
                        "checked_in": {
                          "type": "integer",
                          "example": 157
                        },
                        "not_checked": {
                          "type": "integer",
                          "example": 185
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/event/{id}/ticket-templates": {
      "get": {
        "tags": ["Tickets"],
        "summary": "Get ticket templates for an event",
        "description": "Returns all active packages for the event, each with its ordered list of ticket templates. The `template_html` contains raw HTML with `{{slug}}` placeholders (unparsed). Use `POST /checkin/scan` to get fully parsed templates.\n\nAvailable template slugs: `{{participant_name}}`, `{{qr_number}}`, `{{qr_image_path}}` (base64), `{{event_name}}`, `{{package_name}}`, `{{event_logo}}`, `{{ticket_logo}}`, `{{include_meal}}`",
        "operationId": "eventTicketTemplates",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventId"
          },
          {
            "name": "package_id",
            "in": "query",
            "description": "Filter to a single package",
            "schema": {
              "type": "integer",
              "example": 2
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Packages with their ticket templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PackageWithTickets"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/event/{id}/checkin-logs": {
      "get": {
        "tags": ["Participants"],
        "summary": "Get all check-in logs for an event",
        "description": "Returns a paginated list of all check-in scan events for an event, including participant name and church. Scoped to accessible churches if the token has church restrictions.",
        "operationId": "eventCheckinLogs",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventId"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "order_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["checked_in_at", "participant_name", "church", "checked_in_by"],
              "default": "checked_in_at"
            }
          },
          {
            "name": "order_dir",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["ASC", "DESC"],
              "default": "DESC"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated check-in logs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EventCheckinLogItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/event/{id}/participants": {
      "get": {
        "tags": ["Participants"],
        "summary": "Get participant list for an event",
        "description": "Returns a paginated, filterable list of paid participants for an event. Scoped to accessible churches if the token has church restrictions.",
        "operationId": "eventParticipants",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventId"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "name": "order_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["name", "qr_number", "church", "package", "is_checked_in", "last_checked_in_at"],
              "default": "name"
            }
          },
          {
            "$ref": "#/components/parameters/OrderDir"
          },
          {
            "name": "search",
            "in": "query",
            "description": "Search by participant name or QR number (partial match)",
            "schema": {
              "type": "string",
              "example": "John"
            }
          },
          {
            "name": "is_checked_in",
            "in": "query",
            "description": "Filter by check-in status",
            "schema": {
              "type": "integer",
              "enum": [0, 1],
              "example": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated participant list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ParticipantListItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/event/{id}/certificate": {
      "get": {
        "tags": ["Certificates"],
        "summary": "Get raw certificate template for an event",
        "description": "Returns the unparsed certificate template HTML. The `template_html` contains `{{slug}}` placeholders such as `{{participant_name}}`, `{{event_name}}`, `{{church_name}}`, `{{package_name}}`, `{{event_logo}}`.",
        "operationId": "getCertificateByEvent",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventId"
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Certificate"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No active certificate found for this event",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/checkin/participant/{qr}/certificate": {
      "get": {
        "tags": ["Certificates"],
        "summary": "Get rendered certificate HTML for a participant",
        "description": "Returns the certificate template with all `{{slug}}` placeholders replaced with the participant's actual data. Does not enforce payment or check-in status.\n\nUse `embed_images=1` to get base64-encoded images suitable for offline rendering.",
        "operationId": "certificateHtmlForParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/QrNumber"
          },
          {
            "name": "embed_images",
            "in": "query",
            "description": "Set to `1` to embed images as base64 data URIs",
            "schema": {
              "type": "integer",
              "enum": [0, 1],
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rendered certificate HTML",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "participant_name": {
                      "type": "string",
                      "example": "John Doe"
                    },
                    "certificate_name": {
                      "type": "string",
                      "example": "Certificate of Participation"
                    },
                    "orientation": {
                      "type": "string",
                      "enum": ["portrait", "landscape"],
                      "example": "landscape"
                    },
                    "html": {
                      "type": "string",
                      "description": "Fully rendered HTML with all placeholders replaced",
                      "example": "<div>John Doe has participated in Unlimited Conference 2026</div>"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/checkin/participant/{qr}/certificate/pdf": {
      "get": {
        "tags": ["Certificates"],
        "summary": "Stream certificate PDF for a participant",
        "description": "Streams the certificate as an inline PDF. Does **not** enforce payment or check-in status — use this for preview. The response is a raw PDF binary stream (`Content-Type: application/pdf`).",
        "operationId": "certificatePdfForParticipant",
        "parameters": [
          {
            "$ref": "#/components/parameters/QrNumber"
          }
        ],
        "responses": {
          "200": {
            "description": "PDF binary stream",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/certificate/download": {
      "get": {
        "tags": ["Certificates"],
        "summary": "Download certificate PDF (paid + checked-in only)",
        "description": "Streams the certificate as an inline PDF. **Enforces** both `payment_status = paid` AND `is_checked_in = 1`. Intended for end-user facing download flows.",
        "operationId": "downloadCertificate",
        "parameters": [
          {
            "name": "event_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 1
            }
          },
          {
            "name": "qr_number",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "AB120042CD56"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PDF binary stream",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Payment not completed or participant not checked in",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "success": false,
                  "message": "Certificate is only available after check-in."
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "Missing required query parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "success": false,
                  "message": "event_id and qr_number are required."
                }
              }
            }
          }
        }
      }
    }
  }
}
