{
  "openapi": "3.1.0",
  "info": {
    "title": "Pictage Public API",
    "version": "1.0.0",
    "description": "Bearer PAT surface at /api/v1. Generated from lib/api/v1/schemas.ts. Do not edit the JSON by hand; run pnpm tsx scripts/generate-openapi-v1.ts."
  },
  "servers": [
    {
      "url": "https://pictage.ai/api/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "auth",
      "description": "auth"
    },
    {
      "name": "client-sessions",
      "description": "client-sessions"
    },
    {
      "name": "workspaces",
      "description": "workspaces"
    },
    {
      "name": "galleries",
      "description": "galleries"
    },
    {
      "name": "shoots",
      "description": "shoots"
    },
    {
      "name": "clients",
      "description": "clients"
    },
    {
      "name": "orders",
      "description": "orders"
    },
    {
      "name": "search",
      "description": "search"
    },
    {
      "name": "webhooks-legacy",
      "description": "webhooks-legacy"
    },
    {
      "name": "webhooks",
      "description": "webhooks"
    },
    {
      "name": "photos",
      "description": "photos"
    },
    {
      "name": "upload-sessions",
      "description": "upload-sessions"
    },
    {
      "name": "mobile",
      "description": "mobile"
    },
    {
      "name": "ai",
      "description": "ai"
    },
    {
      "name": "invoices",
      "description": "invoices"
    },
    {
      "name": "tasks",
      "description": "tasks"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/auth/whoami": {
      "get": {
        "operationId": "getAuthWhoami",
        "summary": "Token introspection",
        "description": "Validates the bearer token and returns the owning profile plus workspace scope. 401 on any failed token.",
        "tags": [
          "auth"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token introspection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/client-sessions": {
      "post": {
        "operationId": "createClientSession",
        "summary": "Exchange a portal token for a client session",
        "description": "MOB-06: exchanges a gallery portal token (portal_token, or its magic_code alias) for a short-lived, gallery-scoped client bearer. Unauthenticated - the portal token in the body is the credential - so the bearerAuth marker does not apply to this one route. Returns session.bearer, session.expires_at, and session.gallery_id. 401 for an invalid, expired, non-gallery, or suspended portal. Rate-limited per client IP. The bearer then reaches GET /galleries/{id}, its /photos, and POST|DELETE /favorites for that one gallery only.",
        "tags": [
          "client-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Exchange a portal token for a client session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createClientSessionBody"
              }
            }
          }
        }
      }
    },
    "/workspaces": {
      "get": {
        "operationId": "listWorkspaces",
        "summary": "List workspaces",
        "description": "Workspaces the token owner is a member of. Scoped tokens return only that workspace. Paginated by created_at desc.",
        "tags": [
          "workspaces"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List workspaces",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ]
      }
    },
    "/galleries": {
      "get": {
        "operationId": "listGalleries",
        "summary": "List galleries",
        "description": "Galleries in a workspace, including drafts. Requires membership.",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List galleries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      },
      "post": {
        "operationId": "createGallery",
        "summary": "Create a gallery from keepers",
        "description": "Draft gallery from a shoot's ready keepers. Zapier create_gallery posts here. Refuses with invalid_input when the shoot has no ready keepers.",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Create a gallery from keepers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createGalleryBody"
              }
            }
          }
        }
      }
    },
    "/galleries/{id}": {
      "get": {
        "operationId": "getGallery",
        "summary": "Get a gallery",
        "description": "Single gallery with ordered photos. Photo URLs are signed (1 hour) to the web derivative.",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Get a gallery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Gallery id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/galleries/{id}/photos": {
      "get": {
        "operationId": "listGalleryPhotos",
        "summary": "List gallery photos",
        "description": "Paginated photo list for native clients. Opaque keyset cursor on (order_index, photo_id). Limit 1..200 (default 200). A legacy decimal offset cursor is accepted for one release. original_url is returned only for photographer roles. Client follow-up is MOB-05.",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List gallery photos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Gallery id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/galleries/{id}/favorites": {
      "post": {
        "operationId": "addGalleryFavorite",
        "summary": "Favorite a photo",
        "description": "photo_id may be in the JSON body or the query string (photo_id or photoId).",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Favorite a photo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Gallery id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/favoriteBody"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeGalleryFavorite",
        "summary": "Unfavorite a photo",
        "description": "Same photo_id / photoId contract as POST. Body is optional on DELETE.",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Unfavorite a photo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Gallery id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/favoriteBody"
              }
            }
          }
        }
      }
    },
    "/shoots": {
      "get": {
        "operationId": "listShoots",
        "summary": "List shoots",
        "description": "Shoots in a workspace. Requires membership.",
        "tags": [
          "shoots"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List shoots",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      },
      "post": {
        "operationId": "createShoot",
        "summary": "Create a shoot",
        "description": "Creates a planned shoot. Slug is uniquified within the workspace.",
        "tags": [
          "shoots"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Create a shoot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createShootBody"
              }
            }
          }
        }
      }
    },
    "/clients": {
      "get": {
        "operationId": "listClients",
        "summary": "List clients",
        "description": "Clients in a workspace. Requires membership.",
        "tags": [
          "clients"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List clients",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      },
      "post": {
        "operationId": "createClient",
        "summary": "Create a client",
        "description": "Emails are lower-cased server-side. email, phone, and notes are optional.",
        "tags": [
          "clients"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Create a client",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createClientBody"
              }
            }
          }
        }
      }
    },
    "/orders": {
      "get": {
        "operationId": "listOrders",
        "summary": "List paid orders",
        "description": "Paid print-storefront orders in a workspace, newest-first. Requires membership. INT-04's Zapier Payment Received trigger uses this as its polling fallback.",
        "tags": [
          "orders"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List paid orders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      }
    },
    "/search": {
      "get": {
        "operationId": "search",
        "summary": "Unified cross-entity search",
        "description": "Lexical search across clients, leads, shoots, galleries, albums, and orders in one workspace (SRC-01). q is required and sanitized server-side via websearch_to_tsquery, so a malformed query string never becomes a 500. types is a comma-separated subset of client, lead, shoot, gallery, album, order (an unknown value is dropped, not rejected); omit to search every type. cursor is opaque and keyset over rank, not created_at/id like other list routes in this document.",
        "tags": [
          "search"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Unified cross-entity search",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/webhook-endpoints": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "summary": "List legacy webhook endpoints",
        "description": "Never includes the signing secret.",
        "tags": [
          "webhooks-legacy"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List legacy webhook endpoints",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "summary": "Register a legacy webhook",
        "description": "Admin+ role. signing_secret is returned exactly once. Events: gallery.published, gallery.delivered, ai.cull.completed.",
        "tags": [
          "webhooks-legacy"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Register a legacy webhook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (intra-tenant role shortage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createWebhookEndpointBody"
              }
            }
          }
        }
      }
    },
    "/webhook-endpoints/{id}": {
      "delete": {
        "operationId": "deleteWebhookEndpoint",
        "summary": "Delete a legacy webhook",
        "description": "Admin+ role. Hard-deletes the endpoint and its delivery history.",
        "tags": [
          "webhooks-legacy"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Delete a legacy webhook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (intra-tenant role shortage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Endpoint id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/webhooks/subscriptions": {
      "post": {
        "operationId": "subscribePublicWebhook",
        "summary": "Subscribe a Zapier REST hook",
        "description": "Admin+ role. Registers or reactivates a public_webhook_endpoints row. Signing secret is not returned.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Subscribe a Zapier REST hook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (intra-tenant role shortage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/subscribeWebhookBody"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "unsubscribePublicWebhook",
        "summary": "Delete a Zapier REST hook",
        "description": "Admin+ role. Repeated deletes are idempotent (deleted: false).",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Delete a Zapier REST hook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (intra-tenant role shortage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/unsubscribeWebhookBody"
              }
            }
          }
        }
      }
    },
    "/webhook-endpoints/{id}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List delivery history for a webhook endpoint",
        "description": "Paginated. Status, attempt count, last HTTP status, and next-retry time per delivery. Any member role may read.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List delivery history for a webhook endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Endpoint id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ]
      }
    },
    "/webhook-deliveries/{id}/replay": {
      "post": {
        "operationId": "replayWebhookDelivery",
        "summary": "Replay a webhook delivery",
        "description": "Admin+ role. Re-enqueues the same event_id for delivery. Refuses a delivery that is in_flight or whose endpoint is not active.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Replay a webhook delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (intra-tenant role shortage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Delivery id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ]
      }
    },
    "/webhook-endpoints/{id}/rotate-secret": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "Rotate a webhook endpoint signing secret",
        "description": "Admin+ role. The prior secret keeps validating dispatcher signatures (v0) for 24h. The new secret is returned exactly once.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Rotate a webhook endpoint signing secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "forbidden (intra-tenant role shortage)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Endpoint id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ]
      }
    },
    "/photos": {
      "post": {
        "operationId": "createPhotoUpload",
        "summary": "Start a single-shot upload",
        "description": "Creates a pending photos row and a signed PUT URL. Client PUTs bytes then POSTs /photos/{id}/finalize. Max 50 MB. Allowed mime: jpeg, png, webp, avif, tiff. HEIC/HEIF is refused; export a JPEG sidecar.",
        "tags": [
          "photos"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Start a single-shot upload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createPhotoBody"
              }
            }
          }
        }
      }
    },
    "/photos/{id}/finalize": {
      "post": {
        "operationId": "finalizePhotoUpload",
        "summary": "Finalize a single-shot upload",
        "description": "pending to uploaded and schedule derivatives. Safe to call twice; the second call returns already_finalized: true.",
        "tags": [
          "photos"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Finalize a single-shot upload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Photo id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions": {
      "post": {
        "operationId": "createUploadSession",
        "summary": "Open a multipart upload session",
        "description": "R2-only. Returns 503 storage_not_configured on the storage fallback. Response fields are camelCase.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Open a multipart upload session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict (same Idempotency-Key reused with a different request body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retry key, scoped per API token. Same key + same body returns the original response without re-running the request; same key + a different body is 422 idempotency_conflict. Expires after 24h.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/createUploadSessionBody"
              }
            }
          }
        }
      }
    },
    "/upload-sessions/{id}/files": {
      "post": {
        "operationId": "registerUploadSessionFiles",
        "summary": "Register files on a session",
        "description": "1..500 files. content_policy_accepted is required at runtime.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Register files on a session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/registerUploadFilesBody"
              }
            }
          }
        }
      }
    },
    "/upload-sessions/{id}/files/{fileId}/parts/sign": {
      "post": {
        "operationId": "signUploadParts",
        "summary": "Sign multipart part PUT URLs",
        "description": "1..100 part numbers (1-based, max 10000).",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sign multipart part PUT URLs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "description": "File id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/signUploadPartsBody"
              }
            }
          }
        }
      }
    },
    "/upload-sessions/{id}/files/{fileId}/parts/complete": {
      "post": {
        "operationId": "completeUploadPart",
        "summary": "Record a part ETag",
        "description": "Quotes are stripped. Calling twice for the same part overwrites the ETag.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Record a part ETag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "description": "File id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/completeUploadPartBody"
              }
            }
          }
        }
      }
    },
    "/upload-sessions/{id}/files/{fileId}/complete": {
      "post": {
        "operationId": "completeUploadFile",
        "summary": "Assemble a multipart file",
        "description": "Idempotent. A second call returns idempotent: true. 409 if no parts were recorded.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Assemble a multipart file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "description": "File id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions/{id}/files/{fileId}/abort": {
      "post": {
        "operationId": "abortUploadFile",
        "summary": "Abort one file",
        "description": "Idempotent on an already-aborted file. 409 if the file already completed.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Abort one file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fileId",
            "in": "path",
            "required": true,
            "description": "File id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions/{id}/finalize": {
      "post": {
        "operationId": "finalizeUploadSession",
        "summary": "Close an upload session",
        "description": "Aborts pending/uploading/error stragglers and fires a batched photo.uploaded event.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Close an upload session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions/{id}": {
      "get": {
        "operationId": "getUploadSession",
        "summary": "Read a session and its files",
        "description": "Returns the session status plus its file list. A session in another workspace is hidden as 404.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Read a session and its files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions/{id}/resume": {
      "post": {
        "operationId": "resumeUploadSession",
        "summary": "Get the resume manifest for a session",
        "description": "Returns every non-terminal file with the part numbers already recorded, so a crashed export re-uploads only the missing parts. 409 if the session is completed, aborted, or expired; 404 for a foreign-workspace session.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Get the resume manifest for a session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions/{id}/pause": {
      "post": {
        "operationId": "pauseUploadSession",
        "summary": "Pause an in-flight upload session",
        "description": "Flips active -> paused. While paused, parts/sign and parts/complete return 409. Idempotent (pausing an already-paused session is a 200 no-op). 404 for a foreign-workspace session.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Pause an in-flight upload session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/upload-sessions/{id}/unpause": {
      "post": {
        "operationId": "unpauseUploadSession",
        "summary": "Resume a paused upload session",
        "description": "Flips paused -> active, restoring parts/sign and parts/complete. Idempotent (unpausing an already-active session is a 200 no-op). 404 for a foreign-workspace session.",
        "tags": [
          "upload-sessions"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resume a paused upload session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Session id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/mobile/dashboard": {
      "get": {
        "operationId": "getMobileDashboard",
        "summary": "Mobile home-screen dashboard",
        "description": "One workspace-scoped snapshot for the mobile home screen: today's shoots, upcoming shoots, in-flight AI job count, recent galleries, and a paid-orders money summary. Requires membership.",
        "tags": [
          "mobile"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Mobile home-screen dashboard",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      }
    },
    "/shoots/{id}": {
      "get": {
        "operationId": "getShoot",
        "summary": "Get a shoot",
        "description": "Shoot detail for the mobile app: the shoot core plus its linked client, day-of timeline blocks, shot list, and notes. A missing or foreign shoot is hidden as 404.",
        "tags": [
          "shoots"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Get a shoot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Shoot id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/shoots/{id}/shot-list": {
      "patch": {
        "operationId": "updateShotListItem",
        "summary": "Tick or untick a shot-list item",
        "description": "Marks one shot-list item done or not-done for a shoot. Body carries the item_key and completed flag; the shoot id is in the path. Requires the shoots:write scope.",
        "tags": [
          "shoots"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Tick or untick a shot-list item",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Shoot id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/shotListItemPatchBody"
              }
            }
          }
        }
      }
    },
    "/ai/cull-jobs": {
      "post": {
        "operationId": "startCullJob",
        "summary": "Start a background cull job",
        "description": "Queues a background full-shoot cull against a shoot. Gated by AI compliance, the AI kill switch, membership, and the billable-AI role floor. Returns the created job id and status. Refuses (409) when a cull is already running for the shoot. Requires the photos:write scope.",
        "tags": [
          "ai"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Start a background cull job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/startCullJobBody"
              }
            }
          }
        }
      }
    },
    "/ai/jobs/{id}": {
      "get": {
        "operationId": "getAiJob",
        "summary": "Poll an AI job",
        "description": "Returns one AI job's status, output, and error for progress polling. A missing or foreign job is hidden as 404.",
        "tags": [
          "ai"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Poll an AI job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "AI job id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/galleries/{id}/publish": {
      "post": {
        "operationId": "publishGallery",
        "summary": "Publish a gallery",
        "description": "Publishes a draft gallery and fires the full gallery.published cascade (notify, webhook, analytics, auto-album/blog, shoot-delivered). Assistant+ role. Repeat publishes are a no-op (the cascade fires once). Requires the galleries:write scope.",
        "tags": [
          "galleries"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Publish a gallery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Gallery id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ]
      }
    },
    "/invoices": {
      "get": {
        "operationId": "listInvoices",
        "summary": "List invoices",
        "description": "Invoices in a workspace, newest-first, cursor-paginated like /orders. Requires membership.",
        "tags": [
          "invoices"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List invoices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      }
    },
    "/tasks": {
      "get": {
        "operationId": "listTasks",
        "summary": "List tasks",
        "description": "Tasks in a workspace, newest-first, cursor-paginated like /orders. Requires membership.",
        "tags": [
          "tasks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List tasks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "invalid_input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found (also used to hide foreign tenants)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (600 requests / 60 seconds / token, fail-closed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "workspace_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            }
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API token",
        "description": "Authorization: Bearer pk_live_... minted at /dashboard/settings/api."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ok",
          "error",
          "request_id"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "enum": [
              "unauthorized",
              "forbidden",
              "not_found",
              "invalid_input",
              "invalid_json",
              "conflict",
              "rate_limited",
              "server_error",
              "internal_error",
              "idempotency_conflict",
              "storage_cap",
              "storage_not_configured"
            ]
          },
          "message": {
            "type": "string"
          },
          "field": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "createClientSessionBody": {
        "type": "object",
        "properties": {
          "portal_token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "magic_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          }
        },
        "additionalProperties": false
      },
      "listQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "cursor": {
            "type": "string",
            "minLength": 1
          }
        },
        "additionalProperties": false
      },
      "workspaceListQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "cursor": {
            "type": "string",
            "minLength": 1
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          }
        },
        "required": [
          "workspace_id"
        ],
        "additionalProperties": false
      },
      "createGalleryBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "shoot_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          }
        },
        "required": [
          "workspace_id",
          "shoot_id",
          "title"
        ],
        "additionalProperties": false
      },
      "galleryPhotosQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200
          },
          "cursor": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "favoriteBody": {
        "type": "object",
        "properties": {
          "photo_id": {
            "type": "string",
            "minLength": 1
          },
          "photoId": {
            "type": "string",
            "minLength": 1
          }
        },
        "additionalProperties": false
      },
      "createShootBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "type": {
            "type": "string",
            "enum": [
              "wedding",
              "portrait",
              "newborn",
              "family",
              "event",
              "commercial",
              "real_estate",
              "other"
            ]
          },
          "shoot_date": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "workspace_id",
          "name",
          "type"
        ],
        "additionalProperties": false
      },
      "createClientBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "email": {
            "type": "string",
            "maxLength": 254
          },
          "phone": {
            "type": "string",
            "maxLength": 40
          },
          "notes": {
            "type": "string",
            "maxLength": 2000
          }
        },
        "required": [
          "workspace_id",
          "name"
        ],
        "additionalProperties": false
      },
      "searchQuery": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "cursor": {
            "type": "string",
            "minLength": 1
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "q": {
            "type": "string",
            "minLength": 1
          },
          "types": {
            "type": "string"
          }
        },
        "required": [
          "workspace_id",
          "q"
        ],
        "additionalProperties": false
      },
      "createWebhookEndpointBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "url": {
            "type": "string",
            "maxLength": 2000,
            "format": "uri"
          },
          "description": {
            "type": "string",
            "maxLength": 200
          },
          "events": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "gallery.published",
                "gallery.delivered",
                "ai.cull.completed"
              ]
            }
          }
        },
        "required": [
          "workspace_id",
          "url",
          "events"
        ],
        "additionalProperties": false
      },
      "subscribeWebhookBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "target_url": {
            "type": "string",
            "maxLength": 2000,
            "format": "uri"
          },
          "event_type": {
            "type": "string",
            "enum": [
              "gallery.created",
              "gallery.published",
              "gallery.delivered",
              "shoot.created",
              "shoot.completed",
              "photo.uploaded",
              "ai_job.done",
              "ai.cull.completed",
              "payment.received",
              "order.shipped",
              "order.created",
              "order.sent_to_print",
              "order.delivered",
              "order.cancelled",
              "order.refunded",
              "order.needs_intervention",
              "client.created",
              "booking.requested",
              "booking.accepted",
              "booking.declined",
              "proposal.sent",
              "proposal.viewed",
              "proposal.accepted",
              "proposal.declined",
              "proposal.expired",
              "contract.signed",
              "invoice.paid",
              "review.approved"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 200
          }
        },
        "required": [
          "workspace_id",
          "target_url",
          "event_type"
        ],
        "additionalProperties": false
      },
      "unsubscribeWebhookBody": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "subscription_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          }
        },
        "additionalProperties": false
      },
      "createPhotoBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "shoot_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "filename": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "mime_type": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "size_bytes": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 52428800
          },
          "content_policy_accepted": {
            "type": "boolean"
          },
          "content_hash": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{64}$"
          },
          "upload_anyway": {
            "type": "boolean"
          }
        },
        "required": [
          "workspace_id",
          "filename",
          "mime_type",
          "size_bytes"
        ],
        "additionalProperties": false
      },
      "createUploadSessionBody": {
        "type": "object",
        "properties": {
          "workspace_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "shoot_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "photo",
              "archive"
            ]
          }
        },
        "required": [
          "workspace_id"
        ],
        "additionalProperties": false
      },
      "registerUploadFilesBody": {
        "type": "object",
        "properties": {
          "content_policy_accepted": {
            "type": "boolean"
          },
          "files": {
            "minItems": 1,
            "maxItems": 500,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "filename": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 255
                },
                "mime_type": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "size_bytes": {
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "content_hash": {
                  "type": "string",
                  "pattern": "^[a-fA-F0-9]{64}$"
                },
                "upload_anyway": {
                  "type": "boolean"
                }
              },
              "required": [
                "filename",
                "mime_type",
                "size_bytes"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "files"
        ],
        "additionalProperties": false
      },
      "signUploadPartsBody": {
        "type": "object",
        "properties": {
          "part_numbers": {
            "minItems": 1,
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000
            }
          },
          "checksums": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "additionalProperties": {
              "type": "string",
              "pattern": "^[A-Za-z0-9+/]{43}=$"
            }
          }
        },
        "required": [
          "part_numbers"
        ],
        "additionalProperties": false
      },
      "completeUploadPartBody": {
        "type": "object",
        "properties": {
          "part_number": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10000
          },
          "etag": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "sha256": {
            "type": "string",
            "pattern": "^[A-Za-z0-9+/]{43}=$"
          }
        },
        "required": [
          "part_number",
          "etag"
        ],
        "additionalProperties": false
      },
      "shotListItemPatchBody": {
        "type": "object",
        "properties": {
          "item_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "completed": {
            "type": "boolean"
          }
        },
        "required": [
          "item_key",
          "completed"
        ],
        "additionalProperties": false
      },
      "startCullJobBody": {
        "type": "object",
        "properties": {
          "shoot_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "mode": {
            "type": "string",
            "enum": [
              "conservative",
              "balanced",
              "aggressive"
            ]
          }
        },
        "required": [
          "shoot_id"
        ],
        "additionalProperties": false
      }
    }
  }
}
