Gateway API /ext/v1

The versioned REST surface your extension calls. Authenticate with a short-lived delivery token and send it as Authorization: Bearer <token>.

Tenancy comes from the token, never the request. There is no buildingId parameter. A resource outside your scope returns 404 rather than 403.

Scopes fail closed. An endpoint you have not been granted is denied, and an endpoint with no declared scope is denied to everyone.

Every call is audited and metered, including denied ones. Repeatedly probing an endpoint you lack consent for consumes your quota.

Pagination

List endpoints take ?limit= and ?cursor= and return { "items": [...], "nextCursor": string | null }. Pass the previous response’s cursor to fetch the next page; a null cursor means the end of the collection. Cursors are opaque and bound to the issuing tenant — a cursor from another tenant or another endpoint is rejected, not silently reinterpreted.

Endpoints

MethodPathScopeReturnsDescription
GET/ext/v1/requestsINBOX_REQUEST_VIEWGatewayRequestDto[]Page through management-inbox requests across the token’s tenant scope.
GET/ext/v1/requests/:idINBOX_REQUEST_VIEWGatewayRequestDtoFetch one request by id. A request outside the token’s tenant scope returns 404, never 403.
GET/ext/v1/documentsDOCUMENT_VIEWGatewayDocumentDto[]Page through building documents the acting user is allowed to see.
GET/ext/v1/documents/:idDOCUMENT_VIEWGatewayDocumentDetailDtoFetch one document, including its current version.
GET/ext/v1/leasesLEASE_VIEWGatewayLeaseDto[]Page through leases across the token’s tenant scope.
GET/ext/v1/leases/:idLEASE_VIEWGatewayLeaseDtoFetch one lease by id.
GET/ext/v1/depositsDEPOSIT_LEDGER_VIEWGatewayDepositLedgerDto[]Page through deposit ledgers — money state only; the deduction and dispute narrative stays internal.
GET/ext/v1/deposits/:idDEPOSIT_LEDGER_VIEWGatewayDepositLedgerDtoFetch one deposit ledger by id.
GET/ext/v1/chargesRENT_LEDGER_VIEWGatewayRentChargeDto[]Page through rent charges. Ids match the `chargeId` carried by `rent.charge.*` events.
GET/ext/v1/charges/:idRENT_LEDGER_VIEWGatewayRentChargeDtoFetch one rent charge by id.
GET/ext/v1/paymentsRENT_LEDGER_VIEWGatewayPaymentDto[]Page through resident payments. Ids match the `paymentId` carried by `payment.transaction.*` events.
GET/ext/v1/payments/:idRENT_LEDGER_VIEWGatewayPaymentDtoFetch one resident payment by id.
GET/ext/v1/invoicesINVOICE_VIEWGatewayInvoiceDto[]Page through vendor invoices, including the booked-expense correlation once paid.
GET/ext/v1/invoices/:idINVOICE_VIEWGatewayInvoiceDtoFetch one vendor invoice by id.
GET/ext/v1/calendar/eventsCALENDAR_VIEWGatewayCalendarEventDto[]Page through calendar events visible to the acting user.
GET/ext/v1/directory/membersDIRECTORY_VIEWGatewayDirectoryMemberDto[]Page through the building directory: display name and unit only.
GET/ext/v1/data/:collectionextdata:readExtensionDataKeyMeta[]Page through the keys your installation has stored in a collection. Values are not included.
GET/ext/v1/data/:collection/:keyextdata:readExtensionDataEntryRead one stored value.
PUT/ext/v1/data/:collection/:keyextdata:writeExtensionDataWriteResultCreate or replace one stored value. Body is `{ "value": <any JSON> }`. Honours `Idempotency-Key`.
DELETE/ext/v1/data/:collection/:keyextdata:write—Delete one stored value. Returns 204 whether or not the key existed.

Deprecations

No /ext/v1 surface is currently deprecated. When one is, it is listed here and every response from it carries Deprecation, Sunset, and Link: …; rel="successor-version" headers for at least twelve months before it stops answering.

Response shapes

