Error Codes
Every response from /api/* is JSON, errors included. The body always has a
message; validation errors add a field-level errors map.
| Status | Meaning | Body shape |
|---|---|---|
200 |
OK — report deduplicated into an existing card | { "data": { … }, "deduplicated": true } |
200 |
Dropped — accepted and discarded, no card created | { "dropped": true, "reason": "…" } |
201 |
Created — a new card | { "data": { … }, "deduplicated": false } |
401 |
Missing/invalid key, bad signature, or expired timestamp | { "message": "Invalid or missing project API key." } |
403 |
Publishable key used from a disallowed origin | { "message": "Origin not allowed." } |
422 |
Validation failed (or the project has no board column) | { "message": "…", "errors": { "field": ["…"] } } |
429 |
Per-minute burst limit exceeded | { "message": "…" } + Retry-After |
5xx |
Server error | { "message": "…" } |
A
200is not always a stored card. A report can be accepted and dropped — the server answers200with{ "dropped": true, "reason": "…" }and nodata. This is deliberate: an SDK watching your app must never surface our billing or project state as a failure inside your app. A drop is never retryable.
quota_exceeded: truerides along on every drop as a legacy alias ofdropped, so SDK versions released beforereasonexisted still know not to retry. Preferdroppedandreasonin new code.The card is hidden by default. The SDKs send
X-Bb-Hide-Response, which dropsdatafrom the200/201body — you get a bare{ "deduplicated": true }or{ "deduplicated": false }. The status codes and the outcome flags (deduplicated,dropped,reason) are unchanged; only the card itself is withheld, so a report you encrypted isn't handed back in plaintext. See the API Reference.
Why a report was dropped
reason tells you which of three very different things happened — and each has a
different fix.
reason |
What it means | What to do |
|---|---|---|
quota |
Your account's event allowance for the day is spent. It is shared across every project you own. | Not retryable. It resets at midnight UTC, or upgrade for a bigger allowance. You are emailed at 75%, 90% and 100% before it runs out. |
paused |
The project is paused — read-only, and accepting no events. It still has all of its data. | Not retryable. Resume the project from your projects page. If it was paused because you're over your plan's project limit, free a slot or upgrade. |
archived |
The project is archived — permanently read-only. | Not retryable. Archiving is one-way; point your SDK at a live project. |
Only an active project accepts events. Pausing or archiving a project stops ingestion for it immediately, and that applies to the API exactly as it does to the dashboard.
What to do with each
401/403/422are configuration or payload bugs — fix the request. They are not retryable; retrying sends the same broken request.429is retryable: back off and honour theRetry-Afterheader (see Rate Limits & Quotas).5xxand network errors are retryable with exponential backoff.- A drop (
dropped: true) is never retryable, whatever the reason. Retrying a paused project just burns requests against your per-minute limit.
Never crash the app you're monitoring. When you call the API directly, wrap reporting in a
try/catchand swallow failures. The SDKs do this for you — reporting is fire-and-forget and errors surface only on the SDK's debug channel.
SDK exception mapping
Every SDK maps these statuses to typed exceptions so you can handle them consistently:
| HTTP | JavaScript | PHP | Carries |
|---|---|---|---|
| 401, 403 | BugBoardAuthError |
AuthException |
message |
| 422 | BugBoardValidationError |
ValidationException |
fieldErrors (the errors map) |
| 429 | BugBoardRateLimitError |
RateLimitException |
retryAfter (seconds) |
| 5xx / network | BugBoardServerError |
ServerException |
message, underlying cause |
A drop maps to no exception — it is a
200, and the report simply went nowhere. Nothing is thrown, nothing is retried. Readdropped/reasonoff the body if you want to log why.Treat
401/403/422as non-retryable and retry only429,5xx, and network errors.Retry-Afteris honoured verbatim in seconds and takes precedence over the SDK's own backoff; a non-numeric value is ignored.
Common 422 causes
- Missing
severity— it's the one required field. - Invalid enum —
severityorpriorityoutside its allowed set (see the API Reference).description_typeandstatusare decided by the server and ignored if sent, so they never cause a422. - Oversized field —
title> 255, a tag > 50, ordescription> 65535 characters.