TenantPoint / Developers

TENANTPOINT DEVELOPER PLATFORM · PUBLIC ALPHA

Build secure TenantPoint integrations

Connect commercial property records, vendors, and work orders to your team’s tools through documented, scoped resources. Use workspace-scoped API keys, a synthetic sandbox, and signed webhooks—not browser credentials or an unrestricted accounting API.

Download OpenAPI 3.1Download Postman collection

Access and plans

The public API is alpha, not an unrestricted general-availability promise. Portfolio and Professional include developer access; Custom access is configured by agreement. Owner does not include the API. An active eligible plan and enabled workspace access are required.

An Owner or Admin can check availability in Settings → Developers and create scoped credentials there. Start with the isolated sandbox before accessing operational records. If the panel reports access unavailable, verify the selected workspace and plan, then contact TenantPoint; do not bypass the access check. See plan quotas and the private, invitation-based Zapier integration.

The documented resource catalog below is authoritative. Do not assume undocumented rent-payment, financial-provider, general-ledger, or messaging operations are available through the API.

Quickstart

  1. In TenantPoint, open Settings → Developers as an owner or admin.
  2. Create an isolated sandbox, then create a least-privilege tp_test_ key.
  3. Store the one-time key reveal in your server-side secret manager.
  4. Set TENANTPOINT_API_ORIGIN to the API origin approved for your environment and load TENANTPOINT_API_KEY from your local secret manager.
  5. Call the account endpoint, then a scoped resource endpoint.
curl "$TENANTPOINT_API_ORIGIN/v1/account" \
  -H "Authorization: Bearer $TENANTPOINT_API_KEY"

curl "$TENANTPOINT_API_ORIGIN/v1/vendors?limit=10" \
  -H "Authorization: Bearer $TENANTPOINT_API_KEY"

The response uses a data envelope containing the account, bound workspace and environment. Keep API keys out of browser code, URLs, logs and source control. Dedicated API and developer hostnames are not configured by this guide; use only the operator-approved origin.

Authentication, environments, and requests

API keys

Send Authorization: Bearer <key>. Keys are hashed at rest; the raw value is revealed once on creation. Later list responses cannot retrieve it. Revoke a compromised key and create a replacement.

Live vs test

tp_live_ keys access the live workspace. tp_test_ keys access only the isolated sandbox and its synthetic fixtures. The prefix, stored environment and workspace binding must agree.

Scopes

Grant only the scopes an integration needs. A resource’s write scope grants read access for that same resource only; it does not grant unrelated resource access.

API access requires the organization’s entitlement and administrative enablement. Support sessions cannot manage customer credentials. API keys are server-to-server credentials, not browser credentials; /v1 provides no browser CORS access.

Responses carry x-request-id; error bodies also include request_id. Retain the ID when contacting support. List endpoints use cursor pagination; cursors are opaque and bound to the exact query that created them. Follow pagination.next_cursor until it is null, without changing filters or sort.

Rate-limit failures return HTTP 429. Honor retry-after and the documented rate-limit headers rather than blindly retrying.

Accounts, resources and data formats

An account is the customer organization. Each API key is bound to one workspace; GET /v1/account reports only that bound workspace and environment. Resource IDs are UUIDs, not names. Foreign workspace IDs return not-found responses rather than revealing another customer’s data.

Money values are decimal strings with exactly two fractional digits, such as "1250.00", not JSON numbers or integer minor units. Timestamps use RFC 3339 UTC with a Z suffix; date-only fields use YYYY-MM-DD and month fields use YYYY-MM. Check the generated schema for the exact field format.

A single-resource response is {"data": ...}; lists include data and pagination. On an error, inspect error.code and retain request_id. HTTP 401 indicates key authentication failure; HTTP 403 may indicate missing API enablement or insufficient scopes. HTTP 400/422 indicate request validation issues. Do not retry unchanged invalid inputs.

Webhooks

Create ordinary customer-owned HTTPS endpoints from Settings → Developers. TenantPoint validates destinations server-side and delivers an immutable event body. Human-managed endpoints use HMAC-SHA256; their secret is revealed once on creation or rotation and must be retained in the receiver’s secret manager.

Verify before parsing

For a human-managed endpoint, verify the HMAC over the timestamp, a period and the exact raw request bytes before parsing JSON. Reject signatures outside the five-minute window. Do not stringify a parsed payload to recreate the signed body.

Retry and replay

