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
{
"type": "https://docs.propsocket.io/errors/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Authentication credentials were not provided.",
"instance": "/v1/properties",
"request_id": "req_01HX9P3K2N7QZRWY4B8MJ5VCDF"
}| Field | Meaning |
|---|---|
| type | A URI identifying the error class. Stable — safe to switch on. Resolves to docs. |
| title | Short human-readable summary of the error class. |
| status | The HTTP status code, repeated in the body for convenience. |
| detail | Human-readable specifics for this occurrence. |
| instance | The request path that produced the error. |
| request_id | Correlation 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:
| Code | Meaning | Likely cause |
|---|---|---|
| 400 | Bad request | Malformed query param, unparseable filter value. |
| 401 | Unauthorized | Missing, malformed, revoked, or wrong-environment key. |
| 403 | Forbidden | Valid key, explicit permission denial. |
| 404 | Not found | No such record, soft-deleted, or owned by another Organization. |
| 422 | Validation | Well-formed request, semantically invalid input. |
| 429 | Rate limited | Organization-wide limit exceeded. |
| 502 | Enqueue failed | Write operation was created but couldn't be queued. Poll the Operation — don't re-POST. |
| 5xx | Server error | Our 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.
{
"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.
{
"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.
{
"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=trueand checkdeleted_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.
{
"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.)
{
"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.
{
"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.
{
"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.
{
"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.