Skip to main content
Every Nudj API endpoint uses the same error envelope — the tRPC wire format — so clients can share a single error handler across all three APIs.

Envelope

{
  "message": "Human-readable description of the problem.",
  "code": "TRPC_ERROR_CODE",
  "data": {
    "httpStatus": 400,
    "path": "endpointName"
  }
}
FieldDescription
messageFree-text description. Safe to surface in developer-facing logs. Don’t surface verbatim to end users — messages aren’t localised.
codeStable tRPC error code — e.g. UNAUTHORIZED, FORBIDDEN, NOT_FOUND, BAD_REQUEST, INTERNAL_SERVER_ERROR.
data.httpStatusThe HTTP status code of the response. Mirrors the response status line.
data.pathName of the endpoint procedure that rejected the request. Useful for log correlation.

Common status codes

400 — BAD_REQUEST

Zod validation failure on the request body, a path/query parameter, or a required header.
{
  "message": "Invalid input: expected string for body.communityId",
  "code": "BAD_REQUEST",
  "data": { "httpStatus": 400, "path": "createChallenge" }
}
Fix: read the message — it names the offending field. Re-check your request shape against the endpoint’s schema.

401 — UNAUTHORIZED

Token missing, malformed, expired, or issued for a different organisation’s subdomain.
{
  "message": "You must be logged in to access this endpoint.",
  "code": "UNAUTHORIZED",
  "data": { "httpStatus": 401, "path": "getAchievements" }
}
Fix: see Authentication troubleshooting.

403 — FORBIDDEN

Token is valid but lacks the RBAC permissions for the requested action. Most common on Admin endpoints.
{
  "message": "Insufficient permissions for this operation.",
  "code": "FORBIDDEN",
  "data": { "httpStatus": 403, "path": "deleteChallenge" }
}
Fix: raise the token’s role (if appropriate), or narrow the request to a community the token has access to.

404 — NOT_FOUND

No entity matches the request — wrong id, wrong organisation, or the entity has been deleted.
{
  "message": "Challenge not found.",
  "code": "NOT_FOUND",
  "data": { "httpStatus": 404, "path": "getChallengeById" }
}

409 — CONFLICT

The request would break a uniqueness constraint or violates current state (e.g. trying to claim a reward already claimed, re-use a one-time code, or register a duplicate domain).
{
  "message": "Reward already claimed.",
  "code": "CONFLICT",
  "data": { "httpStatus": 409, "path": "claimReward" }
}

500 — INTERNAL_SERVER_ERROR

An unexpected runtime error. The response body surfaces the tRPC code but the detailed stack trace is logged server-side and not returned.
{
  "message": "An unexpected error occurred.",
  "code": "INTERNAL_SERVER_ERROR",
  "data": { "httpStatus": 500, "path": "getMyProfile" }
}
Response-validation errors can also surface as 500. If the API returns data that fails its own output schema (historically this has happened on a few integration endpoints when new enum values are rolled out), clients receive a generic 500 rather than a typed failure. If you consistently hit a 500 on a previously-working endpoint, file a bug — it’s very likely a schema regression rather than a runtime fault.

Client handling pattern

async function nudjRequest(path, init = {}) {
  const response = await fetch(`https://${DOMAIN}${path}`, {
    ...init,
    headers: {
      'Authorization': `Bearer ${process.env.NUDJ_API_TOKEN}`,
      'Content-Type': 'application/json',
      ...(init.headers ?? {}),
    },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => null);
    // error has shape { message, code, data: { httpStatus, path } }
    throw Object.assign(
      new Error(error?.message ?? `HTTP ${response.status}`),
      { code: error?.code, httpStatus: response.status, path: error?.data?.path },
    );
  }

  return response.json();
}

Retries

The APIs don’t emit Retry-After headers, but 5xx responses (500, 502, 503) are always safe to retry with exponential backoff. Do not blindly retry 4xx — those are request-shape errors and will fail identically a second time.

Authentication

401 and 403 scenarios in depth.

Pagination

Common 400 errors on list endpoints.