# Mason external API Read APIs, plus work-order create and update, estimate and invoice approval and rejection, in-house visit appointment booking, moves, and cancellation, vendor contact creation, and updates to the Mason-maintained fields of contacts, units, and properties, for your property management account. Business-data endpoints require an Authorization: Bearer token scoped to your account; write endpoints require a read & write key. Dates are ISO 8601 timestamps. Null is distinct from false, zero, an empty string, and an empty array. V3 uses one effective work-order priority, Mason work-order references named masonWorkOrderId, and visits. Intake metadata is explicitly allowlisted. Other documented stored JSON retains its existing keys; in v3, nested values are returned in their single declared type. Unknown query parameters, and filter values outside a documented enum, return 400. An unsupported HTTP method on a documented path returns 405 with an Allow header listing the supported methods and the standard error body. A path below /{resource}/{id}, such as /scheduling-blocks/{id}/cancel, matches no endpoint and returns 404 with code unknown_resource and the standard error body for every method. This specification describes implemented endpoints only. API keys: create a key in the Mason app under Settings → API access (the API access card at /mason/settings). Any user on the account can create one; there is no separate admin permission. When creating a key you choose a scope: read-only can call the GET business-data endpoints; read & write can also make the documented POST and PATCH writes (work-order create and update, estimate and invoice approval and rejection, vendor contact creation, contact, unit, and property updates, and visit appointment booking, moves, and cancellation). The secret is shown once, at creation, in an on-page banner; Mason stores only its hash and cannot show or recover it afterward, so copy it into a secret manager immediately. To rotate a key, create a new one, switch your integration to it, then revoke the old one; a key is revoked on its own with the Revoke action on the same card. A revoked key is deactivated immediately and its next request returns 401. Each key is scoped to one property management account and authenticates only that account's data. The API access card also shows the account's Production or Test environment; a key created while viewing a Test account is an ordinary key scoped to that test account, subject to the same test-account behavior described elsewhere in this specification — for example, GET /contacts with a test-account key lists the test account's own contacts, while assignment resolves contacts in its parent account. A public, read-only demo API key is also available for trying this API without creating your own key. Errors and support: every error response is a JSON object with a human-readable error message, a stable machine-readable code, and a requestId. Branch on the HTTP status and code, not on the message text; the Error schema lists every code. Every business-data response, successful or not, carries an X-Mason-Request-Id header, and error bodies repeat it as requestId. You can send your own X-Request-Id to correlate requests with your logs. Contacting support: quote the X-Mason-Request-Id value of the request in question so Mason support can find it. Records owned by your property management system: properties, units, residents (tenant contacts), owners, and vendors are mirrored from your connected property management system (AppFolio, RentVine, PropertyWare, Buildium, or Property Meld), which is the system of record for them. This API reads them. Vendors are the one record it can also create, with POST /contacts: when your account is connected to RentVine or AppFolio, Mason creates the vendor there first, as a new vendor record in your property management system, and then saves it in Mason; accounts with no connected system get a vendor in Mason only. Properties, units, residents, and owners cannot be created here. Fields that come from your property management system cannot be changed here, including on vendors: make the change there and Mason picks it up on its next sync. The fields Mason maintains on these records — a contact's preferred contact method, language, communication block (stopAllComms), and internal notes; access details on units and properties; and a property's owner-outreach pause — can be changed with PATCH and are never written to your property management system. For AppFolio, RentVine, and PropertyWare accounts that sync runs nightly, and RentVine vendors also refresh hourly; ask Mason for the schedule that applies to other systems. Mason does not edit the details of existing properties, units, or contacts in your property management system; it creates a new vendor or owner in AppFolio or RentVine only when your team asks, or authorizes, Mason to add one. At your team's request, Mason can also attach files to property records in your property management system, and remove a specific file from a PropertyWare property. Accounts without any connected system maintain these records in the Mason app; accounts on other connected systems sync on their own schedule. Syncing data: to keep a local copy of a resource in sync, record T1, the current time, before you start a run. List the resource with updatedAfter=T0 where the resource exposes it, otherwise createdAfter=T0; a run with no T0 is a full first load, which is the same traversal with no lower bound — there is no separate bulk-export job. Follow pagination.nextCursor with the same filters until pagination.hasMore is false, then store T1 minus an overlap window as the next run's T0; 15 minutes is a reasonable default. Dedupe incoming records by id, since records inside the overlap window are returned again. Size the overlap to how long each resource keeps changing after its createdAt: calls, emails, text-messages, and scheduling-blocks have no updatedAt column, so createdAfter is their only incremental key. For calls, callStatus, endedAt, transcript, summary, recordingUrl, and masonWorkOrderId/relatedWorkOrderIds are finalized once the call ends and Mason finishes post-call processing. For emails and text-messages, masonWorkOrderId, relatedWorkOrderIds, senderRole, and, for emails, metadata are assigned when Mason processes the message, normally within minutes of createdAt. For scheduling-blocks, start, end, blockType, notes, and tenantConfirmationStatus change later, potentially long after createdAt, when an appointment is moved, reassigned, cancelled, or has its tenant confirmation status updated by a dispatcher; such a change is normally also recorded on the owning work order's GET /work-order-activity-logs, with eventType visit_rescheduled for a move, visit_reassigned for a technician reassignment, visit_unscheduled (portal_visit_unscheduled from the tech or vendor portal) for a cancellation, and tenant_confirmation_status_updated for a dispatcher-recorded tenant confirmation change, so after on that endpoint is an incremental key for catching a scheduling-block change after the block's own creation. As with GET /work-order-activity-logs generally, this is a best-effort change signal with no complete-history guarantee, not proof a scheduling-block field never changed, so also periodically re-read scheduling-blocks for open work orders rather than relying on activity events alone. contacts, estimates, invoices, properties, units, visits, and work-orders expose updatedAt, which advances whenever the record's own fields change; for these seven, updatedAfter is a reliable incremental key on its own, using the same overlap window to cover a write that commits after T1 was recorded. Versioning and compatibility: each major version has its own path (/api/external/v1, /v2, and /v3). Within a version Mason makes only non-breaking changes: new endpoints, new optional request parameters and body fields, new response fields, and new enum values, including new error codes. Clients must ignore response fields they do not recognize and must handle enum values they do not recognize. Breaking changes, such as removing or renaming a field, changing a field's type, adding a required parameter or body field, or rejecting requests that are accepted today, ship only in a new path version. Deprecations are announced in the changelog (GET /api/external/v3/changelog) at least 6 months before removal, and responses from a deprecated version carry Deprecation and Sunset headers. V1 and v2 remain available for existing integrations; no deprecation or sunset date is set for either. info.version identifies the exact revision of this specification, and each changelog entry names the revision that introduced it. Narrowing a field's declared type to one of the types it already permitted (for example number|string to number, with the same values) is a tightening, not a breaking change, and is listed under Changed in the changelog. Contract revision: `3-e7dea5b44010638a` Base URL: `https://mason.enduring-labs.com/api/external/v3` Business-data authentication: `Authorization: Bearer `. Your token is scoped to one property management account. The OpenAPI document is public. Demo token: `mason_f301d439d0a43c613e881b4ca376220cfdf2c5a7d7475b296b7d49a4d31bfbbc` is a public read-only token for a sample account with fictional data. Use it to try requests; use your own token for your account's data. Reference: https://www.thisismason.com/docs/api-reference Full machine-readable contract: https://mason.enduring-labs.com/api/external/v3/openapi.json Agent guidance: Use documented GET endpoints to read. POST /work-orders and PATCH /work-orders/{id} change account data and require authorization for the specific change and a read & write key; the public demo token cannot write. Load your own bearer token from a secret store or environment, never from a prompt; keep it out of logs and shared output. For lists, start with a small limit, follow pagination.nextCursor while pagination.hasMore is true, and resolve related records by ID when needed. Use only the methods and fields documented below. ```sh curl "https://mason.enduring-labs.com/api/external/v3/work-orders?limit=5" \ -H "Authorization: Bearer mason_f301d439d0a43c613e881b4ca376220cfdf2c5a7d7475b296b7d49a4d31bfbbc" ``` Resource lists return `data` and `pagination`. Pass `pagination.nextCursor` as `cursor` until `pagination.hasMore` is false. On-call coverage returns `data` without cursor pagination. ## Endpoints ### work-orders #### GET /work-orders Maintenance work orders belonging to your property management account. Priority describes office attention; isEmergency indicates an emergency. Returned values reflect current stored state, not a historical snapshot. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `status` (string): Exact match on status. Stored work-order lifecycle status. Current values: created, reviewed, estimated, work_in_progress, duplicate, cancelled, not_handled_by_mason, work_done, closed. Historical values are returned unchanged. This is not visit or message-delivery status. - `workOrderType` (string): Exact match on workOrderType. Stored work category: reactive, reactive:recall, preventative, turn, recurring, misc, inspection, or an inspection subtype such as inspection:annual, inspection:move_in, inspection:move_out, inspection:pre_move_out, inspection:onboarding, inspection:semi_annual, or inspection:drive_by. Legacy values are returned unchanged. - `source` (string): Exact match on source. Stored intake channel, commonly pms:portal, pms:text, pms:email, pms:call, pms:system, mason:portal, mason:text, mason:email, mason:call, mason:system, or mason:recurring. It describes the origin/channel, not the requester identity. Legacy values are returned unchanged. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `propertyId` (string (uuid)): Exact match on propertyId. Linked Mason property ID. Resolve using GET /properties/{id}. - `unitId` (string (uuid)): Exact match on unitId. Linked Mason unit ID, when present. Resolve using GET /units/{id}. - `assignedUser` (string): Match work orders whose stored assignedUsers array contains this exact email address. - `priority` (string): Exact match on priority. Effective attention priority. Uses the explicit user override when present, otherwise the assessment. Null means no recognized priority is recorded. Other values return 400. - `isEmergency` (boolean): Exact match on isEmergency. Whether the work order is currently marked as an emergency. - `requesterContactId` (string (uuid)): Exact match on requesterContactId. Mason contact ID of the recorded requester; null means unresolved or not recorded. - `vendorContactId` (string (uuid)): Exact match on vendorContactId. Assigned vendor or in-house technician Mason contact ID. Resolve using GET /contacts/{id}; contactType may be vendor or tech. 200 response: `{ data: WorkOrder[], pagination: Pagination }` #### POST /work-orders Creates a work order the same way a property manager does in Mason. It is also created in your connected property management system, and Mason starts its review, which can continue into vendor outreach and dispatch under your account's workflow settings. Requires a read & write API key. Send a unique Idempotency-Key per work order; a retry with the same key and body returns the original response instead of creating a duplicate. Parameters: - `Idempotency-Key` (string, required): Client-chosen key, unique per work order you intend to create (for example a UUID or your own record ID). 1-255 printable ASCII characters. Reusing it with a different body returns 422. JSON request body (required): `CreateWorkOrderRequest` Work order to create. - `description` (string, required): Problem description as the requester reported it. - `propertyId` (string (uuid), required): Mason property UUID from the properties in this account. - `unitId` (string (uuid)): Mason unit UUID. Must belong to propertyId. Defaults to the tenant's leased unit when tenantContactId is given. - `tenantContactId` (string (uuid)): Mason contact UUID of the tenant reporting the issue. Must lease within propertyId (and unitId when given). - `workOrderType` (string): Canonical work-order type. When omitted, Mason's review classifies the work order. Values: `reactive`, `reactive:recall`, `preventative`, `turn`, `recurring`, `misc`, `inspection`, `inspection:move_in`, `inspection:move_out`, `inspection:pre_move_out`, `inspection:onboarding`, `inspection:semi_annual`, `inspection:drive_by`, `inspection:annual`. - `source` (string): How the request reached you. mason:portal (default) records the property manager as requester; mason:call and mason:text record the tenant; pms:portal records a request first entered in the property management system. Values: `mason:portal`, `mason:call`, `mason:text`, `pms:portal`. 201 response: `{ data: WorkOrder | object, pmsCreateError: string | null }` The created work order. A replayed response carries the header Idempotent-Replayed: true. - `pmsCreateError`: Why the work order could not be created in the property management system, or null. The Mason work order exists either way. Other responses: - `400`: Invalid JSON, unknown or invalid fields, a missing Idempotency-Key, or a property, unit, or tenant that does not match this account. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only (code insufficient_scope), or the account is not live in Mason yet (code account_not_live). Work-order creation is part of every Mason plan at no separate charge. It becomes available once the account is live in Mason; until then this endpoint returns 403 with code account_not_live. - `409`: A request with this Idempotency-Key is still in progress, or an earlier one did not finish. - `422`: This Idempotency-Key was already used with a different request. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request failed and may have partially taken effect: the work order may exist in Mason or only in your property management system. The same Idempotency-Key replays this failure; check both before retrying with a new key. #### GET /work-orders/{id} Maintenance work orders belonging to your property management account. Priority describes office attention; isEmergency indicates an emergency. Returned values reflect current stored state, not a historical snapshot. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. The ETag response header identifies this version of the work order; send it as If-Match when updating it. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: WorkOrder }` #### PATCH /work-orders/{id} Changes the listed fields as a property manager would in Mason, recorded in the activity log as this API key. Description, status, work-order type, and assignment changes are synchronized to your property management system. An assignment dispatches the vendor or in-house technician through the same dispatch path as a manual dispatch from the Mason app, with the differences listed below. Requires a read & write API key. Differences from a manual dispatch in the app: - For vendor assignments, an open emergency escalation is not acknowledged; the on-call chain keeps running until the vendor accepts. - No not-to-exceed amount is sent; the property's maintenance limit applies where one is configured. - No tenant or owner dispatch notification is sent, except that a technician assignment texts the tenant a scheduling link when the account has tenant scheduling enabled. - In-house department vendors are refused; assign one of their technicians with technicianContactId. - The vendor-override feedback the app asks for is not required, and the channel used is not saved as the vendor's preferred contact method. - Activity summaries and dispatch analytics record the assignment as a manual dispatch (source manual_dispatch), attributed to this API key. - A replaced vendor's cancellation says it was replaced "via dispatch modal" on a vendor assignment and "via the Mason API" on a technician assignment. - For vendor assignments, dispatches are serialized so two identical requests cannot both create a visit; technician assignments have no dispatch lock in the app either. - A technician whose previous dispatch on this work order was denied (status denied) is refused with 409, where the app allows re-dispatch; re-dispatch that technician from Mason. - With a test-account key, GET /contacts lists the test account's own contacts, but assignment resolves contacts in its parent account. Optimistic concurrency: this endpoint and GET /work-orders/{id} return an ETag derived from the work order's updatedAt. Sending it as If-Match is strongly recommended: when the work order changed since that read, the request returns 412 before any field is applied. updatedAt advances when the work order's own fields change, including changes made by Mason's own automation and property management system sync, so a 412 can occur without another API client. Changes to related records such as visits, calls, and invoices do not advance it. The check is not a lock: a change made while this request is being applied is not detected. Parameters: - `id` (string (uuid), required): Mason UUID returned in the work-order id field. - `If-Match` (string): Strongly recommended. The ETag from your latest read of this work order; a comma-separated list is accepted. When none matches the current ETag, the request returns 412 and applies nothing. * matches any existing work order. When omitted, the changes apply regardless of intervening updates. JSON request body (required): `UpdateWorkOrderRequest` Fields to change. Omitted fields are left unchanged. Fields are applied in the order workOrderType, description, priority, isEmergency, assignment, status; the request stops at the first failure. - `description` (string): Replacement description. Also synchronized to the property management system. - `status` (string): New lifecycle status. Applied last, after the other fields in the request. Starts the same follow-up workflows as a status change in Mason and is synchronized to the property management system. Values: `created`, `reviewed`, `estimated`, `work_in_progress`, `duplicate`, `cancelled`, `not_handled_by_mason`, `work_done`, `closed`. - `statusNote` (string): Note recorded with the status change. Requires status. - `priority` (string | null): Attention priority override. null clears the override and returns to Mason's assessed priority. Values: `urgent`, `normal`, `low`. - `workOrderType` (string): Canonical work-order type. Subtypes such as inspection:move_in are distinct values. Values: `reactive`, `reactive:recall`, `preventative`, `turn`, `recurring`, `misc`, `inspection`, `inspection:move_in`, `inspection:move_out`, `inspection:pre_move_out`, `inspection:onboarding`, `inspection:semi_annual`, `inspection:drive_by`, `inspection:annual`. - `isEmergency` (boolean): Emergency flag. false cancels any open emergency escalation for this work order. - `assignment` (object | object): Vendor or in-house technician to assign, through the same dispatch path as a manual dispatch from the Mason app, with the differences listed on PATCH /work-orders/{id}. Mason uses the dispatch form's defaults: the work order's current scope as instructions, an estimate request when Mason's review recommends one, the property's synced gate and lockbox codes, the vendor's preferred dispatch channel when it can be reached that way (otherwise email, or a text message when the vendor has no email), and a target completion date three business days out. No not-to-exceed amount is sent; the property's maintenance limit applies where one is configured. A vendor assignment starts Mason's vendor outreach and follow-up and sends no tenant or owner notification. A technician assignment creates and sends the technician's visit ticket and sends no owner notification; when the account has tenant scheduling enabled, the tenant is texted a link to schedule the visit. A previously dispatched vendor is sent a cancellation. The assigned contact is then returned in the work-order vendorContactId field. The assignment is synchronized to the property management system: if it rejects a vendor assignment, nothing is sent and the request returns 502; if it rejects a technician assignment, the Mason assignment stands and the response reports pmsAssignmentError. Assigning a vendor already assigned or dispatched on this work order, or a technician already assigned to it, returns 409. 200 response: `{ data: WorkOrder, pmsAssignmentError: string }` The work order after every requested change. pmsAssignmentError is included only when a technician assignment was applied in Mason but rejected by the property management system. - `pmsAssignmentError`: Present only when a technician assignment in this request was applied in Mason but the property management system rejected it. The Mason assignment stands; update the property management system yourself. Other responses: - `400`: Invalid JSON or fields, no updatable field, an assignment contact that is not an active vendor or technician in this account or is an in-house department, or a change Mason rejected. A rejected change reports the field and any fields already applied. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No work order exists in this account with that ID, or the ID is not a valid UUID. - `409`: The assignment conflicts with the current dispatch state: the vendor is already assigned to or was already dispatched on this work order, the technician is already assigned to it, the same assignment was already made, another vendor dispatch is in progress or has an unconfirmed outcome, Mason is stopped on this work order, or the scope changed and must be reviewed in Mason. Nothing was dispatched; fields listed in appliedFields were already applied. - `412`: If-Match did not match the current ETag. No field was applied. The ETag header carries the current version; read the work order again before retrying. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. - `502`: The property management system rejected a work-order type change; a status change out of duplicate, cancelled, or not handled by Mason that the property management system could not reopen, in which case the status is unchanged; a vendor dispatch was not sent, for example because the property management system rejected the vendor assignment or the vendor has no usable email or phone; or the assignment failed unexpectedly and may have partially taken effect, in which case check the work order in Mason before assigning again. A technician assignment the property management system rejects returns 200 with pmsAssignmentError instead. The error explains why. ### calls #### GET /calls Recorded phone calls scoped to your property management account. Call content and recording availability depend on the stored record. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `callStatus` (string): Exact match on callStatus. Stored call outcome, such as completed, failed, busy, or no-answer. - `callerRole` (string): Exact match on callerRole. Recorded role of the other party, such as tenant, vendor, or property_manager. Legacy roles may occur. - `inBound` (boolean): Exact match on inBound. True for an incoming call; false for an outgoing call. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `contactId` (string (uuid)): Exact match on masonContactId. Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. 200 response: `{ data: Call[], pagination: Pagination }` #### GET /calls/{id} Recorded phone calls scoped to your property management account. Call content and recording availability depend on the stored record. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Call }` ### text-messages #### GET /text-messages Stored inbound and outbound text messages for your property management account. Work-order filtering matches the primary linked work order. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `senderRole` (string): Exact match on senderRole. Recorded sender role, such as tenant, vendor, owner, tech, or property_manager. Legacy roles may occur. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `contactId` (string (uuid)): Exact match on masonContactId. Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `source` (string): Exact match on source. Stored communication origin label, returned verbatim. It is attribution, not proof of delivery or a newly inferred classification. - `provider` (string): Exact match on provider. Recorded communication transport provider, returned verbatim; null means none was recorded. 200 response: `{ data: TextMessage[], pagination: Pagination }` #### GET /text-messages/{id} Stored inbound and outbound text messages for your property management account. Work-order filtering matches the primary linked work order. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: TextMessage }` ### invoices #### GET /invoices Stored invoices, matching results, and review/billing state. Top-level monetary amounts are integer cents. Nested document data is returned as stored, except that line-item quantities and amounts, matched-record identifiers, and party addresses are returned in their documented types; a stored value that cannot be read as that type is null. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `status` (string): Exact match on status. Stored invoice processing status, such as pending, processed, incomplete, not_invoice, reprocessing, failed, failed_with_partial_data, or no_pdf_attachments. - `confidence` (string): Exact match on confidence. Stored extraction confidence or processing classification. Common values include high, medium, low, failed, failed_with_partial_data, and no_pdf_attachments. - `isInvoice` (boolean): Exact match on isInvoice. Whether this record is classified as an invoice. - `isRecurring` (boolean): Exact match on isRecurring. Whether the invoice was classified as recurring. - `reviewed` (boolean): Exact match on reviewed. Whether the invoice has been marked reviewed by the office/accounting process. - `reviewReason` (string): Exact match on reviewReason. Recorded review disposition, commonly bill_created, completed_outside_mason, duplicate, not_an_invoice, or archived. - `pmApproved` (boolean): Exact match on pmApproved. Whether the invoice is marked approved. - `pmRejected` (boolean): Exact match on pmRejected. Whether the invoice is marked rejected. - `needsReview` (boolean): Exact match on needsReview. Whether the recorded validation result requires review. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `workOrderNumber` (string): Case-insensitive literal substring match on workOrderNumber. Human-readable work-order number associated with the invoice. Percent and underscore characters are treated literally, not as wildcards. - `invoiceNumber` (string): Case-insensitive literal substring match on invoiceNumber. Invoice number printed on the document. Percent and underscore characters are treated literally, not as wildcards. - `emailSender` (string): Case-insensitive literal substring match on emailSender. Source email sender. Percent and underscore characters are treated literally, not as wildcards. - `emailSubject` (string): Case-insensitive literal substring match on emailSubject. Source email subject. Percent and underscore characters are treated literally, not as wildcards. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `invoiceDateAfter` (string (date-time)): Inclusive lower bound on invoiceDate. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `invoiceDateBefore` (string (date-time)): Inclusive upper bound on invoiceDate. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `dueDateAfter` (string (date-time)): Inclusive lower bound on dueDate. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `dueDateBefore` (string (date-time)): Inclusive upper bound on dueDate. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `pmsUploadedAfter` (string (date-time)): Inclusive lower bound on pmsUploadedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `pmsUploadedBefore` (string (date-time)): Inclusive upper bound on pmsUploadedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. 200 response: `{ data: Invoice[], pagination: Pagination }` #### GET /invoices/{id} Stored invoices, matching results, and review/billing state. Top-level monetary amounts are integer cents. Nested document data is returned as stored, except that line-item quantities and amounts, matched-record identifiers, and party addresses are returned in their documented types; a stored value that cannot be read as that type is null. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. The ETag response header identifies this version of the invoice; send it as If-Match when deciding it. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Invoice }` #### PATCH /invoices/{id} Approves the invoice to pay or rejects it as a property manager does in Mason, selected by decision in the body. The invoice's status field is its processing state and does not change; pmApproved and pmRejected record the decision. Recorded in the work-order activity log as this API key. Requires a read & write API key. With decision approved, pmApproved becomes true and pmApprovedBy records this API key's name. Approval does not create a bill or payment in your property management system; bills are created separately in Mason. The invoice needs an amount greater than zero and an invoice number, or confirmMissingInvoiceNumber: true. When the invoice's property is inactive in your property management system the request returns 409 unless confirmInactiveProperty is true. No message is sent. With decision rejected, pmRejected becomes true and any approval is withdrawn. When the invoice is linked to a RentVine invoice, Mason declines it in RentVine first and rejects it in Mason only after RentVine confirms. An invoice already processed (reviewed, or uploaded to your property management system) cannot be rejected. No message is sent to the vendor. Repeating a decision is safe and needs no Idempotency-Key: when pmApproved is already true and you send approved, or pmRejected is already true and you send rejected, the request returns 200 with the current invoice and nothing runs. The check is not a lock: two identical requests arriving at the same moment can both run the operation, as in the Mason app. Sending the opposite decision reverses the earlier one, as in Mason. Optimistic concurrency: this endpoint and GET /invoices/{id} return an ETag derived from the invoice's updatedAt. Sending it as If-Match is strongly recommended: when the invoice changed since that read, the request returns 412 before anything runs. updatedAt advances on any change to the invoice record, including changes made by Mason's own processing and property management system sync, so a 412 can occur without another API client. The check is not a lock: a change made while this request is being applied is not detected. A retry after a lost response that re-sends the If-Match value read before the first attempt returns 412, because the first attempt changed the record; re-read the record and compare its state before retrying. Parameters: - `id` (string (uuid), required): Mason UUID returned in the invoice id field. - `If-Match` (string): Strongly recommended. The ETag from your latest read of this invoice; a comma-separated list is accepted. When none matches the current ETag, the request returns 412 and nothing runs. * matches any existing invoice. When omitted, the decision applies regardless of intervening updates. JSON request body (required): `UpdateInvoiceRequest` Invoice decision, selected by decision. The invoice's status field is its processing state and does not change. confirmMissingInvoiceNumber and confirmInactiveProperty are accepted only with approved; reason only with rejected. 200 response: `{ data: Invoice }` The invoice after the decision, or unchanged when it was already in the requested state. Other responses: - `400`: Invalid JSON or fields, a missing or unknown decision, or a field that belongs to the other decision (confirmMissingInvoiceNumber and confirmInactiveProperty are accepted only with approved; reason only with rejected). On approval, also an invoice amount that is not greater than zero, or a missing invoice number without confirmMissingInvoiceNumber: correct the invoice in Mason or send confirmMissingInvoiceNumber: true, and retry. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No invoice exists in this account with that ID, or the ID is not a valid UUID. - `409`: On approval, the invoice's property is inactive in your property management system and confirmInactiveProperty was not true; send confirmInactiveProperty: true and retry. On rejection, the invoice was already processed, changed during the request, or its RentVine link cannot be declined. - `412`: If-Match did not match the current ETag. Nothing ran. The ETag header carries the current version; read the invoice again before retrying. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. - `502`: On rejection, RentVine did not confirm the decline, so Mason did not reject the invoice; or RentVine declined it but Mason could not record the rejection. The error says which. ### contacts #### GET /contacts People and businesses in your property management account. Hydrate contact references with a detail request or the id/ids filters, even when the contact predates your incremental sync window. Tenant (resident), owner, and vendor contacts are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. Technician, property-manager, emergency, and unidentified contacts are maintained in Mason. POST /contacts creates vendor contacts only: in RentVine or AppFolio first when your account is connected to one, otherwise in Mason alone. PATCH /contacts/{id} changes only the Mason-maintained fields preferredContactMethod, language, stopAllComms, and notes, which Mason does not write to your property management system; every other field is read-only here, and fields your property management system provides change only there. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `id` (string): Filter by one or more comma-separated Mason contact UUIDs. id and ids are aliases; when both are supplied their IDs are combined. Invalid UUID entries are ignored, and a set with no valid IDs matches nothing. Results still use normal pagination. - `ids` (string): Filter by one or more comma-separated Mason contact UUIDs. id and ids are aliases; when both are supplied their IDs are combined. Invalid UUID entries are ignored, and a set with no valid IDs matches nothing. Results still use normal pagination. - `contactType` (string): Exact match on contactType. Contact category, such as tenant, vendor, owner, tech, or property_manager. - `isActive` (boolean): Exact match on isActive. Whether the contact is marked active. - `search` (string): Case-insensitive literal substring match on name. Contact display name. Percent and underscore characters are treated literally, not as wildcards. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. 200 response: `{ data: Contact[], pagination: Pagination }` #### POST /contacts Creates a vendor through the same path Mason uses when a property manager asks it to add one. When your account is connected to RentVine or AppFolio, Mason first creates the vendor there, so your property management system gets a new vendor record, then saves it in Mason as a vendor contact linked to that record. Accounts with no connected system get a vendor in Mason only. Accounts on PropertyWare, Buildium, or Property Meld cannot create vendors here and receive 409; create the vendor in your property management system instead, and Mason picks it up on its next sync. Only vendors can be created: send contactType vendor. Before creating anything, Mason looks for the vendor among your contacts in Mason and, when connected, in its synced copy of your RentVine or AppFolio vendors; a vendor added there since the last sync is not seen, so it can be created a second time. A vendor with the same name, ignoring capitalization, and a matching phone number or email is reused instead of duplicated: the request returns 200 with created false. A partial match, such as the same phone number under a different name, returns 409 and creates nothing. Otherwise the request returns 201 with created true. Mason does not message, dispatch, or invite the vendor. Test accounts can create vendors only when they have no connected property management system, where the vendor is created in the test account in Mason alone; a test account connected to RentVine or AppFolio receives 409, because the write would reach the live property management system. The ETag response header identifies this version of the contact; send it as If-Match on PATCH /contacts/{id}. Requires a read & write API key. Send a unique Idempotency-Key per vendor; a retry with the same key and body returns the original response instead of creating the vendor again. Parameters: - `Idempotency-Key` (string, required): Client-chosen key, unique per vendor you intend to create (for example a UUID or your own vendor record ID). 1-255 printable ASCII characters. Reusing it with a different body returns 422. JSON request body (required): `CreateContactRequest` The contact to create. Only vendor contacts can be created today: send contactType "vendor", name, and at least one of phoneNumber or email. - `contactType` (string, required): The kind of contact to create. vendor is the only value. - `name` (string, required): The vendor's business name, 1-200 characters, such as Example Plumbing Co. Mason converts an all-capital or all-lowercase name to title case. Put the person you deal with in firstName and lastName. - `firstName` (string): The first name of your contact person at the vendor, 1-100 characters. Sent to AppFolio as the vendor's contact first name when your account is connected to AppFolio; not stored for RentVine or Mason-only vendors. - `lastName` (string): The last name of your contact person at the vendor, 1-100 characters. Sent to AppFolio as the vendor's contact last name when your account is connected to AppFolio; not stored for RentVine or Mason-only vendors. - `phoneNumber` (string): The vendor's US phone number, such as (555) 555-0188 or +15555550188. Mason stores it in E.164 form (+15555550188); a value that is not a valid 10-digit US number returns 400. - `email` (string (email)): The vendor's email address. 200 response: `{ data: Contact, pmsSyncError: string | null, created: boolean }` The name and contact details matched an existing vendor, so nothing new was created in your property management system. created is false and data is that vendor's Mason contact, linked and updated as the created field describes. A replayed response carries the header Idempotent-Replayed: true. - `pmsSyncError`: Always null for a vendor: when the write to your property management system fails, or Mason cannot save the vendor that system just created, the request returns 502 instead. - `created`: true (status 201) when this request created a new vendor: in RentVine or AppFolio when your account is connected to one, where data is the Mason contact saved for it (a new contact, or an existing Mason vendor contact with the same name and details that was not yet linked to your property management system); otherwise in Mason, as a new contact. false (status 200) when a vendor with the same name, ignoring capitalization, and a matching phone number or email already existed, so nothing new was created in your property management system, and data is that vendor's Mason contact: linked to the matching RentVine or AppFolio vendor if it was not linked already, creating the Mason contact when there was none, or, with no connected system, reused the matching Mason vendor. Either way, Mason marked the contact active, set its name to the name you sent, and may have filled in a phone number or email the contact did not have. An existing phone number or email is never replaced. Other responses: - `400`: Invalid JSON; an unknown field; a contactType other than vendor; a missing or blank name; a body with neither phoneNumber nor email; an invalid email address; a phone number that is not a valid US number; or a missing Idempotency-Key. The phone number is checked after the Idempotency-Key is reserved, so that 400 is replayed for the same key; retry with a new key. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: The API key's property management account was not found. Nothing was created. - `409`: The details conflict with an existing vendor, and Mason created or linked nothing: the phone number or email belongs to a vendor with a different name, in Mason or in your property management system; your property management system already has a vendor with this name but different contact details, or several vendors matching these details; the Mason vendor with this phone number or email has a different email or phone number; the phone number and email belong to different vendors; or the matching Mason vendor is linked to a different property management record. The message names the conflicting vendor where it can; resolve the conflict in Mason or your property management system before retrying. Also returned when the account cannot create vendors here: its property management system is not RentVine or AppFolio, it is a test account connected to one, or its RentVine or AppFolio connection is missing or inactive. This response is stored under the Idempotency-Key, so retry with a new key. A request with this Idempotency-Key that is still in progress or did not finish also returns 409. - `422`: This Idempotency-Key was already used with a different request. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: Either the request failed and may have partially taken effect, so the vendor may exist in your property management system or in Mason, or the vendor was saved in Mason but could not be read back to return it, in which case the message names its id: fetch it with GET /contacts/{id} instead of resending. The same Idempotency-Key replays this failure; check before retrying with a new key. - `502`: The write to your property management system failed: RentVine or AppFolio rejected the vendor or did not confirm whether it was created, or created it but Mason could not save it. The message says which. When the vendor may exist in your property management system, check there before creating it again. #### GET /contacts/{id} People and businesses in your property management account. Hydrate contact references with a detail request or the id/ids filters, even when the contact predates your incremental sync window. Tenant (resident), owner, and vendor contacts are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. Technician, property-manager, emergency, and unidentified contacts are maintained in Mason. POST /contacts creates vendor contacts only: in RentVine or AppFolio first when your account is connected to one, otherwise in Mason alone. PATCH /contacts/{id} changes only the Mason-maintained fields preferredContactMethod, language, stopAllComms, and notes, which Mason does not write to your property management system; every other field is read-only here, and fields your property management system provides change only there. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. The ETag response header identifies this version of the contact; send it as If-Match when changing it. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Contact }` #### PATCH /contacts/{id} Changes only the Mason-maintained fields listed in the request schema, as a property manager would in Mason. Nothing is written to your property management system, and fields that come from it stay read-only here and change only there. Requires a read & write API key. Sending a value a field already has is safe and needs no Idempotency-Key; nothing else runs. Send any of preferredContactMethod, language, stopAllComms, and notes; omitted fields are left unchanged. stopAllComms: true takes effect starting with the next Mason-initiated message to this contact. notes replaces the stored notes in full; send null to clear them. Optimistic concurrency: this endpoint and GET /contacts/{id} return an ETag derived from the contact's updatedAt. Sending it as If-Match is strongly recommended: when the contact changed since that read, the request returns 412 before any field is applied. updatedAt advances when the contact's Mason-maintained fields change. The check is not a lock: a change made while this request is being applied is not detected. Parameters: - `id` (string (uuid), required): Mason UUID returned in the contact id field. - `If-Match` (string): Strongly recommended. The ETag from your latest read of this contact; a comma-separated list is accepted. When none matches the current ETag, the request returns 412 and applies nothing. * matches any existing contact. When omitted, the changes apply regardless of intervening updates. JSON request body (required): `UpdateContactRequest` Mason-maintained contact fields to change. Omitted fields are left unchanged. Fields that come from your property management system cannot be changed here. - `preferredContactMethod` (string): The contact's preferred communication method: phone, text, or email. Maintained in Mason and not written to your property management system. Values: `phone`, `text`, `email`. - `language` (string): The contact's preferred language: a lowercase language code with optional subtags, such as en, es, or en-US, 2-16 characters. Maintained in Mason and not written to your property management system. - `stopAllComms` (boolean): true blocks every Mason-initiated text, email, call, and group-chat add to this contact, and to any other contact in your account with the same phone number or email address. It does not stop inbound replies: messages from the contact still reach Mason. false removes this contact's block. Maintained in Mason and not written to your property management system. - `notes` (string | null): Internal notes about this contact, shown to the Mason team and Mason's agents and never to the contact, 1-5000 characters. Replaces the current notes; null clears them. Maintained in Mason and not written to your property management system. 200 response: `{ data: Contact }` The contact after the change, or unchanged when every field already had the requested value. Other responses: - `400`: Invalid JSON or fields, an unrecognized field or one only your property management system can set, or a body with none of preferredContactMethod, language, stopAllComms, or notes. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No contact exists in this account with that ID, or the ID is not a valid UUID. - `412`: If-Match did not match the current ETag. No field was applied. The ETag header carries the current version; read the contact again before retrying. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. ### visits #### GET /visits Maintenance visit records. Assigned technicians describe assignment, not completed labor. Nested JSON is returned as stored; changes to related records do not necessarily update this record timestamp. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `status` (string): Exact match on status. Recorded disposition, commonly Complete, Still Working, Canceled, Estimate, or an empty string. - `priority` (string): Exact match on priority. Recorded visit priority, commonly urgent, normal, or low. - `noteReviewed` (boolean): Exact match on noteReviewed. Whether the office has reviewed the recorded work notes; null means not recorded. - `assignedTech` (string): Match visits whose assignedTechs contains this exact Mason technician contact ID. - `initialRoutingTech` (string): Match visits whose legacy initialRouting contains this exact technician name. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `logDateAfter` (string (date-time)): Inclusive lower bound on logDate. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `logDateBefore` (string (date-time)): Inclusive upper bound on logDate. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. 200 response: `{ data: Visit[], pagination: Pagination }` #### GET /visits/{id} Maintenance visit records. Assigned technicians describe assignment, not completed labor. Nested JSON is returned as stored; changes to related records do not necessarily update this record timestamp. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Visit }` ### properties #### GET /properties Properties associated with your account, including stored address, ownership, and provider details. No geocoding or owner lookup runs as part of this read. Properties are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. This API does not create properties. PATCH /properties/{id} changes only the Mason-maintained fields access and pauseOwnerOutreach, which Mason does not write to your property management system; every other field is read-only here and changes only in your property management system. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `isActive` (boolean): Exact match on isActive. Whether the property is marked active. - `city` (string): Exact match on city. City. - `state` (string): Exact match on state. State or region. - `search` (string): Case-insensitive literal substring match on formattedAddress. Formatted property address. Percent and underscore characters are treated literally, not as wildcards. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `market` (string): Exact match on the stored propertyDetails.market value. - `propertyClass` (string): Match properties whose stored propertyClass array contains this exact classification. 200 response: `{ data: Property[], pagination: Pagination }` #### GET /properties/{id} Properties associated with your account, including stored address, ownership, and provider details. No geocoding or owner lookup runs as part of this read. Properties are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. This API does not create properties. PATCH /properties/{id} changes only the Mason-maintained fields access and pauseOwnerOutreach, which Mason does not write to your property management system; every other field is read-only here and changes only in your property management system. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. The ETag response header identifies this version of the property; send it as If-Match when changing it. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Property }` #### PATCH /properties/{id} Changes only the Mason-maintained fields listed in the request schema, as a property manager would in Mason. Nothing is written to your property management system, and fields that come from it stay read-only here and change only there. Requires a read & write API key. Sending a value a field already has is safe and needs no Idempotency-Key; nothing else runs. Send access and/or pauseOwnerOutreach; omitted fields are left unchanged. access is merged key by key into the property's stored access details: an omitted key is left unchanged and null clears a key. Access details are not yet included in the dispatch messages Mason sends vendors and technicians; that is a separate change. pauseOwnerOutreach is the Pause owner outreach switch on the Mason property page and takes effect starting with the next owner message. Optimistic concurrency: this endpoint and GET /properties/{id} return an ETag derived from the property's updatedAt. Sending it as If-Match is strongly recommended: when the property changed since that read, the request returns 412 before any field is applied. updatedAt advances when the property's Mason-maintained fields change. The check is not a lock: a change made while this request is being applied is not detected. Parameters: - `id` (string (uuid), required): Mason UUID returned in the property id field. - `If-Match` (string): Strongly recommended. The ETag from your latest read of this property; a comma-separated list is accepted. When none matches the current ETag, the request returns 412 and applies nothing. * matches any existing property. When omitted, the changes apply regardless of intervening updates. JSON request body (required): `UpdatePropertyRequest` Mason-maintained property fields to change. Omitted fields are left unchanged. Fields that come from your property management system cannot be changed here. - `access` (object): Access details. Maintained in Mason for this record and not written to your property management system. Not yet included in vendor or technician dispatches; that is a separate change. Send at least one key: omitted keys are left unchanged and null clears a key. - `pauseOwnerOutreach` (boolean): true mutes every Mason-to-owner message about this property: owner approvals, dispatch and completion emails, estimate emails, and agent owner messages. Owner messages your team explicitly asks Mason to send are still sent. While paused, work that needs owner approval cannot get it through Mason. false resumes owner messages. This is the same setting as the Pause owner outreach switch on the Mason property page. Maintained in Mason and not written to your property management system. 200 response: `{ data: Property }` The property after the change, or unchanged when every field already had the requested value. Other responses: - `400`: Invalid JSON or fields, an unrecognized field or one only your property management system can set, a body with neither access nor pauseOwnerOutreach, or an access object with none of gateCode, lockboxCode, alarmCode, or notes, or with a code over 64 characters. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No property exists in this account with that ID, or the ID is not a valid UUID. - `412`: If-Match did not match the current ETag. No field was applied. The ETag header carries the current version; read the property again before retrying. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. ### scheduling-blocks #### GET /scheduling-blocks Stored availability and appointment blocks. blockType=confirmed identifies a confirmed work window. A confirmed block and tenantConfirmationStatus are distinct facts. Range endpoints can be null for unbounded windows. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `visitId` (string (uuid)): Exact match on visitId. Linked Mason visit ID; resolve using GET /visits/{id}. - `contactId` (string (uuid)): Exact match on contactId. Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. - `blockType` (string): Exact match on blockType. Stored scheduling category, such as tenant_available, vendor_available, tentative, confirmed, tech_scheduling_proposal, blocked, superseded, on_call_assignment, on_call_override, or on_call_cancellation. - `source` (string): Exact match on source. Stored scheduling source, such as tenant_portal, vendor_portal, pm_manual, or ai_suggested. - `tenantConfirmationStatus` (string): Exact match on tenantConfirmationStatus. Stored tenant confirmation status, commonly pending or confirmed; null means legacy or unknown. - `linkKey` (string (uuid)): Exact match on linkKey. Identifier grouping blocks from one scheduling submission. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `startAfter` (string (date-time)): Inclusive lower bound on start. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `startBefore` (string (date-time)): Inclusive upper bound on start. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `endAfter` (string (date-time)): Inclusive lower bound on end. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `endBefore` (string (date-time)): Inclusive upper bound on end. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. 200 response: `{ data: SchedulingBlock[], pagination: Pagination }` #### POST /scheduling-blocks Books a confirmed appointment for an in-house maintenance visit with a technician, the same booking Mason's property manager agent makes when asked to schedule a visit. Recorded in the work-order activity log as this API key. The visit must be open, no visit on its work order may have a confirmed appointment or live scheduling proposal, the technician must be an active technician in this account with no overlapping appointment or blocked time, and start must be in the future. Mason records the appointment as a new confirmed scheduling block and as the work order's scheduled date; unless the technician is already the visit's only assigned technician, makes them the only one, replacing any others, and writes that assignment to the linked work order in your property management system (AppFolio assigned users, RentVine technicians) when the technician is linked to a user there; writes the scheduled date and a note to the linked work order in your property management system; and sends the technician the visit's digital ticket by text and email as their contact details allow. Mason does not message the tenant or ask the tenant to confirm the time; your property management system may send its own notifications. Inspection visits are not supported. Send start and end as exact instants with an offset. Mason converts them to the visit's scheduling timezone, which the returned block's timezone field names, and both must fall on the same calendar day there. For booking, the scheduling timezone is the property market's timezone, otherwise the work order's, otherwise the tenant's or your account's. Requires a read & write API key. Send a unique Idempotency-Key per appointment; a retry with the same key and body returns the original response instead of booking again. Parameters: - `Idempotency-Key` (string, required): Client-chosen key, unique per appointment you intend to book (for example a UUID). 1-255 printable ASCII characters. Reusing it with a different body returns 422. JSON request body (required): `CreateSchedulingBlockRequest` Confirmed appointment to book for an in-house visit. - `visitId` (string (uuid), required): Mason visit UUID to schedule. Resolve it using GET /visits?masonWorkOrderId=. Assigning a technician with PATCH /work-orders/{id} creates the visit. - `technicianContactId` (string (uuid), required): Mason contact UUID of an active technician (contactType tech) in this account. Resolve contacts using GET /contacts. - `start` (string (date-time), required): Appointment start. Must be in the future. ISO 8601 timestamp with Z or a UTC offset, in whole minutes. Mason converts it to the visit's scheduling timezone; start and end must fall on the same calendar day there. - `end` (string (date-time), required): Appointment end. Must be after start. ISO 8601 timestamp with Z or a UTC offset, in whole minutes. Mason converts it to the visit's scheduling timezone; start and end must fall on the same calendar day there. - `notes` (string): Note recorded with the change in the work-order activity log. When the request books or moves an appointment, it is also stored as the new appointment's notes. Mason records a default note when omitted. 201 response: `{ data: SchedulingBlock | object, pmsSyncError: string | null }` The booked appointment. pmsSyncError reports a failed property management system write; the appointment exists in Mason either way. A replayed response carries the header Idempotent-Replayed: true. - `pmsSyncError`: Why the change could not be written to the property management system, or null. The change is recorded in Mason either way; do not repeat it. Other responses: - `400`: Invalid JSON or fields, a missing Idempotency-Key, a visit or technician that does not match this account, an inspection visit, a start in the past, or a window that does not fall on one local day. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `409`: The visit is completed or cancelled or its work order is closed, a visit on the work order (this one or another) already has a confirmed appointment or live scheduling proposal, the technician has an overlapping appointment or blocked time, or the visit's scheduling timezone changed during the request. Nothing was booked; this response is stored under the Idempotency-Key, so retry with a new key. A request with this Idempotency-Key is still in progress or did not finish. - `422`: This Idempotency-Key was already used with a different request. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request failed and may have partially taken effect, in Mason or in your property management system. The same Idempotency-Key replays this failure; check the record before retrying with a new key. #### GET /scheduling-blocks/{id} Stored availability and appointment blocks. blockType=confirmed identifies a confirmed work window. A confirmed block and tenantConfirmationStatus are distinct facts. Range endpoints can be null for unbounded windows. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: SchedulingBlock }` #### PATCH /scheduling-blocks/{id} Moves, reassigns, or cancels a confirmed in-house visit appointment. A move or reassignment is what Mason's property manager agent does; a cancellation is the same action as unscheduling a visit in Mason's dispatcher. The body selects which: start and end and/or technicianContactId, or blockType canceled; fields from the two cannot be combined. Recorded in the work-order activity log as this API key. Requires a read & write API key. No Idempotency-Key is read. With start and end, and optionally technicianContactId, Mason replaces the appointment: the block in the path becomes superseded and the response returns a new confirmed block with a new id (its supersedesBlockId is not set). The appointment keeps its technician unless you send technicianContactId; either way the technician must be an active technician in this account. Mason checks the technician for overlapping appointments or blocked time, records the new scheduled date on the work order and in your property management system with a note, makes the technician the visit's only assigned technician, replacing any others, and writes that assignment to the linked work order in your property management system (AppFolio assigned users, RentVine technicians) when the technician is linked to a user there, and sends the technician a new digital ticket. When the move changes the technician, only the new technician is contacted; the previous technician is not told. Mason cancels pending tenant confirmation follow-ups for the replaced appointment and does not message the tenant about the new time or ask the tenant to confirm it. Inspection visits are not supported. Send start and end as exact instants with an offset. Mason converts them to the visit's scheduling timezone, which the returned block's timezone field names, and both must fall on the same calendar day there. For a move, the scheduling timezone is the work order's timezone, otherwise the tenant's, otherwise the appointment's existing timezone. With technicianContactId alone, Mason reassigns the visit and keeps the appointment time; the block keeps its id. The appointment must not have started and the new technician must be free at that time. The new technician replaces the visit's primary technician (other assigned technicians stay) and the assignment is synchronized to your property management system. Neither technician is notified, no digital ticket is sent, and the tenant is not messaged. With blockType canceled, and optionally reason, Mason cancels the appointment and returns its visit to unscheduled. canceled, spelled with one L as the blockType field reads, is the only accepted blockType. Mason marks the visit's confirmed blocks canceled with your reason appended to their notes, clears the scheduled appointment on the work order in Mason, cancels pending tenant confirmation follow-ups for the appointment, and updates connected technician calendars. The scheduled date in your property management system is cleared only for an appointment that originated there (imported from AppFolio or RentVine): Mason clears it there first, and if that fails, nothing changes in Mason. An appointment booked or moved in Mason, including through POST or PATCH /scheduling-blocks, keeps its scheduled date in your property management system; clear it there if needed. The visit stays open; book it again with POST /scheduling-blocks. Mason sends no tenant or technician message, so a tenant who already confirmed the appointment is not told it was cancelled. Repeating a cancellation is safe: when the block is already canceled, the request returns 200 with the current block and nothing runs. The check is not a lock: two identical requests arriving at the same moment can both run the operation, which cancels only blocks that are still confirmed. Scheduling blocks record no update timestamp, so this endpoint returns no ETag and does not evaluate If-Match. Because a moved appointment supersedes its block, repeating a move against the old block ID returns 409. Parameters: - `id` (string (uuid), required): Mason UUID returned in the scheduling-blocks id field. JSON request body (required): `UpdateSchedulingBlockRequest` Change to a confirmed appointment: a move or reassignment, or a cancellation with blockType canceled. notes is accepted only with a move or reassignment; reason only with blockType. 200 response: `{ data: SchedulingBlock | object, pmsSyncError: string | null }` The appointment after the change: the replacement block for a move, the same block for a reassignment, or the same block with blockType canceled for a cancellation, including one that was already canceled. pmsSyncError reports a failed property management system write for a move or reassignment; the change is recorded in Mason either way. It is always null for a cancellation. - `pmsSyncError`: Why the change could not be written to the property management system, or null. The change is recorded in Mason either way; do not repeat it. Other responses: - `400`: Invalid JSON or fields; a body that combines move or reassignment fields (start, end, technicianContactId, notes) with blockType or reason, or a blockType other than canceled; a technician that is not an active technician in this account (the one sent, or for a move without technicianContactId the appointment's current technician); a move without technicianContactId on an appointment with no technician; an inspection visit; or a window that does not fall on one local day. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No scheduling block exists in this account with that ID, or the ID is not a valid UUID. - `409`: For a move or reassignment: the block is not a confirmed appointment (for example superseded by an earlier move, or cancelled), is not linked to an in-house visit, the visit or work order is closed, the technician has an overlapping appointment or blocked time, the appointment has started (for a reassignment), or the visit changed during the request. For a cancellation: the block is neither confirmed nor canceled (for example superseded by a move), is not linked to a work order, or its work order is completed or cancelled. Nothing was changed. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. - `502`: For a reassignment, the technician's connected calendar could not be checked; nothing was changed. For a cancellation, your property management system could not clear the appointment, so Mason left it unchanged; or it was cleared there but Mason could not finish updating it. The error says which. ### units #### GET /units Current stored units, authorized through their owning property. Vacancy and inspection fields do not provide complete historical cycles or a dated door-count denominator. Units are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. This API does not create units. PATCH /units/{id} changes only the Mason-maintained access field, which Mason does not write to your property management system; every other field is read-only here and changes only in your property management system. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `propertyId` (string (uuid)): Exact match on propertyId. Owning Mason property ID; resolve using GET /properties/{id}. - `pmsUniqueId` (string): Exact match on pmsUniqueId. Stored provider unit identifier, meaningful within the owning property/account. - `isOccupied` (boolean): Exact match on isOccupied. Current stored occupancy flag. - `leasingReadiness` (string): Exact match on leasingReadiness. Current stored leasing readiness. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. 200 response: `{ data: Unit[], pagination: Pagination }` #### GET /units/{id} Current stored units, authorized through their owning property. Vacancy and inspection fields do not provide complete historical cycles or a dated door-count denominator. Units are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. This API does not create units. PATCH /units/{id} changes only the Mason-maintained access field, which Mason does not write to your property management system; every other field is read-only here and changes only in your property management system. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. The ETag response header identifies this version of the unit; send it as If-Match when changing it. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Unit }` #### PATCH /units/{id} Changes only the Mason-maintained fields listed in the request schema, as a property manager would in Mason. Nothing is written to your property management system, and fields that come from it stay read-only here and change only there. Requires a read & write API key. Sending a value a field already has is safe and needs no Idempotency-Key; nothing else runs. access is the only field. It is merged key by key into the unit's stored access details: an omitted key is left unchanged and null clears a key. Access details are not yet included in the dispatch messages Mason sends vendors and technicians; that is a separate change. Mason's phone line can read a unit's access details to a vendor who calls in and is verified as assigned to an active work order at that unit with permission to enter. Optimistic concurrency: this endpoint and GET /units/{id} return an ETag derived from the unit's updatedAt. Sending it as If-Match is strongly recommended: when the unit changed since that read, the request returns 412 before any field is applied. updatedAt advances when the unit's Mason-maintained fields change, and also when your property management system sync refreshes the record; AppFolio units are refreshed nightly, so a 412 can occur without another API client. The check is not a lock: a change made while this request is being applied is not detected. Parameters: - `id` (string (uuid), required): Mason UUID returned in the unit id field. - `If-Match` (string): Strongly recommended. The ETag from your latest read of this unit; a comma-separated list is accepted. When none matches the current ETag, the request returns 412 and applies nothing. * matches any existing unit. When omitted, the changes apply regardless of intervening updates. JSON request body (required): `UpdateUnitRequest` Mason-maintained unit fields to change. Omitted fields are left unchanged. Fields that come from your property management system cannot be changed here. - `access` (object, required): Access details. Maintained in Mason for this record and not written to your property management system. Not yet included in vendor or technician dispatches; that is a separate change. Send at least one key: omitted keys are left unchanged and null clears a key. 200 response: `{ data: Unit }` The unit after the change, or unchanged when every field already had the requested value. Other responses: - `400`: Invalid JSON or fields, an unrecognized field or one only your property management system can set, a body with no access field, or an access object with none of gateCode, lockboxCode, alarmCode, or notes, or with a code over 64 characters. - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No unit exists in this account with that ID, or the ID is not a valid UUID. - `412`: If-Match did not match the current ETag. No field was applied. The ETag header carries the current version; read the unit again before retrying. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. ### emails #### GET /emails Stored email content and sender provenance for your account. No trimming, classification, ingestion, or sending occurs on read. Work-order filters match the primary link only. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `contactId` (string (uuid)): Exact match on masonContactId. Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. - `threadId` (string): Exact match on threadId. Stored provider thread or conversation ID. - `inBound` (boolean): Exact match on inBound. True for received email; false for sent email. - `senderRole` (string): Exact match on senderRole. Recorded sender role; legacy values are returned unchanged. - `source` (string): Exact match on source. Stored communication origin label, returned verbatim. It is attribution, not proof of delivery or a newly inferred classification. - `provider` (string): Exact match on provider. Recorded communication transport provider, returned verbatim; null means none was recorded. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `sentAfter` (string (date-time)): Inclusive lower bound on sentAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `sentBefore` (string (date-time)): Inclusive upper bound on sentAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `receivedAfter` (string (date-time)): Inclusive lower bound on receivedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `receivedBefore` (string (date-time)): Inclusive upper bound on receivedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. 200 response: `{ data: Email[], pagination: Pagination }` #### GET /emails/{id} Stored email content and sender provenance for your account. No trimming, classification, ingestion, or sending occurs on read. Work-order filters match the primary link only. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Email }` ### estimates #### GET /estimates Stored vendor estimates and recorded approval decisions. Money is cents. Legacy PM-prefixed decision fields can represent owner decisions; ownerMessageDrafted does not prove delivery. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by createdAt descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. - `vendorContactId` (string (uuid)): Exact match on vendorContactId. Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. - `status` (string): Exact match on status. Stored estimate lifecycle state. superseded means the vendor replaced this estimate with a newer one; any earlier approval no longer applies. - `source` (string): Exact match on source. Stored estimate source, such as vendor_sms, vendor_call, vendor_email, pm_inbox_email, manual, or appfolio. - `approvedByRole` (string): Exact match on approvedByRole. Role recorded as approving the estimate; null means none recorded. - `createdAfter` (string (date-time)): Inclusive lower bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `createdBefore` (string (date-time)): Inclusive upper bound on createdAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `updatedAfter` (string (date-time)): Inclusive lower bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. - `updatedBefore` (string (date-time)): Inclusive upper bound on updatedAt. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. Related-record changes do not necessarily advance this timestamp. 200 response: `{ data: Estimate[], pagination: Pagination }` #### GET /estimates/{id} Stored vendor estimates and recorded approval decisions. Money is cents. Legacy PM-prefixed decision fields can represent owner decisions; ownerMessageDrafted does not prove delivery. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. The ETag response header identifies this version of the estimate; send it as If-Match when deciding it. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: Estimate }` #### PATCH /estimates/{id} Approves or rejects the estimate as a property manager does in Mason, selected by status in the body. Recorded in the work-order activity log as this API key. Requires a read & write API key. With status approved, the estimate's status becomes approved_for_work with approvedByRole (pm unless you send owner), approvedAmount (the quoted amount unless you send approvedAmount), and pmApprovedBy set to this API key's name. Mason then moves the work order to work_in_progress when its status is created, reviewed, or estimated; records the approval on the estimate in your property management system where Mason supports estimates, logging rather than failing when that write fails; unless skipReschedule is true, supersedes the work order's confirmed appointments, clears its scheduled date, and sends the tenant a scheduling link to collect availability for the follow-up visit where Mason's tenant scheduling applies to the work order; and, when approvedByRole is pm, emails the property owner that work is proceeding, unless owner outreach is paused or no owner email is on file. Send approvedByRole: owner to record an approval the property owner gave outside Mason, for example in your owner portal; Mason then sends the owner no email and otherwise handles the approval as above. Approving does not dispatch a vendor or send the vendor a message. With status rejected, the estimate's status becomes rejected and any approval is withdrawn. Mason records the rejection on the linked AppFolio estimate, when there is one, then queues its vendor follow-up agent to tell the quoting vendor, usually within a few minutes. The agent writes that message itself, by email on the vendor's estimate or dispatch thread or by text, and shares your reason only when it is appropriate for the vendor to read. No notice is sent when Mason is stopped on the work order, the account is not live, or Mason has no contact for the vendor. Rejecting an approved estimate does not cancel a vendor dispatch. Repeating a decision is safe and needs no Idempotency-Key: when the estimate is already approved and you send approved, or already rejected and you send rejected, the request returns 200 with the current estimate and nothing runs, so no side effect repeats. approvedAmount, approvedScope, and skipReschedule are then ignored; an approved estimate keeps its recorded amount and scope. When you also send approvedByRole and the estimate is already approved with a different recorded approvedByRole, the request returns 409 instead and nothing runs; omitting approvedByRole never returns 409. The check is not a lock: two identical requests arriving at the same moment can both run the operation, as in the Mason app. Sending the opposite decision reverses the earlier one, as in Mason. Optimistic concurrency: this endpoint and GET /estimates/{id} return an ETag derived from the estimate's updatedAt. Sending it as If-Match is strongly recommended: when the estimate changed since that read, the request returns 412 before anything runs. updatedAt advances on any change to the estimate record, including changes made by Mason's own processing and property management system sync, so a 412 can occur without another API client. The check is not a lock: a change made while this request is being applied is not detected. A retry after a lost response that re-sends the If-Match value read before the first attempt returns 412, because the first attempt changed the record; re-read the record and compare its state before retrying. Parameters: - `id` (string (uuid), required): Mason UUID returned in the estimate id field. - `If-Match` (string): Strongly recommended. The ETag from your latest read of this estimate; a comma-separated list is accepted. When none matches the current ETag, the request returns 412 and nothing runs. * matches any existing estimate. When omitted, the decision applies regardless of intervening updates. JSON request body (required): `UpdateEstimateRequest` Estimate decision, selected by status. approvedAmount, approvedScope, approvedByRole, and skipReschedule are accepted only with approved; reason and rejectedByRole only with rejected. 200 response: `{ data: Estimate }` The estimate after the decision, or unchanged when it was already in the requested state. Other responses: - `400`: Invalid JSON or fields, a missing or unknown status, or a field that belongs to the other status (approvedAmount, approvedScope, approvedByRole, and skipReschedule are accepted only with approved; reason only with rejected). - `401`: Missing, malformed, invalid, inactive, or expired bearer token. - `403`: The API key is read-only. Create a read & write key to use this endpoint. - `404`: No estimate exists in this account with that ID, or the ID is not a valid UUID. - `409`: The estimate is already approved with a recorded approvedByRole different from the one you sent, or its status is superseded because the vendor replaced it with a newer estimate. Nothing ran. The recorded approver of an approved estimate does not change; omit approvedByRole to accept the estimate as already approved, which returns 200 unchanged. A superseded estimate cannot be approved or rejected; decide the replacement estimate instead. - `412`: If-Match did not match the current ETag. Nothing ran. The ETag header carries the current version; read the estimate again before retrying. - `429`: Too many requests for this property management account. Retry after the number of seconds in the Retry-After header. - `500`: The request could not be completed. The response contains a generic error message. ### work-order-activity-logs #### GET /work-order-activity-logs Allowlisted stored business events, authorized through the owning work order. Unsupported event types/versions and malformed selected payloads are excluded before pagination and from detail reads. No complete-history guarantee; absence is not proof an action never occurred. Sorts by timestamp and ID descending. Filters are combined with AND, except the documented id/ids aliases. Unknown query parameters and filter values outside a parameter's documented enum return 400. Empty values for supported filters are ignored. Results are ordered by timestamp descending, then by ID descending. Continue with nextCursor and the same filters. Cursor pagination is a stable keyset walk on that order: a row present when the traversal starts, and still matching the filters when its page is read, is neither skipped nor returned twice across pages. Each page's filters, including any date bounds, are evaluated against current values rather than a value captured when the traversal began: a row that changes before its own page is read is returned with its current values, and may newly match the filters even though it did not match when the traversal started; a row that changes after its own page has already been read is not folded into the current traversal. In both cases, the next incremental run, using this run's cutoff as its next lower bound, picks up the change. Parameters: - `limit` (integer): Page size, normally 1–100. Defaults to 50 when omitted, zero, or not parseable as an integer; other parsed values are clamped to 1–100. - `cursor` (string): Opaque nextCursor from the preceding page. Omit for the first page; do not construct or modify cursor values. - `masonWorkOrderId` (string (uuid)): Exact match on masonWorkOrderId. Owning Mason work-order ID. Always present; resolve using GET /work-orders/{id}. - `eventType` (string): Exact match on eventType. Allowlisted stored business event type; determines the details structure. - `after` (string (date-time)): Inclusive lower bound on timestamp. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. - `before` (string (date-time)): Inclusive upper bound on timestamp. Use an ISO 8601 timestamp with a timezone. Invalid dates match no records. 200 response: `{ data: WorkOrderActivity[], pagination: Pagination }` #### GET /work-order-activity-logs/{id} Allowlisted stored business events, authorized through the owning work order. Unsupported event types/versions and malformed selected payloads are excluded before pagination and from detail reads. No complete-history guarantee; absence is not proof an action never occurred. Sorts by timestamp and ID descending. Returns one record by its Mason UUID. Missing records and records outside the token's property management account both return 404. Parameters: - `id` (string (uuid), required): Mason UUID returned in the record id field. This is not a provider identifier or display number. 200 response: `{ data: WorkOrderActivity }` ### on-call-coverage #### GET /on-call-coverage Returns recorded effective coverage for the requested time window, ordered by coverage start, rotation name, then row ID. Projected coverage is excluded. This endpoint returns the full requested range and does not use limit/cursor pagination or a detail endpoint. Absence of a record does not establish complete coverage history. Parameters: - `start` (string (date-time), required): Beginning of the requested time window. Use an ISO 8601 timestamp with a timezone. start must precede end and the range must not exceed 400 days. - `end` (string (date-time), required): End of the requested time window. Use an ISO 8601 timestamp with a timezone. start must precede end and the range must not exceed 400 days. 200 response: `{ data: OnCallCoverage[] }` ### documentation #### GET /openapi.json Public OpenAPI 3.1 JSON document bundled with the API deployment. No token is needed. info.version is a content-derived contract revision; it changes when this specification changes. ETag supports identifying the published document. It contains documentation only and performs no customer-data reads. 200 response: `object` #### GET /changelog Public Markdown changelog of API contract changes, newest first. No token is needed. Each dated entry lists Added, Changed, Fixed, Deprecated, and Breaking changes and names the specification revision (info.version) that introduced them. ETag is derived from the changelog content. It contains documentation only and performs no customer-data reads. ## Response models ### Pagination Cursor pagination information. Fetch pages until hasMore is false. Reads are not an immutable historical snapshot. - `hasMore`: boolean — Whether another page of matching records exists at the time of this read. - `nextCursor`: string — Opaque continuation token. Present only when hasMore is true; omitted on the last page. Reuse the same resource and filters with this cursor. ### Error Error response returned instead of a data envelope: a human-readable message, a stable machine-readable code, and the request ID. - `error`: string — Human-readable error message for people. It can change; branch on code instead. No diagnostic stack or execution details are returned. - `code`: string; values: `unauthorized`, `token_expired`, `insufficient_scope`, `account_not_live`, `unknown_resource`, `not_found`, `method_not_allowed`, `invalid_parameter`, `invalid_body`, `idempotency_key_required`, `idempotency_in_progress`, `idempotency_request_unfinished`, `idempotency_key_reused`, `precondition_failed`, `conflict`, `rate_limited`, `pms_write_failed`, `dispatch_failed`, `calendar_unavailable`, `internal_error` — Stable machine-readable error code. A code keeps its meaning once published; new codes may be added, so handle an unrecognized code by its HTTP status. Codes: - unauthorized: The Authorization header is missing or malformed, or the bearer token is unknown or revoked. - token_expired: The bearer token has expired. Create a new API key. - insufficient_scope: The API key is read-only and the request needs a read & write key. - account_not_live: Work-order creation becomes available once the account is live in Mason. - unknown_resource: The path does not name a supported resource or endpoint. - not_found: No record with that ID exists in this account, or the ID is not a valid UUID. - method_not_allowed: The resource does not support this HTTP method. - invalid_parameter: A query parameter is unknown or has an invalid value; the message names it. - invalid_body: The request body is not valid JSON, has unknown fields, or a field value was rejected; the message explains which. - idempotency_key_required: The write requires an Idempotency-Key header: 1-255 printable ASCII characters, unique per write. - idempotency_in_progress: Another request with this Idempotency-Key is still being processed. Retry later with the same key. - idempotency_request_unfinished: An earlier request with this Idempotency-Key did not finish. Check whether it took effect before retrying with a new key. - idempotency_key_reused: This Idempotency-Key was already used with a different request. - precondition_failed: If-Match did not match the current ETag. Nothing was applied; read the record again. - conflict: The change conflicts with the record's current state; the message explains the conflict. - rate_limited: Too many requests for this account. Retry after the Retry-After header. - pms_write_failed: A write to the property management system, or the Mason update that follows it, failed; the message says which. - dispatch_failed: The vendor or technician dispatch could not be completed; the message explains why. - calendar_unavailable: The technician's connected calendar could not be checked. Nothing was changed; retry shortly. - internal_error: The request could not be completed. For a write, the message says whether it may have partially taken effect. - `requestId`: string — Identifier of this request, the same value as the X-Mason-Request-Id response header. Quote it when contacting Mason support. A replayed idempotent response carries the ID of the replaying request. ### OnCallCoverage One recorded on-call coverage segment. Results are ordered by coverage start, rotation name, and row ID. - `id`: string — Opaque coverage-row identifier. It is not a UUID and has no detail endpoint. - `rotationId`: string (uuid) — Mason on-call rotation ID. - `rotationName`: string — Recorded rotation name, or the current name when no snapshot is available. - `markets`: string[] — Recorded property-class coverage labels for the rotation; an empty array means no labels are recorded. - `scheduledShiftStart`: string (date-time) — Original scheduled shift start. - `scheduledShiftEnd`: string (date-time) — Original scheduled shift end. - `coverageStart`: string (date-time) — Start of this effective coverage segment within the requested range. - `coverageEnd`: string (date-time) — End of this effective coverage segment within the requested range. - `contactId`: string (uuid) | null — Covered technician/contact Mason ID, when available. - `contactName`: string — Recorded contact name; Unknown contact when a name is unavailable. - `contactEmail`: string | null - `source`: string; values: `rotation`, `override` — Whether coverage comes from the rotation or an override. - `overrideReason`: string | null - `overrideRecurrence`: string | null; values: `weekly`, `biweekly` — Override repetition; null indicates no repeating override. - `canCorrect`: boolean — Whether the current application permits a correction to this coverage record; this API itself is read-only. - `recordStatus`: string — This endpoint returns recorded coverage only and excludes projected coverage. - `ledgerBlockId`: string (uuid) | null — Scheduling-block ID supporting this coverage record. - `recordedAt`: string (date-time) | null - `recordedBy`: string | null - `ledgerSource`: string | null - `recordReason`: string | null ### WorkOrder Maintenance work orders belonging to your property management account. Priority describes office attention; isEmergency indicates an emergency. Returned values reflect current stored state, not a historical snapshot. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `11111111-1111-4111-8111-111111111111`. - `description`: string — Recorded description of the maintenance request. Examples: `Kitchen faucet is leaking under the sink.`. - `status`: string — Stored work-order lifecycle status. Current values: created, reviewed, estimated, work_in_progress, duplicate, cancelled, not_handled_by_mason, work_done, closed. Historical values are returned unchanged. This is not visit or message-delivery status. Examples: `reviewed`. - `propertyAddress`: string | null — Formatted address associated with the request. Examples: `123 Example St, Springfield, IL 62701`. - `source`: string — Stored intake channel, commonly pms:portal, pms:text, pms:email, pms:call, pms:system, mason:portal, mason:text, mason:email, mason:call, mason:system, or mason:recurring. It describes the origin/channel, not the requester identity. Legacy values are returned unchanged. Examples: `mason:portal`. - `workOrderType`: string — Stored work category: reactive, reactive:recall, preventative, turn, recurring, misc, inspection, or an inspection subtype such as inspection:annual, inspection:move_in, inspection:move_out, inspection:pre_move_out, inspection:onboarding, inspection:semi_annual, or inspection:drive_by. Legacy values are returned unchanged. Examples: `reactive`. - `friendlyId`: string | null — Human-readable Mason work-order number, when assigned. Use id for API lookups. Examples: `WO-1042`. - `isEmergency`: boolean — Whether the work order is currently marked as an emergency. Examples: `false`. - `hasEntryPermission`: boolean | null — Recorded permission to enter: true permits entry, false denies entry, and null means permission was not recorded. Examples: `true`. - `propertyId`: string (uuid) | null — Linked Mason property ID. Resolve using GET /properties/{id}. Examples: `22222222-2222-4222-8222-222222222222`. - `unitId`: string (uuid) | null — Linked Mason unit ID, when present. Resolve using GET /units/{id}. Examples: `33333333-3333-4333-8333-333333333333`. - `tenantContactId`: string (uuid) | null — Primary tenant Mason contact ID. Resolve using GET /contacts/{id}. Examples: `44444444-4444-4444-8444-444444444444`. - `vendorContactId`: string (uuid) | null — Assigned vendor or in-house technician Mason contact ID. Resolve using GET /contacts/{id}; contactType may be vendor or tech. - `intakeMetadata`: object | null — Allowlisted intake metadata. Only reportedByRole is public; missing or malformed role values are omitted. Null means no metadata object is recorded. Examples: `[object Object]`. - `priority`: string | null; values: `low`, `normal`, `urgent` — Effective attention priority. Uses the explicit user override when present, otherwise the assessment. Null means no recognized priority is recorded. Examples: `normal`. - `createdAt`: string (date-time) — When the work order was created, as an ISO 8601 timestamp. Examples: `2026-09-21T15:04:05.000Z`. - `updatedAt`: string (date-time) — Timestamp used by updatedAfter/updatedBefore. Includes initial import into Mason as well as stored work-order updates. Related-record changes and representation-only backfills may not advance it. Examples: `2026-09-21T15:30:00.000Z`. - `assignedUsers`: string[] | null — Stored work-order assignee email addresses; no user identities are inferred. Examples: `manager@example.com`. - `requesterContactId`: string (uuid) | null — Mason contact ID of the recorded requester; null means unresolved or not recorded. Examples: `44444444-4444-4444-8444-444444444444`. - `scopeOfWork`: string | null — Stored scope of the work to perform. Examples: `Replace the kitchen faucet cartridge and check the supply lines for leaks.`. - `entryPermissionConditions`: string | null — Stored conditions attached to tenant entry permission. Examples: `Call 30 minutes before arriving; dog on site.`. - `handledBy`: string — Stored handling party, such as mason or property_manager; returned unchanged. Examples: `mason`. - `workOrderReview`: object | null — Selected stored business classifications; no surrounding analysis is exposed. Examples: `[object Object]`. - `completionAnalysis`: object | null — Stored completion metrics, without recalculation or surrounding analysis. - `vendorWorkOrderRequest`: object | null — Selected stored vendor scheduling fact. - `relationships`: object[] — Incoming and outgoing relationships with both endpoints in your account. Empty when none qualify. Changes to these rows do not advance updatedAt. Examples: ``. - `pms`: object | null — Optional linked PMS identity. Null when no supported provider record is linked within your account. Provider-record changes do not necessarily advance updatedAt. Examples: `[object Object]`. ### Call Recorded phone calls scoped to your property management account. Call content and recording availability depend on the stored record. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb`. - `phoneNumber`: string — Phone number associated with the other party. Examples: `+15555550123`. - `summary`: string | null — Stored readable summary of the call. Examples: `Tenant reported a leaking kitchen faucet and asked for a repair.`. - `transcript`: string | null — Stored call transcript as text. Examples: `Tenant: Hi, my kitchen faucet is leaking under the sink. Mason: Thanks, I have opened a work order for you.`. - `callDuration`: integer | null — Recorded call duration in seconds. Examples: `184`. - `recordingUrl`: string | null — URL of the stored call recording, when available. Examples: `https://recordings.example.com/calls/example-1042.mp3`. - `inBound`: boolean — True for an incoming call; false for an outgoing call. Examples: `true`. - `callStatus`: string | null — Stored call outcome, such as completed, failed, busy, or no-answer. Examples: `completed`. - `callerRole`: string | null — Recorded role of the other party, such as tenant, vendor, or property_manager. Legacy roles may occur. Examples: `tenant`. - `callType`: string | null — Recorded business purpose of the call, such as vendor follow-up, scheduling, or work-order creation. Examples: `work_order_creation`. - `requiresAction`: boolean — Whether the call is currently marked as requiring attention. Examples: `false`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `masonContactId`: string (uuid) | null — Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. Examples: `44444444-4444-4444-8444-444444444444`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-21T15:00:00.000Z`. - `endedAt`: string (date-time) | null — When the call ended, if recorded, as an ISO 8601 timestamp. Examples: `2026-09-21T15:03:04.000Z`. - `callSid`: string | null — Stored call provider identifier. Examples: `CA00000000000000000000000000example`. - `relatedWorkOrderIds`: string (uuid)[] | null — Stored related work-order IDs whose referenced work orders belong to your account. Order and duplicates are preserved; null means no array was recorded. This does not change the primary masonWorkOrderId filter. Examples: ``. ### TextMessage Stored inbound and outbound text messages for your property management account. Work-order filtering matches the primary linked work order. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `cccccccc-cccc-4ccc-8ccc-cccccccccccc`. - `phoneNumber`: string — Phone number associated with the other party. Examples: `+15555550123`. - `message`: string — Stored message text. Examples: `A plumber will contact you today to schedule the faucet repair.`. - `mediaUrls`: string[] | null — Stored URLs of media attached to this message. - `inBound`: boolean — True for a received message; false for a sent message. Examples: `false`. - `senderRole`: string | null — Recorded sender role, such as tenant, vendor, owner, tech, or property_manager. Legacy roles may occur. Examples: `property_manager`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `masonContactId`: string (uuid) | null — Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. Examples: `44444444-4444-4444-8444-444444444444`. - `fromNumber`: string | null — Recorded sending phone number. Examples: `+15555550100`. - `toNumber`: string | null — Recorded receiving phone number. Examples: `+15555550123`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-21T15:10:00.000Z`. - `source`: string — Stored communication origin label, returned verbatim. It is attribution, not proof of delivery or a newly inferred classification. Examples: `mason`. - `provider`: string | null — Recorded communication transport provider, returned verbatim; null means none was recorded. Examples: `twilio`. - `messageSid`: string | null — Stored SMS provider message identifier. Examples: `SM00000000000000000000000000example`. - `conversationSid`: string | null — Stored messaging conversation identifier. - `relatedWorkOrderIds`: string (uuid)[] | null — Stored related work-order IDs whose referenced work orders belong to your account. Order and duplicates are preserved; null means no array was recorded. This does not change the primary masonWorkOrderId filter. - `metadata`: object | null — Recorded sender provenance. Only sentByUserEmail is exposed; missing keys remain absent. ### Invoice Stored invoices, matching results, and review/billing state. Top-level monetary amounts are integer cents. Nested document data is returned as stored, except that line-item quantities and amounts, matched-record identifiers, and party addresses are returned in their documented types; a stored value that cannot be read as that type is null. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `99999999-9999-4999-8999-999999999999`. - `invoiceNumber`: string | null — Invoice number printed on the document. Examples: `INV-2041`. - `invoiceDate`: string (date-time) | null — Invoice date as an ISO 8601 timestamp. Examples: `2026-09-24T00:00:00.000Z`. - `dueDate`: string (date-time) | null — Invoice due date as an ISO 8601 timestamp. Examples: `2026-10-24T00:00:00.000Z`. - `totalAmount`: integer | null — Invoice total in integer cents. Examples: `24500`. - `currency`: string | null — Stored currency code, commonly USD. Examples: `USD`. - `subtotal`: integer | null — Invoice subtotal in integer cents. Examples: `22700`. - `tax`: integer | null — Invoice tax in integer cents. Examples: `1800`. - `vendorInfo`: object | null — Vendor information recorded on the invoice document. Examples: `[object Object]`. - `billToInfo`: object | null — Bill-to party recorded on the invoice document. Examples: `[object Object]`. - `items`: object[] | null — Stored invoice line items. Quantities and amounts are numbers read from the recorded values; amounts are not converted to cents. Examples: `[object Object],[object Object]`. - `workOrderNumber`: string | null — Human-readable work-order number associated with the invoice. Examples: `1042-1`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `confidence`: string | null — Stored extraction confidence or processing classification. Common values include high, medium, low, failed, failed_with_partial_data, and no_pdf_attachments. Examples: `high`. - `isInvoice`: boolean — Whether this record is classified as an invoice. Examples: `true`. - `additionalNotes`: string | null — Additional notes recorded with the invoice. - `isRecurring`: boolean — Whether the invoice was classified as recurring. Examples: `false`. - `recurringDescription`: string | null — Recorded description of the recurring charge. - `matchedVendor`: object | null — Vendor matched to the invoice. Provider-specific keys may be present; this is a stored snapshot, not a fresh vendor lookup. Examples: `[object Object]`. - `matchedProperty`: object | null — Property matched to the invoice. Stored provider snapshot; optional keys vary by provider and record age. Examples: `[object Object]`. - `matchedWorkOrder`: object | null — Work order matched to the invoice. Identifiers here belong to the connected system; use the invoice masonWorkOrderId for Mason API joins. Examples: `[object Object]`. - `emailMessageId`: string | null — Stored source email identifier; it is not necessarily a Mason record UUID. Examples: ``. - `emailSender`: string | null — Source email sender. Examples: `billing@plumbing.example.com`. - `emailSubject`: string | null — Source email subject. Examples: `Invoice INV-2041 for 123 Example St`. - `filename`: string | null — Primary document filename. Examples: `INV-2041.pdf`. - `pdfBlobUrl`: string | null — URL of the primary document. Despite this field name, the document can be an image or another supported attachment type; inspect attachment mimeType. Examples: `https://files.example.com/invoices/INV-2041.pdf`. - `attachments`: object[] | null — Stored attachment records; optional metadata varies by document. Examples: `[object Object]`. - `status`: string — Stored invoice processing status, such as pending, processed, incomplete, not_invoice, reprocessing, failed, failed_with_partial_data, or no_pdf_attachments. Examples: `processed`. - `reviewed`: boolean — Whether the invoice has been marked reviewed by the office/accounting process. Examples: `false`. - `reviewReason`: string | null — Recorded review disposition, commonly bill_created, completed_outside_mason, duplicate, not_an_invoice, or archived. - `pmApproved`: boolean — Whether the invoice is marked approved. Examples: `false`. - `pmApprovedAt`: string (date-time) | null — Recorded approval timestamp. - `pmApprovedBy`: string | null — Recorded approving user identifier, typically an email address. - `pmRejected`: boolean — Whether the invoice is marked rejected. Examples: `false`. - `pmRejectedAt`: string (date-time) | null — Recorded rejection timestamp. - `pmRejectionReason`: string | null — Recorded business reason for rejection. - `pmsUploadedAt`: string (date-time) | null — When bill upload to the connected property management system was recorded. - `pmsBillUrl`: string | null — Link to the bill created in the connected property management system. - `workDoneDescription`: string | null — Recorded description of the completed work used for billing. Examples: `Replaced the kitchen faucet cartridge.`. - `selectedGlAccountId`: string | null — Selected ledger account identifier in the connected system. Examples: `6400`. - `selectedTerms`: string | null — Stored payment terms selected for billing. Examples: `Net 30`. - `selectedMarkupPercentage`: number | null — Markup selected for billing, as a number (percent; 10 means ten percent). It is a signed adjustment and can be negative. Null when no markup is selected or the stored value is not a decimal number with an optional trailing percent sign. Examples: `10`. - `selectedMarkupAccountId`: string | null — Selected ledger account identifier for markup. Examples: `4800`. - `selectedDiscountPercentage`: number | null — Discount selected for billing, as a number (percent; 10 means ten percent). Null when no discount is selected, the selection was cleared, or the stored value is not a decimal number with an optional trailing percent sign. - `shareInvoiceWithOwner`: boolean | null — Whether the invoice is marked to be shared with the owner; this flag alone does not prove delivery. Examples: `false`. - `suggestedBillSplits`: object[] | null — Recorded proposed bill allocations, when available. - `tenantChargeSignal`: object | null — Recorded recommendation about charging the resident. - `needsReview`: boolean — Whether the recorded validation result requires review. Examples: `false`. - `agreementValidation`: object | null — Stored invoice validation result describing whether the vendor, property, and work order agree. The explanation is also displayed in the invoice review UI. Optional historical processing metadata may be present. Examples: `[object Object]`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-24T17:00:00.000Z`. - `updatedAt`: string (date-time) — When this record was last updated, as an ISO 8601 timestamp. Changes to related records do not necessarily advance this value. Examples: `2026-09-24T17:05:00.000Z`. ### Contact People and businesses in your property management account. Hydrate contact references with a detail request or the id/ids filters, even when the contact predates your incremental sync window. Tenant (resident), owner, and vendor contacts are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. Technician, property-manager, emergency, and unidentified contacts are maintained in Mason. POST /contacts creates vendor contacts only: in RentVine or AppFolio first when your account is connected to one, otherwise in Mason alone. PATCH /contacts/{id} changes only the Mason-maintained fields preferredContactMethod, language, stopAllComms, and notes, which Mason does not write to your property management system; every other field is read-only here, and fields your property management system provides change only there. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `44444444-4444-4444-8444-444444444444`. - `name`: string — Contact display name. Examples: `Jordan Example`. - `phoneNumber`: string | null — Recorded contact phone number. Examples: `+15555550123`. - `email`: string | null — Recorded contact email address. Examples: `jordan@example.com`. - `contactType`: string — Contact category, such as tenant, vendor, owner, tech, or property_manager. Examples: `tenant`. - `isActive`: boolean — Whether the contact is marked active. Examples: `true`. - `preferredContactMethod`: string | null — The contact's preferred communication method: phone, text, or email; null when none is recorded. Historical values are returned unchanged. Maintained in Mason and not written to your property management system; change it with PATCH /contacts/{id}. Examples: `text`. - `language`: string | null — The contact's preferred language as a language code, such as en (English), es (Spanish), pt, pa, or zh, optionally with subtags such as en-US; null when none is recorded. Historical values are returned unchanged. Maintained in Mason and not written to your property management system; change it with PATCH /contacts/{id}. Examples: `en`. - `pmsUniqueId`: string | null — Contact identifier from the connected property management system; scoped to your account. Examples: `tenant-5521`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-01-10T12:00:00.000Z`. - `updatedAt`: string (date-time) — When this record was last updated, as an ISO 8601 timestamp. Changes to related records do not necessarily advance this value. Examples: `2026-09-21T15:10:00.000Z`. - `sentiment`: object | null — Latest stored contact-level sentiment assessment, not per-work-order rating history. Null when absent, invalid, or its source work order is unavailable within your account. Summary, evidence, and other internal context are not exposed. Examples: `[object Object]`. - `stopAllComms`: boolean — Whether Mason-initiated texts, emails, calls, and group-chat adds to this contact are blocked. The block also covers other contacts in your account with the same phone number or email address, and it does not stop inbound replies from the contact. Maintained in Mason and not written to your property management system; change it with PATCH /contacts/{id}. Examples: `false`. - `notes`: string | null — Internal notes about this contact, shown to the Mason team and Mason's agents and never to the contact; null when none are recorded. Maintained in Mason and not written to your property management system; change them with PATCH /contacts/{id}. Examples: `Works nights; text before 10 AM only for emergencies.`. ### Visit Maintenance visit records. Assigned technicians describe assignment, not completed labor. Nested JSON is returned as stored; changes to related records do not necessarily update this record timestamp. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `77777777-7777-4777-8777-777777777777`. - `logDate`: string (date-time) — Date represented by this visit, serialized as an ISO 8601 timestamp. Examples: `2026-09-25T00:00:00.000Z`. - `controlNumber`: string — Legacy visit reference. Use id for API lookups. Examples: `CN-1042`. - `location`: string — Recorded location of the work. Examples: `123 Example St, Unit 2B, Springfield, IL 62701`. - `problem`: string — Recorded description of the issue. Examples: `Kitchen faucet is leaking under the sink.`. - `instructions`: string | null — Dispatch instructions for the technician. Examples: `Replace the faucet cartridge and check the supply lines.`. - `priority`: string — Recorded visit priority, commonly urgent, normal, or low. Examples: `normal`. - `status`: string | null — Recorded disposition, commonly Complete, Still Working, Canceled, Estimate, or an empty string. Examples: `Still Working`. - `noteReviewed`: boolean | null — Whether the office has reviewed the recorded work notes; null means not recorded. Examples: `false`. - `initialRouting`: string[] — Legacy routing technician names. Use assignedTechs for contact identifiers. Examples: ``. - `workNotes`: object | null — Recorded work notes. Keys may be absent on older records. - `estimate`: object | null — Recorded visit estimate. Amounts retain their stored representation. - `activity`: object[] — Stored visit activity entries; this is not a complete work-order history. Examples: `[object Object]`. - `assignedTechs`: string[] — Assigned technician Mason contact IDs. These are assignments, not evidence of hours worked. Examples: `66666666-6666-4666-8666-666666666666`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-22T14:00:00.000Z`. - `updatedAt`: string (date-time) — When this record was last updated, as an ISO 8601 timestamp. Changes to related records do not necessarily advance this value. Examples: `2026-09-25T18:30:00.000Z`. - `techHours`: object[] — Recorded applied labor hours. Related-row edits do not necessarily advance the visit updatedAt; refetch the relevant cohort. Examples: `[object Object]`. - `techClockSessions`: object[] — Supporting clock observations. Do not add their duration to techHours: closed sessions may already be applied there. No attendance or extra labor totals are inferred. Examples: ``. ### Property Properties associated with your account, including stored address, ownership, and provider details. No geocoding or owner lookup runs as part of this read. Properties are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. This API does not create properties. PATCH /properties/{id} changes only the Mason-maintained fields access and pauseOwnerOutreach, which Mason does not write to your property management system; every other field is read-only here and changes only in your property management system. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `22222222-2222-4222-8222-222222222222`. - `formattedAddress`: string — Formatted property address. Examples: `123 Example St, Springfield, IL 62701`. - `address`: string — First address line. Examples: `123 Example St`. - `address2`: string | null — Second address line. - `city`: string — City. Examples: `Springfield`. - `state`: string — State or region. Examples: `IL`. - `zip`: string — Postal code. Examples: `62701`. - `isActive`: boolean — Whether the property is marked active. Examples: `true`. - `multiUnit`: boolean — Whether the property is marked as containing multiple units. Examples: `true`. - `ownerInfo`: object[] | null — Stored property owner records; null means no ownership data was recorded, while an empty array contains no listed owners. Examples: `[object Object]`. - `propertyDetails`: object | null — Stored property details. Available keys depend on the connected property management system, and additional keys may be present; documented keys have the types shown. Examples: `[object Object]`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-01-05T12:00:00.000Z`. - `updatedAt`: string (date-time) — When this record was last updated, as an ISO 8601 timestamp. Changes to related records do not necessarily advance this value. Examples: `2026-09-20T03:15:00.000Z`. - `propertyClass`: string[] | null — Stored property classifications. Examples: `residential`. - `access`: object — Access details for this property. Every key is always present. Maintained in Mason and not written to your property management system, and not yet included in vendor or technician dispatches. Lockboxes synced from your property management system are not included. Change them with PATCH /properties/{id}. Examples: `[object Object]`. - `pauseOwnerOutreach`: boolean — Whether Mason's messages to this property's owner are paused: owner approvals, dispatch and completion emails, estimate emails, and agent owner messages. Owner messages your team explicitly asks Mason to send are still sent. False when never set. Maintained in Mason and not written to your property management system; change it with PATCH /properties/{id}. Examples: `false`. ### SchedulingBlock Stored availability and appointment blocks. blockType=confirmed identifies a confirmed work window. A confirmed block and tenantConfirmationStatus are distinct facts. Range endpoints can be null for unbounded windows. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `88888888-8888-4888-8888-888888888888`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `visitId`: string (uuid) | null — Linked Mason visit ID; resolve using GET /visits/{id}. Examples: `77777777-7777-4777-8777-777777777777`. - `contactId`: string (uuid) | null — Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. Examples: `66666666-6666-4666-8666-666666666666`. - `blockType`: string — Stored scheduling category, such as tenant_available, vendor_available, tentative, confirmed, tech_scheduling_proposal, blocked, superseded, on_call_assignment, on_call_override, or on_call_cancellation. Examples: `confirmed`. - `source`: string — Stored scheduling source, such as tenant_portal, vendor_portal, pm_manual, or ai_suggested. Examples: `pm_manual`. - `tenantConfirmationStatus`: string | null — Stored tenant confirmation status, commonly pending or confirmed; null means legacy or unknown. Examples: `pending`. - `timezone`: string — IANA timezone associated with the scheduling block, for example America/Los_Angeles. Examples: `America/Chicago`. - `linkKey`: string (uuid) — Identifier grouping blocks from one scheduling submission. Examples: `15151515-1515-4151-8151-151515151515`. - `notes`: string | null — Stored scheduling notes. Examples: `Scheduled through the Mason API.`. - `start`: string (date-time) | null — Beginning of the stored scheduling window; null means the range is unbounded at the start. Examples: `2026-09-25T14:00:00.000Z`. - `end`: string (date-time) | null — End of the stored scheduling window; null means the range is unbounded at the end. Examples: `2026-09-25T16:00:00.000Z`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-22T14:05:00.000Z`. - `expiresAt`: string (date-time) | null — Expiration timestamp, when one is recorded. Expired records are not automatically excluded by this endpoint. - `createdBy`: string | null — Stored creator identifier for this block. Examples: `External API key "Example integration"`. - `supersedesBlockId`: string (uuid) | null — Mason scheduling-block ID superseded by this record, when present. ### Unit Current stored units, authorized through their owning property. Vacancy and inspection fields do not provide complete historical cycles or a dated door-count denominator. Units are mirrored from your connected property management system, the system of record: change them there and Mason picks up the change on its next sync. This API does not create units. PATCH /units/{id} changes only the Mason-maintained access field, which Mason does not write to your property management system; every other field is read-only here and changes only in your property management system. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `33333333-3333-4333-8333-333333333333`. - `propertyId`: string (uuid) — Owning Mason property ID; resolve using GET /properties/{id}. Examples: `22222222-2222-4222-8222-222222222222`. - `pmsUniqueId`: string | null — Stored provider unit identifier, meaningful within the owning property/account. Examples: `unit-2b`. - `unitNumber`: string | null — Stored unit label, such as A or 1B; null when none is meaningful. Examples: `2B`. - `normalizedUnitNumber`: string | null — Stored normalized unit label; no normalization runs on this read. Examples: `2B`. - `unitFormattedAddress`: string | null — Stored formatted unit address. Examples: `123 Example St, Unit 2B, Springfield, IL 62701`. - `ownerInfo`: object[] | null — Stored unit owners. Null means inherit property owners; [] explicitly means no current unit owner. This API does not materialize inherited owners. - `bedrooms`: integer | null — Stored bedroom count. Examples: `2`. - `bathrooms`: integer | null — Stored full bathroom count. Examples: `1`. - `halfBathrooms`: integer | null — Stored half bathroom count. Examples: `0`. - `squareFeet`: integer | null — Stored floor area in square feet. Examples: `850`. - `rent`: integer | null — Stored monthly rent in integer cents. Examples: `145000`. - `isOccupied`: boolean — Current stored occupancy flag. Examples: `true`. - `vacantSince`: string (date) | null — PM-local date when the current vacancy episode began; null means occupied or unknown onset. - `leasingReadiness`: string; values: `unknown`, `not_ready`, `ready` — Current stored leasing readiness. Examples: `unknown`. - `inspectionDeferUntil`: string (date) | null — Recorded calendar date before which the unit should not be considered for another inspection. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-01-05T12:00:00.000Z`. - `updatedAt`: string (date-time) — When this record was last updated, as an ISO 8601 timestamp. Changes to related records do not necessarily advance this value. Examples: `2026-09-20T03:15:00.000Z`. - `access`: object — Access details for this unit. Every key is always present. Maintained in Mason and not written to your property management system, and not yet included in vendor or technician dispatches. Lockboxes synced from your property management system are not included. Change them with PATCH /units/{id}. Examples: `[object Object]`. ### Email Stored email content and sender provenance for your account. No trimming, classification, ingestion, or sending occurs on read. Work-order filters match the primary link only. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `dddddddd-dddd-4ddd-8ddd-dddddddddddd`. - `messageId`: string — Stored provider message ID. Examples: `msg-000000000001-example`. - `emailMessageId`: string | null — Stored RFC Internet Message-ID header. Examples: ``. - `threadId`: string | null — Stored provider thread or conversation ID. Examples: `thread-000000000001-example`. - `inBound`: boolean — True for received email; false for sent email. Examples: `false`. - `from`: string — Stored sender email address. Examples: `maintenance@pm.example.com`. - `to`: string[] — Stored recipient addresses. Examples: `owner@example.com`. - `cc`: string[] | null — Stored CC addresses; null when absent. - `subject`: string — Stored email subject. Examples: `Work order 1042-1 at 123 Example St: work is proceeding`. - `snippet`: string | null — Stored preview text. Examples: `The estimate for the kitchen faucet repair was approved.`. - `bodyText`: string | null — Stored plain-text message body. Examples: `Hello, The estimate for the kitchen faucet repair at 123 Example St was approved and work is proceeding.`. - `trimmedBody`: string | null — Stored bounded body; returned without further trimming. Examples: `The estimate for the kitchen faucet repair at 123 Example St was approved and work is proceeding.`. - `source`: string — Stored communication origin label, returned verbatim. It is attribution, not proof of delivery or a newly inferred classification. Examples: `mason`. - `provider`: string | null — Recorded communication transport provider, returned verbatim; null means none was recorded. Examples: `gmail`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `relatedWorkOrderIds`: string (uuid)[] | null — Stored related work-order IDs whose referenced work orders belong to your account. Order and duplicates are preserved; null means no array was recorded. This does not change the primary masonWorkOrderId filter. - `senderRole`: string | null — Recorded sender role; legacy values are returned unchanged. Examples: `property_manager`. - `masonContactId`: string (uuid) | null — Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. Examples: `ffffffff-ffff-4fff-8fff-ffffffffffff`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-23T16:00:00.000Z`. - `sentAt`: string (date-time) | null — Stored sending timestamp; null does not match a sentAfter/sentBefore bound. Examples: `2026-09-23T16:00:02.000Z`. - `receivedAt`: string (date-time) | null — Stored receiving timestamp; null does not match a receivedAfter/receivedBefore bound. - `metadata`: object | null — Recorded sender provenance. Only sentByUserEmail is exposed; missing keys remain absent. Examples: `[object Object]`. ### Estimate Stored vendor estimates and recorded approval decisions. Money is cents. Legacy PM-prefixed decision fields can represent owner decisions; ownerMessageDrafted does not prove delivery. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa`. - `masonWorkOrderId`: string (uuid) | null — Linked Mason work-order ID. Resolve it using GET /work-orders/{id}; null means no work order is linked. Examples: `11111111-1111-4111-8111-111111111111`. - `vendorContactId`: string (uuid) | null — Linked Mason contact ID. Resolve it using GET /contacts/{id}; null means no contact is linked. Examples: `55555555-5555-4555-8555-555555555555`. - `vendorName`: string | null — Recorded vendor name. Examples: `Example Plumbing Co.`. - `vendorPhone`: string | null — Recorded vendor phone number. Examples: `+15555550188`. - `status`: string; values: `requested`, `received`, `sent_for_approval`, `approved_for_work`, `rejected`, `superseded` — Stored estimate lifecycle state. superseded means the vendor replaced this estimate with a newer one; any earlier approval no longer applies. Examples: `sent_for_approval`. - `estimateAmount`: integer | null — Stored quoted total in integer cents. Examples: `45000`. - `estimateDescription`: string | null — Stored description of the quoted work. Examples: `Replace the kitchen faucet and both supply lines.`. - `lineItems`: object | null[] | null — Stored estimate items, projected without recalculation; null means no valid item array was recorded. Examples: `[object Object],[object Object]`. - `estimateNotes`: string | null — Stored business notes accompanying the estimate. - `source`: string | null — Stored estimate source, such as vendor_sms, vendor_call, vendor_email, pm_inbox_email, manual, or appfolio. Examples: `vendor_email`. - `sourceMessageId`: string (uuid) | null — Stored source message record ID, when available. - `sourceCallId`: string (uuid) | null — Stored source Mason call ID, when available. - `approvedByRole`: string | null; values: `pm`, `owner` — Role recorded as approving the estimate; null means none recorded. - `approvedAmount`: integer | null — Stored approved amount in integer cents; may differ from the quote. - `approvedScope`: string | null — Stored approved work scope; may differ from the quote. - `pmApprovedAt`: string (date-time) | null — Recorded approval timestamp; may represent a PM or owner decision. - `pmApprovedBy`: string | null — Recorded approver identifier, typically an email; may identify a PM or owner. - `pmRejectedAt`: string (date-time) | null — Recorded rejection timestamp; may represent a PM or owner decision. - `pmRejectionReason`: string | null — Stored business reason for rejection. - `ownerMessageDrafted`: boolean — Whether an owner message was drafted; this is not evidence that it was sent or delivered. Examples: `false`. - `createdAt`: string (date-time) — When the record was created in Mason, as an ISO 8601 timestamp. Examples: `2026-09-22T19:00:00.000Z`. - `updatedAt`: string (date-time) — When this record was last updated, as an ISO 8601 timestamp. Changes to related records do not necessarily advance this value. Examples: `2026-09-22T19:30:00.000Z`. ### WorkOrderActivity Allowlisted stored business events, authorized through the owning work order. Unsupported event types/versions and malformed selected payloads are excluded before pagination and from detail reads. No complete-history guarantee; absence is not proof an action never occurred. Sorts by timestamp and ID descending. - `id`: string (uuid) — Unique Mason record ID. Use this value in the corresponding detail endpoint. Examples: `eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee`. - `masonWorkOrderId`: string (uuid) — Owning Mason work-order ID. Always present; resolve using GET /work-orders/{id}. Examples: `11111111-1111-4111-8111-111111111111`. - `eventType`: string; values: `status_changed`, `work_order_pm_assignee_added`, `work_order_pm_assignee_replaced`, `tenant_confirmed_scheduling`, `tenant_confirmation_status_updated`, `tenant_canceled_scheduling`, `pms_schedule_reconciled`, `schedule_change_reviewed`, `emergency_escalation_started`, `emergency_escalation_acknowledged`, `emergency_escalation_cancelled`, `invoice_approved`, `invoice_rejected`, `invoice_unapproved`, `invoice_rejection_undone`, `vendor_invoice_submitted`, `tenant_work_completed`, `owner_work_completed`, `vendor_work_completed`, `vendor_work_completion_undone`, `visit_rescheduled`, `visit_unscheduled`, `portal_visit_unscheduled`, `visit_reassigned`, `vendor_scheduled_time_confirmed`, `vendor_work_order_confirmed`, `pms_vendor_accepted`, `vendor_reject_work`, `tech_accepted_job`, `tech_denied_job`, `estimate_requested`, `estimate_sent_for_owner_approval`, `estimate_approved`, `estimate_rejected` — Allowlisted stored business event type; determines the details structure. Examples: `status_changed`. - `timestamp`: string (date-time) — Stored event occurrence timestamp. after/before are inclusive bounds on this field. Examples: `2026-09-23T16:00:00.000Z`. - `source`: string — Stored event origin label, returned unchanged. No execution payload or trace accompanies it. Examples: `external_api`. - `details`: object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object | object — Event-specific business fields only. Interpret this structure with eventType. Unrecognized payload versions and malformed selected fields are excluded from list and detail reads. History can be incomplete. Examples: `[object Object]`. ### CreateWorkOrderRequest Work order to create. - `description`: string — Problem description as the requester reported it. - `propertyId`: string (uuid) — Mason property UUID from the properties in this account. - `unitId`: string (uuid) — Mason unit UUID. Must belong to propertyId. Defaults to the tenant's leased unit when tenantContactId is given. - `tenantContactId`: string (uuid) — Mason contact UUID of the tenant reporting the issue. Must lease within propertyId (and unitId when given). - `workOrderType`: string; values: `reactive`, `reactive:recall`, `preventative`, `turn`, `recurring`, `misc`, `inspection`, `inspection:move_in`, `inspection:move_out`, `inspection:pre_move_out`, `inspection:onboarding`, `inspection:semi_annual`, `inspection:drive_by`, `inspection:annual` — Canonical work-order type. When omitted, Mason's review classifies the work order. - `source`: string; values: `mason:portal`, `mason:call`, `mason:text`, `pms:portal` — How the request reached you. mason:portal (default) records the property manager as requester; mason:call and mason:text record the tenant; pms:portal records a request first entered in the property management system. ### UpdateWorkOrderRequest Fields to change. Omitted fields are left unchanged. Fields are applied in the order workOrderType, description, priority, isEmergency, assignment, status; the request stops at the first failure. - `description`: string — Replacement description. Also synchronized to the property management system. - `status`: string; values: `created`, `reviewed`, `estimated`, `work_in_progress`, `duplicate`, `cancelled`, `not_handled_by_mason`, `work_done`, `closed` — New lifecycle status. Applied last, after the other fields in the request. Starts the same follow-up workflows as a status change in Mason and is synchronized to the property management system. - `statusNote`: string — Note recorded with the status change. Requires status. - `priority`: string | null; values: `urgent`, `normal`, `low` — Attention priority override. null clears the override and returns to Mason's assessed priority. - `workOrderType`: string; values: `reactive`, `reactive:recall`, `preventative`, `turn`, `recurring`, `misc`, `inspection`, `inspection:move_in`, `inspection:move_out`, `inspection:pre_move_out`, `inspection:onboarding`, `inspection:semi_annual`, `inspection:drive_by`, `inspection:annual` — Canonical work-order type. Subtypes such as inspection:move_in are distinct values. - `isEmergency`: boolean — Emergency flag. false cancels any open emergency escalation for this work order. - `assignment`: object | object — Vendor or in-house technician to assign, through the same dispatch path as a manual dispatch from the Mason app, with the differences listed on PATCH /work-orders/{id}. Mason uses the dispatch form's defaults: the work order's current scope as instructions, an estimate request when Mason's review recommends one, the property's synced gate and lockbox codes, the vendor's preferred dispatch channel when it can be reached that way (otherwise email, or a text message when the vendor has no email), and a target completion date three business days out. No not-to-exceed amount is sent; the property's maintenance limit applies where one is configured. A vendor assignment starts Mason's vendor outreach and follow-up and sends no tenant or owner notification. A technician assignment creates and sends the technician's visit ticket and sends no owner notification; when the account has tenant scheduling enabled, the tenant is texted a link to schedule the visit. A previously dispatched vendor is sent a cancellation. The assigned contact is then returned in the work-order vendorContactId field. The assignment is synchronized to the property management system: if it rejects a vendor assignment, nothing is sent and the request returns 502; if it rejects a technician assignment, the Mason assignment stands and the response reports pmsAssignmentError. Assigning a vendor already assigned or dispatched on this work order, or a technician already assigned to it, returns 409. ### WorkOrderUpdateError A work-order update that stopped at a failing field, with the standard error fields. - `error`: string — Human-readable error message for people. It can change; branch on code instead. No diagnostic stack or execution details are returned. - `code`: string; values: `unauthorized`, `token_expired`, `insufficient_scope`, `account_not_live`, `unknown_resource`, `not_found`, `method_not_allowed`, `invalid_parameter`, `invalid_body`, `idempotency_key_required`, `idempotency_in_progress`, `idempotency_request_unfinished`, `idempotency_key_reused`, `precondition_failed`, `conflict`, `rate_limited`, `pms_write_failed`, `dispatch_failed`, `calendar_unavailable`, `internal_error` — Stable machine-readable error code. A code keeps its meaning once published; new codes may be added, so handle an unrecognized code by its HTTP status. Codes: - unauthorized: The Authorization header is missing or malformed, or the bearer token is unknown or revoked. - token_expired: The bearer token has expired. Create a new API key. - insufficient_scope: The API key is read-only and the request needs a read & write key. - account_not_live: Work-order creation becomes available once the account is live in Mason. - unknown_resource: The path does not name a supported resource or endpoint. - not_found: No record with that ID exists in this account, or the ID is not a valid UUID. - method_not_allowed: The resource does not support this HTTP method. - invalid_parameter: A query parameter is unknown or has an invalid value; the message names it. - invalid_body: The request body is not valid JSON, has unknown fields, or a field value was rejected; the message explains which. - idempotency_key_required: The write requires an Idempotency-Key header: 1-255 printable ASCII characters, unique per write. - idempotency_in_progress: Another request with this Idempotency-Key is still being processed. Retry later with the same key. - idempotency_request_unfinished: An earlier request with this Idempotency-Key did not finish. Check whether it took effect before retrying with a new key. - idempotency_key_reused: This Idempotency-Key was already used with a different request. - precondition_failed: If-Match did not match the current ETag. Nothing was applied; read the record again. - conflict: The change conflicts with the record's current state; the message explains the conflict. - rate_limited: Too many requests for this account. Retry after the Retry-After header. - pms_write_failed: A write to the property management system, or the Mason update that follows it, failed; the message says which. - dispatch_failed: The vendor or technician dispatch could not be completed; the message explains why. - calendar_unavailable: The technician's connected calendar could not be checked. Nothing was changed; retry shortly. - internal_error: The request could not be completed. For a write, the message says whether it may have partially taken effect. - `requestId`: string — Identifier of this request, the same value as the X-Mason-Request-Id response header. Quote it when contacting Mason support. A replayed idempotent response carries the ID of the replaying request. - `field`: string; values: `workOrderType`, `description`, `priority`, `isEmergency`, `assignment`, `status` — The field whose change failed. - `appliedFields`: string[] — Fields from this request already applied before the failure. They are not rolled back. - `pmsAssignmentError`: string — Present only when a technician assignment in this request was applied in Mason but the property management system rejected it. The Mason assignment stands; update the property management system yourself. ### UpdateEstimateRequest Estimate decision, selected by status. approvedAmount, approvedScope, approvedByRole, and skipReschedule are accepted only with approved; reason and rejectedByRole only with rejected. ### UpdateInvoiceRequest Invoice decision, selected by decision. The invoice's status field is its processing state and does not change. confirmMissingInvoiceNumber and confirmInactiveProperty are accepted only with approved; reason only with rejected. ### CreateSchedulingBlockRequest Confirmed appointment to book for an in-house visit. - `visitId`: string (uuid) — Mason visit UUID to schedule. Resolve it using GET /visits?masonWorkOrderId=. Assigning a technician with PATCH /work-orders/{id} creates the visit. - `technicianContactId`: string (uuid) — Mason contact UUID of an active technician (contactType tech) in this account. Resolve contacts using GET /contacts. - `start`: string (date-time) — Appointment start. Must be in the future. ISO 8601 timestamp with Z or a UTC offset, in whole minutes. Mason converts it to the visit's scheduling timezone; start and end must fall on the same calendar day there. - `end`: string (date-time) — Appointment end. Must be after start. ISO 8601 timestamp with Z or a UTC offset, in whole minutes. Mason converts it to the visit's scheduling timezone; start and end must fall on the same calendar day there. - `notes`: string — Note recorded with the change in the work-order activity log. When the request books or moves an appointment, it is also stored as the new appointment's notes. Mason records a default note when omitted. ### UpdateSchedulingBlockRequest Change to a confirmed appointment: a move or reassignment, or a cancellation with blockType canceled. notes is accepted only with a move or reassignment; reason only with blockType. ### UpdateContactRequest Mason-maintained contact fields to change. Omitted fields are left unchanged. Fields that come from your property management system cannot be changed here. - `preferredContactMethod`: string; values: `phone`, `text`, `email` — The contact's preferred communication method: phone, text, or email. Maintained in Mason and not written to your property management system. - `language`: string — The contact's preferred language: a lowercase language code with optional subtags, such as en, es, or en-US, 2-16 characters. Maintained in Mason and not written to your property management system. - `stopAllComms`: boolean — true blocks every Mason-initiated text, email, call, and group-chat add to this contact, and to any other contact in your account with the same phone number or email address. It does not stop inbound replies: messages from the contact still reach Mason. false removes this contact's block. Maintained in Mason and not written to your property management system. - `notes`: string | null — Internal notes about this contact, shown to the Mason team and Mason's agents and never to the contact, 1-5000 characters. Replaces the current notes; null clears them. Maintained in Mason and not written to your property management system. ### UpdateUnitRequest Mason-maintained unit fields to change. Omitted fields are left unchanged. Fields that come from your property management system cannot be changed here. - `access`: object — Access details. Maintained in Mason for this record and not written to your property management system. Not yet included in vendor or technician dispatches; that is a separate change. Send at least one key: omitted keys are left unchanged and null clears a key. ### UpdatePropertyRequest Mason-maintained property fields to change. Omitted fields are left unchanged. Fields that come from your property management system cannot be changed here. - `access`: object — Access details. Maintained in Mason for this record and not written to your property management system. Not yet included in vendor or technician dispatches; that is a separate change. Send at least one key: omitted keys are left unchanged and null clears a key. - `pauseOwnerOutreach`: boolean — true mutes every Mason-to-owner message about this property: owner approvals, dispatch and completion emails, estimate emails, and agent owner messages. Owner messages your team explicitly asks Mason to send are still sent. While paused, work that needs owner approval cannot get it through Mason. false resumes owner messages. This is the same setting as the Pause owner outreach switch on the Mason property page. Maintained in Mason and not written to your property management system. ### CreateContactRequest The contact to create. Only vendor contacts can be created today: send contactType "vendor", name, and at least one of phoneNumber or email. - `contactType`: string — The kind of contact to create. vendor is the only value. - `name`: string — The vendor's business name, 1-200 characters, such as Example Plumbing Co. Mason converts an all-capital or all-lowercase name to title case. Put the person you deal with in firstName and lastName. - `firstName`: string — The first name of your contact person at the vendor, 1-100 characters. Sent to AppFolio as the vendor's contact first name when your account is connected to AppFolio; not stored for RentVine or Mason-only vendors. - `lastName`: string — The last name of your contact person at the vendor, 1-100 characters. Sent to AppFolio as the vendor's contact last name when your account is connected to AppFolio; not stored for RentVine or Mason-only vendors. - `phoneNumber`: string — The vendor's US phone number, such as (555) 555-0188 or +15555550188. Mason stores it in E.164 form (+15555550188); a value that is not a valid 10-digit US number returns 400. - `email`: string (email) — The vendor's email address.