Skip to content
Documentation · Migrating from the legacy API

Migration

Migrating from the legacy API

The legacy Properly API was a single batched endpoint that acknowledged immediately and delivered every result, including validation errors, later via webhook. The Platform API replaces it with the REST API documented here.

Deprecation timeline

The legacy API (POST https://5l14kpizah.execute-api.us-east-1.amazonaws.com/prod/apiEvent) is deprecated as of the Platform API's general availability on October 9, 2026, and shuts down on April 7, 2027. Legacy API responses carry these headers:

Legacy API response headers
Deprecation: @1791504000
Sunset: Wed, 07 Apr 2027 00:00:00 GMT
Link: <https://www.getproperly.com/developers/migrate>; rel="deprecation"

Your existing legacy x-api-key works on both APIs throughout the window; a key issued from the web app works on the Platform API only.

What changed

Legacy API Platform API
Base URL: https://5l14kpizah.execute-api.us-east-1.amazonaws.com/prod https://platform.getproperly.com
One POST /apiEvent with { "property.create": [...], "booking.create": [...] }, up to 20 records batched One request per record on per-resource routes (POST /v1/properties, POST /v1/bookings, …)
HTTP response was an acknowledgement only; real results arrived via webhook The response body is the result: created resources and their ids return inline (201/200)
Per-record validation errors arrived later via webhook Errors return immediately as HTTP 400 application/problem+json
Unknown body fields silently dropped Unknown fields are rejected; the 400 names each one (code "objectStrict")
Out-of-order batches forgiven (the consumer retried until dependencies appeared) Order matters: create the property first, then use the returned propertyId for pictures, checklists, bookings, and jobs
Listings async via webhook (listing.cleaningJobs, listing.propertyListing) Plain queries returning inline: GET /v1/jobRequests, GET /v1/properties
Webhook setup: POST /setupWebhook with replace-all semantics POST /v1/webhooks plus per-subscription PATCH and DELETE; one signing secret per URL, returned once
Webhook payloads unsigned Signed (Standard Webhooks HMAC). Payload bodies are byte-identical to the legacy API; the signing headers are additive, so an unmodified legacy handler keeps parsing. Verification is new code you add.
Missed webhook → support ticket Self-serve: delivery log (GET /v1/webhooks/{webhookId}/deliveries) and polling backfill (GET /v1/events?after=…)
Rate limits: 50 req/s sustained, burst 100, 30,000/day (enforced by the API gateway) Same numbers, now surfaced via X-RateLimit-* and Retry-After headers
x-api-key header Unchanged: same header, same value
jobRequest.create acknowledged; the snapshot arrived by webhook The job row returns inline in the 201; lifecycle webhooks still arrive later, unchanged
Lifecycle webhook payloads Unchanged shape, now signed

Legacy event → Platform API route

Field names inside each record are unchanged. Base URL: https://platform.getproperly.com.

Legacy API event Platform API call
property.create POST /v1/properties
property.update PATCH /v1/properties/{propertyId}
property.delete DELETE /v1/properties/{propertyId}
propertyPicture.create POST /v1/propertyPictures
propertyPicture.delete DELETE /v1/propertyPictures/{pictureId}
booking.create POST /v1/bookings
booking.update PATCH /v1/bookings/{bookingId}
booking.changed PATCH /v1/bookings/{bookingId}/dates
booking.canceled POST /v1/bookings/{bookingId}/cancel
booking.delete DELETE /v1/bookings/{bookingId}
checklist.create POST /v1/checklists
checklist.update PATCH /v1/checklists/{checklistId}
checklist.disable POST /v1/checklists/{checklistId}/disable
checklist.search GET /v1/properties/{propertyId}/checklists
reminder.create POST /v1/reminders
reminder.delete DELETE /v1/reminders/{reminderId}
jobRequest.create POST /v1/jobRequests
jobRequest.update PATCH /v1/jobRequests/{jobRequestId}
jobRequest.oneOffTaskUpdate POST /v1/jobRequests/{jobRequestId}/oneOffTasks
jobRequest.canceled POST /v1/jobRequests/{jobRequestId}/cancel
jobRequest.updatePaymentStatus PATCH /v1/jobRequests/{jobRequestId}/paymentStatus
listing.cleaningJobs GET /v1/jobRequests?dateFrom=…&dateTo=…&status=…
listing.propertyListing GET /v1/properties?cursor=…&limit=…
POST /api/external/comments/add POST /v1/jobRequests/{jobRequestId}/comments
POST /api/external/comments/get GET /v1/jobRequests/{jobRequestId}/comments
POST /setupWebhook POST /v1/webhooks (plus GET, PATCH, DELETE /v1/webhooks/{webhookId})
— (new in the Platform API) GET /v1/properties/{propertyId}, GET /v1/jobRequests/{jobRequestId}, webhook diagnostics (test, deliveries, rotateSecret), GET /v1/events

Update wire shape

Updates moved from { "<entity>Id": …, "changes": {…} } inside a batch to the id in the URL and the change fields flat in the body:

Legacy API
{ "property.update": [ { "propertyId": "8jL2mQxT4a", "changes": { "name": "Seaside Loft 2B" } } ] }
Platform API
PATCH /v1/properties/8jL2mQxT4a
{ "name": "Seaside Loft 2B" }

Recommended migration path

The legacy and Platform webhook pipelines are independent, so you can run both in parallel and cut over once.

  1. Create a Platform API key. Create a Platform API key in the web app (API & AI assistants) and use it instead of your legacy key.
  2. Register a Platform API webhook pointing at a test receiver. Use an endpoint you control for testing. Your legacy webhook configuration stays untouched and keeps firing, so the two pipelines run independently and real events now flow to both. This includes the calls you make through the Platform API: until you remove the legacy webhook at the promote step, your legacy receiver also gets those events, so expect them there while you port.
  3. Port the client integration. Switch to the new base URL, split batches into per-resource calls, sequence dependent calls, read response bodies for results, move per-record error handling from webhook handlers to HTTP error handling, and remove any unknown or extra body fields.
  4. Add signature verification. Verify webhook-signature against the raw request bytes using the signing secret returned when you registered the subscription. The verifier on the Webhooks page is ready to paste.
  5. Validate against live events. Check payload parity, signatures, and retries at your test receiver. Low-volume integrations can synthesize deliveries with POST /v1/webhooks/{webhookId}/test.
  6. Promote. PATCH /v1/webhooks/{webhookId} flips the subscription url to your production receiver; the signing secret is kept. Remove the legacy webhook configuration in the same window: the legacy POST /setupWebhook replaces the whole configuration on every call, so post the configuration without the old webhook, or an empty list to clear it. The cutover is one-way.
  7. If something breaks after cutover. Fix the receiver, then backfill from GET /v1/events?after=… and check GET /v1/webhooks/{webhookId}/deliveries. Recovery is forward, not back to the legacy API.

Type to search the Platform API docs.