Transient delivery failures retry with bounded backoff. Explicit replay is available for delivered or dead human-managed deliveries. It queues another delivery attempt for the same immutable event; receivers must deduplicate by event ID.

Zapier ES256

Zapier integration-owned subscriptions use ES256/P-256 with a public key ring, not a symmetric webhook secret. The integration verifies raw body, timestamp, event ID, subscription ID, signing key ID and environment. Private signing keys stay with TenantPoint.

Integration-owned endpoints are labelled as managed. Their receiver URLs are withheld, and ordinary human endpoint controls cannot rotate, disable or replay them. Their owning integration uses the canonical subscription lifecycle instead.

Webhook events and testing

Select only the events your receiver needs:

  • vendor.created
  • vendor.updated
  • work_order.created
  • work_order.updated
  • work_order.note.created

First create the test endpoint and confirm your receiver is ready, then make one approved synthetic vendor, work-order or note change through the supported API. Normal domain processing produces the event and webhook delivery; do not fabricate payloads or copy live records. Review Delivery History and validate the signed event before treating HTTP receipt as a successful integration. SSRF and DNS checks remain enforced server-side.

Sandbox

The sandbox is a separate test workspace with canonical synthetic data, not a clone of live records or a normal operational workspace. Create and reset operations derive the organization from the authenticated session.

Reset replaces synthetic business rows and stale idempotency responses. It preserves the sandbox workspace identity, API keys, webhook configuration, event snapshots and delivery history, without emitting a bootstrap event flood. Existing test keys remain bound to the same sandbox. Test writes suppress customer notifications and provider operations; test webhook dispatch remains intentional.

API reference and tools

This reference is rendered from the same current OpenAPI source as the OpenAPI 3.1 download. It covers the implemented /v1 operations, required scopes, parameters, request bodies and response codes. Contract version: 1.0.0-alpha.

The Postman collection ships with empty baseUrl and apiKey variables, and both allowRequests and allowMutations set to false. Use a current Postman client supporting pm.execution.skipRequest(). Configure an approved HTTPS origin without /v1, supply the key through a local secret/vault mechanism, and obtain separate approval before enabling reads or mutations. Never run the entire collection blindly or save populated secrets in an exported collection.

GET /v1/accountGet account

Identity + capability introspection for the authenticated key's workspace: the account (organization), its workspaces, and capabilities. Requires a valid bearer API key and is authorized for the key's workspace; no resource scope is needed (identity endpoint).

Required scope: valid API key

HTTP 200
OK AccountEnvelope
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
GET /v1/propertiesList properties

Workspace-scoped, cursor-paged property list (read-only). Filters: q (substring on name/address). Sort name|created_at (default name asc). Cursor keyset (sort, id). Scope: `properties:read`.

Required scope: properties:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
qqueryNoCase-insensitive substring match on name or address.
sortqueryNoSort column (default name). Unsupported sorts are rejected (invalid_enum_value).
orderqueryNoasc or desc. Only valid together with sort.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/properties/{id}Get a property

Returns one property by id. An id for another workspace returns 404 resource_not_found -- never 403 and never the row. Scope: `properties:read`.

Required scope: properties:read

Request parameters
NameLocationRequiredDescription
idpathYesProperties id (uuid).
HTTP 200
OK PropertyEnvelope
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/vendorsList vendors

Workspace-scoped, cursor-paged vendor list. Filters: is_active, category (comma-separated, exact match as stored), q (substring on name/email/notes). Sort name|created_at (default name asc). Scope: `vendors:read`.

Required scope: vendors:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
is_activequeryNotrue = active vendors only; false = retired vendors only. Absent = all.
categoryqueryNoComma-separated category values, exact match as stored (no case folding).
qqueryNoCase-insensitive substring match on name, email, or notes.
sortqueryNoSort column (default name). Unsupported sorts are rejected (invalid_enum_value).
orderqueryNoasc or desc. Only valid together with sort.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
POST /v1/vendorsCreate a vendor

Creates a vendor in the authenticated key's workspace. The body is strict: only the fields in CreateVendorInput are accepted. `external_references` is response-only in Phase 2E and is rejected on write. Scope: `vendors:write`. A required Idempotency-Key makes retries safe after a completed response; an equivalent request still in progress returns 409 and must be retried.

Required scope: vendors:write

Request parameters
NameLocationRequiredDescription
Idempotency-KeyheaderYesRequired unique token for this create request. It must be 1-255 ASCII token characters (A-Z, a-z, 0-9, -, _, ., :, /, +) with no spaces. Reuse with an equivalent body replays the completed response; reuse with a different body, or while the original request is in progress, returns 409.

