## Authentication (you calling us)

One long-lived bearer partner key, issued by RapidEye when we set you up:

```
Authorization: Bearer rapideye_pk_...
```

Server-side secret only; never ship it to a browser or mobile client. Unknown key
gets `401 invalid_key`; revoked key gets `403 key_revoked`; an integration we have
paused gets `403 partner_disabled`, which is not a problem with the key and is not
fixed by rotating one.

Every non-2xx response uses one envelope; branch on `error.code`, not message text:

```json
{ "error": { "code": "workspace_not_provisioned", "message": "..." } }
```

`code` and `message` are always present. `detail` and `next_steps` are optional
and are **omitted entirely** when they do not apply, never sent as `null` or as
an empty object, so test for the key before reading it. `workspace_not_provisioned`
above carries no specifics, which is why it has neither.

When `detail` is present it is an **object** carrying the machine-actionable
specifics for that code (which parameter was missing, which limit was exceeded,
which property id was not in the roster). Its keys vary by code, so read them per
code rather than assuming a fixed shape.

### Error codes

Every code this surface emits. The status is part of the contract too: branch on
the code, and treat any code you do not recognize as a failure of its status class.

| Code | Status | Meaning |
|---|---|---|
| `missing_parameter` | 400 | A required parameter, field or header is absent or blank. `detail.parameter` names it. |
| `invalid_request` | 400 | The body is unusable, or a field is: malformed JSON, not a JSON object, wrong type, too long, an unknown field, or not yours to set. When one field is at fault `detail.parameter` names it, and a roster item problem also carries `detail.index`. A refused `return_url` also carries `detail.reason`: `not_allowed` or `malformed`. |
| `invalid_email` | 400 | The address on a workspace claim is not one we can send to. |
| `invalid_key` | 401 | The `Authorization` header is missing, malformed, or carries a key we do not recognize. A configuration bug on your side; stop and check the key. |
| `key_revoked` | 403 | The key was valid and we turned it off. Not a configuration bug; email support for a replacement. |
| `partner_disabled` | 403 | Either the whole integration is paused on our side, or we hold no registry row for your slug at all. Your key is not the problem and a new one will not change the answer. Email support. |
| `not_found` | 404 | No such path on this API, or no such `inspection_id` or `import_id` in that workspace. Also what you get for a resource belonging to a different workspace; we do not distinguish the two. |
| `workspace_not_provisioned` | 404 | That workspace id is not provisioned for you. Create it with `POST /v1/workspaces`. |
| `task_not_found` | 404 | No recording link has been issued for that `task_id` in that workspace. Call `GET /v1/recording-link` first. |
| `property_not_found` | 404 | That `property_id` is not in the workspace's roster. Send a roster that contains it. We do not create properties from a link request. |
| `method_not_allowed` | 405 | The path exists; the verb does not. `detail.allowed_methods` lists the ones that do. |
| `claim_requires_admin` | 409 | `POST /v1/workspace-claims` on a workspace your key did not create, or one that already has people in it. Ask RapidEye to nominate the owner. |
| `workspace_already_claimed` | 409 | The workspace already has an owner. They manage their own team from Settings; nothing for you to do. |
| `report_not_ready` | 409 | The inspection exists but has no released report yet, so it has no findings. Wait for `inspection.completed`; do not treat this as a permanent failure. |
| `video_not_ready` | 409 | The inspection's video is not recorded yet, still being processed, or being recorded again. Call again after `inspection.recording_completed` or `inspection.completed`. Not a permanent failure. |
| `video_not_available` | 404 | The inspection has no video to download: it was a photo inspection, or its recording could not be processed. This answer rarely changes, so do not poll for it. |
| `roster_required` | 409 | No roster has been confirmed for this workspace yet, so no recording links can be issued. Send one to `POST /v1/roster-imports`. |
| `property_inactive` | 409 | The property is in the roster but was not in the most recent snapshot you sent, so it is marked inactive. Include it in your next import to bring it back. |
| `property_not_activated` | 409 | The property is in the roster but is not switched on for inspections. Properties switch on a few seconds after an import applies, so if the import has only just applied, wait and retry. Otherwise send the roster again with a new `Idempotency-Key` (or trigger a sync with `POST /v1/roster-syncs` if you host a feed). If this persists, email support. |
| `workspace_not_enabled` | 409 | The account exists and inspections are not switched on for it. The account switches on when a roster import applies, so send the roster again with a new `Idempotency-Key` (or trigger a sync with `POST /v1/roster-syncs` if you host a feed). If this persists, email support. |
| `task_property_conflict` | 409 | That `task_id` already has a link for a different property. A task is bound to one property for its whole life. Use a new `task_id`. |
| `idempotency_conflict` | 409 | That `Idempotency-Key` was already used for a different roster. Reuse a key only to retry the identical request. |
| `roster_revision_conflict` | 409 | The import was built on a roster revision that is no longer current, because another import landed first. Re-read `GET /v1/properties` and send a fresh snapshot with a new `Idempotency-Key`. Appears on the run, in `error.code`. |
| `roster_empty_not_allowed` | 409 | The import carries no properties. An empty roster retires every door, so it is never implicit: re-send with `allow_empty: true` and a new `Idempotency-Key`. Appears on the run, in `error.code`. |
| `roster_confirmation_required` | 409 | The import would deactivate more than half of the present properties. Re-send with `acknowledge_inactive_count` set to the count in `detail` and a new `Idempotency-Key`. Appears on the run, in `error.code`. |
| `roster_duplicate_property` | 409 | A `property_id` appeared twice in one roster. Appears on the run, in `error.code`, for a feed we pulled. A pushed body gets `400 invalid_request` with the repeated ids in `detail.duplicate_property_ids`. |
| `pull_not_configured` | 409 | This workspace pushes its roster; there is no feed for us to poll. |
| `matterport_import_in_progress` | 409 | A Matterport tour is still importing for this home: a different tour on `PUT .../matterport` (`detail.importing` names it), or any tour on `POST .../baseline-photos`, because a home runs one import at a time. `detail.import_id` names the running import. Wait for `setup.matterport.state` to leave `importing`, then send again. |
| `baseline_import_in_progress` | 409 | A baseline photo import is still running for this home, yours or one its managers started, so a new photo batch or a new tour waits: a home runs one import at a time. When the running import is yours, `detail.import_id` names it; one a manager started has no id you can read. Send again once `setup.baseline_photos.state` leaves `importing`. |
| `workspace_already_claimed` | 409 | An owner has already claimed this workspace. |
| `claim_requires_admin` | 409 | This workspace is not one you may nominate an owner for. Email support. |
| `payload_too_large` | 413 | The request body exceeds the endpoint's cap. `detail.limit_bytes` names it. |
| `too_many_requests` | 429 | Too many requests in a short window. Back off and retry; nothing is lost. Provisioning, roster imports, claims and manual syncs each have their own tighter bound. |
| `internal_error` | 500 | Something broke on our side. The request was not processed; retrying is safe. |
| `service_unavailable` | 503 | A dependency of ours is failing, including the one that verifies your key. **Not** a problem with the key; never roll a key in response to this. |
