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.

JSON
{
  "error": "Human-readable description",
  "code": "MACHINE_READABLE_CODE"
}

Quick lookup

Click a row to jump to the full description.

StatusCodeMeaning
401UNAUTHORIZEDNo API key, or the key is malformed or unknown.
401REVOKED_KEYThe API key was revoked.
403INSUFFICIENT_SCOPEThe key is `read_only` but the route requires `read_write`.
403PLAN_UPGRADE_REQUIREDThe landlord’s subscription plan does not include API access.
403WRITE_REQUIRES_ENTERPRISEWrite endpoints are Enterprise-only.
403HTTPS_REQUIREDThe request was made over plain HTTP in production.
429RATE_LIMIT_EXCEEDEDYou hit the per-minute or per-day limit for this key.
503RATE_LIMIT_UNAVAILABLEHomeDash could not check the rate-limit datastore, so the request was rejected to fail closed.
410ENDPOINT_DEPRECATEDThe route is no longer supported.
400VALIDATION_ERRORQuery parameters or request body failed schema validation.
400BAD_REQUESTGeneric 400 returned by `responses.badRequest()` when no specific code applies.
400TENANT_EMAIL_MISSINGCannot send invoice — no tenant email on file and no override in the body.
409INVOICE_EMAIL_IN_PROGRESSAnother request is already sending the invoice for this rent payment.
409INVOICE_EMAIL_FAILEDA previous public API invoice email attempt failed for this rent payment.
409CERTIFICATE_DERIVED_FIELDA certificate on file already derives this renewal date, so it cannot be set by hand.
409IDEMPOTENCY_IN_PROGRESSAnother request with the same `Idempotency-Key` is still running.
404NOT_FOUNDThe resource does not exist or does not belong to the landlord.
409CONFLICTThe request conflicts with existing data.
413FILE_TOO_LARGEThe uploaded file exceeds the allowed size.
403FORBIDDENGeneric 403 for cases not covered by a more specific code.
503SERVICE_UNAVAILABLEGeneric 503 when a downstream dependency is degraded.
500SERVER_ERRORUnexpected error inside HomeDash.

Detailed reference

401

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.
Example body
{
  "error": "Missing API key",
  "code": "UNAUTHORIZED"
}
401

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.
Example body
{
  "error": "API key revoked",
  "code": "REVOKED_KEY"
}
403

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.
Example body
{
  "error": "This API key has read_only scope",
  "code": "INSUFFICIENT_SCOPE"
}
403

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.
Example body
{
  "error": "Upgrade plan to access Public API",
  "code": "PLAN_UPGRADE_REQUIRED"
}
403

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).
Example body
{
  "error": "Upgrade to Enterprise to use write endpoints",
  "code": "WRITE_REQUIRES_ENTERPRISE"
}
403

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://`.
Example body
{
  "error": "HTTPS is required",
  "code": "HTTPS_REQUIRED"
}
429

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.
Example body
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED"
}
503

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).
Example body
{
  "error": "Rate limiting unavailable",
  "code": "RATE_LIMIT_UNAVAILABLE"
}
410

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.
Example body
{
  "error": "This endpoint is deprecated. Use PATCH /api/public/v1/maintenance/issues/{id}.",
  "code": "ENDPOINT_DEPRECATED"
}
400

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.
Example body
{
  "error": "limit: Invalid input",
  "code": "VALIDATION_ERROR"
}
400

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.
Example body
{
  "error": "Bad request",
  "code": "BAD_REQUEST"
}
400

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"}`.
Example body
{
  "error": "Tenant email not found. Cannot send invoice.",
  "code": "TENANT_EMAIL_MISSING"
}
409

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.
Example body
{
  "error": "Invoice email is already in progress for this rent payment",
  "code": "INVOICE_EMAIL_IN_PROGRESS"
}
409

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.
Example body
{
  "error": "Invoice email previously failed for this rent payment. Use the dashboard to resend.",
  "code": "INVOICE_EMAIL_FAILED"
}
409

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.
Example body
{
  "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"
}
409

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.
Example body
{
  "error": "Request with this Idempotency-Key is already in progress",
  "code": "IDEMPOTENCY_IN_PROGRESS"
}
404

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.
Example body
{
  "error": "maintenance issue",
  "code": "NOT_FOUND"
}
409

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.
Example body
{
  "error": "Rent payment already exists for this contract and period",
  "code": "CONFLICT"
}
413

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.
Example body
{
  "error": "File is too large to upload. Please choose a smaller file.",
  "code": "FILE_TOO_LARGE"
}
403

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.
Example body
{
  "error": "Forbidden",
  "code": "FORBIDDEN"
}
503

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.
Example body
{
  "error": "Service unavailable",
  "code": "SERVICE_UNAVAILABLE"
}
500

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.
Example body
{
  "error": "Internal server error",
  "code": "SERVER_ERROR"
}

Defensive coding tips

  • Treat any 5xx as transient and retry once with jitter. If it persists, surface the error to the operator and stop hammering.
  • Honour Retry-After on 429 and 503. Read X-RateLimit-Remaining proactively so you back off before hitting the wall.
  • Never assume a 404 means 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.