ExtensionDataEntry

A single entry's full projection for `GET /data/:collection/:key`.

FieldTypeRequiredDescription
collectionstringyes—
keystringyes—
valueany JSON valueyes—
byteSizenumberyes—
updatedAtstring (ISO-8601)yes—

ExtensionDataKeyMeta

One row's metadata in a `GET /data/:collection` listing — never the value.

FieldTypeRequiredDescription
keystringyes—
byteSizenumberyesStored byte size of the value (the metered unit).
updatedAtstring (ISO-8601)yes—

ExtensionDataWriteResult

Metadata echoed by a `PUT /data/:collection/:key` upsert (never the value).

FieldTypeRequiredDescription
collectionstringyes—
keystringyes—
byteSizenumberyes—
updatedAtstring (ISO-8601)yes—

GatewayCalendarEventDto

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
categorystringyes—
visibilitystringyes—
titlestringyes—
descriptionstring | nullyes—
locationstring | nullyes—
startsAtUtcstringyes—
endsAtUtcstring | nullyes—
isAllDaybooleanyes—
rrulestring | nullyes—
recurrenceEndUtcstring | nullyes—
createdAtstringyes—
updatedAtstringyes—

GatewayDepositLedgerDto

Deposit-ledger projection (§6.4): a summary-level view of a held deposit's money state — agreed amount, running balance, accrued interest, lifecycle status — plus the correlating leaseId and deposit kind. Deliberately excludes the individual DepositMovement log and every free-text field on it. Deposit free text (movement `notes`, `deductionReason`, `disputeReason`, the term's `notes`) is MODERATE PII — it can describe a tenant's conduct or damages (see #1398 / the `deposit.ledger.deducted` event's MODERATE tier) — so it never crosses the Gateway boundary. Also excluded: `referenceNumber` (a cheque/e-transfer financial identifier), `recordedByUserId` (audit actor), `method`/`supportingFileId` (movement internals), and the dispute-event log (attestation/audit trail). An extension needing the money figures gets them; the deduction/dispute narrative stays internal.

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
leaseIdstringyes—
depositKindstringyesLeaseDepositKind (SECURITY / LAST_MONTH / PET …).
agreedAmountCentsnumberyesThe agreed deposit amount from the lease term (not the current balance).
balanceCentsnumberyesRunning balance after every movement.
interestAccruedCentsnumberyesLifetime accrued statutory interest (cumulative).
statusstringyesDepositLedgerStatus (OPEN / PARTIALLY_RETURNED / RETURNED / WRITE_OFF …).
openedAtstringyes—
closedAtstring | nullyes—
reopenedAtstring | nullyes—
createdAtstringyes—
updatedAtstringyes—

GatewayDirectoryMemberDto

Directory-limited member projection (§6.4): the documented contract is display name + unit number only. Role/tenure/verification/status (isOwner/isResident/managerRole/verification/status) are deliberately NOT exposed — `DIRECTORY_VIEW` is a manager-only read of *who lives where*, not a governance view of each member's standing, and widening it here would leak organizing signal (who the owners/council are) beyond the stated contract. Never email/phone/entitlement either.

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
userIdstringyes—
displayNamestring | nullyes—
unitIdstring | nullyes—
unitNumberstring | nullyes—
createdAtstringyes—
updatedAtstringyes—

GatewayDocumentDetailDto

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
titlestringyes—
categorystringyes—
categoryDefIdstring | nullyes—
statusstringyes—
accessLevelstringyes—
tagsstring[]yes—
createdAtstringyes—
updatedAtstringyes—
currentVersionobject | nullyesThe document's current version, or null when no version has been uploaded yet. Object members: `id` (string), `versionNumber` (number, 1-based), `effectiveDate` (ISO-8601 string or null) and `createdAt` (ISO-8601 string). Spelled out here because the docs generator publishes a nested object literal as `object` — this JSDoc is the only description a partner gets.

GatewayDocumentDto

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
titlestringyes—
categorystringyes—
categoryDefIdstring | nullyes—
statusstringyes—
accessLevelstringyes—
tagsstring[]yes—
createdAtstringyes—
updatedAtstringyes—

