Error reference
Every error response uses the same envelope. The code is stable; the error message is human-readable and may change. Match on code in your integration.
{
"error": "Human-readable description",
"code": "MACHINE_READABLE_CODE"
}Quick lookup
Click a row to jump to the full description.
| Status | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | No API key, or the key is malformed or unknown. |
| 401 | REVOKED_KEY | The API key was revoked. |
| 403 | INSUFFICIENT_SCOPE | The key is `read_only` but the route requires `read_write`. |
| 403 | PLAN_UPGRADE_REQUIRED | The landlord’s subscription plan does not include API access. |
| 403 | WRITE_REQUIRES_ENTERPRISE | Write endpoints are Enterprise-only. |
| 403 | HTTPS_REQUIRED | The request was made over plain HTTP in production. |
| 429 | RATE_LIMIT_EXCEEDED | You hit the per-minute or per-day limit for this key. |
| 503 | RATE_LIMIT_UNAVAILABLE | HomeDash could not check the rate-limit datastore, so the request was rejected to fail closed. |
| 410 | ENDPOINT_DEPRECATED | The route is no longer supported. |
| 400 | VALIDATION_ERROR | Query parameters or request body failed schema validation. |
| 400 | BAD_REQUEST | Generic 400 returned by `responses.badRequest()` when no specific code applies. |
| 400 | TENANT_EMAIL_MISSING | Cannot send invoice — no tenant email on file and no override in the body. |
| 409 | INVOICE_EMAIL_IN_PROGRESS | Another request is already sending the invoice for this rent payment. |
| 409 | INVOICE_EMAIL_FAILED | A previous public API invoice email attempt failed for this rent payment. |
| 409 | CERTIFICATE_DERIVED_FIELD | A certificate on file already derives this renewal date, so it cannot be set by hand. |
| 409 | IDEMPOTENCY_IN_PROGRESS | Another request with the same `Idempotency-Key` is still running. |
| 404 | NOT_FOUND | The resource does not exist or does not belong to the landlord. |
| 409 | CONFLICT | The request conflicts with existing data. |
| 413 | FILE_TOO_LARGE | The uploaded file exceeds the allowed size. |
| 403 | FORBIDDEN | Generic 403 for cases not covered by a more specific code. |
| 503 | SERVICE_UNAVAILABLE | Generic 503 when a downstream dependency is degraded. |
| 500 | SERVER_ERROR | Unexpected error inside HomeDash. |
Detailed reference
UNAUTHORIZED
No API key, or the key is malformed or unknown.
- When it happens
- The `Authorization` header is missing, not a Bearer token, or the key cannot be found in HomeDash.
- What to do
- Send `Authorization: Bearer hd_live_…`. Generate a key in the Developer Dashboard if you do not have one.
{
"error": "Missing API key",
"code": "UNAUTHORIZED"
}REVOKED_KEY
The API key was revoked.
- When it happens
- A landlord (or team admin) revoked this key from the Developer Dashboard.
- What to do
- Generate a new key, update your integration, and rotate any stored copies.
{
"error": "API key revoked",
"code": "REVOKED_KEY"
}INSUFFICIENT_SCOPE
The key is `read_only` but the route requires `read_write`.
- When it happens
- You called a write route (for example `POST /rent/{id}/invoice/email`) with a read-only key.
- What to do
- Generate a `read_write` key. Use read-only keys for analytics / AI assistants and read-write keys only for trusted automation.
{
"error": "This API key has read_only scope",
"code": "INSUFFICIENT_SCOPE"
}PLAN_UPGRADE_REQUIRED
The landlord’s subscription plan does not include API access.
- When it happens
- The plan is Free/Essential/Professional (or there is no active subscription). Public API requires Portfolio or Enterprise.
- What to do
- Upgrade in `Account → Subscription`. Read endpoints unlock on Portfolio; write endpoints require Enterprise.
{
"error": "Upgrade plan to access Public API",
"code": "PLAN_UPGRADE_REQUIRED"
}WRITE_REQUIRES_ENTERPRISE
Write endpoints are Enterprise-only.
- When it happens
- You called a non-GET endpoint with a Portfolio plan. Read endpoints work on Portfolio, but writes require Enterprise.
- What to do
- Upgrade to Enterprise, or use the read-only data flow (subscribe to webhooks for state changes instead of polling and writing back).
{
"error": "Upgrade to Enterprise to use write endpoints",
"code": "WRITE_REQUIRES_ENTERPRISE"
}HTTPS_REQUIRED
The request was made over plain HTTP in production.
- When it happens
- A production deployment received an `http://` request. HomeDash never accepts API traffic without TLS.
- What to do
- Switch all URLs to `https://`.
{
"error": "HTTPS is required",
"code": "HTTPS_REQUIRED"
}RATE_LIMIT_EXCEEDED
You hit the per-minute or per-day limit for this key.
- When it happens
- Sliding-window counters in the last 60 seconds or 24 hours exceeded the plan’s allowance.
- What to do
- Inspect the `Retry-After` header (seconds) and back off. Use `X-RateLimit-Remaining` proactively. Cache reads where possible.
{
"error": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED"
}RATE_LIMIT_UNAVAILABLE
HomeDash could not check the rate-limit datastore, so the request was rejected to fail closed.
- When it happens
- Transient database issue inside HomeDash.
- What to do
- Retry after the `Retry-After` value (60 seconds is typical).
{
"error": "Rate limiting unavailable",
"code": "RATE_LIMIT_UNAVAILABLE"
}ENDPOINT_DEPRECATED
The route is no longer supported.
- When it happens
- For example: `PATCH /maintenance/{id}` was replaced by `PATCH /maintenance/issues/{id}`.
- What to do
- Use the successor path named in the `error` message and `Link` response header.
{
"error": "This endpoint is deprecated. Use PATCH /api/public/v1/maintenance/issues/{id}.",
"code": "ENDPOINT_DEPRECATED"
}VALIDATION_ERROR
Query parameters or request body failed schema validation.
- When it happens
- For example: `limit=501` (max 500), invalid email in invoice body, or `POST …/invoice/email` with empty body / invalid JSON (use at least `{}`).
- What to do
- Read the `error` message — it lists the first 10 issues with field paths. Fix and retry.
{
"error": "limit: Invalid input",
"code": "VALIDATION_ERROR"
}BAD_REQUEST
Generic 400 returned by `responses.badRequest()` when no specific code applies.
- When it happens
- Rare. Most 400s use `VALIDATION_ERROR` instead. Used for malformed JSON outside the schema layer.
- What to do
- Check the `error` message and the request format.
{
"error": "Bad request",
"code": "BAD_REQUEST"
}TENANT_EMAIL_MISSING
Cannot send invoice — no tenant email on file and no override in the body.
- When it happens
- `POST /rent/{id}/invoice/email` when the rent payment has no tenant email and you did not pass `{"email":"…"}` in the body.
- What to do
- Add the tenant email in HomeDash, or POST with `{"email":"recipient@example.com"}`.
{
"error": "Tenant email not found. Cannot send invoice.",
"code": "TENANT_EMAIL_MISSING"
}INVOICE_EMAIL_IN_PROGRESS
Another request is already sending the invoice for this rent payment.
- When it happens
- Concurrent `POST /rent/{id}/invoice/email` calls while the first is still in progress (dedup row is `pending`).
- What to do
- Wait a few seconds and retry. If the first request succeeded, a retry returns `200` with the stored payload.
{
"error": "Invoice email is already in progress for this rent payment",
"code": "INVOICE_EMAIL_IN_PROGRESS"
}INVOICE_EMAIL_FAILED
A previous public API invoice email attempt failed for this rent payment.
- When it happens
- Dedup log is `failed` for this rent. Public API blocks a second automatic send.
- What to do
- Resend from the landlord dashboard, or contact support if the failure was transient.
{
"error": "Invoice email previously failed for this rent payment. Use the dashboard to resend.",
"code": "INVOICE_EMAIL_FAILED"
}CERTIFICATE_DERIVED_FIELD
A certificate on file already derives this renewal date, so it cannot be set by hand.
- When it happens
- You sent gscRenewal, eicrRenewal, epcRenewal or landlordLicenceRenewal on PATCH /properties/{id} with a value that contradicts the certificate HomeDash holds for that property. Sending the value the certificate already implies is accepted, and a date for a certificate type with nothing on file is accepted.
- What to do
- Upload the replacement certificate (the expiry is read from it), or drop the field from your patch. Retrying the same body will keep failing until the certificate changes.
{
"error": "gsc_renewal is derived from the GSC_Certificate on file (expires 2027-03-12) and cannot be set by hand. Upload a replacement certificate to change it.",
"code": "CERTIFICATE_DERIVED_FIELD"
}IDEMPOTENCY_IN_PROGRESS
Another request with the same `Idempotency-Key` is still running.
- When it happens
- You sent `Idempotency-Key` on `POST /rent/{id}/invoice/email` and a matching request is `pending`.
- What to do
- Wait and retry with the same key, or retry without the header after the first call completes.
{
"error": "Request with this Idempotency-Key is already in progress",
"code": "IDEMPOTENCY_IN_PROGRESS"
}NOT_FOUND
The resource does not exist or does not belong to the landlord.
- When it happens
- The id was not found, OR it belongs to a different landlord. HomeDash deliberately returns 404 (not 403) on cross-tenant lookups so the API never confirms whether a resource exists outside your portfolio.
- What to do
- Check the id. If you copied it from another HomeDash account, you will not be able to see it.
{
"error": "maintenance issue",
"code": "NOT_FOUND"
}CONFLICT
The request conflicts with existing data.
- When it happens
- For example: duplicate rent payment for the same contract and period, or deleting a property that still has active contracts.
- What to do
- Read the `error` message, resolve the conflict, and retry with corrected data.
{
"error": "Rent payment already exists for this contract and period",
"code": "CONFLICT"
}FILE_TOO_LARGE
The uploaded file exceeds the allowed size.
- When it happens
- A document upload exceeds the storage bucket object size limit (for example on `POST /properties/{id}/documents`).
- What to do
- Compress or split the file, or upload a smaller version, then retry.
{
"error": "File is too large to upload. Please choose a smaller file.",
"code": "FILE_TOO_LARGE"
}FORBIDDEN
Generic 403 for cases not covered by a more specific code.
- When it happens
- Reserved for future policy errors. Most 403s use a more specific code above.
- What to do
- Read the `error` message.
{
"error": "Forbidden",
"code": "FORBIDDEN"
}SERVICE_UNAVAILABLE
Generic 503 when a downstream dependency is degraded.
- When it happens
- Reserved for future use; today the only 503 you will see is `RATE_LIMIT_UNAVAILABLE`.
- What to do
- Retry with exponential backoff.
{
"error": "Service unavailable",
"code": "SERVICE_UNAVAILABLE"
}SERVER_ERROR
Unexpected error inside HomeDash.
- When it happens
- Bug, broken assumption, or unhandled exception. The response intentionally omits stack traces.
- What to do
- Retry once. If it persists, contact support@homedash.co.uk with the timestamp and request path.
{
"error": "Internal server error",
"code": "SERVER_ERROR"
}Defensive coding tips
- Treat any
5xxas transient and retry once with jitter. If it persists, surface the error to the operator and stop hammering. - Honour
Retry-Afteron429and503. ReadX-RateLimit-Remainingproactively so you back off before hitting the wall. - Never assume a
404means the resource has been deleted — it can also mean the id belongs to a different landlord. - On
REVOKED_KEY, do not retry. Surface a configuration error and stop.