{"openapi": "3.1.0", "info": {"title": "FixGrid API", "version": "1", "description": "Bearer-key-authenticated access to a company's FixGrid records, including managing webhook subscriptions. Mint a key on the Integrations page. Keys are company credentials for server-side use only -- never ship one in a browser, a mobile app, or a Claude/MCP connector; user-level OAuth is a later, separate door and is not this key."}, "servers": [{"url": "https://app.fixgrid.app/api/v1"}], "security": [{"bearerKey": []}], "paths": {"/whoami": {"get": {"summary": "Identify the presented key.", "description": "Returns the company and key metadata for the presented bearer key. Accepts any active key regardless of scope.", "tags": ["Meta"], "responses": {"200": {"description": "The key and company.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Whoami"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/tickets": {"get": {"summary": "List tickets.", "description": "A keyset-paginated page of the company's tickets, ordered by id ascending.", "tags": ["Tickets"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "property_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows for this property id."}, {"name": "unit_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows tied to this unit (location) id."}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["Open", "In Progress", "On Hold", "Completed", "Cancelled"]}, "description": "Filter to rows with this status -- see this parameter's enum for the live vocabulary."}, {"name": "updated_since", "in": "query", "required": false, "schema": {"type": "string", "format": "date-time"}, "description": "Only rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z."}, {"name": "ticket_number", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only the ticket with this ticket_number (requires property_id)."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Ticket"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}, "post": {"summary": "Create a ticket.", "description": "Creates a ticket on the given unit, with status always 'Open'. Requires the Idempotency-Key header; a write-scoped key is required.", "tags": ["Tickets"], "x-scope": "write", "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "maxLength": 64}, "description": "A client-chosen key, 64 characters or fewer. A retried request with the same key and the same body replays the original response."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["unit_id", "title", "category"], "properties": {"unit_id": {"type": "integer", "description": "The unit's id, from GET /units."}, "asset_id": {"type": "integer", "description": "Optional. Must belong to the same property as unit_id."}, "title": {"type": "string", "maxLength": 150}, "description": {"type": "string", "description": "Optional. Defaults to an empty string."}, "priority": {"type": "string", "enum": ["Low", "Medium", "High", "Emergency"]}, "category": {"type": "string", "enum": ["HVAC", "Plumbing", "Electrical", "Mechanical", "Life Safety", "Elevator", "Appliance", "Doors/Windows", "Flooring", "Painting", "Roofing", "Leak", "Pest Control", "Structural", "Grounds", "Parking Garage", "Pool", "Security", "Common Area", "General", "Other"]}}}}}}, "responses": {"201": {"description": "The ticket was created, or this response replays an earlier creation for a duplicate Idempotency-Key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ticket"}}}, "headers": {"X-Idempotent-Replay": {"schema": {"type": "string"}, "description": "Present with value '1' when this response is a replay of the original creation for a duplicate Idempotency-Key."}}}, "400": {"description": "`idempotency_key_required` -- An Idempotency-Key header is required to create a ticket. `invalid_request` -- The Idempotency-Key header must be 64 characters or fewer. `invalid_request` -- The request body is not a valid ticket payload.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "`not_found` -- No unit with that id is visible to this key. `not_found` -- No asset with that id is visible to this key. `not_found` -- No asset with that id is visible to this key on that property.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "`idempotency_in_progress` -- A request with this Idempotency-Key is already being processed. `idempotency_mismatch` -- This Idempotency-Key was already used with a different ticket payload. Use a new Idempotency-Key for a different request.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "`invalid_request` -- This endpoint always creates a ticket with status 'Open'. Remove `status` from the request body. `created_by_missing` -- This key has no minting administrator on file. Mint a new key on the Integrations page to create tickets. `invalid_request` -- Unknown ticket priority -- see this field's enum for the live vocabulary. `invalid_request` -- The `category` field must be one of the values in this field's enum. `invalid_request` -- The `title` field must be 150 characters or fewer.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/tickets/{ticket_id}": {"get": {"summary": "Fetch a ticket.", "description": "A single ticket by id.", "tags": ["Tickets"], "parameters": [{"name": "ticket_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ticket"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}, "patch": {"summary": "Change a ticket's status.", "description": "Changes a ticket's status. There is no transition graph -- any status is reachable from any other, including reopening a Completed or Cancelled ticket. `note` is required (5+ words) only when status is 'Completed'; `cancellation_reason_code` is required only when status is 'Cancelled', and `cancellation_notes` is accepted only when `cancellation_reason_code` is 'see_notes'. Requires the Idempotency-Key header; a write-scoped key is required.", "tags": ["Tickets"], "x-scope": "write", "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "maxLength": 64}, "description": "A client-chosen key, 64 characters or fewer. A retried request with the same key and the same body replays the original response."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["status"], "properties": {"status": {"type": "string", "enum": ["Open", "In Progress", "On Hold", "Completed", "Cancelled"]}, "note": {"type": "string", "description": "Required, 5+ words, only when status is 'Completed'. Not accepted for any other status."}, "cancellation_reason_code": {"type": "string", "description": "Required only when status is `Cancelled`. Not accepted for any other status.", "enum": ["duplicate_ticket", "no_access_repeated", "resident_cancelled", "resolved_independently", "scheduled_for_later", "see_notes", "wrong_property"]}, "cancellation_notes": {"type": "string", "description": "Accepted only when status is `Cancelled` and `cancellation_reason_code` is `see_notes`, 5+ words. Not accepted otherwise."}}}}}}, "responses": {"200": {"description": "The ticket status was changed, or this response replays an earlier change for a duplicate Idempotency-Key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Ticket"}}}, "headers": {"X-Idempotent-Replay": {"schema": {"type": "string"}, "description": "Present with value '1' when this response is a replay of the original change for a duplicate Idempotency-Key."}}}, "400": {"description": "`idempotency_key_required` -- An Idempotency-Key header is required to change a ticket's status. `invalid_request` -- The Idempotency-Key header must be 64 characters or fewer. `invalid_request` -- The request body is not a valid ticket status payload. `invalid_request` -- The `cancellation_notes` field is accepted only when `cancellation_reason_code` is `see_notes`.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "`not_found` -- No ticket with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "`idempotency_in_progress` -- A request with this Idempotency-Key is already being processed. `idempotency_mismatch` -- This Idempotency-Key was already used with a different ticket request. Use a new Idempotency-Key for a different request.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "`invalid_request` -- The `status` field must be one of the values in this field's enum. `invalid_request` -- a field in the request body is not accepted for the given `status` -- recognized fields are legal only for the statuses that need them. `created_by_missing` -- This key has no minting administrator on file. Mint a new key on the Integrations page to change a ticket status. `invalid_request` -- this company requires a photo on the ticket before it can be completed, and the ticket has none. `invalid_request` -- the status change was refused by FixGrid's own rules for the target status (a missing or too-short closing note on Completed, a missing cancellation reason on Cancelled, or too-short cancellation notes) -- see `message` for the exact sentence.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/properties": {"get": {"summary": "List properties.", "description": "A keyset-paginated page of the company's properties.", "tags": ["Properties"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Property"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/properties/{property_id}": {"get": {"summary": "Fetch a property.", "description": "A single property by id.", "tags": ["Properties"], "parameters": [{"name": "property_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Property"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/units": {"get": {"summary": "List units.", "description": "A keyset-paginated page of the company's units (locations with location_type='unit').", "tags": ["Units"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "property_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows for this property id."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Unit"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/units/{unit_id}": {"get": {"summary": "Fetch a unit.", "description": "A single unit by id.", "tags": ["Units"], "parameters": [{"name": "unit_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Unit"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/units/{unit_id}/notice": {"post": {"summary": "Record a notice to vacate.", "description": "Records a departure on the unit's current resident. Requires the Idempotency-Key header; a write-scoped key is required.", "tags": ["Occupancy"], "x-scope": "write", "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "maxLength": 64}, "description": "A client-chosen key, 64 characters or fewer. A retried request with the same key and the same body replays the original response."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["move_out_date", "provider"], "properties": {"move_out_date": {"type": "string", "format": "date"}, "ntv_reason_code": {"type": "string"}, "departure_destination_code": {"type": "string"}, "notes": {"type": "string"}, "provider": {"type": "string", "maxLength": 40, "description": "Required. A short slug identifying the feeding system, e.g. rentvine, zapier, api."}, "external_id": {"type": "string", "description": "Optional. Rule 230 companion.", "maxLength": 120}}}}}}, "responses": {"201": {"description": "The notice was recorded, or this response replays an earlier recording for a duplicate Idempotency-Key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OccupancyRecord"}}}, "headers": {"X-Idempotent-Replay": {"schema": {"type": "string"}, "description": "Present with value '1' when this response is a replay of the original recording for a duplicate Idempotency-Key."}}}, "400": {"description": "`idempotency_key_required` -- An Idempotency-Key header is required to record a notice. `invalid_request` -- The Idempotency-Key header must be 64 characters or fewer. `invalid_request` -- The request body is not a valid notice payload. `invalid_request` -- A provider slug is required. `invalid_request` -- The provider slug must be 40 characters or fewer.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "`not_found` -- No unit with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "`idempotency_in_progress` -- A request with this Idempotency-Key is already being processed. `idempotency_mismatch` -- This Idempotency-Key was already used with a different ticket request. Use a new Idempotency-Key for a different request.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "`created_by_missing` -- This key has no minting administrator on record. `invalid_request` -- No active resident found on this unit.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/units/{unit_id}/move-in": {"post": {"summary": "Record a move-in.", "description": "Creates a new resident tenancy on the unit. Parties are stored with named leaseholders first (a stable reorder); guarantors and any other entries follow in their original relative order. The response echoes the STORED order, which may differ from the request order. Requires the Idempotency-Key header; a write-scoped key is required.", "tags": ["Occupancy"], "x-scope": "write", "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "maxLength": 64}, "description": "A client-chosen key, 64 characters or fewer. A retried request with the same key and the same body replays the original response."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["move_in_date", "lease_start_date", "parties", "provider"], "properties": {"move_in_date": {"type": "string", "format": "date"}, "lease_start_date": {"type": "string", "format": "date"}, "lease_end_date": {"type": "string", "format": "date", "description": "Optional. Present implies a fixed-term lease; absent implies month-to-month."}, "predecessor_transition_id": {"type": "integer", "description": "Optional. Names the outgoing resident this move-in succeeds, when the unit is currently occupied."}, "provider": {"type": "string", "maxLength": 40, "description": "Required. A short slug identifying the feeding system, e.g. rentvine, zapier, api."}, "external_id": {"type": "string", "description": "Optional. Rule 230 companion.", "maxLength": 120}, "parties": {"type": "array", "maxItems": 20, "items": {"type": "object", "properties": {"first_name": {"type": "string", "maxLength": 80}, "last_name": {"type": "string", "maxLength": 80}, "email": {"type": "string", "format": "email", "maxLength": 150}, "phone": {"type": "string", "maxLength": 20}, "role": {"type": "string", "enum": ["leaseholder", "guarantor"]}, "external_id": {"type": "string", "maxLength": 120}}}}}}}}}, "responses": {"201": {"description": "The move-in was recorded, or this response replays an earlier recording for a duplicate Idempotency-Key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OccupancyRecord"}}}, "headers": {"X-Idempotent-Replay": {"schema": {"type": "string"}, "description": "Present with value '1' when this response is a replay of the original recording for a duplicate Idempotency-Key."}}}, "400": {"description": "`idempotency_key_required` -- An Idempotency-Key header is required to record a move-in. `invalid_request` -- The Idempotency-Key header must be 64 characters or fewer. `invalid_request` -- The request body is not a valid move-in payload. `invalid_request` -- A provider slug is required. `invalid_request` -- The provider slug must be 40 characters or fewer.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "`not_found` -- No unit with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "`idempotency_in_progress` -- A request with this Idempotency-Key is already being processed. `idempotency_mismatch` -- This Idempotency-Key was already used with a different ticket request. Use a new Idempotency-Key for a different request.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "`created_by_missing` -- This key has no minting administrator on record. `invalid_request` -- An active resident already occupies this unit. `invalid_request` -- No outgoing transition with that id is visible on this unit. `invalid_request` -- The `parties` field must be a non-empty list of at most 20 entries, with at least one leaseholder carrying a first and last name. `invalid_request` -- The `lease_end_date` field must be on or after `lease_start_date`.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/assets": {"get": {"summary": "List assets.", "description": "A keyset-paginated page of the company's assets, including retired ones.", "tags": ["Assets"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "property_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows for this property id."}, {"name": "unit_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows tied to this unit (location) id."}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["Active", "Needs Service", "Out of Service", "Retired"]}, "description": "Filter to rows with this status -- see this parameter's enum for the live vocabulary."}, {"name": "updated_since", "in": "query", "required": false, "schema": {"type": "string", "format": "date-time"}, "description": "Only rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Asset"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/assets/{asset_id}": {"get": {"summary": "Fetch an asset.", "description": "A single asset by id, including a retired one.", "tags": ["Assets"], "parameters": [{"name": "asset_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Asset"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/vendors": {"get": {"summary": "List vendors.", "description": "A keyset-paginated page of the company's vendor relationships, including inactive ones.", "tags": ["Vendors"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Vendor"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/vendors/{vendor_id}": {"get": {"summary": "Fetch a vendor.", "description": "A single vendor relationship by id, including an inactive one.", "tags": ["Vendors"], "parameters": [{"name": "vendor_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Vendor"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/inspections": {"get": {"summary": "List inspections.", "description": "A keyset-paginated page of the company's ops inspections. Consulting-engagement-tied inspections (DD/PCA/DD Unit Walk projects) are excluded -- they belong to the consulting product, not this resource.", "tags": ["Inspections"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "property_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows for this property id."}, {"name": "unit_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows tied to this unit (location) id."}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["Scheduled", "In Progress", "Completed"]}, "description": "Filter to rows with this status -- see this parameter's enum for the live vocabulary."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Inspection"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/inspections/{inspection_id}": {"get": {"summary": "Fetch an inspection.", "description": "A single ops inspection by id.", "tags": ["Inspections"], "parameters": [{"name": "inspection_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Inspection"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/turns": {"get": {"summary": "List turns.", "description": "A keyset-paginated page of the company's unit transitions -- occupancy and turnover records, ordered by id ascending. A turn IS the transition record (Ruling 1): every UnitTransition row lists, including a plain first-ever move-in with no move_out_date. The same object the turn.status_changed and turn.schedule_changed webhooks' `data` carries.", "tags": ["Turns"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "property_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows for this property id."}, {"name": "unit_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows tied to this unit (location) id."}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string", "enum": ["Active", "Completed", "Cancelled"]}, "description": "Filter to rows with this status -- see this parameter's enum for the live vocabulary."}, {"name": "updated_since", "in": "query", "required": false, "schema": {"type": "string", "format": "date-time"}, "description": "Only rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Turn"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "A filter value is outside its enum.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/turns/{turn_id}": {"get": {"summary": "Fetch a turn.", "description": "A single unit transition by id -- the same object the turn.status_changed and turn.schedule_changed webhooks send.", "tags": ["Turns"], "parameters": [{"name": "turn_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Turn"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/meters": {"get": {"summary": "List utility meters.", "description": "A keyset-paginated page of the company's utility meters (electric, gas, water, sewer, and trash), across all scopes (master, common area, and unit sub-meter).", "tags": ["Meters"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "property_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows for this property id."}, {"name": "unit_id", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Only rows tied to this unit (location) id."}, {"name": "updated_since", "in": "query", "required": false, "schema": {"type": "string", "format": "date-time"}, "description": "Only rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UtilityMeter"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/meters/{meter_id}": {"get": {"summary": "Fetch a utility meter.", "description": "A single utility meter by id.", "tags": ["Meters"], "parameters": [{"name": "meter_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "The record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UtilityMeter"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/meters/{meter_id}/readings": {"get": {"summary": "List a meter's readings.", "description": "A keyset-paginated page of one meter's consumption readings, ordered by id ascending -- chronological, oldest first; the last page carries the latest periods.", "tags": ["Meters"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}, {"name": "meter_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UtilityReading"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "404": {"description": "No record with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "x-scope": "read"}}, "/webhooks": {"get": {"summary": "List webhook subscriptions.", "description": "A keyset-paginated page of the company's webhook subscriptions. Accepts any active key regardless of scope.", "tags": ["Webhooks"], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200}, "description": "Max rows to return (default 50, maximum 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous page's next_cursor."}], "responses": {"200": {"description": "A page of results.", "content": {"application/json": {"schema": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/WebhookSubscription"}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}}}}, "400": {"description": "Bad request -- an invalid limit or cursor was supplied.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}, "post": {"summary": "Create a webhook subscription.", "description": "Registers a destination URL to receive signed FixGrid-Signature POSTs for the given events. Requires the Idempotency-Key header; a webhook_only-scoped key is required (a write-scoped key also satisfies this).", "tags": ["Webhooks"], "x-scope": "webhook_only", "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "maxLength": 64}, "description": "A client-chosen key, 64 characters or fewer. A retried request with the same key and the same body replays the original response."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["destination_url"], "properties": {"destination_url": {"type": "string", "format": "uri", "description": "https on port 443 to a publicly resolvable host."}, "events": {"type": "array", "items": {"type": "string", "enum": ["inspection.stale", "pm.stale", "property.notice.published", "ticket.created", "ticket.stale", "ticket.status_changed", "turn.schedule_changed", "turn.status_changed", "turn.task.stale", "vendor.hold"]}, "description": "One or more event keys."}}}}}}, "responses": {"201": {"description": "The webhook subscription was created, or this response replays an earlier creation for a duplicate Idempotency-Key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/WebhookSubscriptionCreated"}}}, "headers": {"X-Idempotent-Replay": {"schema": {"type": "string"}, "description": "Present with value '1' when this response is a replay of the original creation for a duplicate Idempotency-Key; the replayed body omits secret."}}}, "400": {"description": "`idempotency_key_required` -- An Idempotency-Key header is required to create a webhook subscription. `invalid_request` -- The Idempotency-Key header must be 64 characters or fewer. `invalid_request` -- The request body is not a valid webhook subscription payload.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "`idempotency_in_progress` -- A request with this Idempotency-Key is already being processed. `idempotency_mismatch` -- This Idempotency-Key was already used with a different destination_url or events. Use a new Idempotency-Key for a different request.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "422": {"description": "`invalid_destination_url` -- That destination URL is not allowed. `events_empty` -- Choose at least one event to subscribe to. `events_unknown_key` -- One or more selected events are not on the allow-list.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "503": {"description": "`vault_unavailable` -- FixGrid will not store a signing secret it cannot encrypt.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/webhooks/{sub_id}": {"delete": {"summary": "Delete a webhook subscription.", "description": "Hard-deletes the subscription and its delivery history. A webhook_only-scoped key is required (a write-scoped key also satisfies this).", "tags": ["Webhooks"], "x-scope": "webhook_only", "parameters": [{"name": "sub_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"204": {"description": "The webhook subscription was deleted."}, "404": {"description": "`not_found` -- No webhook subscription with that id is visible to this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "Missing or invalid API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "403": {"description": "The key's scope does not allow this, or the company is inactive.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded for this key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/openapi.json": {"get": {"summary": "This document.", "description": "Returns this OpenAPI document. Public -- no key required.", "tags": ["Meta"], "security": [], "responses": {"200": {"description": "The OpenAPI document.", "content": {"application/json": {"schema": {"type": "object"}}}}}}}}, "components": {"securitySchemes": {"bearerKey": {"type": "http", "scheme": "bearer"}}, "schemas": {"Error": {"type": "object", "properties": {"error": {"type": "object", "properties": {"code": {"type": "string"}, "message": {"type": "string"}}}}}, "Page": {"type": "object", "properties": {"data": {"type": "array", "items": {}}, "next_cursor": {"type": ["string", "null"]}, "limit": {"type": "integer"}}}, "Whoami": {"type": "object", "properties": {"api_version": {"type": "string"}, "company": {"type": "object", "properties": {"name": {"type": "string"}, "slug": {"type": "string"}}}, "key": {"type": "object", "properties": {"prefix": {"type": "string"}, "label": {"type": "string"}, "scope": {"type": "string"}}}}}, "Ticket": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'ticket' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid ticket id."}, "ticket_number": {"type": ["integer", "null"], "description": "The ticket number shown to staff and residents."}, "title": {"type": "string", "description": "Short summary of the ticket."}, "description": {"type": ["string", "null"], "description": "The full reported description."}, "category": {"type": "string", "description": "The ticket category."}, "subcategory": {"type": ["string", "null"], "description": "The ticket subcategory, if set."}, "priority": {"type": "string", "description": "One of 'Low', 'Medium', 'High', 'Emergency'.", "enum": ["Low", "Medium", "High", "Emergency"]}, "status": {"type": "string", "description": "One of 'Open', 'In Progress', 'On Hold', 'Completed', 'Cancelled'.", "enum": ["Open", "In Progress", "On Hold", "Completed", "Cancelled"]}, "property": {"type": "object", "description": "The property this ticket belongs to.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "unit": {"type": ["object", "null"], "description": "The unit this ticket is tied to, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "unit_number": {"type": ["string", "null"], "description": "The free-text unit number captured on the ticket."}, "room": {"type": ["string", "null"], "description": "The room within the unit, if captured."}, "room_instance": {"type": ["string", "null"], "description": "Disambiguates multiple rooms of the same type."}, "permission_to_enter": {"type": "boolean", "description": "Whether the resident granted permission to enter unattended."}, "preferred_access_time": {"type": ["string", "null"], "description": "Free-text preferred access window."}, "assignee": {"type": ["object", "null"], "description": "The staff user assigned, or null if unassigned.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "assigned_vendor": {"type": ["object", "null"], "description": "The assigned vendor relationship, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "photo_url": {"type": ["string", "null"], "description": "URL of the ticket photo, if one was attached."}, "created_at": {"type": "string", "format": "date-time", "description": "ISO 8601 UTC creation timestamp."}, "updated_at": {"type": "string", "format": "date-time", "description": "ISO 8601 UTC last-updated timestamp."}, "completed_at": {"type": ["string", "null"], "format": "date-time", "description": "ISO 8601 UTC completion timestamp, or null."}, "cancelled_at": {"type": ["string", "null"], "format": "date-time", "description": "ISO 8601 UTC cancellation timestamp, or null."}, "cancellation_reason_code": {"type": ["string", "null"], "description": "The locked cancellation reason code, or null."}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created, e.g. 'resident_portal'."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this record."}}}, "Property": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'property' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid property id."}, "name": {"type": "string", "description": "The property name."}, "address": {"type": "string", "description": "The street address."}, "city": {"type": ["string", "null"]}, "state": {"type": ["string", "null"]}, "postal_code": {"type": ["string", "null"], "description": "ZIP/postal code (Rule 230 B wire name)."}, "building_type": {"type": ["string", "null"]}, "construction_type": {"type": ["string", "null"]}, "unit_count": {"type": ["integer", "null"]}, "year_built": {"type": ["integer", "null"]}, "latitude": {"type": ["number", "null"]}, "longitude": {"type": ["number", "null"]}, "created_at": {"type": "string", "format": "date-time"}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this record."}}}, "Unit": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'unit' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid unit (location) id."}, "name": {"type": "string", "description": "e.g. 'Unit 204B'."}, "unit_number": {"type": ["string", "null"]}, "unit_status": {"type": ["string", "null"]}, "property": {"type": "object", "description": "The property this unit belongs to.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "floorplan": {"type": ["object", "null"], "description": "The unit's floorplan, or null if unassigned.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "kind": {"type": "string"}, "bedrooms": {"type": ["integer", "null"]}, "bathrooms": {"type": ["number", "null"]}, "square_feet": {"type": ["integer", "null"]}}}, "building": {"type": ["object", "null"], "description": "The building this unit sits in, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "floor": {"type": ["object", "null"], "description": "The declared floor, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "created_at": {"type": "string", "format": "date-time"}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "projected_ready_on": {"type": ["string", "null"], "format": "date", "description": "The projected date this unit's active turn will be ready, derived from the Turn Board; null when the unit has no active turn. FixGrid-native, not the MITS MadeReadyDate (an actual completed date, a different fact)."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this record."}}}, "Asset": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'asset' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid asset id."}, "name": {"type": "string"}, "category": {"type": "string"}, "sub_category": {"type": ["string", "null"]}, "asset_tag": {"type": ["string", "null"]}, "make": {"type": "string"}, "model": {"type": "string"}, "serial_number": {"type": ["string", "null"]}, "status": {"type": "string", "description": "One of 'Active', 'Needs Service', 'Out of Service', 'Retired'. A retired asset still lists and fetches.", "enum": ["Active", "Needs Service", "Out of Service", "Retired"]}, "property": {"type": "object", "description": "The property this asset belongs to.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "location": {"type": ["object", "null"], "description": "The location, or null. May be a mechanical room, not just a unit.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "location_type": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "year": {"type": ["integer", "null"]}, "installed_on": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, no time component."}, "warranty_expires_on": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, no time component."}, "expected_life_years": {"type": ["integer", "null"]}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this record."}}}, "Vendor": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'vendor' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid vendor relationship id."}, "name": {"type": "string", "description": "The nickname, else the legal name."}, "business": {"type": "object", "description": "The shared vendor legal entity (hand-picked allowlist -- never the full business record).", "properties": {"id": {"type": "integer"}, "legal_name": {"type": "string"}, "license_number": {"type": ["string", "null"]}, "website": {"type": ["string", "null"]}, "external_id": {"type": ["string", "null"]}, "source": {"type": "string"}}}, "avg_rating": {"type": ["number", "null"]}, "rating_count": {"type": "integer"}, "is_active": {"type": "boolean", "description": "An inactive relationship still lists and fetches."}, "property_visibility": {"type": "string"}, "onboarded_at": {"type": "string", "format": "date-time"}, "retired_at": {"type": ["string", "null"], "format": "date-time"}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this record."}, "last_compliance_state": {"type": "string", "description": "One of 'compliant', 'expiring', 'noncompliant', 'missing', 'unknown'. NULL serialises as 'unknown'.", "enum": ["compliant", "expiring", "noncompliant", "missing", "unknown"]}}}, "Inspection": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'inspection' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid inspection id."}, "inspection_number": {"type": ["integer", "null"], "description": "Display-only per-property sequence number."}, "title": {"type": ["string", "null"], "description": "Short title, or null on a legacy row created before the launch seam existed."}, "inspection_type": {"type": ["string", "null"], "description": "The template's type, e.g. 'General', 'Fire Alarm'."}, "status": {"type": "string", "description": "The inspection's lifecycle status -- see this property's enum for the live vocabulary. A row finalized on the legacy plain-checklist path publishes as 'Completed' here; its pass/fail verdict rides the separate `result` field instead.", "enum": ["Scheduled", "In Progress", "Completed"]}, "result": {"type": ["string", "null"], "description": "The inspection's pass/fail verdict, or null when none applies -- one of 'passed', 'failed'.", "enum": ["passed", "failed", null]}, "property": {"type": "object", "description": "The property this inspection belongs to.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "unit": {"type": ["object", "null"], "description": "The unit this inspection is tied to, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "scheduled_date": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, no time component."}, "completed_date": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, no time component."}, "assignee": {"type": ["object", "null"], "description": "The staff user assigned, or null if unassigned.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "is_compliance_inspection": {"type": "boolean", "description": "Whether this inspection tracks a compliance obligation."}, "compliance_type": {"type": ["string", "null"], "description": "The compliance log type this inspection is tied to, if any."}, "created_at": {"type": "string", "format": "date-time"}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this record."}}}, "Turn": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'turn' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid transition id."}, "status": {"type": "string", "description": "The turn's lifecycle status -- see this property's enum for the live vocabulary.", "enum": ["Active", "Completed", "Cancelled"]}, "property": {"type": "object", "description": "The property this turn belongs to.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "unit": {"type": "object", "description": "The unit this turn is on.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "move_out_date": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, no time component. Null on a transition with no departing resident, e.g. a first-ever move-in."}, "move_in_date": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, no time component."}, "projected_ready_on": {"type": ["string", "null"], "format": "date", "description": "The projected date this turn will be ready, derived from the Turn Board; null when this turn has no move_out_date (nothing to make ready) or no board yet. FixGrid-native, not the MITS MadeReadyDate (an actual completed date, a different fact)."}, "occupancy_source": {"type": ["string", "null"], "description": "The feed provider slug (lower-cased, stripped), or null when FixGrid staff manage this transition."}, "occupancy_fed_at": {"type": ["string", "null"], "format": "date-time", "description": "ISO 8601 UTC timestamp of the last external feed write, or null."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this turn's record (the transition detail page)."}}}, "UtilityMeter": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'utility_meter' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid utility meter id."}, "utility_type": {"type": "string", "description": "The utility this meter tracks -- see this field's enum for the live vocabulary.", "enum": ["electric", "gas", "water", "sewer", "trash", "other"]}, "meter_scope": {"type": "string", "description": "One of 'master' (property-wide), 'common_area', or 'unit_submeter' (true per-unit consumption). No published enum this slice."}, "unit_of_measure": {"type": ["string", "null"], "description": "The consumption unit, e.g. kWh. Null for a flat-fee meter with no consumption unit (e.g. trash)."}, "serial_number": {"type": ["string", "null"]}, "provider": {"type": ["string", "null"], "description": "The utility company, if on file."}, "account_number": {"type": ["string", "null"], "description": "The provider's account number, if on file."}, "property": {"type": "object", "description": "The property this meter belongs to.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "unit": {"type": ["object", "null"], "description": "The unit/area this meter measures, or null for a master (whole-property) meter.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}, "asset": {"type": ["object", "null"], "description": "The physical asset this meter is optionally linked to, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "data_source": {"type": "string", "description": "How this meter's readings arrive -- manual, file_import, api, or iot. No published enum this slice."}, "is_active": {"type": "boolean", "description": "Whether this meter is active. An inactive (soft-retired) meter still lists and fetches -- its history is never hidden."}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this meter's readings page."}}}, "UtilityReading": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'utility_reading' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid utility reading id."}, "period_start": {"type": "string", "format": "date", "description": "ISO 8601 date, no time component."}, "period_end": {"type": "string", "format": "date", "description": "ISO 8601 date, no time component."}, "utility_type": {"type": "string", "description": "Copied down from the parent meter -- a reading has no utility_type column of its own."}, "unit_of_measure": {"type": ["string", "null"], "description": "Copied down from the parent meter."}, "consumption": {"type": "number", "description": "The metered consumption for this period, in the parent meter's unit_of_measure."}, "cost": {"type": ["number", "null"], "description": "Optional -- captured for usage visibility even though billing is paused."}, "entered_by": {"type": ["object", "null"], "description": "The staff user who entered this reading, or null.", "properties": {"id": {"type": "integer"}, "name": {"type": "string"}}}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if synced."}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "created_at": {"type": "string", "format": "date-time"}}}, "WebhookSubscription": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'webhook_subscription' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid webhook subscription id."}, "destination_url": {"type": "string", "format": "uri", "description": "https on port 443 to a publicly resolvable host."}, "events": {"type": "array", "items": {"type": "string"}, "description": "The event keys this subscription is registered for."}, "health": {"type": "string", "description": "Derived delivery health for this subscription: one of 'healthy', 'retrying', 'dead_lettered', or 'paused'."}, "secret_prefix": {"type": "string", "description": "The first 12 characters of the signing secret, for display only -- never the full secret."}, "created_at": {"type": "string", "format": "date-time"}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}}}, "OccupancyRecord": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'occupancy' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid id of the underlying transition record."}, "unit_id": {"type": "integer", "description": "The unit's id, from GET /units."}, "status": {"type": "string", "description": "The transition's own status column, e.g. 'active'."}, "move_in_date": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, or null."}, "move_out_date": {"type": ["string", "null"], "format": "date", "description": "ISO 8601 date, or null on a move-in response."}, "occupancy_source": {"type": ["string", "null"], "description": "The feed provider slug (lower-cased, stripped), or null."}, "occupancy_fed_at": {"type": ["string", "null"], "format": "date-time", "description": "ISO 8601 UTC timestamp of this write, or null."}, "external_id": {"type": ["string", "null"], "description": "Rule 230 companion: the external system id, if supplied."}, "lease": {"type": ["object", "null"], "description": "The lease this move-in created, or null on a notice response.", "properties": {"lease_type": {"type": "string"}, "start": {"type": ["string", "null"], "format": "date"}, "end": {"type": ["string", "null"], "format": "date"}}}, "parties": {"type": "array", "description": "The leaseholders/guarantors on this lease, or [] on a notice response. Never carries email or phone.", "items": {"type": "object", "properties": {"name": {"type": "string"}, "external_id": {"type": ["string", "null"]}}}}, "url": {"type": "string", "format": "uri", "description": "Absolute FixGrid link to this unit's transitions."}}}, "WebhookSubscriptionCreated": {"type": "object", "properties": {"schema_version": {"type": "integer", "description": "The DTO shape version. Always 1 today."}, "entity_type": {"type": "string", "description": "Always 'webhook_subscription' for this resource."}, "entity_id": {"type": "integer", "description": "The FixGrid webhook subscription id."}, "destination_url": {"type": "string", "format": "uri", "description": "https on port 443 to a publicly resolvable host."}, "events": {"type": "array", "items": {"type": "string"}, "description": "The event keys this subscription is registered for."}, "health": {"type": "string", "description": "Derived delivery health for this subscription: one of 'healthy', 'retrying', 'dead_lettered', or 'paused'."}, "secret_prefix": {"type": "string", "description": "The first 12 characters of the signing secret, for display only -- never the full secret."}, "created_at": {"type": "string", "format": "date-time"}, "source": {"type": "string", "description": "Rule 230 companion: how this record was created."}, "secret": {"type": "string", "description": "The raw signing secret, 'whsec_' + 43 URL-safe characters. Returned ONCE, on the original 201 only; an idempotent replay of the same Idempotency-Key omits this property."}}}}}, "x-rate-limit": "120 per minute"}