JSON request body: CreateVendorInput (required)

HTTP 201
Created VendorEnvelope
HTTP 400
Validation error. codes: malformed_json, missing_parameter, invalid_parameter. ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 409
Idempotency conflict (idempotency_key_conflict): the key was used for a different request body, or an equivalent request is still in progress. ErrorBody
HTTP 422
Strict request-body validation error. codes: unknown_field, null_not_allowed. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
GET /v1/vendors/{id}Get a vendor

Returns one vendor by id. An id for another workspace returns 404 resource_not_found -- never 403 and never the row. Scope: `vendors:read`.

Required scope: vendors:read

Request parameters
NameLocationRequiredDescription
idpathYesVendors id (uuid).
HTTP 200
OK VendorEnvelope
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
PATCH /v1/vendors/{id}Update a vendor

Partially updates a vendor in the authenticated key's workspace. Omitted fields are unchanged; category, email, phone, and notes accept null to clear their values. An empty object has no change to apply and returns `400 invalid_parameter`. No DELETE endpoint exists; set `is_active` to false to retire a vendor. Scope: `vendors:write`.

Required scope: vendors:write

Request parameters
NameLocationRequiredDescription
idpathYesResource id (uuid).

JSON request body: UpdateVendorInput (required)

HTTP 200
OK VendorEnvelope
HTTP 400
Validation error. codes: malformed_json, invalid_parameter, invalid_uuid. ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 422
Strict request-body validation error. codes: unknown_field, null_not_allowed. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
GET /v1/work-ordersList work orders

Workspace-scoped, cursor-paged work order list. Filters: property_id (verified to the workspace), status, priority (comma-separated), created_* and updated_* timestamp ranges. Sort created_at (default desc). Other catalog filters (tenant_id, category_id, hold_reason, is_emergency, q) are deferred to a later phase. Scope: `work_orders:read`.

Required scope: work_orders:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
property_idqueryNoOnly work orders for this workspace property. A property_id outside your workspace returns 404 resource_not_found.
statusqueryNoComma-separated statuses.
priorityqueryNoComma-separated priorities.
created_afterqueryNoOnly rows created at or after this instant.
created_beforequeryNoOnly rows created at or before this instant.
updated_afterqueryNoOnly rows updated at or after this instant.
updated_beforequeryNoOnly rows updated at or before this instant.
sortqueryNoSort column (default created_at). Unsupported sorts are rejected (invalid_enum_value).
orderqueryNoasc or desc. Only valid together with sort.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
POST /v1/work-ordersCreate a work order

Creates a work order in the authenticated key's workspace. The referenced property and category must be usable within that workspace. The body is strict: only CreateWorkOrderInput fields are accepted, and `external_references` is response-only in Phase 2E. Scope: `work_orders:write`. A required Idempotency-Key makes retries safe after a completed response; an equivalent request still in progress returns 409 and must be retried.

Required scope: work_orders:write

Request parameters
NameLocationRequiredDescription
Idempotency-KeyheaderYesRequired unique token for this create request. It must be 1-255 ASCII token characters (A-Z, a-z, 0-9, -, _, ., :, /, +) with no spaces. Reuse with an equivalent body replays the completed response; reuse with a different body, or while the original request is in progress, returns 409.

JSON request body: CreateWorkOrderInput (required)

HTTP 201
Created WorkOrderEnvelope
HTTP 400
Validation error. codes: malformed_json, missing_parameter, invalid_parameter, invalid_enum_value. ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 409
Idempotency conflict (idempotency_key_conflict): the key was used for a different request body, or an equivalent request is still in progress. ErrorBody
HTTP 422
Strict request-body validation error. codes: unknown_field, null_not_allowed. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
GET /v1/work-orders/{id}Get a work order

Returns one work order by id (full state-machine state). An id for another workspace returns 404 resource_not_found -- never 403 and never the row. Scope: `work_orders:read`.

Required scope: work_orders:read

Request parameters
NameLocationRequiredDescription
idpathYesWork Orders id (uuid).
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
PATCH /v1/work-orders/{id}Update a work order

Partially updates a work order in the authenticated key's workspace. `hold_reason` and `reopen_reason` are permitted only when the request also supplies `status`; status transitions are checked against the work-order state machine. An empty object has no change to apply and returns `400 invalid_parameter`. Scope: `work_orders:write`.

