Error Handling
Every NeuralRepo API error returns a JSON object with an error field. The HTTP status gives
the category; the body sometimes carries more.
Error Response Format
Section titled “Error Response Format”Most errors are a single string:
{ "error": "Idea not found"}Some errors add fields alongside error. Where they do, error may be a machine-readable
code rather than a sentence — POST /ideas over the Free-plan cap returns
"error": "idea_limit_reached" with the human-readable text in message.
Status Codes
Section titled “Status Codes”400 Bad Request
Section titled “400 Bad Request”The path, body, or query parameters failed validation.
{ "error": { "title": ["Too small: expected string to have >=1 characters"] }}Common causes:
- Missing required fields (
titleon idea creation,qon search) - A field over its limit (body > 50,000 characters, more than 20 tags, more than 50 bulk ids)
- Invalid format (colour not
#rrggbb, unparseable URL, unknownmodeorlink_type) - A non-numeric
:idin the path —{ "error": "Invalid idea ID" } - A relation that would create a cycle — despite the wording of the message, this is a
400
401 Unauthorized
Section titled “401 Unauthorized”Authentication is missing or invalid. The body is always identical:
{ "error": "Unauthorized"}There is no separate message for expired, revoked, malformed, or absent credentials — all four produce this exact response. Common causes:
- No
Authorization,X-API-Key, or cookie credential - An
X-API-Keyvalue that does not begin withnrp_(it is ignored entirely) - A session older than 30 days with no activity
- A revoked key
403 Forbidden
Section titled “403 Forbidden”Authenticated, but not allowed. Three distinct bodies:
Plan gate — POST /user/api-keys, BYOK save/test, relation writes, agent WebSocket:
{ "error": "Idea relations requires a Pro plan", "pro_required": true, "feature": "Idea relations", "upgrade_url": "https://neuralrepo.com/upgrade"}Free-plan idea cap — POST /ideas only:
{ "error": "idea_limit_reached", "message": "You've captured 50 ideas on the free plan. Upgrade to Pro for unlimited ideas.", "currentCount": 50, "limit": 50, "upgradeUrl": "https://neuralrepo.com/upgrade", "featureRequestUrl": "https://support.neuralrepo.com/feature-requests"}Scope — MCP OAuth tokens only:
{ "error": "Insufficient scope: requires ideas:write"}Branch on pro_required and on error === "idea_limit_reached" rather than on the sentence.
404 Not Found
Section titled “404 Not Found”The resource does not exist, or belongs to another account — the two are deliberately indistinguishable.
{ "error": "Idea not found"}Common causes:
- An id that is not yours
- Passing a display number (
#42) where the API wants a databaseid— the single most common cause of a spurious404 - A duplicate detection that has already been dismissed or merged
Note that some deletes do not 404 on a bad id: DELETE /ideas/:id/links/:linkId and
DELETE /map/relations/:id both return { "success": true } regardless.
409 Conflict
Section titled “409 Conflict”Only two situations produce a 409:
{ "error": "Tag name already exists"}POST /tagswith a name you already use.POST /map/relationswhen a user-created relation already exists between that pair in that direction —{ "error": "Relation already exists" }. Uniqueness ignores the relation type, so a second edge of a different type between the same two ideas also conflicts.
429 Too Many Requests
Section titled “429 Too Many Requests”The daily quota is exhausted. See Rate Limits.
{ "error": "Rate limit exceeded"}No Retry-After header is sent — the response has no rate-limit headers of any kind. The
window is a calendar day in UTC, so there is nothing to poll for until midnight.
500 Internal Server Error
Section titled “500 Internal Server Error”An unhandled exception. In production the body is generic:
{ "error": "Internal server error"}The most reproducible cause is renaming a tag onto a name you already use — the unique
constraint is not caught, so it surfaces here rather than as a 409.
502 Bad Gateway
Section titled “502 Bad Gateway”An upstream service the request depends on failed:
| Endpoint | Cause |
|---|---|
POST /ideas/:id/develop | Your BYOK provider rejected or failed the call; body includes provider |
GET /user/support-link | The support site could not mint a link |
503 Service Unavailable
Section titled “503 Service Unavailable”Returned only by GET /api/v1/health, when the database probe fails:
{ "status": "error", "error": "Database unavailable"}Failures that are not error statuses
Section titled “Failures that are not error statuses”Several operations report failure inside a 2xx response. Checking res.ok is not enough:
| Endpoint | Success status | What to check |
|---|---|---|
PATCH /ideas/bulk | 200 | errors count and the per-idea results array |
POST /map/relations (bulk) | 201 | errors count and the per-link results array |
POST /user/byok/:provider/test | 200 | ok — a rejected key is still a 200 |
GET /ideas/search | 200 | semantic_limit_reached, and search_type if you assumed semantic |
GET /ideas/duplicates | 200 | On Free, the list is always empty regardless of what was detected |
Handling Errors in Code
Section titled “Handling Errors in Code”function formatError(error) { if (typeof error === "string") return error; if (error && typeof error === "object") { return Object.entries(error) .map(([field, msgs]) => `${field}: ${[].concat(msgs).join(", ")}`) .join("; "); } return "Unknown error";}
const res = await fetch("https://neuralrepo.com/api/v1/ideas", { method: "POST", headers: { "X-API-Key": "nrp_YOUR_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ title: "" }),});
if (!res.ok) { const body = await res.json(); switch (res.status) { case 400: console.error("Validation error:", formatError(body.error)); break; case 401: console.error("Auth error — check your API key"); break; case 403: console.error( body.error === "idea_limit_reached" ? body.message : body.pro_required ? `${body.feature} needs Pro: ${body.upgrade_url}` : formatError(body.error) ); break; case 429: // No Retry-After header exists — the quota resets at 00:00 UTC. console.error("Daily rate limit exhausted"); break; default: console.error(`Error ${res.status}:`, formatError(body.error)); }}# curl shows the HTTP status with -w and the body togethercurl -s -w "\nHTTP Status: %{http_code}\n" \ -X POST https://neuralrepo.com/api/v1/ideas \ -H "X-API-Key: nrp_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"title": ""}'Summary Table
Section titled “Summary Table”| Status | Name | Retryable | error type | Description |
|---|---|---|---|---|
400 | Bad Request | No | String or object | Fix the request and resend; includes refused cycles |
401 | Unauthorized | No | String | Always "Unauthorized" |
403 | Forbidden | No | String or code | Plan gate, idea cap, or insufficient scope |
404 | Not Found | No | String | Wrong id — check you passed id, not number |
409 | Conflict | No | String | Tag name taken, or that relation pair already exists |
429 | Too Many Requests | Not today | String | Quota resets at 00:00 UTC; no Retry-After |
500 | Internal Server Error | Sometimes | String | Retry once; a tag rename collision will never succeed |
502 | Bad Gateway | Yes | String | Upstream AI provider or support service |
503 | Service Unavailable | Yes | String | Health check only — the database is unreachable |