Errors
HarborGuard returns errors in a single envelope across every endpoint. The HTTP status is always set; the JSON body adds a machine-readable code, a human message, and an optional list of validation issues.
Error envelope
issues appears only for validation failures. Each issue carries a dotted JSON path into the request body and the offending message.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
BAD_REQUEST | 400 | Malformed request — bad JSON, bad query params, missing required body |
VALIDATION_ERROR | 400 | Body parsed but failed schema validation; see issues |
UNAUTHORIZED | 401 | No credential or unknown / expired credential |
FORBIDDEN | 403 | Credential is valid but role is below the endpoint's minimum |
PLAN_LIMIT | 402 | The organization has exhausted a plan-based quota (scans, members, registries) |
NOT_FOUND | 404 | Resource does not exist or is not visible to your organization |
RATE_LIMITED | 429 | Sliding-window rate limit exceeded — see Retry-After |
INTERNAL_ERROR | 500 | Unexpected server error; the original cause is logged but not leaked |
SERVICE_UNAVAILABLE | 503 | Database or cache temporarily unavailable; retry with backoff |
TIMEOUT | 504 | Request exceeded the server statement timeout |
Specific status notes
404responses use the resource name in the message:"Scan not found","Registry not found". Treat them identically across resources.402 PLAN_LIMITis not a payment failure — it means a resource cap on your current plan is reached. Upgrade or free a slot.503 SERVICE_UNAVAILABLEtypically indicates a transient connection issue; retry after 1–5 s.500 INTERNAL_ERRORis intentionally opaque. The full stack is logged server-side; capture the request URL and timestamp when contacting support.
Handling errors
Webhook errors
The Stripe webhook endpoint (POST /api/billing/webhook) rejects malformed signatures with 400 BAD_REQUEST and never retries from the HarborGuard side. It acknowledges unknown event types with 200 so the upstream provider does not retry indefinitely.