Required scope: work_orders:write

Request parameters
NameLocationRequiredDescription
idpathYesResource id (uuid).

JSON request body: UpdateWorkOrderInput (required)

HTTP 400
Validation error. codes: malformed_json, invalid_parameter, invalid_uuid, invalid_enum_value. ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 409
Illegal work-order status transition (invalid_transition). ErrorBody
HTTP 422
Strict request-body validation error. codes: unknown_field, null_not_allowed. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
GET /v1/documentsList documents

Workspace-scoped, cursor-paged document list (read-only). Filters: property_id (verified to the workspace), kind (comma-separated, exact match), extraction_status (comma-separated), created_* timestamp range, q (substring on filename). Sort created_at (default desc). Scope: `documents:read`.

Required scope: documents:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
property_idqueryNoOnly documents for this workspace property. A property_id outside your workspace returns 404 resource_not_found.
kindqueryNoComma-separated document kind values, exact match as stored (no case folding).
extraction_statusqueryNoComma-separated extraction statuses.
created_afterqueryNoOnly rows created at or after this instant.
created_beforequeryNoOnly rows created at or before this instant.
qqueryNoCase-insensitive substring match on filename.
sortqueryNoSort column (default created_at). Unsupported sorts are rejected (invalid_enum_value).
orderqueryNoasc or desc. Only valid together with sort.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/documents/{id}Get a document

Returns one document by id. An id for another workspace returns 404 resource_not_found -- never 403 and never the row. Scope: `documents:read`.

Required scope: documents:read

Request parameters
NameLocationRequiredDescription
idpathYesDocuments id (uuid).
HTTP 200
OK DocumentEnvelope
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/transactionsList transactions

Workspace-scoped, cursor-paged bank transaction list (read-only). Filters: property_id (verified), status, allocation_type, source (comma-separated), date_* and created_* ranges, q (substring on description/merchant_name). Sort date|created_at (default date desc). Scope: `transactions:read`.

Required scope: transactions:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
property_idqueryNoOnly transactions for this workspace property (as allocated). A property_id outside your workspace returns 404 resource_not_found.
statusqueryNoComma-separated statuses.
allocation_typequeryNoComma-separated allocation types.
sourcequeryNoComma-separated sources.
date_afterqueryNoOnly rows with transaction date at or after this day.
date_beforequeryNoOnly rows with transaction date at or before this day.
created_afterqueryNoOnly rows created at or after this instant.
created_beforequeryNoOnly rows created at or before this instant.
qqueryNoCase-insensitive substring match on description or merchant_name.
sortqueryNoSort column (default date). Unsupported sorts are rejected (invalid_enum_value).
orderqueryNoasc or desc. Only valid together with sort.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/transactions/{id}Get a transaction

Returns one bank transaction by id. An id for another workspace returns 404 resource_not_found -- never 403 and never the row. Scope: `transactions:read`.

Required scope: transactions:read

Request parameters
NameLocationRequiredDescription
idpathYesTransactions id (uuid).
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/paymentsList payments

Workspace-scoped, cursor-paged rent payment list (read-only). Filters: property_id, tenant_id (both verified to the workspace), month (comma-separated YYYY-MM), status, method (comma-separated), received_* date range. Sort received_date|month|created_at (default created_at desc). Scope: `payments:read`.

Required scope: payments:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
property_idqueryNoOnly payments for this workspace property. A property_id outside your workspace returns 404 resource_not_found.
tenant_idqueryNoOnly payments from this workspace tenant. A tenant_id outside your workspace returns 404 resource_not_found.
monthqueryNoComma-separated YYYY-MM billing months (e.g. month=2026-09,2026-10).
statusqueryNoComma-separated statuses.
methodqueryNoComma-separated payment method values, exact match as stored (no case folding).
received_afterqueryNoOnly rows received at or after this day.
received_beforequeryNoOnly rows received at or before this day.
sortqueryNoSort column (default created_at). Unsupported sorts are rejected (invalid_enum_value).
orderqueryNoasc or desc. Only valid together with sort.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
GET /v1/payments/{id}Get a payment

Returns one rent payment by id. An id for another workspace returns 404 resource_not_found -- never 403 and never the row. Scope: `payments:read`.

Required scope: payments:read

Request parameters
NameLocationRequiredDescription
idpathYesPayments id (uuid).
HTTP 200
OK PaymentEnvelope
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
POST /v1/work-orders/{id}/notesAdd a work-order note

