{
    "openapi": "3.0.0",
    "info": {
        "title": "Boxli Public API",
        "description": "Read-only public API for B2B partners. Use it to pull\n *                  scans, properties, floors, rooms, and photos by UUID\n *                  into your own systems.\n *\n *                  All endpoints require an API key sent in the\n *                  `X-API-Key` header. API keys are provisioned by\n *                  Boxli staff — contact help@boxli.ai to request one\n *                  for a new integration.",
        "contact": {
            "name": "Boxli Support",
            "email": "help@boxli.ai"
        },
        "version": "2.0.0"
    },
    "servers": [
        {
            "url": "https://api.boxli.ai",
            "description": "Boxli Public API"
        }
    ],
    "paths": {
        "/api/v2/public/auth/me": {
            "get": {
                "tags": [
                    "Auth"
                ],
                "summary": "Current authenticated user",
                "description": "Returns the identity associated with the API key on the request. Use this to confirm a key is wired up correctly and to display the partner's own name/email in your UI.",
                "operationId": "8ead64f78855b75ea0445ba5f715b983",
                "responses": {
                    "200": {
                        "description": "The user record.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "user": {
                                            "properties": {
                                                "id": {
                                                    "type": "integer"
                                                },
                                                "email": {
                                                    "type": "string"
                                                },
                                                "first_name": {
                                                    "type": "string"
                                                },
                                                "last_name": {
                                                    "type": "string"
                                                },
                                                "role": {
                                                    "type": "integer"
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid X-API-Key.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/auth/me/activity": {
            "get": {
                "tags": [
                    "Auth"
                ],
                "summary": "List your recent API requests",
                "description": "Returns the most-recent requests YOUR API key has made to the public API, newest first. Useful for confirming an integration is hitting the right endpoints and for debugging failures — every request includes its `request_id` (matches the `X-Request-Id` response header you would have received). Scoped to your user only; you cannot see other partners' traffic.",
                "operationId": "f2607b4c0b7cf9f55270db67321c23c8",
                "parameters": [
                    {
                        "name": "status_code",
                        "in": "query",
                        "description": "Filter to a single HTTP status code (e.g. 200, 401, 404).",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "method",
                        "in": "query",
                        "description": "Filter by HTTP method.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "GET",
                                "POST",
                                "PUT",
                                "PATCH",
                                "DELETE"
                            ]
                        }
                    },
                    {
                        "name": "path_contains",
                        "in": "query",
                        "description": "Substring match against the request path (e.g. `scans`).",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 80
                        }
                    },
                    {
                        "name": "after",
                        "in": "query",
                        "description": "ISO 8601 — only return requests with `created_at >= after`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "before",
                        "in": "query",
                        "description": "ISO 8601 — only return requests with `created_at <= before`.",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 1
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Page of your activity.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ApiActivityEntry"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Unauthenticated.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/properties": {
            "get": {
                "tags": [
                    "Properties"
                ],
                "summary": "List mobile-scanned properties",
                "operationId": "a7e03de6090ab8083fafc00b27a285fd",
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "default": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "default": 15,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "uuids",
                        "in": "query",
                        "description": "Bulk fetch: comma-separated UUID list (max 100). Malformed UUIDs are silently dropped.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "property_type",
                        "in": "query",
                        "description": "Filter by property_type. Empty/'string' placeholder ignored.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "scan_complete",
                        "in": "query",
                        "description": "Filter to only complete (or only incomplete) properties.",
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "created_after",
                        "in": "query",
                        "description": "ISO-8601. Only properties created at or after this time.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "created_before",
                        "in": "query",
                        "description": "ISO-8601. Only properties created at or before this time.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated property list.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Property"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Unauthenticated.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/properties/{uuid}": {
            "get": {
                "tags": [
                    "Properties"
                ],
                "summary": "Get a single property with count aggregates",
                "operationId": "019159c818b043b13b479f7d0d0a76ac",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The property.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/PropertyDetail"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Property not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/properties/{uuid}/floors": {
            "get": {
                "tags": [
                    "Floors"
                ],
                "summary": "List floors of a property",
                "operationId": "858127236b820af939e273b1d1f23bc8",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Floors (ordered by floor_number).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/PropertyFloor"
                                            }
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Property not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/properties/{uuid}/floorplan.json": {
            "get": {
                "tags": [
                    "Floors"
                ],
                "summary": "Get programmatic floor geometry (walls, doors, rooms, labels) for a property",
                "description": "Returns the same canonical floor-plan JSON shape the boxli dashboard uses internally. Partners can use it to render their own floor diagrams without parsing scan binaries.",
                "operationId": "2f0baf0b466f6a25d3a0bd8d127b4a1f",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Floor plan geometry. Shape follows boxli's internal floorplan.json schema.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "description": "Open-shape JSON. See dashboard docs for the field list.",
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Property not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/properties/{uuid}/photos": {
            "get": {
                "tags": [
                    "Photos"
                ],
                "summary": "List photos of a property",
                "description": "Paginated photos with presigned download + thumbnail URLs.",
                "operationId": "8f7e7029ff6506dbc01d79a566501de2",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "default": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "room_uuid",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    },
                    {
                        "name": "floor_uuid",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    },
                    {
                        "name": "photo_code",
                        "in": "query",
                        "description": "UPD code (e.g. STRUCTURE_REAR).",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated photo list.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/PropertyPhoto"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Property not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/photos/{uuid}": {
            "get": {
                "tags": [
                    "Photos"
                ],
                "summary": "Get a single photo with presigned URLs",
                "operationId": "156cb0b8ed0f484720183579fdea302d",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The photo.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/PropertyPhoto"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Photo not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/properties/{uuid}/floors/{floorUuid}/rooms": {
            "get": {
                "tags": [
                    "Rooms"
                ],
                "summary": "List rooms of a floor",
                "operationId": "5b760e36de337d5a4393005bb2a1b985",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    },
                    {
                        "name": "floorUuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Rooms on this floor (with presigned room-model URLs when available).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/PropertyRoom"
                                            }
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Property or floor not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/scans": {
            "get": {
                "tags": [
                    "Scans"
                ],
                "summary": "List scans the authenticated user can access",
                "description": "Paginated list of scans owned by the authenticated user or any sub-user (hierarchy child).",
                "operationId": "0d9582388f7b703d756b4ba5e072346e",
                "parameters": [
                    {
                        "name": "page",
                        "in": "query",
                        "description": "Page number (default 1).",
                        "schema": {
                            "type": "integer",
                            "default": 1
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "description": "Items per page (max 100).",
                        "schema": {
                            "type": "integer",
                            "default": 15,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "uuids",
                        "in": "query",
                        "description": "Bulk fetch: comma-separated UUID list (max 100). Malformed UUIDs are silently dropped.",
                        "schema": {
                            "type": "string",
                            "example": "uuid1,uuid2,uuid3"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "description": "Filter by status. Invalid values are silently ignored.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "pending",
                                "processing",
                                "complete",
                                "failed"
                            ]
                        }
                    },
                    {
                        "name": "source",
                        "in": "query",
                        "description": "Filter by metadata.source (e.g. 'mobile'). Empty / 'string' placeholder ignored.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "created_after",
                        "in": "query",
                        "description": "ISO-8601 date or datetime. Only scans created at or after this time.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "created_before",
                        "in": "query",
                        "description": "ISO-8601 date or datetime. Only scans created at or before this time.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated scan list.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Scan"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Unauthenticated.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/scans/{uuid}": {
            "get": {
                "tags": [
                    "Scans"
                ],
                "summary": "Get a single scan by UUID",
                "operationId": "3c605494759752fcf963dab829b3705b",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The scan.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ScanDetail"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Scan not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/scans/{uuid}/report": {
            "get": {
                "tags": [
                    "Scans"
                ],
                "summary": "Get a presigned download URL for the scan's PDF report",
                "description": "Returns a short-lived presigned S3 URL to the scan's PDF report. Reports are generated server-side via the dashboard. Returns 404 if no report has been generated for this scan yet.",
                "operationId": "19c739c52c202568ac623cf86a967b84",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Presigned report URL.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/PresignedDownload"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Scan not found or no report available.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Scans"
                ],
                "summary": "Generate (or regenerate) the scan's PDF report",
                "description": "Synchronously generates a PDF report from the scan + property data, persists it to S3 at scans/{userId}/{scanUuid}/report.pdf, updates the scan's metadata.report_path, and returns a presigned download URL. Idempotent — re-calling overwrites the existing report. Generation can take 5-15 seconds for large properties.",
                "operationId": "71a15b1147e9625d881167bd918fb8b7",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Report generated. Returns the presigned URL.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/PresignedDownload"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Scan not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Report generation failed.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/scans/{uuid}/download": {
            "get": {
                "tags": [
                    "Scans"
                ],
                "summary": "Get a presigned download URL for the scan file",
                "description": "Returns a short-lived presigned S3 URL (5-min default TTL). Local-disk scans return 404 — only S3-backed files are downloadable via the public API.",
                "operationId": "467998234dc2d162967e6d418f5643d9",
                "parameters": [
                    {
                        "name": "uuid",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid",
                            "example": "00000000-0000-0000-0000-000000000000"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Presigned download URL.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/PresignedDownload"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Scan not found or not downloadable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Presigning failed.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/webhooks": {
            "get": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "List your active webhook subscriptions",
                "operationId": "cb72fa3ea9c7ace85c11bfc074663e23",
                "responses": {
                    "200": {
                        "description": "Owned subscriptions (secret is hidden).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WebhookSubscription"
                                            }
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "Create a webhook subscription",
                "description": "Returns the signing secret ONCE in the response. Store it — there's no way to retrieve it later. Use it to verify the X-Boxli-Signature header on incoming deliveries.",
                "operationId": "30ac2b4876fed8b772c121d215e3f629",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "event",
                                    "url"
                                ],
                                "properties": {
                                    "event": {
                                        "type": "string",
                                        "enum": [
                                            "scan.completed",
                                            "scan.failed"
                                        ],
                                        "example": "scan.completed"
                                    },
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "example": "https://hooks.partner.example/boxli"
                                    },
                                    "name": {
                                        "type": "string",
                                        "example": "Production scan webhook",
                                        "nullable": true
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created. `secret` is in this response and ONLY this response.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WebhookSubscription"
                                        },
                                        "secret": {
                                            "description": "HMAC signing secret. Store securely — not shown again.",
                                            "type": "string"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation failed.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/webhooks/{id}": {
            "delete": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "Delete a webhook subscription",
                "operationId": "8ce792407b93d0d0f6483e907f6aef6f",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted."
                    },
                    "404": {
                        "description": "Subscription not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/webhooks/{id}/deliveries": {
            "get": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "List delivery attempts for a subscription",
                "description": "Returns the most-recent delivery rows for the given subscription, newest first. Use this to debug 'why didn't my webhook fire?' — every fan-out (event-fire) creates a row, every retry updates the same row, and every manual replay creates a new row.",
                "operationId": "c39e7b4bae07c49c86f9e69aa44e0d9d",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "description": "Filter: pending | delivered | exhausted",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "pending",
                                "delivered",
                                "exhausted"
                            ]
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 25,
                            "maximum": 100
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Deliveries page.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/WebhookDelivery"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/PaginationMeta"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Subscription not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        },
        "/api/v2/public/webhooks/deliveries/{id}/replay": {
            "post": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "Re-fire a previous delivery",
                "description": "Creates a NEW delivery row referencing the same payload + event, dispatched to the subscription's CURRENT url. The original row is preserved unchanged. Use when a partner endpoint was down during the original attempt and you want to manually re-trigger.",
                "operationId": "89a43fe9c60896455f8eb40d336fcaf4",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "The delivery row id (NOT the subscription id).",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Replay scheduled.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WebhookDelivery"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Delivery not found.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorEnvelope"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "apiKeyAuth": []
                    }
                ]
            }
        }
    },
    "components": {
        "schemas": {
            "PropertyDetail": {
                "properties": {
                    "uuid": {
                        "description": "Public-API detail shape for `App\\Models\\Property`. Adds count\naggregates so a single detail call gives clients enough to render\na summary card without N+1'ing through floors/rooms/photos.",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "name": {
                        "type": "string",
                        "nullable": true
                    },
                    "address": {
                        "type": "string",
                        "nullable": true
                    },
                    "apartment": {
                        "type": "string",
                        "nullable": true
                    },
                    "city": {
                        "type": "string",
                        "nullable": true
                    },
                    "state": {
                        "type": "string",
                        "nullable": true
                    },
                    "postal_code": {
                        "type": "string",
                        "nullable": true
                    },
                    "latitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "longitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "altitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "property_type": {
                        "type": "string",
                        "nullable": true
                    },
                    "number_of_floors": {
                        "type": "integer"
                    },
                    "has_finished_attic": {
                        "type": "boolean"
                    },
                    "basement_levels": {
                        "type": "integer"
                    },
                    "scan_complete": {
                        "type": "boolean"
                    },
                    "floors_count": {
                        "type": "integer"
                    },
                    "rooms_count": {
                        "type": "integer"
                    },
                    "photos_count": {
                        "type": "integer"
                    },
                    "links": {
                        "description": "Hypermedia links to related endpoints.",
                        "properties": {
                            "floors": {
                                "type": "string",
                                "format": "uri",
                                "example": "https://api.boxli.ai/api/v2/public/properties/{uuid}/floors"
                            },
                            "photos": {
                                "type": "string",
                                "format": "uri",
                                "example": "https://api.boxli.ai/api/v2/public/properties/{uuid}/photos"
                            }
                        },
                        "type": "object"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "updated_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "PropertyFloor": {
                "properties": {
                    "uuid": {
                        "description": "Public-API shape for `App\\Models\\PropertyFloor`.\n\nDeliberately omits the heavy `dashboard_overlay` JSON blob,\n`rendered_geometry`, and `floor_structure_path` (raw S3 key).",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "floor_number": {
                        "type": "integer",
                        "example": 1
                    },
                    "name": {
                        "type": "string",
                        "example": "First Floor",
                        "nullable": true
                    },
                    "is_basement": {
                        "type": "boolean"
                    },
                    "rooms_count": {
                        "type": "integer"
                    },
                    "compass_heading": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "total_area_sq_ft": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    }
                },
                "type": "object"
            },
            "PropertyPhoto": {
                "properties": {
                    "uuid": {
                        "description": "Public-API shape for `App\\Models\\PropertyPhoto`. Includes presigned\ndownload + thumbnail URLs.",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "photo_code": {
                        "type": "string",
                        "example": "STRUCTURE_REAR",
                        "nullable": true
                    },
                    "display_label": {
                        "type": "string",
                        "example": "Structure Rear",
                        "nullable": true
                    },
                    "caption": {
                        "type": "string",
                        "nullable": true
                    },
                    "rotation": {
                        "type": "integer",
                        "example": 0
                    },
                    "captured_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "latitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "longitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "position": {
                        "properties": {
                            "x": {
                                "type": "number",
                                "format": "double"
                            },
                            "y": {
                                "type": "number",
                                "format": "double"
                            },
                            "z": {
                                "type": "number",
                                "format": "double"
                            }
                        },
                        "type": "object",
                        "nullable": true
                    },
                    "room_uuid": {
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000",
                        "nullable": true
                    },
                    "floor_uuid": {
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000",
                        "nullable": true
                    },
                    "download_url": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/PresignedDownload"
                            }
                        ],
                        "nullable": true
                    },
                    "thumbnail_url": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/PresignedDownload"
                            }
                        ],
                        "nullable": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "Property": {
                "properties": {
                    "uuid": {
                        "description": "Public-API list shape for `App\\Models\\Property` (mobile-source\nscanned properties).",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "name": {
                        "type": "string",
                        "nullable": true
                    },
                    "address": {
                        "type": "string",
                        "nullable": true
                    },
                    "apartment": {
                        "type": "string",
                        "nullable": true
                    },
                    "city": {
                        "type": "string",
                        "nullable": true
                    },
                    "state": {
                        "type": "string",
                        "nullable": true
                    },
                    "postal_code": {
                        "type": "string",
                        "nullable": true
                    },
                    "latitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "longitude": {
                        "type": "number",
                        "format": "double",
                        "nullable": true
                    },
                    "property_type": {
                        "type": "string",
                        "nullable": true
                    },
                    "number_of_floors": {
                        "type": "integer"
                    },
                    "scan_complete": {
                        "type": "boolean"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "PropertyRoom": {
                "properties": {
                    "uuid": {
                        "description": "Public-API shape for `App\\Models\\PropertyRoom`. Includes a\npresigned URL for the room's 3D model (USDZ) when one exists on S3.",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "name": {
                        "type": "string",
                        "nullable": true
                    },
                    "room_type": {
                        "type": "string",
                        "nullable": true
                    },
                    "room_types": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "dimensions": {
                        "properties": {
                            "width": {
                                "type": "number",
                                "format": "double",
                                "nullable": true
                            },
                            "length": {
                                "type": "number",
                                "format": "double",
                                "nullable": true
                            },
                            "height": {
                                "type": "number",
                                "format": "double",
                                "nullable": true
                            }
                        },
                        "type": "object"
                    },
                    "position": {
                        "properties": {
                            "x": {
                                "type": "number",
                                "format": "double",
                                "nullable": true
                            },
                            "y": {
                                "type": "number",
                                "format": "double",
                                "nullable": true
                            },
                            "z": {
                                "type": "number",
                                "format": "double",
                                "nullable": true
                            }
                        },
                        "type": "object"
                    },
                    "model_download": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/PresignedDownload"
                            }
                        ],
                        "nullable": true
                    },
                    "photo_count": {
                        "type": "integer"
                    }
                },
                "type": "object"
            },
            "ScanDetail": {
                "properties": {
                    "uuid": {
                        "description": "Public-API detail shape for `App\\Models\\Scan`. Extends the list\nresource with `description` and a whitelisted subset of `metadata`\nkeys. Whitelisting prevents new metadata fields (e.g. an internal\ndebugging blob added by a future feature) from auto-leaking.",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string",
                        "nullable": true
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "processing",
                            "complete",
                            "failed"
                        ]
                    },
                    "source": {
                        "type": "string",
                        "nullable": true
                    },
                    "file_type": {
                        "type": "string",
                        "nullable": true
                    },
                    "file_size": {
                        "type": "integer"
                    },
                    "property_uuid": {
                        "description": "Mobile-source property this scan represents. Use it with /properties/{uuid}/photos to fetch the scan's photos. Null for legacy non-mobile scans.",
                        "type": "string",
                        "format": "uuid",
                        "nullable": true
                    },
                    "has_report": {
                        "description": "True when a PDF report has been generated for this scan. Use links.report to fetch it.",
                        "type": "boolean"
                    },
                    "exterior_photos": {
                        "description": "Outdoor exterior photos (front / left / right / back / extra). 1–5 entries, each with a presigned download URL.",
                        "type": "array",
                        "items": {
                            "properties": {
                                "index": {
                                    "description": "1=front, 2..5=other angles.",
                                    "type": "integer",
                                    "example": 1
                                },
                                "download": {
                                    "oneOf": [
                                        {
                                            "$ref": "#/components/schemas/PresignedDownload"
                                        }
                                    ],
                                    "nullable": true
                                }
                            },
                            "type": "object"
                        }
                    },
                    "metadata": {
                        "description": "Whitelisted metadata keys only (source, device, version, rooms_scanned, room_photo_count). Internal fields are never exposed.",
                        "type": "object"
                    },
                    "links": {
                        "description": "Hypermedia links to related endpoints. Empty for non-mobile scans.",
                        "properties": {
                            "property": {
                                "type": "string",
                                "format": "uri",
                                "example": "https://api.boxli.ai/api/v2/public/properties/{uuid}"
                            },
                            "photos": {
                                "type": "string",
                                "format": "uri",
                                "example": "https://api.boxli.ai/api/v2/public/properties/{uuid}/photos"
                            },
                            "floors": {
                                "type": "string",
                                "format": "uri",
                                "example": "https://api.boxli.ai/api/v2/public/properties/{uuid}/floors"
                            }
                        },
                        "type": "object"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "processed_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    }
                },
                "type": "object"
            },
            "Scan": {
                "properties": {
                    "uuid": {
                        "description": "Public-API list shape for `App\\Models\\Scan`.\n\nDeliberately HIDES: `file_path` (raw S3 key), `metadata.storage_disk`\n(internal), `user_id` (clients don't need it; they're already\nscoped to their own data by middleware), raw `metadata` (may contain\ndevice fingerprints we don't want to leak).",
                        "type": "string",
                        "format": "uuid",
                        "example": "00000000-0000-0000-0000-000000000000"
                    },
                    "name": {
                        "type": "string",
                        "example": "Bedford St — Main Building"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "processing",
                            "complete",
                            "failed"
                        ]
                    },
                    "source": {
                        "type": "string",
                        "example": "mobile",
                        "nullable": true
                    },
                    "file_type": {
                        "type": "string",
                        "example": "clearbox",
                        "nullable": true
                    },
                    "file_size": {
                        "description": "Bytes.",
                        "type": "integer",
                        "example": 8421376
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "processed_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    }
                },
                "type": "object"
            },
            "ErrorEnvelope": {
                "description": "Standard error response shape. Every 4xx/5xx response\n *                  follows this envelope.",
                "properties": {
                    "error": {
                        "properties": {
                            "code": {
                                "description": "Top-level OpenAPI annotations for the Boxli Public Read-Only Client\nAPI. This file contains NO PHP code — it exists solely so swagger-php\nhas a stable place to find `@OA\\Info`, `@OA\\Server`,\n`@OA\\SecurityScheme`, and `@OA\\Tag` declarations.\n\nAll other annotations live alongside the controllers and Resources\nthey describe.",
                                "type": "string",
                                "example": "not_found"
                            },
                            "message": {
                                "type": "string",
                                "example": "Scan not found."
                            },
                            "details": {
                                "type": "object",
                                "nullable": true
                            }
                        },
                        "type": "object"
                    }
                },
                "type": "object"
            },
            "PaginationMeta": {
                "description": "Pagination metadata included alongside list responses.",
                "properties": {
                    "current_page": {
                        "type": "integer",
                        "example": 1
                    },
                    "last_page": {
                        "type": "integer",
                        "example": 5
                    },
                    "per_page": {
                        "type": "integer",
                        "example": 15
                    },
                    "total": {
                        "type": "integer",
                        "example": 72
                    }
                },
                "type": "object"
            },
            "PresignedDownload": {
                "description": "A short-lived presigned URL for fetching a binary\n *                  asset directly from S3.",
                "properties": {
                    "url": {
                        "type": "string",
                        "format": "uri",
                        "example": "https://s3.us-east-1.amazonaws.com/..."
                    },
                    "expires_at": {
                        "type": "string",
                        "format": "date-time",
                        "example": "2026-05-21T12:34:56+00:00"
                    }
                },
                "type": "object"
            },
            "WebhookSubscription": {
                "description": "A registered webhook subscription. The `secret` field\n *                  is NEVER included in subsequent fetches; it is shown\n *                  only once in the POST /webhooks response.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "event": {
                        "type": "string",
                        "enum": [
                            "scan.completed",
                            "scan.failed"
                        ],
                        "example": "scan.completed"
                    },
                    "url": {
                        "type": "string",
                        "format": "uri"
                    },
                    "name": {
                        "type": "string",
                        "nullable": true
                    },
                    "enabled": {
                        "type": "boolean"
                    },
                    "last_delivery_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "last_status": {
                        "description": "HTTP status code from the last delivery attempt. Null if never delivered.",
                        "type": "integer",
                        "nullable": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "ApiActivityEntry": {
                "description": "One row from your API activity log — the public-facing slice of `public_api_request_logs`. Sensitive fields (IP, User-Agent, full headers) are stripped; the rest is what you sent + what we returned. Useful for self-service debugging — every entry's `request_id` matches the `X-Request-Id` header you would have received on that response, so you can correlate failed-integration tickets without our help.",
                "properties": {
                    "request_id": {
                        "description": "Stable per-request id. Same value as the `X-Request-Id` response header you originally received.",
                        "type": "string",
                        "format": "uuid"
                    },
                    "auth_method": {
                        "description": "Which credential made this call. Null on rare unauthenticated paths.",
                        "type": "string",
                        "enum": [
                            "api_key",
                            "jwt"
                        ],
                        "nullable": true
                    },
                    "key_id": {
                        "description": "Public prefix of the API key that authenticated the request (e.g. `pk_AbCd1234…`). Null when authenticated via JWT.",
                        "type": "string",
                        "nullable": true
                    },
                    "method": {
                        "type": "string",
                        "example": "GET"
                    },
                    "path": {
                        "type": "string",
                        "example": "/api/v2/public/scans"
                    },
                    "query": {
                        "description": "Query parameters as sent. Secret-shaped keys (`password`, `token`, etc.) are redacted.",
                        "type": "object",
                        "nullable": true
                    },
                    "status_code": {
                        "type": "integer",
                        "example": 200
                    },
                    "response_ms": {
                        "description": "Time we spent producing the response, in milliseconds.",
                        "type": "integer",
                        "nullable": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "WebhookDelivery": {
                "description": "A single webhook delivery audit row. One row per fan-out attempt-set; retries update the same row's `attempts` / `last_status`. A manual replay creates a NEW row referencing the same envelope.",
                "properties": {
                    "id": {
                        "description": "Delivery row id. Use this with POST /webhooks/deliveries/{id}/replay.",
                        "type": "integer"
                    },
                    "subscription_id": {
                        "type": "integer"
                    },
                    "event": {
                        "type": "string",
                        "example": "scan.completed"
                    },
                    "url": {
                        "description": "The URL we POSTed to. Snapshot of the subscription's URL at fire-time; mid-flight retries stick to this even if you edit the subscription.",
                        "type": "string",
                        "format": "uri"
                    },
                    "delivery_uuid": {
                        "description": "Stable across all retries of THIS row. Sent to the partner as both the `id` field in the envelope and the `X-Boxli-Delivery-Id` header.",
                        "type": "string",
                        "format": "uuid"
                    },
                    "status": {
                        "description": "pending = in-flight or in backoff; delivered = got a 2xx (terminal); exhausted = max attempts reached without 2xx (terminal — partner must replay).",
                        "type": "string",
                        "enum": [
                            "pending",
                            "delivered",
                            "exhausted"
                        ]
                    },
                    "attempts": {
                        "description": "Number of HTTP attempts made so far. Max 5.",
                        "type": "integer"
                    },
                    "next_attempt_at": {
                        "description": "When the next retry will be attempted. Null on terminal rows.",
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "last_status": {
                        "description": "HTTP status code from the last attempt. Null until first attempt completes. 0 for network errors.",
                        "type": "integer",
                        "nullable": true
                    },
                    "last_error": {
                        "description": "Short truncated error string for the last attempt. Null on success.",
                        "type": "string",
                        "nullable": true
                    },
                    "delivered_at": {
                        "description": "When status flipped to delivered. Null otherwise.",
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "WebhookEventEnvelope": {
                "description": "The common envelope shape sent to your endpoint for every webhook event. The `data` field shape varies by `event` — see the per-event payload schemas (e.g. `ScanCompletedPayload`).",
                "properties": {
                    "id": {
                        "description": "Per-delivery UUID. Use it as the idempotency key on your receiving endpoint — if you see the same id twice, you've already processed this event.",
                        "type": "string",
                        "format": "uuid",
                        "example": "f8a92c1e-9b8e-4a5c-bd6f-1a2b3c4d5e6f"
                    },
                    "event": {
                        "description": "Event name. Matches the subscription's event filter.",
                        "type": "string",
                        "example": "scan.completed"
                    },
                    "created_at": {
                        "description": "When the event was generated on the boxli side (not delivery time).",
                        "type": "string",
                        "format": "date-time",
                        "example": "2026-05-21T15:42:01+00:00"
                    },
                    "data": {
                        "description": "Event-specific payload. Shape depends on `event`.",
                        "type": "object"
                    }
                },
                "type": "object"
            },
            "ScanCompletedPayload": {
                "description": "The `data` field shape for `event=scan.completed`. Fires when a scan transitions to status=`complete`.",
                "properties": {
                    "scan": {
                        "properties": {
                            "uuid": {
                                "type": "string",
                                "format": "uuid"
                            },
                            "name": {
                                "type": "string",
                                "example": "165 Flower of Scotland Ave"
                            },
                            "status": {
                                "type": "string",
                                "example": "complete"
                            },
                            "processed_at": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                            }
                        },
                        "type": "object"
                    },
                    "property_uuid": {
                        "description": "Pivot UUID for the rich property hierarchy. Use it with GET /properties/{uuid}/photos to fetch the scan's photos. Null for legacy non-mobile scans.",
                        "type": "string",
                        "format": "uuid",
                        "nullable": true
                    }
                },
                "type": "object"
            },
            "ScanFailedPayload": {
                "description": "The `data` field shape for `event=scan.failed`. Fires when a scan transitions to status=`failed`. Envelope shape mirrors ScanCompletedPayload (same `scan` + `property_uuid` fields) so partners can share envelope-parsing code between the two events; the `event` field on the outer WebhookEventEnvelope is what you switch on.",
                "properties": {
                    "scan": {
                        "properties": {
                            "uuid": {
                                "type": "string",
                                "format": "uuid"
                            },
                            "name": {
                                "type": "string",
                                "example": "165 Flower of Scotland Ave"
                            },
                            "status": {
                                "type": "string",
                                "example": "failed"
                            },
                            "processed_at": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                            }
                        },
                        "type": "object"
                    },
                    "failure_reason": {
                        "description": "Optional human-readable hint about what went wrong (set by the processor when available). Treat as a hint, not a contract — the wording may change between processor versions. Null when the processor didn't record one.",
                        "type": "string",
                        "example": "Invalid file format: expected .e57, got .zip",
                        "nullable": true
                    },
                    "property_uuid": {
                        "description": "Pivot UUID for the rich property hierarchy. Null for non-mobile scans or when the failure happened before a property was created.",
                        "type": "string",
                        "format": "uuid",
                        "nullable": true
                    }
                },
                "type": "object"
            }
        },
        "securitySchemes": {
            "apiKeyAuth": {
                "type": "apiKey",
                "description": "API key for server-to-server access. Sent as\n *                  `X-API-Key: <key_id>.<secret>` on every request\n *                  (the two halves separated by a single dot — both\n *                  required). Keys never expire; soft-revoke via the\n *                  Boxli admin tooling if a credential is compromised.\n *\n *                  **API keys are the only accepted credential.**\n *                  There is no login / refresh flow — API access is a\n *                  provisioned commercial relationship; partners\n *                  request a key from Boxli staff and use it directly.\n *\n *                  The key inherits the data scope of the user it was\n *                  minted for — same access the human would have in\n *                  the dashboard, no more.\n *\n *                  Contact help@boxli.ai to request a key for a new\n *                  integration.",
                "name": "X-API-Key",
                "in": "header"
            }
        }
    },
    "tags": [
        {
            "name": "Auth",
            "description": "Identity + self-audit (`/auth/me`, `/auth/me/activity`)."
        },
        {
            "name": "Scans",
            "description": "Dashboard-source scans (list, detail, download)."
        },
        {
            "name": "Properties",
            "description": "Mobile-source scanned properties."
        },
        {
            "name": "Floors",
            "description": "Property floors (nested under a property)."
        },
        {
            "name": "Rooms",
            "description": "Floor rooms (nested under a floor)."
        },
        {
            "name": "Photos",
            "description": "Property + room photos with presigned download URLs."
        },
        {
            "name": "Webhooks",
            "description": "Subscribe to push notifications.\n *\n * **Available events:**\n * - `scan.completed` — fires when a scan transitions to status=`complete`. Payload: `ScanCompletedPayload`.\n * - `scan.failed`    — fires when a scan transitions to status=`failed`.    Payload: `ScanFailedPayload`.\n *\n * **Delivery headers** sent to your endpoint:\n * - `Content-Type: application/json`\n * - `X-Boxli-Event: <event name>` (e.g. `scan.completed`)\n * - `X-Boxli-Delivery-Id: <uuid>` — unique per delivery, dedupe key\n * - `X-Boxli-Signature: <hex>` — HMAC-SHA256 of the request body, using your subscription's secret\n *\n * **Verifying the signature (pseudocode):**\n * ```\n * expected = hmac_sha256(your_secret, raw_request_body_bytes).hex()\n * if not constant_time_equal(expected, request.headers['X-Boxli-Signature']):\n *     reject()\n * ```\n *\n * **Delivery contract (v2 — async):**\n * - Asynchronous via the `webhooks` queue; the event-trigger returns immediately.\n * - 3-second HTTP timeout per attempt.\n * - **Automatic retry on non-2xx / network error** with exponential backoff:\n *   attempt 1 immediate → attempt 2 +1m → attempt 3 +5m → attempt 4 +30m → attempt 5 +2h.\n *   After 5 attempts without a 2xx, the delivery is marked `exhausted` and the partner must manually replay.\n * - `X-Boxli-Delivery-Id` is **stable across retries of the same delivery** — your endpoint MUST be idempotent on this header (insert-only into a `processed_deliveries` table, etc.).\n * - **A manual replay creates a NEW delivery row** with a NEW `X-Boxli-Delivery-Id`, so you can distinguish 'this is a retry of what I already saw' from 'this is a fresh redelivery I should re-process.'\n * - Inspect delivery history via `GET /webhooks/{id}/deliveries`; replay via `POST /webhooks/deliveries/{id}/replay`."
        }
    ]
}