Objects

Eight listable resources — tickets, properties, units, assets, vendors, inspections, turns and meters — plus occupancy ingest. Every list uses the same envelope and the same cursor.

The list envelope

GET/api/v1/tickets?limit=2scope: read
json
{
        "data": [ { /* one ticket object */ }, { /* … */ } ],
        "next_cursor": "v1:48213",
        "limit": 2
      }
KeyTypeNotes
dataarrayUp to limit objects, ascending by id. Never null; an empty page is [].
next_cursorstring|nullPass it back as ?cursor= for the next page. null means you have reached the end. Treat the value as opaque.
limitintegerThe limit actually applied. Default 50, maximum 200.

Filters

Every filter is optional and is sent as a query parameter, alongside limit and cursor.

GET /tickets

KeyTypeNotes
property_idintegerOnly rows for this property id.
unit_idintegerOnly rows tied to this unit (location) id.
statusstringOne of Open · In Progress · On Hold · Completed · Cancelled.
updated_sincestring/date-timeOnly rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z.
ticket_numberintegerOnly the ticket with this ticket_number (requires property_id).

GET /assets

KeyTypeNotes
property_idintegerOnly rows for this property id.
unit_idintegerOnly rows tied to this unit (location) id.
statusstringOne of Active · Needs Service · Out of Service · Retired. A retired asset still lists and fetches.
updated_sincestring/date-timeOnly rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z.

GET /units

KeyTypeNotes
property_idintegerOnly rows for this property id.

GET /inspections

KeyTypeNotes
property_idintegerOnly rows for this property id.
unit_idintegerOnly rows tied to this unit (location) id.
statusstringOne of Scheduled · In Progress · Completed. There is no updated_since — inspections have no updated_at.

GET /turns

KeyTypeNotes
property_idintegerOnly rows for this property id.
unit_idintegerOnly rows tied to this unit (location) id.
statusstringOne of Active · Completed · Cancelled.
updated_sincestring/date-timeOnly rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z.

GET /meters

KeyTypeNotes
property_idintegerOnly rows for this property id.
unit_idintegerOnly rows tied to this unit (location) id.
updated_sincestring/date-timeOnly rows updated at or after this ISO 8601 UTC timestamp, e.g. 2026-01-01T00:00:00Z.

/properties and /vendors take limit and cursor only. A meter's readings take limit and cursor on GET /meters/{meter_id}/readings — the meter is in the path. Status values are Title-Case. An unknown ?status= is 422“A filter value is outside its enum.” — not an empty page. The document lists that 422 on tickets, properties, units, assets, vendors, inspections and turns.

Two ways a filter fails
The code is always invalid_request. The status tells syntax (400) from semantics (422). An unknown query key is 400: “Unsupported query parameter(s): foo.” A status outside its enum is 422: “The `status` parameter must be one of: Open, In Progress, On Hold, Completed, Cancelled.” or “The `status` parameter must be one of: Active, Needs Service, Out of Service, Retired.” or “The `status` parameter must be one of: Scheduled, In Progress, Completed.” or “The `status` parameter must be one of: Active, Completed, Cancelled.” A timestamp with no UTC offset is 400: “The `updated_since` parameter must include a UTC offset -- a timestamp with no offset is ambiguous.” A ticket number without its property is 400: “The `ticket_number` parameter requires `property_id` -- a ticket number is unique only within one property.”

Paging

Keyset cursors, not offsets: a page is “the next limit records after this id,” so inserts while you page do not shift or duplicate rows. Follow next_cursor until it is null. There is no total count.

Filters compose with cursor: a next_cursor issued under one filter set is replayed under the same set. Do not change filters mid-walk.

curl
curl "https://app.fixgrid.app/api/v1/tickets?limit=200&cursor=v1:48213" \
        -H "Authorization: Bearer $FIXGRID_KEY"

A malformed cursor is a 400. A limit above 200 is silently clamped to 200 — the applied value comes back as limit in the response, so read it rather than assuming yours was honoured. A limit that is not a positive integer is a 400 — see Errors.

Fetch one

GET/api/v1/tickets/{ticket_id}scope: read
Another company's record is a 404
Fetching an id that exists but belongs to a different company answers 404 not_found with “No ticket with that id is visible to this key.” It is never a 403 — a 403 would tell you the record exists, and that is itself a leak. Do not treat 404 as “deleted.”

Shared keys

KeyTypeNotes
schema_versionintegerCurrently 1. Additive changes do not bump it; see versioning.
entity_typestringOne of ticket · property · unit · asset · vendor · inspection · turn · utility_meter · utility_reading · occupancy · webhook_subscription. Safe to switch on.
entity_idintegerStable for the life of the record, and the id used in the fetch path (the transition id on occupancy and on turn).
*_atstring|nullAll timestamps are ISO 8601 in UTC. Date-only fields are YYYY-MM-DD.
urlstring/uriAbsolute FixGrid link to this record, when the record has a page. Last field on ticket, property, unit, asset, inspection, turn and utility_meter; on vendor it precedes last_compliance_state. A unit's url opens its property's Unit Status page. Absent on utility_reading and webhook_subscription — there is no per-reading page and a subscription is not a record you open.

ticket

Field names follow MITS conventions where MITS has an opinion. The full table for every object is generated into the reference; this is the shape.