Appends a note to a work order in the authenticated key's workspace. Notes are append-only in v1; visibility is `internal` by default and may be `internal` or `tenant`. This endpoint does not implement idempotency in Phase 2E. Scope: `work_orders:write`.

Required scope: work_orders:write

Request parameters
NameLocationRequiredDescription
idpathYesResource id (uuid).

JSON request body: CreateWorkOrderNoteInput (required)

HTTP 201
Created WorkOrderNoteEnvelope
HTTP 400
Validation error. codes: malformed_json, invalid_parameter, invalid_uuid, invalid_enum_value. ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 422
Strict request-body validation error. codes: unknown_field, null_not_allowed. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
GET /v1/work-order-categoriesList work-order categories

List work-order categories

Required scope: work_orders:read

Request parameters
NameLocationRequiredDescription
limitqueryNoNumber of rows to return (max 100; default 50). Values above the cap or non-numeric strings are rejected (invalid_parameter).
cursorqueryNoOpaque cursor from the previous response's pagination.next_cursor. Signed to THIS exact query (filters + sort + order); reusing one from any other query returns invalid_cursor.
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
POST /v1/webhook-subscriptionsCreate an API-key-owned Zapier subscription

Also requires vendors:read or work_orders:read for the selected event. Caller supplies no ownership or secret fields. TenantPoint signs delivery with its dedicated ES256 key; the response contains only the non-secret signing key id. Repeated subscription identity returns 409. No generic endpoint management is exposed.

Required scope: webhooks:write

JSON request body: CreateWebhookSubscriptionInput (required)

HTTP 201
Created with non-secret ES256 signing metadata. Cache-Control: no-store. CreatedWebhookSubscriptionEnvelope
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody
POST /v1/webhook-subscriptions/{id}/disableDisable an owned Zapier subscription

Same owning API key required, even within the same workspace. Foreign IDs return 404. Repeated disable succeeds without deleting history. In-flight requests cannot be recalled.

Required scope: webhooks:write

Request parameters
NameLocationRequiredDescription
idpathYes{"type":"string","format":"uuid"}
HTTP 400
Validation error. codes: unknown_parameter (unknown query key), invalid_enum_value (enum mismatch), invalid_uuid (malformed uuid), invalid_cursor (bad/misused cursor), invalid_parameter (bad limit, missing sort companion, reversed date range). ErrorBody
HTTP 401
Authentication error. codes: api_key_missing, api_key_invalid, api_key_revoked. ErrorBody
HTTP 403
Permission error. codes: api_not_enabled (plan/workspace disabled), api_key_scope_insufficient, forbidden. ErrorBody
HTTP 404
Not found. The resource id does not exist, or identifies a row/workspace the key cannot see (resource_not_found). Foreign-workspace ids are deliberately indistinguishable from missing rows. ErrorBody
HTTP 429
Rate limited (rate_limited). ErrorBody
HTTP 500
Internal error (internal_error). ErrorBody
HTTP 503
Public API is temporarily unavailable because the emergency API shutdown is active (api_temporarily_unavailable). ErrorBody

Request and response schemas

Expand a schema for its exact field types, required fields, enums and validation constraints. JSON Schema references point to other named schemas in this section.

