> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prophia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and permissions

> Status codes, the error body, the capabilities model, and why 404 sometimes means 403

For how to obtain and send an API key, see [Authentication](https://docs.prophia.com/developers/authentication.md). This page covers what happens once your key is on the request.

## Status codes

| Status | Meaning |
| - | - |
| `400` | Malformed request — an unknown `include` key, an invalid parameter value, or a body that failed validation or could not be parsed. On `POST /docs` (create via URL) it also covers a document URL that could not be fetched, including one whose content is over 200 MB. The body's `message` says what was wrong (unknown-include errors list every supported key; validation errors name each field). |
| `401` | A JWT was sent where an API key was expected. This response carries only `error`. |
| `403` | The key was refused: it is missing, unknown or revoked, is scoped to a different account than the path, or is valid but lacks a required capability or module or isn't allowed to see the requested building. When the block is a missing capability the message names which; other denials return a generic message rather than disclosing the reason. |
| `404` | The resource isn't visible in the account named in the path — which may mean it doesn't exist, or that it exists and you can't see it (see below). Building-scoped paths (`/buildings/{buildingId}/…`) answer `403` instead when the building isn't one your key can see. |
| `409` | The write conflicts with something already there. On both document-create endpoints, a non-deleted document with that filename already exists on the lease — nothing is stored. |
| `413` | The file sent to `POST /docs/upload` (create via multipart upload) exceeds 200 MB. Only the multipart endpoint uses this code; the URL-based create reports the same limit as a `400`. |

## The error body

Errors raised by an endpoint share one JSON body:

```json theme={null}
{
  "error": "Entity Not Found",
  "errorCode": "EntityNotFound",
  "message": "This entity is not found in the system.",
  "status": "404 NOT_FOUND",
  "timestamp": "2026-09-02T20:41:03.118Z",
  "localizedMessage": "This entity is not found in the system."
}
```

| Field | Use it for |
| - | - |
| `errorCode` | Programmatic handling. Stable identifiers such as `InvalidInput`, `AccessDenied`, `EntityNotFound`, `NonUniqueEntry`, `UploadTooLarge`. |
| `error` | The same category as a readable label. |
| `message` | Human-readable detail — what was wrong with the request, or which capabilities are missing. Do not match on it. |
| `status` | The HTTP status as code and reason phrase, matching the response's own status line. |
| `timestamp` | When the error was produced, as an ISO-8601 UTC instant. Quote it when contacting support. |
| `localizedMessage` | The same text as `message`. Prefer `message`. |

The body may also include `stackTrace` and `suppressed` as empty arrays; ignore them.

Refusals decided before the request reaches an endpoint — a missing, unknown or revoked key, or a key scoped to a different account — return the platform's standard `403` error body instead, and the JWT `401` carries only `error`.

## The capabilities model

Every operation requires a **capability** — a read or write grant on a resource type: account, property, building, building space, lease, document, tag, or custom tag. Which capabilities your key carries depends on the key:

* **Account keys** carry the REST API and MCP permissions chosen when the key was created. On accounts without granular permissions, they carry the account's full grant set.
* **User keys** carry their user's permissions, or the subset chosen when the key was created — two keys on the same account can see different data.

Capability checks apply to the **primary resource and to each `include` key**. `GET /leases/{leaseId}` needs lease read; adding `?include=tags` additionally needs tag read. A request missing any required capability is rejected with `403` — embedded collections are never silently dropped.

Each endpoint page in this reference states the capabilities that operation requires.

## Writes need the REST write module

`POST`, `PATCH`, and `DELETE` require the **REST API write module** to be enabled on the account, on top of the relevant write capability. If writes fail with `403` while reads work, the module is the first thing to check. See [Write access](https://docs.prophia.com/developers/write-access.md) for how it is granted.

## When 404 means "no access"

> [!NOTE]
>
> For resource ids inside an account your key can see, the API deliberately does not distinguish "doesn't exist" from "exists but you can't see it" — so callers can't probe for the existence of data they aren't entitled to.

Practically: a `404` during integration debugging is often a **permissions problem**, not a wrong id. Check, in order:

1. Is the `{accountId}` in the path the account the resource actually belongs to? A key scoped to a different account is refused with `403` before the lookup, so a `404` means the account matched but the resource did not.
2. Does your key have the read capability for this resource type?
3. Is it a user key whose user lacks access to this building? Building-level permissions filter what you can see, and building-scoped paths report that as `403`.
4. Only then: is the id actually wrong?


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.