GatewayInvoiceDto

Vendor-invoice projection (§6.4): the money + lifecycle state of a vendor invoice an accounting partner needs to sync — amounts, dates, status, the settlement fields, and the `expenseId` correlating it to the booked expense. Deliberately excludes the free-text `description`/`notes`/`rejectionReason` (operator-entered narrative — can name people or describe disputes), the `paymentReference` (a cheque/e-transfer financial identifier, dropped like the deposit DTO's `referenceNumber`), the internal `documentUrl`, and every audit actor id (`submittedByUserId`/`approvedByUserId`/`rejectedByUserId`). The status-transition timestamps (`approvedAt`/`paidAt`) are kept; the actors are not. `invoiceNumber` is the vendor's own document number (a business identifier, not personal data) — required for reconciliation.

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
vendorIdstringyes—
workOrderIdstring | nullyes—
invoiceNumberstringyes—
invoiceDatestringyes—
dueDatestring | nullyes—
amountCentsnumberyesPre-tax invoice amount.
taxCentsnumberyes—
currencystringyes—
statusstringyesInvoiceStatus (DRAFT / APPROVED / PAID …).
approvedAtstring | nullyes—
paidAtstring | nullyes—
paymentMethodstring | nullyesPaymentMethod the invoice was settled with, when paid.
expenseIdstring | nullyesOpaque id of the booked BuildingExpense, once the invoice is marked PAID.
createdAtstringyes—
updatedAtstringyes—

GatewayLeaseDto

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
unitIdstringyes—
statusstringyes—
termTypestringyes—
startDatestringyes—
endDatestring | nullyes—
monthlyRentCentsnumberyes—
rentDueDayOfMonthnumberyes—
rentCollectionModestringyes—
renewalModestringyes—
landlordUserIdstring | nullyes—
namedTenantUserIdsstring[]yes—
jurisdictionCodestringyes—
createdAtstringyes—
updatedAtstringyes—

GatewayPaymentDto

Resident-payment projection (§6.4): a Stripe-backed resident payment — the `paymentId` carried by the `payment.transaction.*` catalog events. The money state (amount, fee, refunds), lifecycle (status + paid/failed timestamps), and the opaque source correlation (sourceType/sourceId) an accounting partner needs to reconcile a transaction. Deliberately excludes the free-text `description`, the arbitrary `metadata` JSON (either can carry operator-entered PII), and every Stripe identifier (`stripePaymentIntentId`/`stripeChargeId`/`stripeTransferId` — payment-rail account identifiers, out of the partner contract by the same rule the deposit DTO drops `referenceNumber`). `userId` is the opaque payer id, consistent with the other Gateway DTOs that expose it.

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
userIdstringyesOpaque payer user id.
amountCentsnumberyesAmount charged, in cents (before platform fee).
applicationFeeCentsnumber | nullyesPastrata's platform fee in cents, when one applied.
currencystringyes—
sourceTypestringyesPaymentSourceType (BOOKING / CHARGE …) — what the payment settled.
sourceIdstringyesOpaque id of the settled source row (booking, charge, …).
statusstringyesResidentPaymentStatus (PENDING / SUCCEEDED / FAILED …).
paidAtstring | nullyes—
failedAtstring | nullyes—
failureReasonstring | nullyesFailure reason (e.g. CARD_DECLINED) — LOW, mirrors the event's `reason`.
totalRefundedCentsnumberyesCumulative amount refunded against this payment, in cents.
createdAtstringyes—
updatedAtstringyes—

GatewayRentChargeDto

Rent-ledger charge projection (§6.4): a rent period's expected charge — the `chargeId` carried by the `rent.charge.*` catalog events, so a partner that hears a charge event can fetch its detail here. Scoped through the parent lease (RentPeriod has no direct buildingId). Money figures + billing-cycle dates only; no payer/tenant identity beyond the opaque leaseId (the correlating lease read is a separate `LEASE_VIEW` grant). Date columns are rendered as full ISO-8601 for consistency with the lease DTO.

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
leaseIdstringyes—
statusstringyesRentPeriodStatus (UPCOMING / DUE / LATE / PAID …).
periodStartstringyes—
periodEndstringyes—
dueDatestringyes—
expectedCentsnumberyesExpected rent for the period.
currencystringyes—
createdAtstringyes—
updatedAtstringyes—

GatewayRequestDto

FieldTypeRequiredDescription
idstringyes—
buildingIdstringyes—
statusstringyes—
customStatusstring | nullyes—
titlestring | nullyes—
isUrgentbooleanyes—
isOutboundbooleanyes—
sourcestringyes—
categoryIdstring | nullyes—
typeIdstring | nullyes—
requesterUnitIdstring | nullyes—
createdByUserIdstringyes—
assigneeUserIdstring | nullyes—
lastActivityAtstringyes—
resolvedAtstring | nullyes—
closedAtstring | nullyes—
createdAtstringyes—
updatedAtstringyes—

Scopes

Scopes are requested in your manifest and granted by the installing manager at consent time. A platform scope names a platform surface with no user-facing analogue; every other scope mirrors a permission a human manager holds, and is never broader than what that manager could do themselves.

ScopeKindGrants
AGREEMENT_VIEWCapabilityView own agreements
BUILDING_SETTINGS_VIEWCapabilityView building settings
CALENDAR_VIEWCapabilityView calendar events
CHANNEL_VIEWCapabilityView channels
DEPOSIT_LEDGER_VIEWCapabilityView deposit ledgers
DIRECTORY_VIEWCapabilityView resident directory
DOCUMENT_VIEWCapabilityView building documents
EXTENSION_MANAGECapabilityInstall and manage extensions
FEED_MODERATECapabilityModerate community feed (delete posts/comments, pin, close polls, view voters, handle reports)
GUEST_PASS_MANAGECapabilityManage guest passes
INBOX_REQUEST_VIEWCapabilityView inbox requests
INCIDENT_MANAGECapabilityReport and view incidents
INVOICE_VIEWCapabilityView vendor invoices
LEASE_VIEWCapabilityView leases
MEETING_ATTENDCapabilityAttend and participate in meetings
MESSAGE_VIEWCapabilityView own direct messages
PACKAGE_VIEWCapabilityView and pick up packages
PRESCRIBED_NOTICE_MANAGECapabilityManage prescribed tenancy notices (draft, issue, withdraw, supersede)
RENT_LEDGER_VIEWCapabilityView rent ledger (landlord-owners auto-granted for own units)
SHIFT_NOTESCapabilityRead/write shift notes
VISITOR_MANAGECapabilityLog and manage visitors
extdata:readPlatformRead your extension’s own stored data
extdata:writePlatformWrite your extension’s own stored data

Errors

Errors are returned as { "statusCode": number, "errorCode": string, "message": string }. Match on errorCode — the message is localized and may be reworded.

Returnable by any endpoint

Error codeMeaning
extensions_gateway_quota_exceededThe extension has exceeded its Gateway call quota for this period
extensions_gateway_quota_unavailableUnable to verify the extension call quota
extensions_gateway_scope_deniedThe extension is not granted the {capability} scope for this tenant
extensions_gateway_token_requiredA Bearer extension token is required
extensions_token_expiredExtension token has expired
extensions_token_invalidExtension token is invalid
extensions_token_revokedExtension token is no longer valid

Endpoint-specific

Error codeMeaning
extensions_extdata_invalid_nameThe extension data {field} must be 1-{max} characters with no control characters
extensions_extdata_quota_exceededThe extension has exhausted its {limit}-byte data storage quota
extensions_extdata_quota_unavailableUnable to verify the extension data storage quota
extensions_extdata_row_limit_reachedThe extension has reached its {limit}-row data limit for this installation
extensions_extdata_row_too_largeThe extension data value is {size} bytes; the per-row limit is {limit} bytes
extensions_gateway_invalid_cursorThe pagination cursor is invalid
extensions_gateway_not_foundResource not found