WorkOrderCategory
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "name": {
      "type": "string"
    },
    "default_priority": {
      "type": "string",
      "enum": [
        "low",
        "normal",
        "high",
        "emergency"
      ]
    },
    "is_emergency_default": {
      "type": "boolean"
    }
  },
  "required": [
    "id",
    "name",
    "default_priority",
    "is_emergency_default"
  ],
  "additionalProperties": false
}
CreateWebhookSubscriptionInput
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "integration": {
      "type": "string",
      "const": "zapier"
    },
    "subscription_identity": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "pattern": "^[A-Za-z0-9._:-]+$"
    },
    "target_url": {
      "type": "string",
      "minLength": 9,
      "maxLength": 2048
    },
    "event_type": {
      "type": "string",
      "enum": [
        "vendor.created",
        "vendor.updated",
        "work_order.created",
        "work_order.updated",
        "work_order.note.created"
      ]
    }
  },
  "required": [
    "integration",
    "subscription_identity",
    "target_url",
    "event_type"
  ],
  "additionalProperties": false
}
CreatedWebhookSubscription
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "event_type": {
      "type": "string",
      "enum": [
        "vendor.created",
        "vendor.updated",
        "work_order.created",
        "work_order.updated",
        "work_order.note.created"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "active",
        "disabled"
      ]
    },
    "environment": {
      "type": "string",
      "enum": [
        "live",
        "test"
      ]
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
    },
    "signing_key_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{1,64}$"
    }
  },
  "required": [
    "id",
    "event_type",
    "status",
    "environment",
    "created_at",
    "signing_key_id"
  ],
  "additionalProperties": false
}
WebhookSubscription
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "event_type": {
      "type": "string",
      "enum": [
        "vendor.created",
        "vendor.updated",
        "work_order.created",
        "work_order.updated",
        "work_order.note.created"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "active",
        "disabled"
      ]
    },
    "environment": {
      "type": "string",
      "enum": [
        "live",
        "test"
      ]
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
    }
  },
  "required": [
    "id",
    "event_type",
    "status",
    "environment",
    "created_at"
  ],
  "additionalProperties": false
}
Account
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "environment": {
      "type": "string",
      "enum": [
        "live",
        "test"
      ]
    },
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "name": {
      "type": "string"
    },
    "created_at": {
      "type": "string"
    },
    "workspaces": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "capabilities": {
      "type": "object",
      "properties": {
        "api_enabled": {
          "type": "boolean"
        }
      },
      "required": [
        "api_enabled"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "environment",
    "id",
    "name",
    "created_at",
    "workspaces",
    "capabilities"
  ],
  "additionalProperties": false,
  "description": "An account groups a customer's workspaces."
}
WorkspaceSummary
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "name": {
      "type": "string"
    },
    "created_at": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "name",
    "created_at"
  ],
  "additionalProperties": false,
  "description": "Workspace summary inside an Account"
}
Property
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "name": {
      "type": "string"
    },
    "address": {
      "type": "string"
    },
    "created_at": {
      "type": "string"
    },
    "updated_at": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "name",
    "address",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false,
  "description": "A rental property (read-only in V1)"
}
Vendor
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "category": {
      "type": [
        "string",
        "null"
      ]
    },
    "email": {
      "type": [
        "string",
        "null"
      ]
    },
    "phone": {
      "type": [
        "string",
        "null"
      ]
    },
    "notes": {
      "type": [
        "string",
        "null"
      ]
    },
    "is_active": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string"
    },
    "updated_at": {
      "type": "string"
    },
    "external_references": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "namespace": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]{1,63}$"
          },
          "external_id": {
            "type": "string",
            "pattern": "^[\\x21-\\x7e][\\x20-\\x7e]{0,254}$"
          }
        },
        "required": [
          "namespace",
          "external_id"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "id",
    "name",
    "category",
    "email",
    "phone",
    "notes",
    "is_active",
    "created_at",
    "updated_at",
    "external_references"
  ],
  "additionalProperties": false,
  "description": "A vendor (read/create/update, retire-by-flag)"
}
CreateVendorInput
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "category": {
      "type": [
        "string",
        "null"
      ]
    },
    "email": {
      "type": [
        "string",
        "null"
      ]
    },
    "phone": {
      "type": [
        "string",
        "null"
      ]
    },
    "notes": {
      "type": [
        "string",
        "null"
      ]
    },
    "is_active": {
      "type": "boolean"
    }
  },
  "required": [
    "name"
  ],
  "additionalProperties": false
}
UpdateVendorInput
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "category": {
      "type": [
        "string",
        "null"
      ]
    },
    "email": {
      "type": [
        "string",
        "null"
      ]
    },
    "phone": {
      "type": [
        "string",
        "null"
      ]
    },
    "notes": {
      "type": [
        "string",
        "null"
      ]
    },
    "is_active": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}
