MakeReadyOS exposes a JSON API for self-hosted integrations. Browser users authenticate with HttpOnly cookie sessions. External integrations should use scoped API tokens.
An OpenAPI 3.1 contract is available from a running instance:
curl http://localhost:4000/api/openapi.jsonThe contract includes the stable handwritten path surface, generated component schemas for the Zod validators used by the route layer, file-exchange schemas such as NativeBackup, and response-envelope schemas for public surfaces. Generated request/query schemas are intentionally named with their route purpose, for example MakeReadyCreateRequest, UnitImportRequest, WebhookCreateRequest, and OperationalLibraryPack. Response schemas use names such as MakeReadyItemResponse, VendorAssignmentsResponse, AutomationRunsResponse, OperationalLibraryInstallSummary, WebhookEndpoint, and AuthSessionResponse.
The contract documents the stable integration surface: auth shape, API-token security, core make-ready item endpoints, operations setup, custom fields, saved views, comments/attachments, dashboard/analytics/risk summaries, planning, vendors, Property Wiki, maps, Projects, Pest Control, Lease Compliance, activity, notifications, property templates, automation/operational-library routes, backup transfer, admin user/storage metadata, and admin integration registration. It is intentionally conservative; route-specific docs in this file remain the source of operational detail where an endpoint has more filters or workflow-specific behavior than the baseline contract describes.
JSON contracts intended for file exchange are also published in the repository:
docs/schemas/makereadyos-library-pack.schema.jsondocs/schemas/makereadyos-native-backup.schema.json
Admins create tokens in Admin -> Integrations. Tokens are shown once, stored only as SHA-256 hashes, and can be revoked without deleting audit history.
Use:
curl -H "Authorization: Bearer $MAKEREADYOS_TOKEN" \
http://localhost:4000/api/make-ready-itemsAPI token requests do not use CSRF headers. Cookie-session requests still require the normal CSRF protection for writes.
Protected /api routes optionally accept X-MROS-Expected-User containing the creating user's exact ID. The server compares it with the authenticated cookie-session user before the route handler. A different account or API-token authentication returns 409 with code SESSION_ACCOUNT_CHANGED; an empty or malformed value returns 400. Missing authentication still returns 401, and matching writes still require CSRF and normal role/property permissions. This header restricts an existing session; it is not a credential or impersonation feature. Auth endpoints do not use this constraint.
The browser's offline queue sends this constraint for each queued write and retains the initiating user's ID on new jobs. Queue reads and replay require a verified current account. Legacy jobs without an owner remain on the device, held from replay and automatic deletion; they are not assigned to whichever user signs in next. Browser profile deletion can still remove local work. This is account isolation in the application, not encryption of browser storage or a guarantee of exactly-once delivery after an unconfirmed network response.
read:items: read make-ready items, metadata, and saved views.write:items: create/update/archive make-ready items.- Refrigerant read/write endpoints currently use
read:itemsandwrite:itemsAPI-token scopes plus the creating user's role/property permissions. Dedicated refrigerant token scopes are a future hardening item. write:comments: create item comments and upload item attachments.read:vendors: read vendors and vendor assignments.write:vendors: manage vendors and vendor assignments.read:dashboard: read dashboard summaries.read:activity: read activity logs allowed by the token/user scope.read:maps: read property map and unit-location data.read:library: read operational library data.write:library: manage operational library imports where allowed.
Tokens can also be scoped to specific properties. Property scope is an additional restriction; it does not bypass normal role/property permissions for the user who created the token.
Built-in status definitions have a fixed value and an optional displayName. Editing a display name changes presentation, not turn records or readiness semantics. Filters, item writes, automation conditions and imported status values continue to use value. PATCH /api/operations/options/:id rejects a different value with 409; use displayName instead, or null to restore the canonical label. This does not repair historical values renamed before this protection existed.
Creating, editing, reordering, archiving and restoring shared status definitions require ADMIN because these choices apply across properties. Managers may read/use existing definitions. Property-template and library imports by managers may reuse existing definitions, but adding shared definitions requires an admin. Direct create/edit requests reject duplicate visible names within a field, including archived choices.
Native exports and property templates preserve displayName. Native merge import retains existing shared definitions and reports conflicting presentation settings instead of overwriting them; old backups without display names remain supported. Merge import is not an exact overwrite restore.
Project mutations enforce the parent project's property scope for all roles, including property-restricted admin API tokens. A project's property cannot be changed through record PATCH (409); linked tasks, attachments and references are not silently transferred. Direct user assignments use the same active-role/property eligibility as the project picker and reject viewers. This does not grant new task-completion privileges to leasing or cleaner roles.
Project category/map links must resolve within the project property (shared global categories are allowed). Newly selected links must be active, and maps cannot be archived; unchanged valid historical links remain editable. Clearing a category clears its saved label; clearing a map clears its pin. New project wiki links require active targets from that same property. These direct-route rules do not establish complete native-import reference validation or automatically repair old invalid links.
PATCH /api/pm/templates/:id rejects a different propertyId with 409. Templates and generated tasks cannot be transferred through an ordinary edit. Same-property edits retain normal role and property authorization.
Changing assignedRole also validates any retained assignee. If that user is not active, property-eligible and of the new role, the request returns 400 before updating the template; supply an eligible replacement or explicit assignedUserId: null. Unrelated edits do not clear historical assignments.
Pest create/edit requests validate unit and vendor property ownership and assignee access. Newly selected references must be active; new assignees must have a Pest editing role (admin, manager, tech or leasing). Explicit linked make-ready items must belong to the selected unit/property and cannot be newly selected when archived. Unchanged valid historical inactive links remain editable. A changed issue propertyId is rejected with 409 rather than ignored. These rules do not automatically repair historical links or guarantee native-import consistency.
POST /api/admin/import returns dryRun, applied, mode, and summary. applied: false means preview or rejection before the write transaction; no merge records were changed. applied: true means the merge and its audit entry committed together. The summary can still contain conflicts for matching settings retained during a merge, so conflicts alone do not imply rollback. A workspace refresh failure after a confirmed response does not undo the import. If the response is lost, review current data before retrying; this endpoint does not yet provide an idempotency key or durable client-visible request receipt.
Project comments, tasks, attachments and wiki references in a native import must use the same property as their incoming parent project. Imported project wiki targets must also match that property. A mismatch is a preflight rejection (applied: false), not a partial merge. Existing-directory partial restores and legacy category identity remain separate compatibility limitations.
GET /api/metaGET /api/operations/propertiesGET /api/operations/units?propertyId=POST /api/operations/units/importPOST /api/operations/availability/importGET /api/operations/board-sections?propertyId=GET /api/operations/options?fieldKey=GET /api/operations/floor-plans?propertyId=GET /api/operations/schedule-tracksGET /api/operations/operating-calendarsGET /api/custom-fieldsGET /api/saved-views?includeArchived=&module=GET /api/make-ready-items?propertyId=&boardSection=&vacancyStatus=&assignedTech=&riskCategory=&moveInWindow=&sortBy=&sortDirection=&includeArchived=&limit=&offset=POST /api/make-ready-itemsPATCH /api/make-ready-items/:idPOST /api/make-ready-items/batchPOST /api/make-ready-items/:id/commentsGET /api/make-ready-items/:id/collaborationGET /api/charge-price-sheet-items?propertyId=GET /api/checklist-templatesGET /api/my-workGET /api/dashboardGET /api/analytics/summaryGET /api/risk/itemsGET /api/planningGET /api/planning/blocksGET /api/planning/capacitiesGET /api/activity?limit=&offset=GET /api/notifications?limit=&offset=&unreadOnly=GET /api/vendorsGET /api/pm/overviewGET /api/pm/templatesGET /api/pm/tasksGET /api/pm/calendarGET /api/pm/historyGET /api/pm/export.csvGET /api/pm/export.xlsGET /api/pm/report.htmlGET /api/lease-compliance/overviewGET /api/lease-compliance/issuesGET /api/lease-compliance/issue-types?propertyId=GET /api/lease-compliance/settings?propertyId=GET /api/lease-compliance/export.csvGET /api/lease-compliance/report.htmlGET /api/lease-compliance/report.pdfGET /api/refrigerant/overviewGET /api/refrigerant/typesGET /api/refrigerant/cylindersGET /api/refrigerant/historyGET /api/refrigerant/export.csvGET /api/property-wiki/overviewGET /api/property-wiki/profile?propertyId=GET /api/property-wiki/entries?propertyId=§ion=&includeInactive=&q=GET /api/property-wiki/vendors?propertyId=&includeInactive=&q=GET /api/property-wiki/assets?propertyId=&kind=&entryId=&vendorId=&q=GET /api/property-wiki/context?module=&propertyId=&recordType=&recordId=&floorPlan=&unitNumber=&building=&facilityName=&equipmentQuery=&query=GET /api/property-wiki/records/:targetType/:idGET /api/property-wiki/search?propertyId=&q=GET /api/property-mapsGET /api/property-map-areas?propertyId=&mapId=GET /api/unit-map-locationsGET /api/property-templates?includeArchived=POST /api/property-templates/:id/archivePOST /api/property-templates/:id/restoreDELETE /api/property-templates/:idGET /api/automationsPOST /api/automations/previewGET /api/operational-library/packs
Automation rule payloads are defined in the served OpenAPI contract and now include structured action variants such as setField, setCustomField, setDateFromField, addAuditNote, and the scheduled-only assignLeastLoadedStaff action for planning-aware automatic assignment.
Lease Compliance now has exact served OpenAPI route coverage for overview, settings, issue types, issue list/create/update, notes, persistence checks, notice workflow, resolve/archive actions, recurring-warning dismissal, evidence upload/download/delete, and CSV/HTML/PDF reporting.
Admin-only token management remains session-only:
GET /api/admin/integrationsGET /api/admin/usersGET /api/admin/propertiesGET /api/admin/storagePOST /api/admin/integrations/api-tokensPOST /api/admin/integrations/api-tokens/:id/revokePOST /api/admin/integrations/webhooksPOST /api/admin/integrations/webhooks/:id/test-payloadGET /api/admin/integrations/webhooks/:id/deliveries?limit=&offset=GET /api/admin/integrations/webhooks/:id/health
The Admin Integrations UI exposes API token usage metadata such as last-used time, total request count, and last-used request path/method, plus webhook health, attempt/failure counts, recent delivery attempts, signed dry-run payload creation, and queued test payload creation. Queued attempts are delivered only by ./run-webhooks.sh.
Endpoints that can grow large use limit and offset where implemented. Keep integrations conservative and avoid polling large unbounded datasets.
GET /api/make-ready-items currently preserves its legacy array response body and exposes pagination metadata as response headers:
x-total-countx-limitx-offsetx-has-morex-next-offset
Other newer collection endpoints may return a pagination object in the JSON body.
High-use board filters can be pushed into the item query for integrations that need bounded slices instead of full board exports. Supported filters include property, section/group, board-section type, vacancy, assignee, scope, make-ready status, risk level, risk category, move-in window, updated-since, active custom-field filters, and common operational flags such as overdue, missing dates, pest issues, flooring needed, paint needed, and move-in risk. Item queries also support deterministic bounded sorting through sortBy and sortDirection; valid sort fields include boardGroup, unitNumber, moveInDate, makeReadyDate, vacatedDate, flooringDate, daysVacant, riskScore, riskLevel, assignedTech, updatedAt, and createdAt. Custom-field filters are passed as a JSON-encoded customFieldFilters array using the same operators as saved views; the API validates active field/option references and narrows through custom-field value indexes before returning the paged item array.
POST /api/operations/units/import updates permanent unit-directory inventory only. It does not create active make-ready turns.
POST /api/operations/availability/import updates directory availability and creates or updates active make-ready turns for operational statuses such as VACANT NOT LEASED READY, VACANT NOT LEASED NOT READY, NTV NOT LEASED, NTV LEASED, VACANT LEASED READY, VACANT LEASED NOT READY, DOWN, and MODEL. Legacy underscore aliases remain accepted for compatibility. The import requires an existing propertyId, is merge-style, and does not delete units or turns that are missing from the imported report. Detailed CSV conversion guidance lives in docs/UNIT_DIRECTORY_AND_OCCUPANCY.md.
Errors return JSON:
{ "message": "Property access denied" }Use HTTP status codes for programmatic handling.
Runnable examples live under:
examples/api/curl/examples/api/node/
Set MAKEREADYOS_URL and MAKEREADYOS_TOKEN before running them.
- Tokens are never shown again after creation.
- API token requests are protected by a configurable database-backed per-token window limiter shared across API replicas. Tune
API_TOKEN_RATE_LIMIT_MAXandAPI_TOKEN_RATE_LIMIT_WINDOW_MINUTESfor the deployment, and keep an internet-facing instance behind a trusted reverse proxy or edge rate limiter. - Token hashes, password hashes, sessions, CSRF tokens, and secrets are not included in native transfer exports.
- Do not create broad write tokens unless an integration genuinely needs them.
- New webhook endpoints store the signing secret encrypted at rest so queued deliveries can be HMAC-signed. Older endpoints without encrypted secrets must be recreated or rotated before delivery can be enabled.
- Admins can create signed dry-run webhook payloads with
/api/admin/integrations/webhooks/:id/test-payload; pass{"enqueue": true}to queue that same test payload for the explicit./run-webhooks.shdelivery runner. - Subscribed application events are queued for item create/update/assignment/archive/restore, risk-level changes, comment creation, attachment create/delete, checklist completion, and vendor assignment changes.
- Webhook health is available through
/api/admin/integrations/webhooks/:id/healthand includes aggregate status counts, event counts, pending work, latest failure metadata, and a coarseREADY/PENDING/FAILING/DISABLEDstate. - Webhook delivery is script-driven, not an always-running worker.
./run-webhooks.shprocesses queued delivery attempts with bounded batch size, HTTP timeout, retry/backoff, delivery history, optional endpoint auto-disable after repeated consecutive failures, and optional private/local URL blocking for public deployments. TuneWEBHOOK_DELIVERY_BATCH_SIZE,WEBHOOK_DELIVERY_TIMEOUT_MS,WEBHOOK_DELIVERY_MAX_ATTEMPTS,WEBHOOK_AUTO_DISABLE_FAILURES,WEBHOOK_ALLOW_PRIVATE_URLS, andWEBHOOK_ALLOWED_HOSTS. - The current OpenAPI path list is still intentionally conservative, but request/query component schemas are generated from shared route validators where they exist. Common long-tail integration responses now have exact serializers for options, floor plans, schedule tracks, checklist/price-sheet metadata, automation run history, operational library installs, API tokens, webhook endpoints, webhook delivery attempts, analytics/risk summary payloads, the core Projects/Pest/Lease Compliance overview and record envelopes, full Refrigerant type/cylinder/transaction/compliance payloads, and Property Maps pin/area payloads. Remaining route-specific serializers are tracked in API_SPEC_PLAN.md; webhook delivery behavior is documented in WEBHOOK_DELIVERY_PLAN.md.