KeyTypeNotes
ticket_numberinteger|nullThe ticket number shown to staff and residents.
titlestringOne line, resident- or staff-authored.
statusstringOne of Open · In Progress · On Hold · Completed · Cancelled. Also the subject of the ticket.status_changed event.
prioritystringOne of Low · Medium · High · Emergency.
property · unitobject|nullWhere the work is. A ticket with no property is not returned by this API at all — the tenant boundary is the property, so an unattached ticket is invisible by construction.
external_id · sourcestring|nullYour own identifier, if the ticket came from you, and the system it came from. This is how you reconcile without keeping a map.
cancellation_reason_codestring|nullThe locked cancellation reason code, or null. One vocabulary of seven, published as the enum on the PATCH body: duplicate_ticket · no_access_repeated · resident_cancelled · resolved_independently · scheduled_for_later · see_notes · wrong_property.
+ 15 moredescription, category, subcategory, unit_number, room, room_instance, permission_to_enter, preferred_access_time, assignee, assigned_vendor, photo_url, created_at, updated_at, completed_at, cancelled_at. Generated in full into the reference.

Writing a ticket

Scope write. Both verbs require the Idempotency-Key header (64 characters or fewer). Full request bodies are generated into the reference.

POST/api/v1/ticketsscope: write

Creates a ticket on the given unit, status always Open. Required body: unit_id, title, category. Sending status is 422 invalid_request — the document: “This endpoint always creates a ticket with status 'Open'. Remove `status` from the request body.” A key with no minting administrator is 422 created_by_missing. Replay and mismatch are 409.

PATCH/api/v1/tickets/{ticket_id}scope: write

Changes a ticket's status. There is no transition graph — any status is reachable from any other, including reopening Completed or Cancelled. note is required (5+ words) only when status is Completed; cancellation_reason_code only when Cancelled, and only one of the seven codes in the table above — any other value is 422 invalid_request: “The `cancellation_reason_code` field must be one of: resident_cancelled, duplicate_ticket, resolved_independently, wrong_property, no_access_repeated, scheduled_for_later, see_notes.” Any extra key is one 422. cancellation_notes without see_notes is 400. A company that requires a close-out photo refuses Completed as 422. Replay and mismatch are 409. Sentences are on the reference row.

vendor

The full table is generated into the reference; this is the shape that is unique to vendor.

KeyTypeNotes
last_compliance_statestringOne of compliant · expiring · noncompliant · missing · unknown. The hourly stored COI-compliance snapshot. unknown means never computed. NULL serialises as unknown.
urlstring/uriAbsolute FixGrid link to this record. Second-to-last field on vendor; last_compliance_state follows it.

inspection

Ops inspections only. Consulting-engagement walks (DD / PCA / DD Unit Walk) are excluded. A standalone Unit Acceptance walk with no engagement stays in. Fetching an id this key cannot see is 404 not_found“No inspection with that id is visible to this key.” Full table in the reference.

KeyTypeNotes
statusstringOne of Scheduled · In Progress · Completed. Also the ?status= filter. A row finalized on the legacy plain-checklist path publishes as Completed; the pass/fail verdict rides result.
resultstring|nullThe pass/fail verdict, or null when none applies — one of passed · failed.
is_compliance_inspection · compliance_typeboolean · string|nullWhether this walk is a regulated-systems inspection, and which type when it is.

turn

A turn is the transition record — an occupancy record: move-out, move-in, projected ready date — not the unit. Every transition lists, including a plain first-ever move-in with no move_out_date; the unit's own status rides GET /units. It is the same object the turn.status_changed and turn.schedule_changed webhooks carry (see the events). Fetching an id this key cannot see is 404 not_found“No turn with that id is visible to this key.” Full table in the reference.

KeyTypeNotes
statusstringOne of Active · Completed · Cancelled. Also the ?status= filter.
move_out_date · move_in_datestring|null/dateISO 8601 date, no time component. move_out_date is null on a transition with no departing resident, e.g. a first-ever move-in.
projected_ready_onstring|null/dateThe 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). Also the subject of turn.schedule_changed.
occupancy_source · occupancy_fed_atstring|nullThe feed provider slug (lower-cased, stripped), or null when FixGrid staff manage this transition; the ISO 8601 UTC timestamp of the last external feed write, or null.
urlstring/uriAbsolute FixGrid link to this turn's record (the transition detail page). Last field.

meter

Utility meters across electric, gas, water, sewer, trash and other, including inactive ones. Fetching an id this key cannot see is 404 not_found“No meter with that id is visible to this key.” Readings are a third paginated route, oldest first. Full tables in the reference.

KeyTypeNotes
utility_typestringOne of electric · gas · water · sewer · trash · other.
GET /meters/{meter_id}/readingspagePeriod readings for that meter, ordered by id ascending (oldest first; the last page is the latest periods). A reading has no url — there is no per-reading page; open the meter.

occupancy

Ingest only — there is no occupancy list or fetch. Scope write. Both verbs require Idempotency-Key and a provider slug: required, lower-cased, 40 characters max. The response is an OccupancyRecord. Full bodies in the reference.

POST/api/v1/units/{unit_id}/noticescope: write

Records a notice to vacate on the unit's current resident. Required: move_out_date, provider. external_id is capped at 120 characters; over the cap is 400 invalid_request before anything is written — see field caps.

POST/api/v1/units/{unit_id}/move-inscope: write

Creates a new resident tenancy on the unit. Required: move_in_date, lease_start_date, parties, provider. The document on ordering: “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.” Every party field has a cap — first_name 80 · last_name 80 · email 150 · phone 20 · external_id 120 — and so does the top-level external_id (120). Over a cap is 400 invalid_request before anything is written — see field caps.

A key with no minting administrator cannot write
Ticket create, ticket status, notice and move-in all 422 created_by_missing when created_by_id on the key is null. Mint a new key on the Integrations page — see Errors.