WorkOrder
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "property_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "tenant_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "category_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "category_name": {
      "type": "string"
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "description": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "submitted",
        "triaged",
        "assigned",
        "scheduled",
        "in_progress",
        "on_hold",
        "pending_tenant",
        "pending_approval",
        "completed",
        "closed",
        "cancelled"
      ]
    },
    "priority": {
      "type": "string",
      "enum": [
        "low",
        "normal",
        "high",
        "emergency"
      ]
    },
    "hold_reason": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "awaiting_tenant",
            "awaiting_vendor",
            "awaiting_parts",
            "awaiting_approval",
            "other"
          ]
        },
        {
          "type": "null"
        }
      ]
    },
    "is_emergency": {
      "type": "boolean"
    },
    "access_instructions": {
      "type": [
        "string",
        "null"
      ]
    },
    "parent_work_order_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "duplicate_of_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "vendor_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "submitted_by_type": {
      "type": "string",
      "enum": [
        "tenant",
        "manager",
        "system"
      ]
    },
    "created_at": {
      "type": "string"
    },
    "updated_at": {
      "type": "string"
    },
    "closed_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "external_references": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "namespace": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]{1,63}$"
          },
          "external_id": {
            "type": "string",
            "pattern": "^[\\x21-\\x7e][\\x20-\\x7e]{0,254}$"
          }
        },
        "required": [
          "namespace",
          "external_id"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "id",
    "property_id",
    "tenant_id",
    "category_id",
    "category_name",
    "title",
    "description",
    "status",
    "priority",
    "hold_reason",
    "is_emergency",
    "access_instructions",
    "parent_work_order_id",
    "duplicate_of_id",
    "vendor_id",
    "submitted_by_type",
    "created_at",
    "updated_at",
    "closed_at",
    "external_references"
  ],
  "additionalProperties": false,
  "description": "A work order in its full state-machine state"
}
CreateWorkOrderInput
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "property_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "category_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "tenant_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "description": {
      "type": "string"
    },
    "priority": {
      "type": "string",
      "enum": [
        "low",
        "normal",
        "high",
        "emergency"
      ]
    },
    "is_emergency": {
      "type": "boolean"
    },
    "access_instructions": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "property_id",
    "title",
    "category_id"
  ],
  "additionalProperties": false
}
UpdateWorkOrderInput
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "description": {
      "type": "string"
    },
    "access_instructions": {
      "type": [
        "string",
        "null"
      ]
    },
    "priority": {
      "type": "string",
      "enum": [
        "low",
        "normal",
        "high",
        "emergency"
      ]
    },
    "is_emergency": {
      "type": "boolean"
    },
    "status": {
      "type": "string",
      "enum": [
        "submitted",
        "triaged",
        "assigned",
        "scheduled",
        "in_progress",
        "on_hold",
        "pending_tenant",
        "pending_approval",
        "completed",
        "closed",
        "cancelled"
      ]
    },
    "hold_reason": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "awaiting_tenant",
            "awaiting_vendor",
            "awaiting_parts",
            "awaiting_approval",
            "other"
          ]
        },
        {
          "type": "null"
        }
      ]
    },
    "reopen_reason": {
      "type": "string"
    },
    "parent_work_order_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "additionalProperties": false
}
WorkOrderNote
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "work_order_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "body": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4000
    },
    "visibility": {
      "type": "string",
      "enum": [
        "internal",
        "tenant",
        "vendor"
      ]
    },
    "author_type": {
      "type": "string",
      "enum": [
        "manager",
        "tenant",
        "vendor",
        "system"
      ]
    },
    "created_at": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "work_order_id",
    "body",
    "visibility",
    "author_type",
    "created_at"
  ],
  "additionalProperties": false
}
CreateWorkOrderNoteInput
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "body": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4000
    },
    "visibility": {
      "type": "string",
      "enum": [
        "internal",
        "tenant"
      ]
    }
  },
  "required": [
    "body"
  ],
  "additionalProperties": false
}
Document
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "property_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "filename": {
      "type": "string"
    },
    "kind": {
      "type": "string"
    },
    "content_type": {
      "type": "string"
    },
    "size": {
      "type": "integer",
      "minimum": 0,
      "maximum": 9007199254740991
    },
    "status": {
      "type": "string"
    },
    "extraction_status": {
      "type": "string",
      "enum": [
        "pending",
        "processing",
        "review",
        "verified",
        "failed"
      ]
    },
    "verified_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "created_at": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "property_id",
    "filename",
    "kind",
    "content_type",
    "size",
    "status",
    "extraction_status",
    "verified_at",
    "created_at"
  ],
  "additionalProperties": false,
  "description": "An uploaded document (read-only in V1)"
}
Transaction
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "property_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "date": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "amount": {
      "type": "string",
      "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]{2})$"
    },
    "category": {
      "type": [
        "string",
        "null"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "review",
        "reconciled",
        "ignored"
      ]
    },
    "allocation_type": {
      "type": "string",
      "enum": [
        "unassigned",
        "house",
        "tenant",
        "selected",
        "all"
      ]
    },
    "source": {
      "type": "string",
      "enum": [
        "csv_import",
        "plaid_sync"
      ]
    },
    "merchant_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "pending": {
      "type": "boolean"
    },
    "work_order_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "created_at": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "property_id",
    "date",
    "description",
    "amount",
    "category",
    "status",
    "allocation_type",
    "source",
    "merchant_name",
    "pending",
    "work_order_id",
    "created_at"
  ],
  "additionalProperties": false,
  "description": "A bank transaction (read-only in V1)"
}
Payment
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "property_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "tenant_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "month": {
      "type": "string",
      "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
    },
    "amount": {
      "type": "string",
      "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]{2})$"
    },
    "received_date": {
      "type": "string"
    },
    "method": {
      "type": "string"
    },
    "reference": {
      "type": [
        "string",
        "null"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "confirmed",
        "returned"
      ]
    },
    "bank_transaction_id": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        },
        {
          "type": "null"
        }
      ]
    },
    "created_at": {
      "type": "string"
    }
  },
  "required": [
    "id",
    "property_id",
    "tenant_id",
    "month",
    "amount",
    "received_date",
    "method",
    "reference",
    "status",
    "bank_transaction_id",
    "created_at"
  ],
  "additionalProperties": false,
  "description": "An operational rent payment (read-only in V1)"
}
Pagination
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "has_more": {
      "type": "boolean"
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "has_more",
    "next_cursor"
  ],
  "additionalProperties": false,
  "description": "Cursor pagination block"
}
ErrorBody
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "error": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "authentication_error",
            "permission_error",
            "not_found_error",
            "validation_error",
            "conflict_error",
            "rate_limit_error",
            "internal_error"
          ]
        },
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "param": {
          "type": "string"
        },
        "request_id": {
          "type": "string",
          "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        }
      },
      "required": [
        "type",
        "code",
        "message",
        "request_id"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "error"
  ],
  "additionalProperties": false,
  "description": "The single error envelope"
}
AccountEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Account"
    }
  }
}
WorkOrderCategoriesEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/WorkOrderCategory"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
CreatedWebhookSubscriptionEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/CreatedWebhookSubscription"
    }
  }
}
WebhookSubscriptionEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/WebhookSubscription"
    }
  }
}
WorkOrderNoteEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/WorkOrderNote"
    }
  }
}
PropertiesListEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Property"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
PropertyEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Property"
    }
  }
}
VendorsListEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Vendor"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
VendorEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Vendor"
    }
  }
}
WorkOrdersListEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/WorkOrder"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
WorkOrderEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/WorkOrder"
    }
  }
}
DocumentsListEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Document"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
DocumentEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Document"
    }
  }
}
TransactionsListEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Transaction"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
TransactionEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Transaction"
    }
  }
}
PaymentsListEnvelope
{
  "type": "object",
  "required": [
    "data",
    "pagination"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Payment"
      }
    },
    "pagination": {
      "$ref": "#/components/schemas/Pagination"
    }
  }
}
PaymentEnvelope
{
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Payment"
    }
  }
}

