{"openapi": "3.1.0", "info": {"title": "camspect API", "version": "1.0.0", "description": "The Camspect API lets you read and update your workspace's projects, photos, videos, documents, todos, comments and inspections from your own tools.\n\n## Authentication\n\nEvery request needs a personal access token. An Admin or Manager generates one in Camspect under **Integrations > Personal access tokens**. The token is shown once, so store it somewhere safe, such as a secrets manager.\n\nSend it in the `X-API-Key` header:\n\n```bash\ncurl http://api.camspect.com/api/v1/projects/ \\\n  -H \"X-API-Key: cspat_your_token\"\n```\n\n- A token acts as the person who generated it, with their role. It stops working when it expires (you pick a date, or never, when you generate it), if it's disabled or deleted, or if that person is deactivated or is no longer an Admin or Manager.\n- Tokens work only on the endpoints documented here.\n- Call the API from a server. Never put a token in a browser, a mobile app or a public repository.\n- Each token can make 600 requests a minute. Past that, requests get a `429` response.\n- A missing or invalid token gets a `401` response.\n"}, "paths": {"/api/v1/projects/": {"get": {"operationId": "projects_api_list_projects", "summary": "List Projects", "parameters": [{"in": "query", "name": "member_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Member Id"}, "required": false}, {"in": "query", "name": "near_lat", "schema": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Near Lat"}, "required": false}, {"in": "query", "name": "near_lng", "schema": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Near Lng"}, "required": false}, {"in": "query", "name": "radius_km", "schema": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Radius Km"}, "required": false}, {"in": "query", "name": "ordering", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Ordering"}, "required": false}, {"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ProjectOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "projects_api_create_project", "summary": "Create Project", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/assignable-users": {"get": {"operationId": "projects_api_list_assignable_users", "summary": "List Assignable Users", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AssignableUsersOut"}}}}}, "description": "Active company users who can be assigned to (or own) a project.\n\nSorted alphabetically for a stable picker. The web also reads it as the\nworkspace's people directory (profile photos by user id).", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/members/": {"get": {"operationId": "projects_api_list_project_members", "summary": "List Project Members", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ProjectMemberOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "projects_api_add_project_member", "summary": "Add Project Member", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectMemberOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectMemberOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectMemberAddIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/members/{member_id}": {"delete": {"operationId": "projects_api_remove_project_member", "summary": "Remove Project Member", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}, {"in": "path", "name": "member_id", "schema": {"title": "Member Id", "type": "integer"}, "required": true}], "responses": {"204": {"description": "No Content"}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/cover": {"put": {"operationId": "projects_api_set_project_cover", "summary": "Set Project Cover", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Set (or with `photo_id: null`, clear) the project's cover photo:\nthe image on its list rows and pipeline cards. Needs edit rights on\nthe project; the photo must be one of the project's own.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectCoverIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/owner": {"put": {"operationId": "projects_api_set_project_owner", "summary": "Set Project Owner", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Hand the project to another teammate.\n\nOnly admins, managers and the current owner may do this. The new\nowner must be an active user of the workspace; they're assigned to\nthe project if they weren't already. The previous owner stays\nassigned.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOwnerIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/statuses": {"get": {"operationId": "projects_api_list_statuses", "summary": "List Statuses", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/StatusOut"}, "title": "Response", "type": "array"}}}}}, "description": "All project statuses in the caller's company, kanban-order.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "projects_api_create_status", "summary": "Create Status", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Create a custom status for the caller's company.\n\nOffline-first: the client supplies `payload.id`. Sending the same\nid twice for the same company is a no-op \u2014 returns 200 with the\nexisting row. Same id but a different company \u2192 409. Same slug\n(derived from name) but a different id \u2192 also 409, since two\nstages with the same label would confuse the pipeline UI.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/statuses/manage": {"get": {"operationId": "projects_api_list_statuses_manage", "summary": "List Statuses Manage", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/StatusManageRow"}, "title": "Response", "type": "array"}}}}}, "description": "Statuses + non-archived project counts for the Manage Statuses UI.\n\nKept separate from `list_statuses` so the light shape used by\nfilter dropdowns and the map pin cache doesn't have to carry a\nCount subquery per row. Reads admin-only per convention (reps\ndon't manage the stage list), but we don't 403 here \u2014 a rep who\nlands on this endpoint just sees rows they'll never mutate. The\nwrite endpoints (`patch`, `delete`, `reorder`) enforce the gate.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/statuses/reorder": {"post": {"operationId": "projects_api_reorder_statuses", "summary": "Reorder Statuses", "parameters": [], "responses": {"204": {"description": "No Content"}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Rewrite every status's `sort_order` to match the given order.\n\nAdmins only. The payload must contain the *full* set of status\nids for the company \u2014 the server rejects partial reorders so a\nstale client can't accidentally drop or duplicate rows.\nRewrites in one atomic UPDATE per row (all inside a transaction);\nvalues are `(1..N) * 10` so future single-status inserts (which\ndefault to `max + 10`) still slot naturally at the end.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusReorderIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/statuses/{status_id}": {"patch": {"operationId": "projects_api_patch_status", "summary": "Patch Status", "parameters": [{"in": "path", "name": "status_id", "schema": {"format": "uuid", "title": "Status Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Rename, recolor, move or open/close a status. A name that is\nanother status's (`projects.names`) is a 409; nothing changed: no\nsave, no audit entry.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StatusPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "projects_api_delete_status", "summary": "Delete Status", "parameters": [{"in": "path", "name": "status_id", "schema": {"format": "uuid", "title": "Status Id", "type": "string"}, "required": true}, {"in": "query", "name": "reassign_to_status_id", "schema": {"format": "uuid", "title": "Reassign To Status Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Delete a status, reassigning every project on it to another one.\n\n`reassign_to_status_id` is required \u2014 a status can't just vanish\nwhile projects still point at it (Project.status is PROTECT'd).\nThe client is expected to prompt for the destination up front.\n\nEverything happens in one transaction: move every project's\nstatus_id, insert a ProjectStatusTransition row per moved project\nso history reflects the reassignment, bump status_entered_at on\neach moved project, then delete the source status.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/list": {"get": {"operationId": "projects_api_list_projects_page", "summary": "List Projects Page", "parameters": [{"in": "query", "name": "tab", "schema": {"default": "all", "title": "Tab", "type": "string"}, "required": false}, {"in": "query", "name": "search", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Search"}, "required": false}, {"in": "query", "name": "status_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Status Id"}, "required": false}, {"in": "query", "name": "member_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Member Id"}, "required": false}, {"in": "query", "name": "window", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Window"}, "required": false}, {"in": "query", "name": "start_date", "schema": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Start Date"}, "required": false}, {"in": "query", "name": "end_date", "schema": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "End Date"}, "required": false}, {"in": "query", "name": "bbox", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Bbox"}, "required": false}, {"in": "query", "name": "page", "schema": {"default": 1, "title": "Page", "type": "integer"}, "required": false}, {"in": "query", "name": "page_size", "schema": {"default": 25, "title": "Page Size", "type": "integer"}, "required": false}, {"in": "query", "name": "sort", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Sort"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectListPage"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/pins": {"get": {"operationId": "projects_api_list_pins", "summary": "List Pins", "parameters": [{"in": "query", "name": "bbox", "schema": {"title": "Bbox", "type": "string"}, "required": true}, {"in": "query", "name": "page", "schema": {"default": 1, "title": "Page", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PinsPage"}}}}}, "description": "Project pins inside a bounding box, for the map viewport.\n\n`bbox` is `minLng,minLat,maxLng,maxLat` (GeoJSON order \u2014 long\nfirst, matches Leaflet's `getBounds().toBBoxString()`). Paginated\nat PINS_PAGE_SIZE per page; `page` is 1-indexed.\n\nReturns only the fields needed to paint a dot on the map: id,\nname, lat, lng. Full details fetch on click via `/projects/{id}`.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/pipeline": {"get": {"operationId": "projects_api_pipeline_column", "summary": "Pipeline Column", "parameters": [{"in": "query", "name": "status_id", "schema": {"format": "uuid", "title": "Status Id", "type": "string"}, "required": true}, {"in": "query", "name": "page", "schema": {"default": 1, "title": "Page", "type": "integer"}, "required": false}, {"in": "query", "name": "page_size", "schema": {"default": 100, "title": "Page Size", "type": "integer"}, "required": false}, {"in": "query", "name": "search", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Search"}, "required": false}, {"in": "query", "name": "member_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Member Id"}, "required": false}, {"in": "query", "name": "window", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Window"}, "required": false}, {"in": "query", "name": "start_date", "schema": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Start Date"}, "required": false}, {"in": "query", "name": "end_date", "schema": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "End Date"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PipelineColumnPage"}}}}}, "description": "One page of cards for a single kanban column.\n\nThe frontend calls this once per status (in parallel). Ordering is\n\"most-recently-entered-this-status first\" \u2014 reads straight from\nthe `(status, -status_entered_at)` index. Membership-scoped by\n`scope_projects`; archived projects excluded so the pipeline\nonly reflects active work.\n\nSearch / member / window filters mirror the shape used by\n`/projects/list` so the pipeline toolbar can reuse the same UI\nwithout a schema fork. Filters are applied *within* the status\ncolumn \u2014 they narrow the cards you see, they don't move cards\nbetween columns.\n\n`status_id` is echoed in the response so parallel requests can be\nrouted to the right column on arrival.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}": {"get": {"operationId": "projects_api_get_project", "summary": "Get Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "projects_api_patch_project", "summary": "Patch Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}}, "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "projects_api_delete_project", "summary": "Delete Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/media-contributors": {"get": {"operationId": "projects_api_list_project_media_contributors", "summary": "List Project Media Contributors", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectMediaContributors"}}}}}, "description": "Per-user media contribution counts for the project detail sidebar.\n\nGroups `Photo`, `Video`, and `Document` rows by `uploaded_by` and\nreturns a unified breakdown per user. Uploads whose FK has been\nSET_NULL, or point at a user we can't fetch (cross-tenant guard),\ncollapse into a single \"Unknown\" bucket so the totals still sum to\nthe project's photo / video / document counts.\n\nRows are sorted by combined contribution desc, then alphabetically\nby full name / email \u2014 heaviest contributor shows first, stable\nordering for ties.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/favorite": {"post": {"operationId": "projects_api_favorite_project", "summary": "Favorite Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Idempotent: starring an already-starred project returns 204.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "projects_api_unfavorite_project", "summary": "Unfavorite Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/archive": {"post": {"operationId": "projects_api_archive_project", "summary": "Archive Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/unarchive": {"post": {"operationId": "projects_api_unarchive_project", "summary": "Unarchive Project", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/contacts": {"get": {"operationId": "projects_api_list_contacts", "summary": "List Contacts", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/ContactOut"}, "title": "Response", "type": "array"}}}}}, "description": "Contacts on a project. Ordered by `sort_order` then created_at.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "projects_api_create_contact", "summary": "Create Contact", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Add a contact to a project.\n\nIdempotent on `id`: submitting the same UUID for the same project\nreturns the existing row (200). Same UUID against a different\nproject \u2192 409. At least one of `phone`/`email` must be provided \u2014\na contact you can't reach isn't useful.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/contacts/{contact_id}": {"patch": {"operationId": "projects_api_patch_contact", "summary": "Patch Contact", "parameters": [{"in": "path", "name": "contact_id", "schema": {"format": "uuid", "title": "Contact Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Update any subset of {name, phone, email, note, is_policyholder} on\na contact.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "projects_api_delete_contact", "summary": "Delete Contact", "parameters": [{"in": "path", "name": "contact_id", "schema": {"format": "uuid", "title": "Contact Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/contacts/{contact_id}/set-default": {"post": {"operationId": "projects_api_set_default_contact", "summary": "Set Default Contact", "parameters": [{"in": "path", "name": "contact_id", "schema": {"format": "uuid", "title": "Contact Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ContactOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Promote this contact to the project's primary contact.\n\nAtomically clears any previous default on the same project and\nflips this row to `is_default=True`. Calling this on a contact\nthat is already the default is a no-op \u2014 no audit event fires and\nthe row is returned unchanged.\n\nThe DB carries a partial unique index on (project) filtered to\n`is_default=True`, so the pre-clear inside the transaction is the\nload-bearing invariant that keeps the flip from racing.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/tags/": {"get": {"operationId": "projects_api_list_tags", "summary": "List Tags", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/TagOut"}, "title": "Response", "type": "array"}}}}}, "description": "Light tag list used by the photo picker + filter dropdowns.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "projects_api_create_tag", "summary": "Create Tag", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Create a tag with the client's id (idempotent: the same id again\nreturns the tag). A name the company already has, by its slug (see\n`projects.tags`), returns that tag instead (200), including when another\nrequest creates it at the same moment: the caller selects what comes\nback. 409 only when the id is another company's tag.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/tags/manage": {"get": {"operationId": "projects_api_list_tags_manage", "summary": "List Tags Manage", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/TagManageRow"}, "title": "Response", "type": "array"}}}}}, "description": "Tags + how many photos, videos and documents carry each + creator,\nfor the Manage Tags screen (and what a delete would remove the tag from).\n\nKept separate from `list_tags` so the light shape used by pickers\ndoesn't have to carry the count subqueries or the created_by join.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/tags/{tag_id}": {"patch": {"operationId": "projects_api_patch_tag", "summary": "Patch Tag", "parameters": [{"in": "path", "name": "tag_id", "schema": {"format": "uuid", "title": "Tag Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Rename a tag. Re-slugs so the URL-safe form tracks the label.\n\nReturns 409 if the new name is the same tag as another of the company's\ntags (`projects.tags.same_name_tag`): the frontend surfaces this to the\nuser. A name that is empty or too long is a 422.", "tags": ["projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "projects_api_delete_tag", "summary": "Delete Tag", "parameters": [{"in": "path", "name": "tag_id", "schema": {"format": "uuid", "title": "Tag Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Delete a tag. M2M rows against Photos are cascade-cleaned.", "tags": ["projects"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/uploads/prepare": {"post": {"operationId": "photos_api_prepare_upload", "summary": "Prepare Upload", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UploadPrepareOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["photos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UploadPrepareIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/": {"get": {"operationId": "photos_api_list_photos", "summary": "List Photos", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}, {"in": "query", "name": "near_lat", "schema": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Near Lat"}, "required": false}, {"in": "query", "name": "near_lng", "schema": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Near Lng"}, "required": false}, {"in": "query", "name": "radius_km", "schema": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Radius Km"}, "required": false}, {"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}, {"in": "query", "name": "offset", "schema": {"default": 0, "minimum": 0, "title": "Offset", "type": "integer"}, "required": false}, {"in": "query", "name": "sort", "schema": {"anyOf": [{"enum": ["latest", "oldest", "name-asc", "name-desc", "uploaded", "uploaded-oldest"], "type": "string"}, {"type": "null"}], "title": "Sort"}, "required": false}, {"in": "query", "name": "tag_ids", "schema": {"description": "Photos with ANY of these tags.", "items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "required": false, "description": "Photos with ANY of these tags."}, {"in": "query", "name": "uploaded_by", "schema": {"description": "Photos uploaded by ANY of these users.", "items": {"type": "integer"}, "title": "Uploaded By", "type": "array"}, "required": false, "description": "Photos uploaded by ANY of these users."}, {"in": "query", "name": "uploaded_after", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Uploaded at or after this instant.", "title": "Uploaded After"}, "required": false, "description": "Uploaded at or after this instant."}, {"in": "query", "name": "uploaded_before", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Uploaded strictly before this instant.", "title": "Uploaded Before"}, "required": false, "description": "Uploaded strictly before this instant."}, {"in": "query", "name": "q", "schema": {"anyOf": [{"maxLength": 200, "type": "string"}, {"type": "null"}], "description": "Search: description, filename, uploader name or tag name contains this.", "title": "Q"}, "required": false, "description": "Search: description, filename, uploader name or tag name contains this."}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/PhotoOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["photos"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "photos_api_create_photo", "summary": "Create Photo", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["photos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/summary": {"get": {"operationId": "photos_api_photo_summary", "summary": "Photo Summary", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}, {"in": "query", "name": "tag_ids", "schema": {"items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "required": false}, {"in": "query", "name": "uploaded_by", "schema": {"items": {"type": "integer"}, "title": "Uploaded By", "type": "array"}, "required": false}, {"in": "query", "name": "uploaded_after", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Uploaded After"}, "required": false}, {"in": "query", "name": "uploaded_before", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Uploaded Before"}, "required": false}, {"in": "query", "name": "q", "schema": {"anyOf": [{"maxLength": 200, "type": "string"}, {"type": "null"}], "title": "Q"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoSummaryOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "description": "Counts for a paged photo grid: every photo, the ones that match the\nfilters, and the tags / uploaders found on those photos (with how many\neach covers) to offer as filter choices. For one project, or without\n`project_id` for every photo the caller can see.\n\nRegistered before `/{photo_id}` so \"summary\" isn't read as an id.", "tags": ["photos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/page": {"get": {"operationId": "photos_api_page_photos", "summary": "Page Photos", "parameters": [{"in": "query", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}, {"in": "query", "name": "limit", "schema": {"maximum": 100, "minimum": 1, "title": "Limit", "type": "integer"}, "required": true}, {"in": "query", "name": "cursor", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Cursor"}, "required": false}, {"in": "query", "name": "sort", "schema": {"default": "desc", "enum": ["asc", "desc"], "title": "Sort", "type": "string"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoPage"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["photos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/{photo_id}": {"get": {"operationId": "photos_api_get_photo", "summary": "Get Photo", "parameters": [{"in": "path", "name": "photo_id", "schema": {"format": "uuid", "title": "Photo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoOut"}}}}}, "tags": ["photos"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "photos_api_patch_photo", "summary": "Patch Photo", "parameters": [{"in": "path", "name": "photo_id", "schema": {"format": "uuid", "title": "Photo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoOut"}}}}}, "description": "Change a photo's description or tags, with edit rights on its project\n(`can_edit_project`; hidden is 404, visible but not editable 403). Tags:\n`tag_ids` replaces them all; `add_tag_ids` / `remove_tag_ids` change\nonly those named and keep the rest, so a change made elsewhere\nmeanwhile survives. Ids that aren't the company's tags are ignored\neither way. Logged only when something changed.", "tags": ["photos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "photos_api_delete_photo", "summary": "Delete Photo", "parameters": [{"in": "path", "name": "photo_id", "schema": {"format": "uuid", "title": "Photo Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["photos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/bulk/tags": {"post": {"operationId": "photos_api_bulk_set_tags", "summary": "Bulk Set Tags", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoBulkResultOut"}}}}}, "description": "Replace the tags of many photos at once: each listed photo ends up\nwith exactly `tag_ids` (unknown tag ids are ignored, like `PATCH`).\n\nSame rule as editing one photo's tags: edit rights on its project\n(`can_edit_project`; the others are skipped as forbidden). Photos whose\ntags already match are reported as done without a change (and without\nan audit entry). All or nothing: one transaction.", "tags": ["photos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoBulkTagsIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/bulk/tag-changes": {"post": {"operationId": "photos_api_bulk_change_tags", "summary": "Bulk Change Tags", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoBulkResultOut"}}}}}, "description": "Add and remove tags on many photos at once, keeping each photo's\nother tags (unlike `/bulk/tags`, which replaces them). Edit rights on\neach photo's project (`can_edit_project`); unknown tag ids are ignored.\nPhotos already as asked are reported as done without a change (and\nwithout an audit entry). All or nothing: one transaction.", "tags": ["photos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoBulkTagChangesIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/photos/bulk/delete": {"post": {"operationId": "photos_api_bulk_delete", "summary": "Bulk Delete", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoBulkResultOut"}}}}}, "description": "Delete many photos at once. Same rule as deleting one: edit rights on\nits project (`can_edit_project`). Their files go in the background once\nthe delete commits. All or nothing: one transaction.", "tags": ["photos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PhotoBulkDeleteIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/videos/uploads/prepare": {"post": {"operationId": "videos_api_prepare_upload", "summary": "Prepare Upload", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UploadPrepareOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["videos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UploadPrepareIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/videos/": {"get": {"operationId": "videos_api_list_videos", "summary": "List Videos", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}, {"in": "query", "name": "near_lat", "schema": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Near Lat"}, "required": false}, {"in": "query", "name": "near_lng", "schema": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Near Lng"}, "required": false}, {"in": "query", "name": "radius_km", "schema": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Radius Km"}, "required": false}, {"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/VideoOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["videos"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "videos_api_create_video", "summary": "Create Video", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["videos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/videos/{video_id}": {"get": {"operationId": "videos_api_get_video", "summary": "Get Video", "parameters": [{"in": "path", "name": "video_id", "schema": {"format": "uuid", "title": "Video Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["videos"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "videos_api_patch_video", "summary": "Patch Video", "parameters": [{"in": "path", "name": "video_id", "schema": {"format": "uuid", "title": "Video Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "description": "Change a video's description or tags, as `PATCH /photos/{id}`: edit\nrights on its project (`can_edit_project`; hidden is 404, visible but\nnot editable 403); `tag_ids` replaces them all; `add_tag_ids` /\n`remove_tag_ids` change only those named. Logged only when something\nchanged.", "tags": ["videos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "videos_api_delete_video", "summary": "Delete Video", "parameters": [{"in": "path", "name": "video_id", "schema": {"format": "uuid", "title": "Video Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["videos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/videos/bulk/tag-changes": {"post": {"operationId": "videos_api_bulk_change_tags", "summary": "Bulk Change Tags", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoBulkResultOut"}}}}}, "description": "Add and remove tags on many videos at once, keeping each video's\nother tags (as `POST /photos/bulk/tag-changes`). Videos the caller can't\nsee are skipped as `not_found` (never saying whether they exist), those\non a project they can't edit as `forbidden`. All or nothing: one\ntransaction.", "tags": ["videos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/VideoBulkTagChangesIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/documents/uploads/prepare": {"post": {"operationId": "documents_api_prepare_upload", "summary": "Prepare Upload", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UploadPrepareOut"}}}}}, "tags": ["documents"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UploadPrepareIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/documents/": {"get": {"operationId": "documents_api_list_documents", "summary": "List Documents", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/DocumentOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["documents"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "documents_api_create_document", "summary": "Create Document", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["documents"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/documents/page": {"get": {"operationId": "documents_api_page_documents", "summary": "Page Documents", "parameters": [{"in": "query", "name": "search", "schema": {"default": "", "description": "In the file name or the project's name.", "title": "Search", "type": "string"}, "required": false, "description": "In the file name or the project's name."}, {"in": "query", "name": "file_type", "schema": {"description": "Documents of ANY of these file types.", "items": {"enum": ["pdf", "word", "excel", "image"], "type": "string"}, "title": "File Type", "type": "array"}, "required": false, "description": "Documents of ANY of these file types."}, {"in": "query", "name": "source", "schema": {"anyOf": [{"enum": ["uploaded", "report"], "type": "string"}, {"type": "null"}], "description": "`uploaded`: files people added; `report`: reports Camspect generated.", "title": "Source"}, "required": false, "description": "`uploaded`: files people added; `report`: reports Camspect generated."}, {"in": "query", "name": "report_type", "schema": {"description": "Reports of ANY of these types.", "items": {"enum": ["inspection", "project"], "title": "ReportType", "type": "string"}, "title": "Report Type", "type": "array"}, "required": false, "description": "Reports of ANY of these types."}, {"in": "query", "name": "project_id", "schema": {"description": "Documents on ANY of these projects.", "items": {"format": "uuid", "type": "string"}, "title": "Project Id", "type": "array"}, "required": false, "description": "Documents on ANY of these projects."}, {"in": "query", "name": "uploaded_by", "schema": {"description": "Documents added by ANY of these users.", "items": {"type": "integer"}, "title": "Uploaded By", "type": "array"}, "required": false, "description": "Documents added by ANY of these users."}, {"in": "query", "name": "tag_ids", "schema": {"description": "Documents with ANY of these tags.", "items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "required": false, "description": "Documents with ANY of these tags."}, {"in": "query", "name": "uploaded_after", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Added at or after this instant.", "title": "Uploaded After"}, "required": false, "description": "Added at or after this instant."}, {"in": "query", "name": "uploaded_before", "schema": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "Added strictly before this instant.", "title": "Uploaded Before"}, "required": false, "description": "Added strictly before this instant."}, {"in": "query", "name": "sort", "schema": {"default": "newest", "enum": ["newest", "oldest", "name-asc", "name-desc", "largest"], "title": "Sort", "type": "string"}, "required": false}, {"in": "query", "name": "page", "schema": {"default": 1, "minimum": 1, "title": "Page", "type": "integer"}, "required": false}, {"in": "query", "name": "page_size", "schema": {"default": 25, "maximum": 100, "minimum": 1, "title": "Page Size", "type": "integer"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentPageOut"}}}}}, "description": "One page of the documents the caller can see, across projects\n(newest first by default), with the total that match. Registered\nbefore `/{doc_id}` so \"page\" isn't read as an id.", "tags": ["documents"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/documents/summary": {"get": {"operationId": "documents_api_documents_summary", "summary": "Documents Summary", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentSummaryOut"}}}}}, "description": "What the documents the caller can see hold, with counts: projects,\nuploaders, tags, file types, report types, and reports vs uploads. The Documents\npage offers these as filter choices.", "tags": ["documents"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/documents/{doc_id}": {"get": {"operationId": "documents_api_get_document", "summary": "Get Document", "parameters": [{"in": "path", "name": "doc_id", "schema": {"format": "uuid", "title": "Doc Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentOut"}}}}}, "tags": ["documents"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "documents_api_patch_document", "summary": "Patch Document", "parameters": [{"in": "path", "name": "doc_id", "schema": {"format": "uuid", "title": "Doc Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentOut"}}}}}, "tags": ["documents"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DocumentPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "documents_api_delete_document", "summary": "Delete Document", "parameters": [{"in": "path", "name": "doc_id", "schema": {"format": "uuid", "title": "Doc Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["documents"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/": {"get": {"operationId": "todos_api_list_todos", "summary": "List Todos", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}, {"in": "query", "name": "status", "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Status"}, "required": false}, {"in": "query", "name": "assignee_id", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Assignee Id"}, "required": false}, {"in": "query", "name": "ordering", "schema": {"anyOf": [{"enum": ["-created_at", "due-priority"], "type": "string"}, {"type": "null"}], "title": "Ordering"}, "required": false}, {"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/TodoOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "todos_api_create_todo", "summary": "Create Todo", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["todos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/assignable-users": {"get": {"operationId": "todos_api_list_assignable_users", "summary": "List Assignable Users", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/AssignableUserOut"}, "title": "Response", "type": "array"}}}}}, "description": "Users who can be assigned a todo.\n\nWith `project_id`: the users who can see that project (its members\nplus admins and managers), the picker's first choices. Without it:\nevery active company user, all of whom can be assigned (someone not\non the project is added to it).", "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/{todo_id}": {"get": {"operationId": "todos_api_get_todo", "summary": "Get Todo", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}}, "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "todos_api_patch_todo", "summary": "Patch Todo", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}}, "tags": ["todos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "todos_api_delete_todo", "summary": "Delete Todo", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/{todo_id}/done": {"post": {"operationId": "todos_api_mark_todo_done", "summary": "Mark Todo Done", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}}, "tags": ["todos"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoDoneIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/{todo_id}/open": {"post": {"operationId": "todos_api_mark_todo_open", "summary": "Mark Todo Open", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}}, "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/{todo_id}/cancel": {"post": {"operationId": "todos_api_mark_todo_canceled", "summary": "Mark Todo Canceled", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TodoOut"}}}}}, "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/todos/{todo_id}/events": {"get": {"operationId": "todos_api_list_todo_events", "summary": "List Todo Events", "parameters": [{"in": "path", "name": "todo_id", "schema": {"format": "uuid", "title": "Todo Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/TodoEventOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["todos"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/comments/": {"get": {"operationId": "comments_api_list_comments", "summary": "List Comments", "parameters": [{"in": "query", "name": "target_kind", "schema": {"title": "Target Kind", "type": "string"}, "required": true}, {"in": "query", "name": "target_id", "schema": {"format": "uuid", "title": "Target Id", "type": "string"}, "required": true}, {"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}, {"in": "query", "name": "before", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Before"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/CommentOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["comments"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "comments_api_create_comment", "summary": "Create Comment", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["comments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/comments/summary": {"get": {"operationId": "comments_api_comment_summary", "summary": "Comment Summary", "parameters": [{"in": "query", "name": "target_kind", "schema": {"title": "Target Kind", "type": "string"}, "required": true}, {"in": "query", "name": "target_id", "schema": {"format": "uuid", "title": "Target Id", "type": "string"}, "required": true}, {"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}, {"in": "query", "name": "before", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Before"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentSummaryOut"}}}}}, "tags": ["comments"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/comments/inbox": {"get": {"operationId": "comments_api_list_inbox", "summary": "List Inbox", "parameters": [{"in": "query", "name": "limit", "schema": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Limit"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/InboxItemOut"}, "title": "Response", "type": "array"}}}}}, "description": "Recent @mentions of the caller: in comments on any target kind,\nand in project notes (`source: \"note\"`), newest first.", "tags": ["comments"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/comments/assignable-users": {"get": {"operationId": "comments_api_list_assignable_users", "summary": "List Assignable Users", "parameters": [{"in": "query", "name": "project_id", "schema": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Project Id"}, "required": false}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/AssignableUserOut"}, "title": "Response", "type": "array"}}}}}, "description": "Users eligible to be @mentioned in a comment.", "tags": ["comments"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/comments/{comment_id}": {"get": {"operationId": "comments_api_get_comment", "summary": "Get Comment", "parameters": [{"in": "path", "name": "comment_id", "schema": {"format": "uuid", "title": "Comment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentOut"}}}}}, "tags": ["comments"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "comments_api_patch_comment", "summary": "Patch Comment", "parameters": [{"in": "path", "name": "comment_id", "schema": {"format": "uuid", "title": "Comment Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentOut"}}}}}, "tags": ["comments"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/CommentPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "comments_api_delete_comment", "summary": "Delete Comment", "parameters": [{"in": "path", "name": "comment_id", "schema": {"format": "uuid", "title": "Comment Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["comments"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/insurance-carriers/": {"get": {"operationId": "insurance_api_list_carriers", "summary": "List Carriers", "parameters": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/InsuranceCarrierOut"}, "title": "Response", "type": "array"}}}}}, "description": "The company's insurance companies, by name.", "tags": ["insurance-carriers"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "insurance_api_create_carrier", "summary": "Create Carrier", "parameters": [], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InsuranceCarrierOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["insurance-carriers"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InsuranceCarrierIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/insurance-carriers/{carrier_id}": {"patch": {"operationId": "insurance_api_update_carrier", "summary": "Update Carrier", "parameters": [{"in": "path", "name": "carrier_id", "schema": {"format": "uuid", "title": "Carrier Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InsuranceCarrierOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "tags": ["insurance-carriers"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InsuranceCarrierPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "insurance_api_delete_carrier", "summary": "Delete Carrier", "parameters": [{"in": "path", "name": "carrier_id", "schema": {"format": "uuid", "title": "Carrier Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "description": "Its projects are left without a carrier; their claim details stay.", "tags": ["insurance-carriers"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/inspections": {"get": {"operationId": "inspections_api_list_inspections", "summary": "List Inspections", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"items": {"$ref": "#/components/schemas/InspectionOut"}, "title": "Response", "type": "array"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}, "post": {"operationId": "inspections_api_create_inspection", "summary": "Create Inspection", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "description": "Create an inspection under a client-generated id. A replay of the\nsame id returns the existing inspection (200, nothing logged).\n\nWith `template_id` (one of the company's inspection templates) its\nline items are copied in, and the name defaults to the template's.\nWith `items` the inspection is created with those line items instead\n(e.g. a reviewed draft from Create with AI, `source: \"ai\"`). Both\ntogether: the items are the template's tasks as the client copied\nthem (an offline client creating from a template), and the audit\nsays \"from template\". Items may carry client ids (idempotent with the\ninspection; an id taken elsewhere is a 409 `line_item_id_conflict`).", "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}": {"get": {"operationId": "inspections_api_get_inspection", "summary": "Get Inspection", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}, "patch": {"operationId": "inspections_api_patch_inspection", "summary": "Patch Inspection", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "inspections_api_delete_inspection", "summary": "Delete Inspection", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/projects/{project_id}/inspections/reorder": {"post": {"operationId": "inspections_api_reorder_inspections", "summary": "Reorder Inspections", "parameters": [{"in": "path", "name": "project_id", "schema": {"format": "uuid", "title": "Project Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionReorderIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}/line-items": {"post": {"operationId": "inspections_api_create_line_item", "summary": "Create Line Item", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemOut"}}}}, "201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}}, "description": "Create a line item under a client-generated id; a replay of the\nsame id returns the existing item (200, nothing logged).", "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/line-items/{line_item_id}": {"patch": {"operationId": "inspections_api_patch_line_item", "summary": "Patch Line Item", "parameters": [{"in": "path", "name": "line_item_id", "schema": {"format": "uuid", "title": "Line Item Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemPatch"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}, "delete": {"operationId": "inspections_api_delete_line_item", "summary": "Delete Line Item", "parameters": [{"in": "path", "name": "line_item_id", "schema": {"format": "uuid", "title": "Line Item Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}/line-items/reorder": {"post": {"operationId": "inspections_api_reorder_line_items", "summary": "Reorder Line Items", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemReorderIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/line-items/{line_item_id}/close": {"post": {"operationId": "inspections_api_close_line_item", "summary": "Close Line Item", "parameters": [{"in": "path", "name": "line_item_id", "schema": {"format": "uuid", "title": "Line Item Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/line-items/{line_item_id}/reopen": {"post": {"operationId": "inspections_api_reopen_line_item", "summary": "Reopen Line Item", "parameters": [{"in": "path", "name": "line_item_id", "schema": {"format": "uuid", "title": "Line Item Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/line-items/{line_item_id}/photos": {"post": {"operationId": "inspections_api_link_photos", "summary": "Link Photos", "parameters": [{"in": "path", "name": "line_item_id", "schema": {"format": "uuid", "title": "Line Item Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Attach existing Photos (already uploaded to the project) to a\nline item. Idempotent \u2014 repeat calls are a no-op for already-linked\nphotos. Refuses cross-project photos. (A new upload can attach at\ncreate instead: `PhotoIn.line_item_ids`.)", "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LineItemPhotosLinkIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/line-items/{line_item_id}/photos/{photo_id}": {"delete": {"operationId": "inspections_api_unlink_photo", "summary": "Unlink Photo", "parameters": [{"in": "path", "name": "line_item_id", "schema": {"format": "uuid", "title": "Line Item Id", "type": "string"}, "required": true}, {"in": "path", "name": "photo_id", "schema": {"format": "uuid", "title": "Photo Id", "type": "string"}, "required": true}], "responses": {"204": {"description": "No Content"}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}/reports/csv": {"post": {"operationId": "inspections_api_create_csv_report", "summary": "Create Csv Report", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionReportOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Export the inspection as CSV: saved to the project's Documents as an\ninspection report (a new, dated snapshot each time), and returned with\na download link. See `inspections/reports.py` for the layout.", "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InspectionReportIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}/reports/pdf/options": {"get": {"operationId": "inspections_api_pdf_report_options", "summary": "Pdf Report Options", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PdfReportOptionsOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}/reports/pdf/cover": {"get": {"operationId": "inspections_api_pdf_report_cover", "summary": "Pdf Report Cover", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}, {"in": "query", "name": "template_id", "schema": {"format": "uuid", "title": "Template Id", "type": "string"}, "required": true}, {"in": "query", "name": "items", "schema": {"default": "all", "title": "Items", "type": "string"}, "required": false}, {"in": "query", "name": "timezone", "schema": {"default": "UTC", "title": "Timezone", "type": "string"}, "required": false}, {"in": "query", "name": "claim", "schema": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Claim"}, "required": false}], "responses": {"200": {"description": "OK"}}, "description": "One cover page as standalone HTML, for the export dialog's previews\n(same template and data as page 1 of the PDF). `claim` previews it with\nor without the insurance claim block; left out, as the PDF will be\n(with it on insurance jobs).", "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspections/{inspection_id}/reports/pdf": {"post": {"operationId": "inspections_api_create_pdf_report", "summary": "Create Pdf Report", "parameters": [{"in": "path", "name": "inspection_id", "schema": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "required": true}], "responses": {"201": {"description": "Created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PdfReportJobOut"}}}}, "400": {"description": "Bad Request", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "403": {"description": "Forbidden", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "404": {"description": "Not Found", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}, "409": {"description": "Conflict", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PdfReportConflictOut"}}}}, "429": {"description": "Too Many Requests", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MessageOut"}}}}}, "description": "Export the inspection as PDF: rendered in the background, then saved\nto the project's Documents as an inspection report. Poll\n`GET /inspection-reports/{job_id}` for the result. When nothing has\nchanged since a report with these options was filed, the job comes\nback already succeeded with that report (`reused`). With `email`, the\nreport is emailed (one email to everyone) once it's filed instead of\ndownloaded (`emailed_to`).", "tags": ["inspections"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PdfReportIn"}}}, "required": true}, "security": [{"AccessTokenAuth": []}]}}, "/api/v1/inspection-reports/{job_id}": {"get": {"operationId": "inspections_api_get_pdf_report", "summary": "Get Pdf Report", "parameters": [{"in": "path", "name": "job_id", "schema": {"format": "uuid", "title": "Job Id", "type": "string"}, "required": true}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PdfReportJobOut"}}}}}, "tags": ["inspections"], "security": [{"AccessTokenAuth": []}]}}}, "components": {"schemas": {"ProjectCoverOut": {"description": "A project's cover photo at each size, for avatars.", "properties": {"signed_thumb_url": {"default": "", "title": "Signed Thumb Url", "type": "string"}, "signed_preview_url": {"default": "", "title": "Signed Preview Url", "type": "string"}, "signed_display_url": {"default": "", "title": "Signed Display Url", "type": "string"}}, "title": "ProjectCoverOut", "type": "object"}, "ProjectMemberOut": {"description": "A user assigned to the project.\n\nKept flat (user_id + name + email inlined, not nested under a\n`user` object) so list responses stay compact and the frontend\ncan render the avatar stack without an extra join lookup.\n\n`id` is the ProjectMember PK, used to DELETE the assignment.", "properties": {"id": {"title": "Id", "type": "integer"}, "user_id": {"title": "User Id", "type": "integer"}, "user_role": {"enum": ["admin", "manager", "standard"], "title": "User Role", "type": "string"}, "role": {"deprecated": true, "description": "Deprecated: projects no longer have per-member roles. Always `office` for older clients. Use `user_role`.", "title": "Role", "type": "string"}, "full_name": {"title": "Full Name", "type": "string"}, "email": {"title": "Email", "type": "string"}, "assigned_at": {"format": "date-time", "title": "Assigned At", "type": "string"}}, "required": ["id", "user_id", "user_role", "role", "full_name", "email", "assigned_at"], "title": "ProjectMemberOut", "type": "object"}, "StatusOut": {"description": "Nested status shape used inside ProjectOut and pins.\n\nKept minimal \u2014 pill rendering only needs id + color + label. The\nslug is exposed so the frontend can do stable lookups (e.g.\n\"which status is 'completed'?\") without depending on the name.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "slug": {"title": "Slug", "type": "string"}, "color": {"title": "Color", "type": "string"}, "is_closed": {"default": false, "title": "Is Closed", "type": "boolean"}}, "required": ["id", "name", "slug", "color"], "title": "StatusOut", "type": "object"}, "MessageOut": {"properties": {"detail": {"title": "Detail", "type": "string"}}, "required": ["detail"], "title": "MessageOut", "type": "object"}, "ContactOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "phone": {"title": "Phone", "type": "string"}, "email": {"title": "Email", "type": "string"}, "note": {"title": "Note", "type": "string"}, "sort_order": {"title": "Sort Order", "type": "integer"}, "is_default": {"default": false, "title": "Is Default", "type": "boolean"}, "is_policyholder": {"default": false, "title": "Is Policyholder", "type": "boolean"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "phone", "email", "note", "sort_order", "created_at", "updated_at"], "title": "ContactOut", "type": "object"}, "InsuranceCarrierRefOut": {"description": "A carrier as a project shows it.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}}, "required": ["id", "name"], "title": "InsuranceCarrierRefOut", "type": "object"}, "LocationRefOut": {"description": "A location as other resources show it (a project's, a review\nrequest's): its name, and its address for a hover.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "address": {"title": "Address", "type": "string"}}, "required": ["id", "name", "address"], "title": "LocationRefOut", "type": "object"}, "ProjectInsuranceOut": {"properties": {"carrier_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Carrier Id"}, "claim_number": {"default": "", "title": "Claim Number", "type": "string"}, "policy_number": {"default": "", "title": "Policy Number", "type": "string"}, "date_of_loss": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Date Of Loss"}, "claim_filed_on": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Claim Filed On"}, "cause_of_loss": {"default": "", "enum": ["", "hail", "wind", "hurricane", "water", "fire", "other"], "title": "Cause Of Loss", "type": "string"}, "claim_status": {"default": "na", "enum": ["na", "filed", "approved", "adjuster_meeting", "denied", "lost"], "title": "Claim Status", "type": "string"}, "adjuster_name": {"default": "", "title": "Adjuster Name", "type": "string"}, "adjuster_phone": {"default": "", "title": "Adjuster Phone", "type": "string"}, "adjuster_email": {"default": "", "title": "Adjuster Email", "type": "string"}, "carrier": {"anyOf": [{"$ref": "#/components/schemas/InsuranceCarrierRefOut"}, {"type": "null"}]}}, "title": "ProjectInsuranceOut", "type": "object"}, "ProjectOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "display_title": {"default": "", "title": "Display Title", "type": "string"}, "address_line_1": {"title": "Address Line 1", "type": "string"}, "address_line_2": {"title": "Address Line 2", "type": "string"}, "city": {"title": "City", "type": "string"}, "state": {"title": "State", "type": "string"}, "country": {"title": "Country", "type": "string"}, "zipcode": {"title": "Zipcode", "type": "string"}, "latitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Longitude"}, "bid_type": {"default": "cash", "enum": ["cash", "insurance", "finance"], "title": "Bid Type", "type": "string"}, "job_type": {"default": "retail", "enum": ["retail", "commercial", "designer"], "title": "Job Type", "type": "string"}, "members": {"default": [], "items": {"$ref": "#/components/schemas/ProjectMemberOut"}, "title": "Members", "type": "array"}, "owner_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Owner Id"}, "cover_photo_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Cover Photo Id"}, "cover_version": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Cover Version"}, "cover": {"anyOf": [{"$ref": "#/components/schemas/ProjectCoverOut"}, {"type": "null"}]}, "status": {"$ref": "#/components/schemas/StatusOut"}, "contacts": {"default": [], "items": {"$ref": "#/components/schemas/ContactOut"}, "title": "Contacts", "type": "array"}, "company_location": {"anyOf": [{"$ref": "#/components/schemas/LocationRefOut"}, {"type": "null"}]}, "insurance": {"allOf": [{"$ref": "#/components/schemas/ProjectInsuranceOut"}], "default": {"carrier_id": null, "claim_number": "", "policy_number": "", "date_of_loss": null, "claim_filed_on": null, "cause_of_loss": "", "claim_status": "na", "adjuster_name": "", "adjuster_phone": "", "adjuster_email": "", "carrier": null}}, "photos_count": {"default": 0, "title": "Photos Count", "type": "integer"}, "videos_count": {"default": 0, "title": "Videos Count", "type": "integer"}, "documents_count": {"default": 0, "title": "Documents Count", "type": "integer"}, "inspections_count": {"default": 0, "title": "Inspections Count", "type": "integer"}, "todos_count": {"default": 0, "title": "Todos Count", "type": "integer"}, "notes_count": {"default": 0, "title": "Notes Count", "type": "integer"}, "custom_values": {"additionalProperties": true, "default": {}, "title": "Custom Values", "type": "object"}, "conversation_count": {"default": 0, "title": "Conversation Count", "type": "integer"}, "is_archived": {"default": false, "title": "Is Archived", "type": "boolean"}, "is_favorite": {"default": false, "title": "Is Favorite", "type": "boolean"}, "created_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Created By Id"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "address_line_1", "address_line_2", "city", "state", "country", "zipcode", "status", "created_at", "updated_at"], "title": "ProjectOut", "type": "object"}, "ProjectIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "address_line_1": {"default": "", "title": "Address Line 1", "type": "string"}, "address_line_2": {"default": "", "title": "Address Line 2", "type": "string"}, "city": {"default": "", "title": "City", "type": "string"}, "state": {"default": "", "title": "State", "type": "string"}, "country": {"default": "", "title": "Country", "type": "string"}, "zipcode": {"default": "", "title": "Zipcode", "type": "string"}, "latitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Longitude"}, "status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Status Id"}, "bid_type": {"default": "cash", "enum": ["cash", "insurance", "finance"], "title": "Bid Type", "type": "string"}, "job_type": {"default": "retail", "enum": ["retail", "commercial", "designer"], "title": "Job Type", "type": "string"}}, "required": ["id", "name"], "title": "ProjectIn", "type": "object"}, "AssignableUserRow": {"properties": {"id": {"title": "Id", "type": "integer"}, "full_name": {"title": "Full Name", "type": "string"}, "email": {"title": "Email", "type": "string"}, "role": {"enum": ["admin", "manager", "standard"], "title": "Role", "type": "string"}, "avatar_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Avatar Url"}}, "required": ["id", "full_name", "email", "role"], "title": "AssignableUserRow", "type": "object"}, "AssignableUsersOut": {"description": "Active workspace users. Any of them can be assigned to, or own, a\nproject.", "properties": {"users": {"items": {"$ref": "#/components/schemas/AssignableUserRow"}, "title": "Users", "type": "array"}}, "required": ["users"], "title": "AssignableUsersOut", "type": "object"}, "ProjectMemberAddIn": {"properties": {"user_id": {"title": "User Id", "type": "integer"}, "role": {"anyOf": [{"type": "string"}, {"type": "null"}], "deprecated": true, "description": "Deprecated and ignored: assignments have no role.", "title": "Role"}}, "required": ["user_id"], "title": "ProjectMemberAddIn", "type": "object"}, "ProjectCoverIn": {"description": "Pick one of the project's photos as its cover; null clears it.", "properties": {"photo_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Photo Id"}}, "title": "ProjectCoverIn", "type": "object"}, "ProjectOwnerIn": {"description": "Hand the project to another active user of the workspace.", "properties": {"user_id": {"title": "User Id", "type": "integer"}}, "required": ["user_id"], "title": "ProjectOwnerIn", "type": "object"}, "StatusIn": {"description": "Payload for creating a custom status.\n\nOffline-first: the client generates the UUID and sends it in\n`id`. Sending the same id twice for the same company returns the\nexisting row (200) instead of 409, so retries after a network\nblip are safe. The name is trimmed, 1 to 64 characters; the color\n`#RRGGBB` (422 otherwise).", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "color": {"title": "Color", "type": "string"}, "sort_order": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Sort Order"}, "is_closed": {"default": false, "title": "Is Closed", "type": "boolean"}}, "required": ["id", "name", "color"], "title": "StatusIn", "type": "object"}, "StatusManageRow": {"description": "Row shape for the Manage Statuses screen.\n\nDeliberately separate from `StatusOut` (which is embedded in every\nProjectOut and pin) so we can add management-only fields \u2014 most\nimportantly `project_count` \u2014 without inflating unrelated\nresponses.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "slug": {"title": "Slug", "type": "string"}, "color": {"title": "Color", "type": "string"}, "sort_order": {"title": "Sort Order", "type": "integer"}, "is_closed": {"default": false, "title": "Is Closed", "type": "boolean"}, "project_count": {"title": "Project Count", "type": "integer"}, "created_by": {"anyOf": [{"$ref": "#/components/schemas/UserSummary"}, {"type": "null"}]}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "name", "slug", "color", "sort_order", "project_count", "created_at"], "title": "StatusManageRow", "type": "object"}, "UserSummary": {"description": "Compact user reference embedded in management-row responses.\n\nNullable at call sites because rows created before the caller\nconcept existed (seed data / data migrations) have no\n`created_by`, and users can be deleted (SET_NULL on the FK).", "properties": {"id": {"title": "Id", "type": "integer"}, "full_name": {"title": "Full Name", "type": "string"}, "email": {"title": "Email", "type": "string"}}, "required": ["id", "full_name", "email"], "title": "UserSummary", "type": "object"}, "StatusReorderIn": {"description": "Payload for POST /projects/statuses/reorder.\n\n`status_ids` is the full ordered list of the caller's statuses.\nThe server rewrites `sort_order` to `(1..N) * 10` in that exact\norder. Sending a partial list is rejected \u2014 the frontend always\nknows the whole set.", "properties": {"status_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Status Ids", "type": "array"}}, "required": ["status_ids"], "title": "StatusReorderIn", "type": "object"}, "StatusPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "color": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Color"}, "sort_order": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Sort Order"}, "is_closed": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Closed"}}, "title": "StatusPatch", "type": "object"}, "ProjectListItem": {"description": "Row shape for GET /projects/list.\n\nDenormalized for the table view: signed URLs pre-baked, counts\npre-annotated, up to 5 recent photos included. Avoids the client\nhaving to N+1 for stats or thumbnails.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "hero_img_url": {"title": "Hero Img Url", "type": "string"}, "cover": {"anyOf": [{"$ref": "#/components/schemas/ProjectCoverOut"}, {"type": "null"}]}, "address_short": {"title": "Address Short", "type": "string"}, "status": {"$ref": "#/components/schemas/StatusOut"}, "bid_type": {"default": "cash", "enum": ["cash", "insurance", "finance"], "title": "Bid Type", "type": "string"}, "job_type": {"default": "retail", "enum": ["retail", "commercial", "designer"], "title": "Job Type", "type": "string"}, "members": {"default": [], "items": {"$ref": "#/components/schemas/ProjectMemberOut"}, "title": "Members", "type": "array"}, "owner_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Owner Id"}, "is_favorite": {"title": "Is Favorite", "type": "boolean"}, "is_archived": {"title": "Is Archived", "type": "boolean"}, "photos_count": {"title": "Photos Count", "type": "integer"}, "documents_count": {"title": "Documents Count", "type": "integer"}, "inspections_count": {"title": "Inspections Count", "type": "integer"}, "inspections_total": {"title": "Inspections Total", "type": "integer"}, "inspections_completed": {"title": "Inspections Completed", "type": "integer"}, "conversation_count": {"default": 0, "title": "Conversation Count", "type": "integer"}, "notes_count": {"default": 0, "title": "Notes Count", "type": "integer"}, "recent_photos": {"items": {"$ref": "#/components/schemas/ProjectListPhoto"}, "title": "Recent Photos", "type": "array"}, "status_entered_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Status Entered At"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "hero_img_url", "address_short", "status", "is_favorite", "is_archived", "photos_count", "documents_count", "inspections_count", "inspections_total", "inspections_completed", "recent_photos", "created_at", "updated_at"], "title": "ProjectListItem", "type": "object"}, "ProjectListPage": {"properties": {"items": {"items": {"$ref": "#/components/schemas/ProjectListItem"}, "title": "Items", "type": "array"}, "total": {"title": "Total", "type": "integer"}, "page": {"title": "Page", "type": "integer"}, "page_size": {"title": "Page Size", "type": "integer"}, "has_more": {"title": "Has More", "type": "boolean"}, "tab_counts": {"$ref": "#/components/schemas/ProjectTabCounts"}}, "required": ["items", "total", "page", "page_size", "has_more", "tab_counts"], "title": "ProjectListPage", "type": "object"}, "ProjectListPhoto": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "signed_thumb_url": {"title": "Signed Thumb Url", "type": "string"}, "signed_display_url": {"default": "", "title": "Signed Display Url", "type": "string"}}, "required": ["id", "signed_thumb_url"], "title": "ProjectListPhoto", "type": "object"}, "ProjectTabCounts": {"description": "Raw per-tab totals for the company. Deliberately unfiltered \u2014\nthe tab badges show the true bucket sizes so switching tabs is\npredictable. The \"N projects\" toolbar pill carries the filtered\ncount for the currently-selected tab.", "properties": {"all": {"title": "All", "type": "integer"}, "mine": {"title": "Mine", "type": "integer"}, "starred": {"title": "Starred", "type": "integer"}, "archived": {"title": "Archived", "type": "integer"}}, "required": ["all", "mine", "starred", "archived"], "title": "ProjectTabCounts", "type": "object"}, "PinOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "lat": {"title": "Lat", "type": "number"}, "lng": {"title": "Lng", "type": "number"}, "color": {"title": "Color", "type": "string"}, "status_name": {"title": "Status Name", "type": "string"}}, "required": ["id", "name", "lat", "lng", "color", "status_name"], "title": "PinOut", "type": "object"}, "PinsPage": {"description": "Paginated pin response.\n\n`total` is the true count in the bbox, not the number returned in\nthis page \u2014 the map's top-center chip reads it verbatim (\"x of N\njobs\"). `has_more` is a convenience for the client so it doesn't\nhave to compute `page * page_size < total` itself.", "properties": {"items": {"items": {"$ref": "#/components/schemas/PinOut"}, "title": "Items", "type": "array"}, "total": {"title": "Total", "type": "integer"}, "page": {"title": "Page", "type": "integer"}, "page_size": {"title": "Page Size", "type": "integer"}, "has_more": {"title": "Has More", "type": "boolean"}}, "required": ["items", "total", "page", "page_size", "has_more"], "title": "PinsPage", "type": "object"}, "PipelineCard": {"description": "Card shape used by the pipeline (kanban) columns: only what the\ncard shows (name, address, cover, counts, assignees, last update).\nColumns are ordered by when each job entered the status.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "address_short": {"title": "Address Short", "type": "string"}, "status": {"$ref": "#/components/schemas/StatusOut"}, "bid_type": {"default": "cash", "enum": ["cash", "insurance", "finance"], "title": "Bid Type", "type": "string"}, "job_type": {"default": "retail", "enum": ["retail", "commercial", "designer"], "title": "Job Type", "type": "string"}, "members": {"default": [], "items": {"$ref": "#/components/schemas/ProjectMemberOut"}, "title": "Members", "type": "array"}, "cover": {"anyOf": [{"$ref": "#/components/schemas/ProjectCoverOut"}, {"type": "null"}]}, "is_favorite": {"title": "Is Favorite", "type": "boolean"}, "photos_count": {"title": "Photos Count", "type": "integer"}, "documents_count": {"title": "Documents Count", "type": "integer"}, "inspections_total": {"title": "Inspections Total", "type": "integer"}, "conversation_count": {"default": 0, "title": "Conversation Count", "type": "integer"}, "notes_count": {"default": 0, "title": "Notes Count", "type": "integer"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "address_short", "status", "is_favorite", "photos_count", "documents_count", "inspections_total", "updated_at"], "title": "PipelineCard", "type": "object"}, "PipelineColumnPage": {"description": "One page of cards for a single status column.\n\n`status_id` echoes the request so the frontend can route the\nresponse into the right column when several are in flight.", "properties": {"status_id": {"format": "uuid", "title": "Status Id", "type": "string"}, "items": {"items": {"$ref": "#/components/schemas/PipelineCard"}, "title": "Items", "type": "array"}, "total": {"title": "Total", "type": "integer"}, "page": {"title": "Page", "type": "integer"}, "page_size": {"title": "Page Size", "type": "integer"}, "has_more": {"title": "Has More", "type": "boolean"}}, "required": ["status_id", "items", "total", "page", "page_size", "has_more"], "title": "PipelineColumnPage", "type": "object"}, "ProjectPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "display_title": {"anyOf": [{"maxLength": 120, "type": "string"}, {"type": "null"}], "title": "Display Title"}, "address_line_1": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Address Line 1"}, "address_line_2": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Address Line 2"}, "city": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "City"}, "state": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "State"}, "country": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Country"}, "zipcode": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Zipcode"}, "latitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Longitude"}, "status_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Status Id"}, "bid_type": {"anyOf": [{"enum": ["cash", "insurance", "finance"], "type": "string"}, {"type": "null"}], "title": "Bid Type"}, "job_type": {"anyOf": [{"enum": ["retail", "commercial", "designer"], "type": "string"}, {"type": "null"}], "title": "Job Type"}, "company_location_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Company Location Id"}, "insurance_carrier_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Insurance Carrier Id"}, "claim_number": {"anyOf": [{"maxLength": 64, "type": "string"}, {"type": "null"}], "title": "Claim Number"}, "policy_number": {"anyOf": [{"maxLength": 64, "type": "string"}, {"type": "null"}], "title": "Policy Number"}, "date_of_loss": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Date Of Loss"}, "claim_filed_on": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Claim Filed On"}, "cause_of_loss": {"anyOf": [{"enum": ["", "hail", "wind", "hurricane", "water", "fire", "other"], "type": "string"}, {"type": "null"}], "title": "Cause Of Loss"}, "claim_status": {"anyOf": [{"enum": ["na", "filed", "approved", "adjuster_meeting", "denied", "lost"], "type": "string"}, {"type": "null"}], "title": "Claim Status"}, "adjuster_name": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Adjuster Name"}, "adjuster_phone": {"anyOf": [{"maxLength": 32, "pattern": "^(\\+\\d{4,20})?$", "type": "string"}, {"type": "null"}], "title": "Adjuster Phone"}, "adjuster_email": {"anyOf": [{"maxLength": 254, "type": "string"}, {"type": "null"}], "title": "Adjuster Email"}}, "title": "ProjectPatch", "type": "object"}, "MediaContributorRow": {"description": "One row in the project detail sidebar's media-contribution breakdown.\n\nRepresents a user + how many photos / videos / documents they've\ncontributed to this project. `user_id` is null when the uploader\nhas been deleted (SET_NULL on the FKs), rolled into an \"Unknown\"\nbucket so the counts still sum to the totals.\n\n`videos` is a placeholder \u2014 the Video model doesn't exist yet; the\nfield is wired up in the response shape so the frontend can render\nthe badge in advance and the backend can start filling it in when\nthe resource lands.", "properties": {"user_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "User Id"}, "full_name": {"title": "Full Name", "type": "string"}, "email": {"title": "Email", "type": "string"}, "photos": {"default": 0, "title": "Photos", "type": "integer"}, "videos": {"default": 0, "title": "Videos", "type": "integer"}, "documents": {"default": 0, "title": "Documents", "type": "integer"}}, "required": ["full_name", "email"], "title": "MediaContributorRow", "type": "object"}, "ProjectMediaContributors": {"description": "Aggregate response for GET /projects/{id}/media-contributors.\n\nEach `total_*` echoes the sum of the matching column across rows,\nso the client can render a header total without re-summing.", "properties": {"total_photos": {"title": "Total Photos", "type": "integer"}, "total_videos": {"title": "Total Videos", "type": "integer"}, "total_documents": {"title": "Total Documents", "type": "integer"}, "rows": {"items": {"$ref": "#/components/schemas/MediaContributorRow"}, "title": "Rows", "type": "array"}}, "required": ["total_photos", "total_videos", "total_documents", "rows"], "title": "ProjectMediaContributors", "type": "object"}, "ContactIn": {"description": "Payload for POST /projects/{project_id}/contacts.\n\nOffline-first: the client supplies `id`. Duplicate submissions of\nthe same id (same project) are idempotent and return the existing\nrow. All content fields are optional here at the schema level, but\nthe endpoint enforces that at least one of `phone`/`email` is\npresent \u2014 a contact you can't reach isn't useful.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"default": "", "title": "Name", "type": "string"}, "phone": {"default": "", "title": "Phone", "type": "string"}, "email": {"default": "", "title": "Email", "type": "string"}, "note": {"default": "", "title": "Note", "type": "string"}, "is_policyholder": {"default": false, "title": "Is Policyholder", "type": "boolean"}}, "required": ["id"], "title": "ContactIn", "type": "object"}, "ContactPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Phone"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Email"}, "note": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Note"}, "is_policyholder": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Is Policyholder"}}, "title": "ContactPatch", "type": "object"}, "TagOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "slug": {"title": "Slug", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "name", "slug", "created_at"], "title": "TagOut", "type": "object"}, "TagIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}}, "required": ["id", "name"], "title": "TagIn", "type": "object"}, "TagManageRow": {"description": "Row shape for the Manage Tags screen.\n\nExtends TagOut with per-row metadata the management surface needs\n(attachment counts, creator). Kept separate so the light `/tags/`\nused by the photo picker doesn't have to carry the count subqueries\nor the created_by join.\n\nCounts leave out items on archived projects; deleting a tag removes\nit from those too.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "slug": {"title": "Slug", "type": "string"}, "photo_count": {"title": "Photo Count", "type": "integer"}, "video_count": {"default": 0, "title": "Video Count", "type": "integer"}, "document_count": {"default": 0, "title": "Document Count", "type": "integer"}, "created_by": {"anyOf": [{"$ref": "#/components/schemas/UserSummary"}, {"type": "null"}]}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "name", "slug", "photo_count", "created_at"], "title": "TagManageRow", "type": "object"}, "TagPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}}, "title": "TagPatch", "type": "object"}, "UploadPrepareOut": {"properties": {"key": {"title": "Key", "type": "string"}, "upload_url": {"title": "Upload Url", "type": "string"}, "expires_at": {"format": "date-time", "title": "Expires At", "type": "string"}, "content_type": {"title": "Content Type", "type": "string"}}, "required": ["key", "upload_url", "expires_at", "content_type"], "title": "UploadPrepareOut", "type": "object"}, "ErrorOut": {"description": "Body of a coded error response (documents the shape in OpenAPI).", "properties": {"detail": {"title": "Detail", "type": "string"}, "code": {"title": "Code", "type": "string"}, "field": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Field"}}, "required": ["detail", "code"], "title": "ErrorOut", "type": "object"}, "UploadPrepareIn": {"properties": {"project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "content_type": {"default": "", "title": "Content Type", "type": "string"}, "size_bytes": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Size Bytes"}}, "required": ["project_id", "filename"], "title": "UploadPrepareIn", "type": "object"}, "PhotoOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "project_name": {"default": "", "title": "Project Name", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "description": {"title": "Description", "type": "string"}, "taken_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Taken At"}, "taken_at_utc_offset_min": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Taken At Utc Offset Min"}, "latitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Longitude"}, "size_bytes": {"title": "Size Bytes", "type": "integer"}, "width": {"title": "Width", "type": "integer"}, "height": {"title": "Height", "type": "integer"}, "mime_type": {"title": "Mime Type", "type": "string"}, "uploaded_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Uploaded By Id"}, "uploaded_by_name": {"default": "", "title": "Uploaded By Name", "type": "string"}, "tag_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "comments_count": {"default": 0, "title": "Comments Count", "type": "integer"}, "has_rendition": {"title": "Has Rendition", "type": "boolean"}, "signed_display_url": {"title": "Signed Display Url", "type": "string"}, "signed_original_url": {"title": "Signed Original Url", "type": "string"}, "signed_preview_url": {"title": "Signed Preview Url", "type": "string"}, "signed_thumb_url": {"title": "Signed Thumb Url", "type": "string"}, "signed_large_url": {"title": "Signed Large Url", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "added_at": {"title": "Added At", "type": "string"}, "edit_revision": {"default": 0, "title": "Edit Revision", "type": "integer"}, "has_edits": {"default": false, "title": "Has Edits", "type": "boolean"}, "edited_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Edited At"}, "edited_by_name": {"default": "", "title": "Edited By Name", "type": "string"}, "display_width": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Display Width"}, "display_height": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Display Height"}, "timestamp_burned": {"default": false, "title": "Timestamp Burned", "type": "boolean"}, "inspections_count": {"default": 0, "title": "Inspections Count", "type": "integer"}, "is_before_after": {"default": false, "title": "Is Before After", "type": "boolean"}, "before_photo_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Before Photo Id"}, "after_photo_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "After Photo Id"}}, "required": ["id", "project_id", "filename", "description", "size_bytes", "width", "height", "mime_type", "tag_ids", "has_rendition", "signed_display_url", "signed_original_url", "signed_preview_url", "signed_thumb_url", "signed_large_url", "created_at", "updated_at", "added_at"], "title": "PhotoOut", "type": "object"}, "PhotoIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "original_key": {"title": "Original Key", "type": "string"}, "rendition_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Rendition Key"}, "description": {"default": "", "title": "Description", "type": "string"}, "taken_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Taken At"}, "taken_at_utc_offset_min": {"anyOf": [{"maximum": 840, "minimum": -840, "type": "integer"}, {"type": "null"}], "title": "Taken At Utc Offset Min"}, "added_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Added At"}, "latitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Longitude"}, "exif": {"additionalProperties": true, "default": {}, "title": "Exif", "type": "object"}, "size_bytes": {"default": 0, "title": "Size Bytes", "type": "integer"}, "width": {"default": 0, "title": "Width", "type": "integer"}, "height": {"default": 0, "title": "Height", "type": "integer"}, "mime_type": {"default": "", "title": "Mime Type", "type": "string"}, "tag_ids": {"default": [], "items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "line_item_ids": {"default": [], "items": {"format": "uuid", "type": "string"}, "title": "Line Item Ids", "type": "array"}, "quality": {"anyOf": [{"enum": ["standard", "high", "original"], "type": "string"}, {"type": "null"}], "title": "Quality"}, "timestamp_burned": {"default": false, "title": "Timestamp Burned", "type": "boolean"}}, "required": ["id", "project_id", "filename", "original_key"], "title": "PhotoIn", "type": "object"}, "PhotoFacetTag": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["id", "count"], "title": "PhotoFacetTag", "type": "object"}, "PhotoFacetUploader": {"properties": {"id": {"title": "Id", "type": "integer"}, "name": {"title": "Name", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["id", "name", "count"], "title": "PhotoFacetUploader", "type": "object"}, "PhotoSummaryOut": {"description": "`GET /photos/summary`: counts for a project's paged photo grid.", "properties": {"total": {"title": "Total", "type": "integer"}, "matching": {"title": "Matching", "type": "integer"}, "tags": {"items": {"$ref": "#/components/schemas/PhotoFacetTag"}, "title": "Tags", "type": "array"}, "uploaders": {"items": {"$ref": "#/components/schemas/PhotoFacetUploader"}, "title": "Uploaders", "type": "array"}}, "required": ["total", "matching", "tags", "uploaders"], "title": "PhotoSummaryOut", "type": "object"}, "PhotoPage": {"description": "One page of a project's photos (see `GET /photos/page`).", "properties": {"items": {"items": {"$ref": "#/components/schemas/PhotoOut"}, "title": "Items", "type": "array"}, "next_cursor": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Next Cursor"}}, "required": ["items", "next_cursor"], "title": "PhotoPage", "type": "object"}, "PhotoPatch": {"properties": {"description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "type": "array"}, {"type": "null"}], "title": "Tag Ids"}, "add_tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "type": "array"}, {"type": "null"}], "title": "Add Tag Ids"}, "remove_tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "type": "array"}, {"type": "null"}], "title": "Remove Tag Ids"}}, "title": "PhotoPatch", "type": "object"}, "PhotoBulkResultOut": {"description": "Which photos were changed (`done`) and which weren't, and why.", "properties": {"done": {"items": {"format": "uuid", "type": "string"}, "title": "Done", "type": "array"}, "skipped": {"items": {"$ref": "#/components/schemas/PhotoBulkSkippedOut"}, "title": "Skipped", "type": "array"}}, "required": ["done", "skipped"], "title": "PhotoBulkResultOut", "type": "object"}, "PhotoBulkSkippedOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "reason": {"enum": ["not_found", "forbidden"], "title": "Reason", "type": "string"}}, "required": ["id", "reason"], "title": "PhotoBulkSkippedOut", "type": "object"}, "PhotoBulkTagsIn": {"description": "Replace the tags of every listed photo with exactly `tag_ids`\n(an empty list clears them).", "properties": {"photo_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 500, "minItems": 1, "title": "Photo Ids", "type": "array"}, "tag_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "title": "Tag Ids", "type": "array"}}, "required": ["photo_ids"], "title": "PhotoBulkTagsIn", "type": "object"}, "PhotoBulkTagChangesIn": {"description": "Add and remove tags on every listed photo, keeping their other tags\n(`POST /photos/bulk/tag-changes`).", "properties": {"photo_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 500, "minItems": 1, "title": "Photo Ids", "type": "array"}, "add_tag_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "title": "Add Tag Ids", "type": "array"}, "remove_tag_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "title": "Remove Tag Ids", "type": "array"}}, "required": ["photo_ids"], "title": "PhotoBulkTagChangesIn", "type": "object"}, "PhotoBulkDeleteIn": {"properties": {"photo_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 500, "minItems": 1, "title": "Photo Ids", "type": "array"}}, "required": ["photo_ids"], "title": "PhotoBulkDeleteIn", "type": "object"}, "VideoOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "description": {"title": "Description", "type": "string"}, "taken_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Taken At"}, "latitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Longitude"}, "size_bytes": {"title": "Size Bytes", "type": "integer"}, "width": {"title": "Width", "type": "integer"}, "height": {"title": "Height", "type": "integer"}, "mime_type": {"title": "Mime Type", "type": "string"}, "duration_ms": {"title": "Duration Ms", "type": "integer"}, "duration_seconds": {"title": "Duration Seconds", "type": "integer"}, "taken_at_utc_offset_min": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Taken At Utc Offset Min"}, "source": {"title": "Source", "type": "string"}, "uploaded_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Uploaded By Id"}, "uploaded_by_name": {"default": "", "title": "Uploaded By Name", "type": "string"}, "tag_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "signed_original_url": {"title": "Signed Original Url", "type": "string"}, "signed_poster_url": {"title": "Signed Poster Url", "type": "string"}, "signed_preview_url": {"title": "Signed Preview Url", "type": "string"}, "signed_thumb_url": {"title": "Signed Thumb Url", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "added_at": {"title": "Added At", "type": "string"}, "comments_count": {"default": 0, "title": "Comments Count", "type": "integer"}}, "required": ["id", "project_id", "filename", "description", "size_bytes", "width", "height", "mime_type", "duration_ms", "duration_seconds", "source", "tag_ids", "signed_original_url", "signed_poster_url", "signed_preview_url", "signed_thumb_url", "created_at", "updated_at", "added_at"], "title": "VideoOut", "type": "object"}, "VideoIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "original_key": {"title": "Original Key", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "taken_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Taken At"}, "taken_at_utc_offset_min": {"anyOf": [{"maximum": 840, "minimum": -840, "type": "integer"}, {"type": "null"}], "title": "Taken At Utc Offset Min"}, "added_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Added At"}, "latitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Longitude"}, "exif": {"additionalProperties": true, "default": {}, "title": "Exif", "type": "object"}, "size_bytes": {"default": 0, "title": "Size Bytes", "type": "integer"}, "width": {"default": 0, "title": "Width", "type": "integer"}, "height": {"default": 0, "title": "Height", "type": "integer"}, "mime_type": {"default": "", "title": "Mime Type", "type": "string"}, "duration_ms": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "title": "Duration Ms"}, "duration_seconds": {"default": 0, "title": "Duration Seconds", "type": "integer"}, "source": {"anyOf": [{"enum": ["camera", "import"], "type": "string"}, {"type": "null"}], "title": "Source"}, "poster_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Poster Key"}, "tag_ids": {"default": [], "items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}}, "required": ["id", "project_id", "filename", "original_key"], "title": "VideoIn", "type": "object"}, "VideoPatch": {"properties": {"description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "type": "array"}, {"type": "null"}], "title": "Tag Ids"}, "add_tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "type": "array"}, {"type": "null"}], "title": "Add Tag Ids"}, "remove_tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "type": "array"}, {"type": "null"}], "title": "Remove Tag Ids"}}, "title": "VideoPatch", "type": "object"}, "VideoBulkResultOut": {"description": "Which videos were changed (`done`) and which weren't, and why.", "properties": {"done": {"items": {"format": "uuid", "type": "string"}, "title": "Done", "type": "array"}, "skipped": {"items": {"$ref": "#/components/schemas/VideoBulkSkippedOut"}, "title": "Skipped", "type": "array"}}, "required": ["done", "skipped"], "title": "VideoBulkResultOut", "type": "object"}, "VideoBulkSkippedOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "reason": {"enum": ["not_found", "forbidden"], "title": "Reason", "type": "string"}}, "required": ["id", "reason"], "title": "VideoBulkSkippedOut", "type": "object"}, "VideoBulkTagChangesIn": {"description": "Add and remove tags on every listed video, keeping their other tags\n(`POST /videos/bulk/tag-changes`).", "properties": {"video_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 500, "minItems": 1, "title": "Video Ids", "type": "array"}, "add_tag_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "title": "Add Tag Ids", "type": "array"}, "remove_tag_ids": {"items": {"format": "uuid", "type": "string"}, "maxItems": 100, "title": "Remove Tag Ids", "type": "array"}}, "required": ["video_ids"], "title": "VideoBulkTagChangesIn", "type": "object"}, "DocumentOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "description": {"title": "Description", "type": "string"}, "latitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Longitude"}, "size_bytes": {"title": "Size Bytes", "type": "integer"}, "mime_type": {"title": "Mime Type", "type": "string"}, "page_count": {"title": "Page Count", "type": "integer"}, "uploaded_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Uploaded By Id"}, "uploaded_by_name": {"default": "", "title": "Uploaded By Name", "type": "string"}, "project_name": {"default": "", "title": "Project Name", "type": "string"}, "project_cover": {"anyOf": [{"$ref": "#/components/schemas/ProjectCoverOut"}, {"type": "null"}]}, "project_address": {"default": "", "title": "Project Address", "type": "string"}, "file_type": {"default": "other", "title": "File Type", "type": "string"}, "comments_count": {"default": 0, "title": "Comments Count", "type": "integer"}, "report_type": {"default": "", "title": "Report Type", "type": "string"}, "report_type_label": {"default": "", "title": "Report Type Label", "type": "string"}, "inspection_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Inspection Id"}, "tag_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "signed_original_url": {"title": "Signed Original Url", "type": "string"}, "signed_preview_url": {"title": "Signed Preview Url", "type": "string"}, "signed_thumb_url": {"title": "Signed Thumb Url", "type": "string"}, "signed_large_url": {"default": "", "title": "Signed Large Url", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "project_id", "filename", "description", "size_bytes", "mime_type", "page_count", "tag_ids", "signed_original_url", "signed_preview_url", "signed_thumb_url", "created_at", "updated_at"], "title": "DocumentOut", "type": "object"}, "DocumentIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "filename": {"title": "Filename", "type": "string"}, "original_key": {"title": "Original Key", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "latitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Latitude"}, "longitude": {"anyOf": [{"type": "number"}, {"type": "string"}, {"type": "null"}], "title": "Longitude"}, "size_bytes": {"default": 0, "title": "Size Bytes", "type": "integer"}, "mime_type": {"default": "", "title": "Mime Type", "type": "string"}, "page_count": {"default": 0, "title": "Page Count", "type": "integer"}, "tag_ids": {"default": [], "items": {"format": "uuid", "type": "string"}, "title": "Tag Ids", "type": "array"}, "added_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "description": "When the document was added on the client (an upload made offline or later). Used as its date unless more than 5 minutes in the future; else now.", "title": "Added At"}}, "required": ["id", "project_id", "filename", "original_key"], "title": "DocumentIn", "type": "object"}, "ReportType": {"enum": ["inspection"], "title": "ReportType", "type": "string"}, "DocumentPageOut": {"properties": {"items": {"items": {"$ref": "#/components/schemas/DocumentOut"}, "title": "Items", "type": "array"}, "total": {"title": "Total", "type": "integer"}, "page": {"title": "Page", "type": "integer"}, "page_size": {"title": "Page Size", "type": "integer"}}, "required": ["items", "total", "page", "page_size"], "title": "DocumentPageOut", "type": "object"}, "DocumentSummaryOut": {"description": "What the documents the caller can see contain, with counts, to offer\nas filter choices (so no choice leads to an empty list).", "properties": {"total": {"title": "Total", "type": "integer"}, "projects": {"items": {"$ref": "#/components/schemas/ProjectFacet"}, "title": "Projects", "type": "array"}, "uploaders": {"items": {"$ref": "#/components/schemas/UploaderFacet"}, "title": "Uploaders", "type": "array"}, "tags": {"items": {"$ref": "#/components/schemas/TagFacet"}, "title": "Tags", "type": "array"}, "file_types": {"items": {"$ref": "#/components/schemas/FileTypeFacet"}, "title": "File Types", "type": "array"}, "report_types": {"items": {"$ref": "#/components/schemas/ReportTypeFacet"}, "title": "Report Types", "type": "array"}, "reports": {"title": "Reports", "type": "integer"}, "uploads": {"title": "Uploads", "type": "integer"}}, "required": ["total", "projects", "uploaders", "tags", "file_types", "report_types", "reports", "uploads"], "title": "DocumentSummaryOut", "type": "object"}, "FileTypeFacet": {"properties": {"type": {"enum": ["pdf", "word", "excel", "image"], "title": "Type", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["type", "count"], "title": "FileTypeFacet", "type": "object"}, "ProjectFacet": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["id", "name", "count"], "title": "ProjectFacet", "type": "object"}, "ReportTypeFacet": {"properties": {"type": {"$ref": "#/components/schemas/ReportType"}, "label": {"title": "Label", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["type", "label", "count"], "title": "ReportTypeFacet", "type": "object"}, "TagFacet": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["id", "count"], "title": "TagFacet", "type": "object"}, "UploaderFacet": {"properties": {"id": {"title": "Id", "type": "integer"}, "name": {"title": "Name", "type": "string"}, "count": {"title": "Count", "type": "integer"}}, "required": ["id", "name", "count"], "title": "UploaderFacet", "type": "object"}, "DocumentPatch": {"properties": {"description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "tag_ids": {"anyOf": [{"items": {"format": "uuid", "type": "string"}, "type": "array"}, {"type": "null"}], "title": "Tag Ids"}, "filename": {"anyOf": [{"maxLength": 1000, "type": "string"}, {"type": "null"}], "title": "Filename"}}, "title": "DocumentPatch", "type": "object"}, "TodoOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "project_name": {"title": "Project Name", "type": "string"}, "title": {"title": "Title", "type": "string"}, "description": {"title": "Description", "type": "string"}, "status": {"enum": ["open", "done", "canceled"], "title": "Status", "type": "string"}, "priority": {"enum": ["low", "normal", "high"], "title": "Priority", "type": "string"}, "assignee_ids": {"default": [], "items": {"type": "integer"}, "title": "Assignee Ids", "type": "array"}, "due_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Due Date"}, "completed_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Completed At"}, "completed_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Completed By Id"}, "completion_note": {"default": "", "title": "Completion Note", "type": "string"}, "created_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Created By Id"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "comments_count": {"default": 0, "title": "Comments Count", "type": "integer"}}, "required": ["id", "project_id", "project_name", "title", "description", "status", "priority", "created_at", "updated_at"], "title": "TodoOut", "type": "object"}, "TodoIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "title": {"title": "Title", "type": "string"}, "description": {"default": "", "title": "Description", "type": "string"}, "priority": {"default": "normal", "enum": ["low", "normal", "high"], "title": "Priority", "type": "string"}, "assignee_ids": {"default": [], "items": {"type": "integer"}, "title": "Assignee Ids", "type": "array"}, "due_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Due Date"}}, "required": ["id", "project_id", "title"], "title": "TodoIn", "type": "object"}, "AssignableUserOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "full_name": {"title": "Full Name", "type": "string"}, "email": {"title": "Email", "type": "string"}, "role": {"title": "Role", "type": "string"}}, "required": ["id", "full_name", "email", "role"], "title": "AssignableUserOut", "type": "object"}, "TodoPatch": {"properties": {"title": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Title"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Description"}, "priority": {"anyOf": [{"enum": ["low", "normal", "high"], "type": "string"}, {"type": "null"}], "title": "Priority"}, "assignee_ids": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "title": "Assignee Ids"}, "due_date": {"anyOf": [{"format": "date", "type": "string"}, {"type": "null"}], "title": "Due Date"}}, "title": "TodoPatch", "type": "object"}, "TodoDoneIn": {"description": "Body for `POST /todos/{id}/done`.\n\n`note` is optional; when present it is stored on `completion_note`\nand surfaced next to the completion metadata on the frontend.", "properties": {"note": {"default": "", "title": "Note", "type": "string"}}, "title": "TodoDoneIn", "type": "object"}, "TodoEventOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "kind": {"title": "Kind", "type": "string"}, "actor_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Actor Id"}, "from_value": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "From Value"}, "to_value": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "title": "To Value"}, "note": {"default": "", "title": "Note", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "kind", "created_at"], "title": "TodoEventOut", "type": "object"}, "MentionRef": {"description": "Server-filled display metadata for a single @mention.\n\nUsed on responses so the client can render mention pills without a\nround-trip. `name` is the user's current full_name (falls back to\nemail if the name is blank).", "properties": {"user_id": {"title": "User Id", "type": "integer"}, "name": {"title": "Name", "type": "string"}}, "required": ["user_id", "name"], "title": "MentionRef", "type": "object"}, "CommentAttachmentOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "kind": {"const": "audio", "title": "Kind", "type": "string"}, "url": {"title": "Url", "type": "string"}, "content_type": {"title": "Content Type", "type": "string"}, "size_bytes": {"title": "Size Bytes", "type": "integer"}, "playback_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Playback Url"}, "duration_ms": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Duration Ms"}, "waveform": {"default": [], "items": {"type": "integer"}, "title": "Waveform", "type": "array"}}, "required": ["id", "kind", "url", "content_type", "size_bytes"], "title": "CommentAttachmentOut", "type": "object"}, "CommentOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "target_kind": {"enum": ["project", "photo", "video", "document", "todo", "line_item", "note"], "title": "Target Kind", "type": "string"}, "target_id": {"format": "uuid", "title": "Target Id", "type": "string"}, "body": {"title": "Body", "type": "string"}, "mentions": {"default": [], "items": {"$ref": "#/components/schemas/MentionRef"}, "title": "Mentions", "type": "array"}, "attachments": {"default": [], "items": {"$ref": "#/components/schemas/CommentAttachmentOut"}, "title": "Attachments", "type": "array"}, "author_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Author Id"}, "author_name": {"default": "", "title": "Author Name", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "edited_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Edited At"}}, "required": ["id", "target_kind", "target_id", "body", "created_at", "updated_at"], "title": "CommentOut", "type": "object"}, "CommentAttachmentIn": {"properties": {"kind": {"const": "audio", "title": "Kind", "type": "string"}, "key": {"title": "Key", "type": "string"}, "duration_ms": {"exclusiveMinimum": 0, "maximum": 301000, "title": "Duration Ms", "type": "integer"}, "waveform": {"items": {"maximum": 100, "minimum": 0, "type": "integer"}, "maxItems": 128, "title": "Waveform", "type": "array"}}, "required": ["kind", "key", "duration_ms"], "title": "CommentAttachmentIn", "type": "object"}, "CommentIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "target_kind": {"enum": ["project", "photo", "video", "document", "todo", "line_item", "note"], "title": "Target Kind", "type": "string"}, "target_id": {"format": "uuid", "title": "Target Id", "type": "string"}, "body": {"default": "", "title": "Body", "type": "string"}, "mentioned_user_ids": {"default": [], "items": {"type": "integer"}, "title": "Mentioned User Ids", "type": "array"}, "attachments": {"items": {"$ref": "#/components/schemas/CommentAttachmentIn"}, "maxItems": 1, "title": "Attachments", "type": "array"}}, "required": ["id", "target_kind", "target_id"], "title": "CommentIn", "type": "object"}, "CommentSummaryOut": {"description": "Counts for a target's comment feed (project conversation launcher).", "properties": {"count": {"title": "Count", "type": "integer"}, "participant_count": {"title": "Participant Count", "type": "integer"}}, "required": ["count", "participant_count"], "title": "CommentSummaryOut", "type": "object"}, "InboxItemOut": {"description": "One @mention of the caller, flattened for the Home Inbox widget.\n\nCarries just enough for the row: an already-truncated preview\n(mention tokens rewritten to display names), the target's kind + id\nfor a deep-link, the containing project's id and name, an optional\nlabel for photo/document/todo targets, and the author's display name.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "source": {"default": "comment", "enum": ["comment", "note"], "title": "Source", "type": "string"}, "body_preview": {"title": "Body Preview", "type": "string"}, "target_kind": {"enum": ["project", "photo", "video", "document", "todo", "line_item", "note"], "title": "Target Kind", "type": "string"}, "target_id": {"format": "uuid", "title": "Target Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "project_name": {"title": "Project Name", "type": "string"}, "target_label": {"title": "Target Label", "type": "string"}, "target_thumb_url": {"default": "", "title": "Target Thumb Url", "type": "string"}, "target_parent_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Target Parent Id"}, "author_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Author Id"}, "author_name": {"title": "Author Name", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}}, "required": ["id", "body_preview", "target_kind", "target_id", "project_id", "project_name", "target_label", "author_name", "created_at"], "title": "InboxItemOut", "type": "object"}, "CommentPatch": {"properties": {"body": {"default": "", "title": "Body", "type": "string"}, "mentioned_user_ids": {"default": [], "items": {"type": "integer"}, "title": "Mentioned User Ids", "type": "array"}}, "title": "CommentPatch", "type": "object"}, "InsuranceCarrierOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "phone": {"title": "Phone", "type": "string"}, "fax": {"title": "Fax", "type": "string"}, "email": {"title": "Email", "type": "string"}, "website": {"title": "Website", "type": "string"}, "address_line_1": {"title": "Address Line 1", "type": "string"}, "address_line_2": {"title": "Address Line 2", "type": "string"}, "city": {"title": "City", "type": "string"}, "state": {"title": "State", "type": "string"}, "country": {"title": "Country", "type": "string"}, "zipcode": {"title": "Zipcode", "type": "string"}, "address": {"title": "Address", "type": "string"}, "notes": {"title": "Notes", "type": "string"}, "projects_count": {"title": "Projects Count", "type": "integer"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "name", "phone", "fax", "email", "website", "address_line_1", "address_line_2", "city", "state", "country", "zipcode", "address", "notes", "projects_count", "created_at", "updated_at"], "title": "InsuranceCarrierOut", "type": "object"}, "InsuranceCarrierIn": {"properties": {"phone": {"anyOf": [{"maxLength": 32, "pattern": "^(\\+\\d{4,20})?$", "type": "string"}, {"type": "null"}], "title": "Phone"}, "fax": {"anyOf": [{"maxLength": 32, "pattern": "^(\\+\\d{4,20})?$", "type": "string"}, {"type": "null"}], "title": "Fax"}, "website": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Website"}, "address_line_1": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Address Line 1"}, "address_line_2": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Address Line 2"}, "city": {"anyOf": [{"maxLength": 128, "type": "string"}, {"type": "null"}], "title": "City"}, "state": {"anyOf": [{"maxLength": 128, "type": "string"}, {"type": "null"}], "title": "State"}, "country": {"anyOf": [{"maxLength": 64, "type": "string"}, {"type": "null"}], "title": "Country"}, "zipcode": {"anyOf": [{"maxLength": 32, "type": "string"}, {"type": "null"}], "title": "Zipcode"}, "notes": {"anyOf": [{"maxLength": 5000, "type": "string"}, {"type": "null"}], "title": "Notes"}, "name": {"maxLength": 200, "minLength": 1, "title": "Name", "type": "string"}, "email": {"maxLength": 254, "title": "Email", "type": "string"}}, "required": ["name", "email"], "title": "InsuranceCarrierIn", "type": "object"}, "InsuranceCarrierPatch": {"description": "Only the fields sent change.", "properties": {"phone": {"anyOf": [{"maxLength": 32, "pattern": "^(\\+\\d{4,20})?$", "type": "string"}, {"type": "null"}], "title": "Phone"}, "fax": {"anyOf": [{"maxLength": 32, "pattern": "^(\\+\\d{4,20})?$", "type": "string"}, {"type": "null"}], "title": "Fax"}, "website": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Website"}, "address_line_1": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Address Line 1"}, "address_line_2": {"anyOf": [{"maxLength": 255, "type": "string"}, {"type": "null"}], "title": "Address Line 2"}, "city": {"anyOf": [{"maxLength": 128, "type": "string"}, {"type": "null"}], "title": "City"}, "state": {"anyOf": [{"maxLength": 128, "type": "string"}, {"type": "null"}], "title": "State"}, "country": {"anyOf": [{"maxLength": 64, "type": "string"}, {"type": "null"}], "title": "Country"}, "zipcode": {"anyOf": [{"maxLength": 32, "type": "string"}, {"type": "null"}], "title": "Zipcode"}, "notes": {"anyOf": [{"maxLength": 5000, "type": "string"}, {"type": "null"}], "title": "Notes"}, "name": {"anyOf": [{"maxLength": 200, "minLength": 1, "type": "string"}, {"type": "null"}], "title": "Name"}, "email": {"anyOf": [{"maxLength": 254, "type": "string"}, {"type": "null"}], "title": "Email"}}, "title": "InsuranceCarrierPatch", "type": "object"}, "InspectionReportIn": {"properties": {"timezone": {"default": "UTC", "maxLength": 64, "title": "Timezone", "type": "string"}}, "title": "InspectionReportIn", "type": "object"}, "InspectionAssigneeOut": {"properties": {"id": {"title": "Id", "type": "integer"}, "full_name": {"title": "Full Name", "type": "string"}, "email": {"title": "Email", "type": "string"}}, "required": ["id", "full_name", "email"], "title": "InspectionAssigneeOut", "type": "object"}, "InspectionOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "project_id": {"format": "uuid", "title": "Project Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "assignees": {"items": {"$ref": "#/components/schemas/InspectionAssigneeOut"}, "title": "Assignees", "type": "array"}, "sort_order": {"title": "Sort Order", "type": "integer"}, "status_code": {"title": "Status Code", "type": "string"}, "total_line_items": {"title": "Total Line Items", "type": "integer"}, "closed_line_items": {"title": "Closed Line Items", "type": "integer"}, "line_items": {"items": {"$ref": "#/components/schemas/LineItemOut"}, "title": "Line Items", "type": "array"}, "recent_photos": {"items": {"$ref": "#/components/schemas/InspectionRecentPhotoOut"}, "title": "Recent Photos", "type": "array"}, "photos_total": {"title": "Photos Total", "type": "integer"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}}, "required": ["id", "project_id", "name", "assignees", "sort_order", "status_code", "total_line_items", "closed_line_items", "line_items", "recent_photos", "photos_total", "created_at", "updated_at"], "title": "InspectionOut", "type": "object"}, "InspectionRecentPhotoOut": {"description": "Small photo thumb for the inspection row 'Recent photos' column.\n\nPhotos here are the union of every photo linked to any line item of\nthe inspection, ordered by most-recent first.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "signed_thumb_url": {"title": "Signed Thumb Url", "type": "string"}, "signed_display_url": {"default": "", "title": "Signed Display Url", "type": "string"}}, "required": ["id", "signed_thumb_url"], "title": "InspectionRecentPhotoOut", "type": "object"}, "LineItemOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "inspection_id": {"format": "uuid", "title": "Inspection Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "instructions": {"title": "Instructions", "type": "string"}, "photos_required": {"title": "Photos Required", "type": "boolean"}, "sort_order": {"title": "Sort Order", "type": "integer"}, "is_closed": {"title": "Is Closed", "type": "boolean"}, "closed_at": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "null"}], "title": "Closed At"}, "closed_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Closed By Id"}, "closed_by_name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Closed By Name"}, "assignees": {"items": {"$ref": "#/components/schemas/InspectionAssigneeOut"}, "title": "Assignees", "type": "array"}, "comments_count": {"default": 0, "title": "Comments Count", "type": "integer"}, "created_by_id": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Created By Id"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "updated_at": {"format": "date-time", "title": "Updated At", "type": "string"}, "photos": {"items": {"$ref": "#/components/schemas/LineItemPhotoOut"}, "title": "Photos", "type": "array"}}, "required": ["id", "inspection_id", "name", "instructions", "photos_required", "sort_order", "is_closed", "closed_at", "closed_by_id", "closed_by_name", "assignees", "created_at", "updated_at", "photos"], "title": "LineItemOut", "type": "object"}, "LineItemPhotoOut": {"description": "Compact photo shape embedded inside LineItemOut.", "properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "filename": {"default": "", "title": "Filename", "type": "string"}, "signed_thumb_url": {"title": "Signed Thumb Url", "type": "string"}, "signed_display_url": {"default": "", "title": "Signed Display Url", "type": "string"}, "number": {"anyOf": [{"type": "integer"}, {"type": "null"}], "title": "Number"}}, "required": ["id", "signed_thumb_url"], "title": "LineItemPhotoOut", "type": "object"}, "InspectionIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"default": "", "title": "Name", "type": "string"}, "template_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Template Id"}, "items": {"items": {"$ref": "#/components/schemas/InspectionItemIn"}, "maxItems": 500, "title": "Items", "type": "array"}, "source": {"anyOf": [{"const": "ai", "type": "string"}, {"type": "null"}], "title": "Source"}}, "required": ["id"], "title": "InspectionIn", "type": "object"}, "InspectionItemIn": {"description": "A line item created with its inspection (`InspectionIn.items`).", "properties": {"id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Id"}, "name": {"maxLength": 200, "title": "Name", "type": "string"}, "instructions": {"default": "", "maxLength": 20000, "title": "Instructions", "type": "string"}, "photos_required": {"default": true, "title": "Photos Required", "type": "boolean"}}, "required": ["name"], "title": "InspectionItemIn", "type": "object"}, "InspectionPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}}, "title": "InspectionPatch", "type": "object"}, "InspectionReorderIn": {"description": "Ordered list of inspection UUIDs for a project.", "properties": {"inspection_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Inspection Ids", "type": "array"}}, "required": ["inspection_ids"], "title": "InspectionReorderIn", "type": "object"}, "LineItemIn": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "instructions": {"default": "", "title": "Instructions", "type": "string"}, "photos_required": {"default": true, "title": "Photos Required", "type": "boolean"}, "assignee_ids": {"default": [], "items": {"type": "integer"}, "title": "Assignee Ids", "type": "array"}}, "required": ["id", "name"], "title": "LineItemIn", "type": "object"}, "LineItemPatch": {"properties": {"name": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Name"}, "instructions": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Instructions"}, "photos_required": {"anyOf": [{"type": "boolean"}, {"type": "null"}], "title": "Photos Required"}, "assignee_ids": {"anyOf": [{"items": {"type": "integer"}, "type": "array"}, {"type": "null"}], "title": "Assignee Ids"}}, "title": "LineItemPatch", "type": "object"}, "LineItemReorderIn": {"properties": {"line_item_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Line Item Ids", "type": "array"}}, "required": ["line_item_ids"], "title": "LineItemReorderIn", "type": "object"}, "LineItemPhotosLinkIn": {"properties": {"photo_ids": {"items": {"format": "uuid", "type": "string"}, "title": "Photo Ids", "type": "array"}}, "required": ["photo_ids"], "title": "LineItemPhotosLinkIn", "type": "object"}, "InspectionReportOut": {"description": "The report saved to the project's Documents, and a link that\ndownloads it (signed, 1 h, with its filename).", "properties": {"document": {"$ref": "#/components/schemas/DocumentOut"}, "download_url": {"title": "Download Url", "type": "string"}}, "required": ["document", "download_url"], "title": "InspectionReportOut", "type": "object"}, "PdfReportOptionsOut": {"description": "What the export dialog needs to offer its choices.", "properties": {"open_count": {"title": "Open Count", "type": "integer"}, "closed_count": {"title": "Closed Count", "type": "integer"}, "has_image": {"title": "Has Image", "type": "boolean"}, "has_image_closed": {"title": "Has Image Closed", "type": "boolean"}}, "required": ["open_count", "closed_count", "has_image", "has_image_closed"], "title": "PdfReportOptionsOut", "type": "object"}, "PdfReportJobOut": {"properties": {"id": {"format": "uuid", "title": "Id", "type": "string"}, "status": {"title": "Status", "type": "string"}, "error": {"default": "", "title": "Error", "type": "string"}, "created_at": {"format": "date-time", "title": "Created At", "type": "string"}, "document": {"anyOf": [{"$ref": "#/components/schemas/DocumentOut"}, {"type": "null"}]}, "download_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Download Url"}, "reused": {"default": false, "title": "Reused", "type": "boolean"}, "emailed_to": {"default": [], "items": {"type": "string"}, "title": "Emailed To", "type": "array"}}, "required": ["id", "status", "created_at"], "title": "PdfReportJobOut", "type": "object"}, "PdfReportConflictOut": {"properties": {"detail": {"title": "Detail", "type": "string"}, "job": {"$ref": "#/components/schemas/PdfReportJobOut"}}, "required": ["detail", "job"], "title": "PdfReportConflictOut", "type": "object"}, "PdfReportEmailIn": {"description": "Email the report once it's made (one email to everyone), instead of\ndownloading it. The body's `{{link}}` block becomes a \"Download report\"\nbutton with the PDF's permanent link (added at the end if missing).", "properties": {"recipients": {"items": {"format": "email", "type": "string"}, "maxItems": 10, "minItems": 1, "title": "Recipients", "type": "array"}, "subject": {"maxLength": 200, "minLength": 1, "title": "Subject", "type": "string"}, "body": {"maxLength": 20000, "minLength": 1, "title": "Body", "type": "string"}}, "required": ["recipients", "subject", "body"], "title": "PdfReportEmailIn", "type": "object"}, "PdfReportIn": {"description": "The report follows the company's PDF design (`reports.design`).\nAdmins and managers may override it for one export with `items`,\n`photos_per_row` and `cover_template_id`; for anyone else they're\nignored.", "properties": {"items": {"anyOf": [{"enum": ["all", "closed"], "type": "string"}, {"type": "null"}], "title": "Items"}, "photos_per_row": {"anyOf": [{"enum": [2, 3, 4], "type": "integer"}, {"type": "null"}], "title": "Photos Per Row"}, "cover_template_id": {"anyOf": [{"format": "uuid", "type": "string"}, {"type": "null"}], "title": "Cover Template Id"}, "timezone": {"default": "UTC", "maxLength": 64, "title": "Timezone", "type": "string"}, "email": {"anyOf": [{"$ref": "#/components/schemas/PdfReportEmailIn"}, {"type": "null"}]}}, "title": "PdfReportIn", "type": "object"}}, "securitySchemes": {"AccessTokenAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key"}}}, "servers": []}