Errors

PropSocket returns errors as RFC 7807application/problem+json. Every error body has the same top-level fields, plus arequest_id you can quote in a support ticket. Check the HTTP status first; readdetail for the human-readable specifics.

The shape

application/problem+json
{
  "type": "https://docs.propsocket.io/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication credentials were not provided.",
  "instance": "/v1/properties",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}
FieldMeaning
typeA URI identifying the error class. Stable — safe to switch on. Resolves to docs.
titleShort human-readable summary of the error class.
statusThe HTTP status code, repeated in the body for convenience.
detailHuman-readable specifics for this occurrence.
instanceThe request path that produced the error.
request_idCorrelation ID. Mirrors the x-request-id response header. Quote it in tickets.
errors[]Field-level detail on 422 only — see below.

The x-request-id header

Every PropSocket response — success or error — carries an x-request-id header. On errors, the same value is mirrored into the body as request_id. If you send your own x-request-id on a request, we echo it back so you can thread your correlation ID end-to-end; otherwise we generate one. Include this value whenever you open a support ticket — it's how we find your exact request in our logs in seconds.

Status codes

Quick map before the per-code detail:

CodeMeaningLikely cause
400Bad requestMalformed query param, unparseable filter value.
401UnauthorizedMissing, malformed, revoked, or wrong-environment key.
403ForbiddenValid key, explicit permission denial.
404Not foundNo such record, soft-deleted, or owned by another Organization.
422ValidationWell-formed request, semantically invalid input.
429Rate limitedOrganization-wide limit exceeded.
502Enqueue failedWrite operation was created but couldn't be queued. Poll the Operation — don't re-POST.
5xxServer errorOur fault. Retry with backoff.

400 — Bad Request

The request itself is malformed — a query parameter we can't parse, or a filter value of the wrong type.

400
{
  "type": "https://docs.propsocket.io/errors/bad_request",
  "title": "Bad Request",
  "status": 400,
  "detail": "Invalid value for query parameter 'limit': expected an integer, got 'abc'.",
  "instance": "/v1/units",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

401 — Unauthorized

We can't authenticate you: no key, a malformed header, a revoked key, or a key sent to the wrong environment. Fix the credential. See authentication.

401
{
  "type": "https://docs.propsocket.io/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Authentication credentials were not provided.",
  "instance": "/v1/properties",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

403 — Forbidden

We know who you are, but you're explicitly denied. This is a permission-level denial, not a data-ownership one. Note the cross-org subtlety below.

403
{
  "type": "https://docs.propsocket.io/errors/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "You do not have permission to access this resource.",
  "instance": "/v1/properties/prp_01HX0G5T8N3KEWBYV2QMR4DCFA",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

404 — Not Found

The record doesn't exist for your key. That covers three cases:

  • It never existed.
  • It's soft-deleted. List endpoints hide soft-deleted records by default — retry the lookup or list with ?include_deleted=true and check deleted_at.
  • It belongs to another Organization. Cross-tenant access returns 404, not 403 — PropSocket filters other tenants' records out before the lookup, so to your key they don't exist. This is a deliberate information-leak defense: a 403 would confirm the ID is real.
404
{
  "type": "https://docs.propsocket.io/errors/not_found",
  "title": "Not Found",
  "status": 404,
  "detail": "Property with id 'prp_01HX0G5T8N3KEWBYV2QMR4DCFA' was not found.",
  "instance": "/v1/properties/prp_01HX0G5T8N3KEWBYV2QMR4DCFA",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

422 — Unprocessable Entity

The request is well-formed and every value parses to its declared type, but a value breaks a rule — an unknown sort field, a value out of range, a write to a non-writable field. 422 adds anerrors array with one entry per offending field, so a client can map problems back to inputs without parsing prose. (A value that can't be parsed at all — bad JSON, a non-numericlimit — is a 400 instead; see above.)

422
{
  "type": "https://docs.propsocket.io/errors/validation_error",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "One or more query parameters failed validation.",
  "instance": "/v1/leases",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF",
  "errors": [
    { "pointer": "order-by", "detail": "Unknown field 'foo'. Valid: created_at, updated_at, start_date." },
    { "pointer": "limit", "detail": "Ensure this value is less than or equal to 100." }
  ]
}

429 — Too Many Requests

Your Organization exceeded its request rate. Read the Retry-After header (seconds) and back off — don't hammer. Rate-limit headers and the full backoff story are inrate limits.

429
{
  "type": "https://docs.propsocket.io/errors/rate_limit_exceeded",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Retry after 23 seconds.",
  "instance": "/v1/units",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

502 — Enqueue Failed

Specific to async writes. Your create or update waspersisted as an Operation, but PropSocket couldn't hand it to the background queue that applies it. The detail carries the Operation ID.

Do not blindly re-POST — the Operation already exists, so a naive retry risks a duplicate write. Instead poll GET /v1/operations/{id} (the ID is indetail): the queue often catches up and the Operation reachessucceeded on its own. If it stays non-terminal, re-submit the original request with the same Idempotency-Key so the retry collapses onto the existing Operation rather than creating a second one.

502
{
  "type": "https://docs.propsocket.io/errors/enqueue_failed",
  "title": "Bad Gateway",
  "status": 502,
  "detail": "operation created but could not be queued; retry via GET /v1/operations/op_01HX0G5T8N3KEWBYV2QMR4DCFA",
  "instance": "/v1/units",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

5xx — Server Error

Something broke on our side. These are safe to retry with exponential backoff. If a 5xx persists, open a support ticket with therequest_id — it points us straight at the failed request.

500
{
  "type": "https://docs.propsocket.io/errors/internal_error",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An internal error occurred. Reference this request_id when contacting support.",
  "instance": "/v1/leases",
  "request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}

Next

Handle 429s properly with rate limits, or sort out a 401 with the authentication guide.