Errors, idempotency, and versioning

TenantPoint versions the public API in the URL. Additive changes can arrive in /v1; breaking changes require a new major version. Create operations that require an Idempotency-Key safely replay the completed equivalent request and reject a conflicting body.

Webhook payloads are versioned independently. Build receivers to reject unsupported schema versions safely and to tolerate additive fields.

Public API changelog

v1 current alpha surface

The current contract includes workspace-scoped API-key authentication, cursor-paged reads, vendor and work-order create/update, append-only work-order notes, account capabilities, work-order categories and API-key-owned Zapier subscriptions.

The Developer Platform adds session-bound management of API keys, human webhooks and synthetic sandbox lifecycle without changing the public /v1 contract. Zapier subscriptions remain integration-owned and use ES256 public-key verification.

Compatibility policy

Additive fields, optional parameters and endpoints may be added to /v1. Removing or renaming a documented field, changing its meaning or type, or changing documented validation, authentication or scoping behavior requires a new major API version. Deprecations will be documented before retirement; no fixed sunset window is currently promised.

Support and integration patterns

For troubleshooting, retain the request ID or delivery ID, endpoint environment and safe error code, then use the TenantPoint contact page. Never send API keys, signing secrets, private keys or webhook payloads containing customer data to support channels.

Common patterns include syncing vendors to a CRM, creating work orders from a field-service tool, forwarding signed events to an ERP, and using the private Zapier integration for no-code workflows. For chat notifications, a trusted server-side receiver can validate an event before forwarding a sanitized message to Slack or Teams. These are integration patterns, not claims of built-in accounting or messaging actions.