Operations & async write-back

Writes to PropSocket are asynchronous. A create or update doesn't change the source PMS inline — it returns an Operationimmediately, and PropSocket reconciles the change to the PMS in the background. You poll GET /v1/operations/{id} until the Operation reaches a terminal status.

1. Submit the write

Every write requires an Idempotency-Key header — a caller-generated, one-per-logical-write value. Reusing the same key with the same payload safely returns the same Operation; reusing it with adifferent payload is a 409 (see below).

POST /v1/units
curl -sS -X POST https://api.propsocket.io/v1/units \
  -H "Authorization: Bearer ps_live_..." \
  -H "Idempotency-Key: unit-create-maple-court-1042" \
  -H "Content-Type: application/json" \
  -d '{"unit_number":"1042","bedrooms":2,"bathrooms":2,"square_feet":1080,"max_occupants":4,"note":"South-facing, renovated 2025."}'

2. Receive a pending Operation

The response is 202 Accepted with an Operation in a non-terminal state. Note resource.id and resource.x_id arenull — the record doesn't exist in the PMS yet.

202 Accepted
{
  "id": "opr_01HXABCDEFGHJKMNPQRSTVWXYZ",
  "object": "operation",
  "type": "unit.create",
  "status": "queued",
  "source": {
    "state": null,
    "applied_at": null
  },
  "reconcile": {
    "state": "pending",
    "attempts": 0,
    "next_attempt_at": "2026-07-05T18:00:05Z",
    "last_error": null
  },
  "resource": {
    "type": "unit",
    "id": null,
    "x_id": null
  },
  "result": null,
  "idempotency_key": "unit-create-maple-court-1042",
  "created_at": "2026-07-05T18:00:00Z"
}

3. Poll until terminal

Poll GET /v1/operations/{id} with backoff, stopping whenstatus becomes succeeded or failed. The poll itself always returns 200 — success and failure are carried in the Operation body, not as HTTP errors.

Poll loop
import time, requests

def wait_for(op_id, key):
    while True:
        op = requests.get(
            f"https://api.propsocket.io/v1/operations/{op_id}",
            headers={"Authorization": f"Bearer {key}"},
        ).json()
        if op["status"] in ("succeeded", "failed"):
            return op
        time.sleep(min(2 ** op["reconcile"]["attempts"], 30))  # backoff, cap 30s

4a. Terminal — succeeded

On success, resource.id and resource.x_id are now populated — this is how you learn the CDM id of the record you created, to read it back or reference it later.

Operation: succeeded
{
  "id": "opr_01HXABCDEFGHJKMNPQRSTVWXYZ",
  "object": "operation",
  "type": "unit.create",
  "status": "succeeded",
  "source": {
    "state": "applied",
    "applied_at": "2026-07-05T18:00:12Z"
  },
  "reconcile": {
    "state": "reconciled",
    "attempts": 1,
    "next_attempt_at": null,
    "last_error": null
  },
  "resource": {
    "type": "unit",
    "id": "unt_01HX1H6V9P4MFXCZW3RNS5EDGB",
    "x_id": "entrata-unit-55120"
  },
  "result": {
    "id": "unt_01HX1H6V9P4MFXCZW3RNS5EDGB"
  },
  "idempotency_key": "unit-create-maple-court-1042",
  "created_at": "2026-07-05T18:00:00Z"
}

4b. Terminal — failed

On failure, status is failed andreconcile.last_error explains why. Use it to distinguish a caller mistake (fix and resubmit with a new key) from a PropSocket-side issue (escalate to support with the Operationid).

Operation: failed
{
  "id": "opr_01HXABCDEFGHJKMNPQRSTVWXYZ",
  "object": "operation",
  "type": "unit.create",
  "status": "failed",
  "source": {
    "state": "rejected",
    "applied_at": null
  },
  "reconcile": {
    "state": "failed",
    "attempts": 3,
    "next_attempt_at": null,
    "last_error": "Entrata rejected unit_number '1042': a unit with this number already exists for property entrata-property-3391."
  },
  "resource": {
    "type": "unit",
    "id": null,
    "x_id": null
  },
  "result": null,
  "idempotency_key": "unit-create-maple-court-1042",
  "created_at": "2026-07-05T18:00:00Z"
}

Idempotency conflicts

Reusing an Idempotency-Key with a different payload returns409 as application/problem+json:

409 Conflict
{
  "type": "https://propsocket.io/problems/idempotency-key-conflict",
  "title": "Idempotency-Key conflict",
  "status": 409,
  "detail": "Idempotency-Key 'unit-create-maple-court-1042' was already used with a different request payload.",
  "request_id": "req_01JQ5ZK8Y0N9V3W7X2M4B6D8F"
}

Reference

get/v1/operations/{id}

Retrieve an operation

Path & headers

  • id · path · required

Response · 200

The operation

FieldTypeDescription
id*string
object*string
type*string
status*string
sourceobject
FieldTypeDescription
statestring | null
applied_atstring (date-time) | null
reconcileobject
FieldTypeDescription
statestring | null
attemptsinteger | null
next_attempt_atstring (date-time) | null
last_errorstring | null
resourceobject
FieldTypeDescription
typestring | null
idstring | null
x_idstring | null
resultobject | null
idempotency_keystring | null
created_at*string (date-time)

Errors

  • 401 — Missing or invalid API key
  • 404 — Resource not found
  • 429 — Rate limit exceeded

Next

Back to the API reference overview, or see the writable entities: Units,Residents, Leases.