ReferenceError Codes

Error Codes

Every error in the HummyTummy API is returned through a single, standard error envelope. On the client, branch on the stable errorCode field — not on the human-readable message.

Error envelope

Every error response follows this shape:

{
  "statusCode": 409,
  "message": "User with email '[email protected]' already exists",
  "error": "RESOURCE_ALREADY_EXISTS",
  "errorCode": "RESOURCE_ALREADY_EXISTS",
  "timestamp": "2026-06-23T08:14:05.123Z",
  "path": "/api/v1/auth/register",
  "requestId": "1750665245123-a1b2c3d4e"
}
FieldTypeDescription
statusCodenumberHTTP status code.
messagestring | string[]User-facing message. May be an array for validation errors.
errorstringA human/category label (sometimes localized, or class-validator’s "Bad Request"). Do not branch on this.
errorCodestring?The stable, machine-readable code. The client should branch only on this. Present only when the thrown exception attaches one.
timestampstringRequest timestamp (ISO 8601).
pathstringRequest path.
requestIdstring?Tracking id — include it in support requests.
actionableobject?Structured remediation payload. Today it carries the entitlement denial: requirement, offer, licenseRequired, reason.
detailsany?Present only in the development environment.
stackstring?Present only in the development environment.

errorCode carries no PII, so it is surfaced in every environment (production included). details and stack, by contrast, appear only in the development environment.

⚠️

The error field may be localized or fall back to a framework-generated label. Always branch behaviour on errorCode; when errorCode is absent, fall back to statusCode.

errorCode values

The following codes come from the ErrorCode enum and are attached to business logic exceptions (BusinessException).

Some gates (the entitlement guard, the purchasability gate, the stock gate) throw their own code in a code field; the global error filter standardises it onto errorCode, so the client still branches on a single field. Those codes are listed under Entitlements and purchasing.

Authentication

errorCodeMeaning
INVALID_CREDENTIALSEmail or password is wrong.
TOKEN_EXPIREDThe token has expired.
TOKEN_INVALIDThe token is invalid/malformed.
UNAUTHORIZEDAuthentication is required.

Authorization

errorCodeMeaning
FORBIDDENAccess denied.
INSUFFICIENT_PERMISSIONSThe role/permission is insufficient for this action.

Resource

errorCodeMeaning
RESOURCE_NOT_FOUNDThe requested record does not exist.
RESOURCE_ALREADY_EXISTSThe same record already exists (e.g. duplicate email).
RESOURCE_CONFLICTConflicts with the resource’s current state.

Validation

errorCodeMeaning
VALIDATION_ERRORGeneric input validation failed.
INVALID_INPUTAn input value is invalid.
MISSING_REQUIRED_FIELDA required field is missing.

Business logic

errorCodeMeaning
INSUFFICIENT_STOCKNot enough stock for the product. details: { productName, available, requested }.
ORDER_ALREADY_PAIDThe order is already paid.
TABLE_OCCUPIEDThe table is occupied.
INVALID_ORDER_STATUSThe action isn’t allowed in the order’s current status.
QUOTA_EXCEEDEDThe credit balance is insufficient (HTTP 402). details: { kind, remaining, requested, offerCode }offerCode is the credit pack the client can deep-link to.
SUBSCRIPTION_REQUIREDDormant. Retired with the plan rail; no code path throws it any more. It stays in the enum only so old clients don’t break.
FEATURE_NOT_AVAILABLEDormant. Replaced by ENTITLEMENT_REQUIRED (HTTP 403), whose envelope carries the missing entitlement and the product that unlocks it, with its price.

Payment

errorCodeMeaning
PAYMENT_FAILEDThe payment failed.
PAYMENT_PROCESSING_ERRORAn error occurred while processing the payment.
INVALID_PAYMENT_METHODInvalid payment method.

System

errorCodeMeaning
INTERNAL_SERVER_ERRORUnexpected server error.
SERVICE_UNAVAILABLEThe service is temporarily unavailable.
DATABASE_ERRORA database error.
EXTERNAL_SERVICE_ERRORAn external service error (e.g. the payment provider).

Rate limiting

errorCodeMeaning
TOO_MANY_REQUESTSToo many requests (HTTP 429).

Tenant

errorCodeMeaning
TENANT_NOT_FOUNDTenant not found.
INVALID_TENANTInvalid tenant.

Entitlements and purchasing

These codes are not in the ErrorCode enum; the gates throw them in a code field and the global filter moves it onto errorCode, so the client still branches on a single field.

errorCodeHTTPMeaning
ENTITLEMENT_REQUIRED403The route requires an entitlement the tenant does not hold. actionable carries requirement, offer, licenseRequired, reason.
LICENSE_REQUIRED409A cart line needs a licence, and there is none owned or in the cart.
ADDON_ALREADY_OWNED409The product (or the licence) is already active.
ADDON_ALREADY_GRANTED409Everything the product grants is already in the entitlement set.
ADDON_REQUIRES_DEPENDENCY409Its dependency is neither owned nor in the cart.
ADDON_LIMIT_REDUNDANT409The capacity it adds is already unlimited.
ADDON_MAX_QUANTITY409The catalog quantity ceiling would be exceeded.
HARDWARE_OUT_OF_STOCK409Not enough real stock for a hardware line.
CATALOG_INVALID400A superadmin catalog write violates the product invariants; message carries every problem as an array.

Every 409 above is returned before any charge, inside POST /v1/checkout/intent: the rejected line stops the whole cart, no CheckoutIntent row is written, and PayTR is never called. Details in the Licensing & Billing API and the Entitlement Matrix.

Database errors → HTTP

Prisma’s known database errors (PrismaClientKnownRequestError) are translated to meaningful HTTP status codes in the global exception filter. In these responses the error field carries the category label; errorCode is not present here (these are not BusinessExceptions).

Prisma codeHTTPerrorMeaning
P2002409 ConflictUniqueConstraintViolationUnique constraint violation (duplicate record).
P2025404 Not FoundRecordNotFoundRecord not found.
P2003400 Bad RequestForeignKeyConstraintViolationRelated record missing, or can’t delete due to dependencies.
P2014400 Bad RequestRequiredRelationViolationThe change would violate a required relation.
P2016400 Bad RequestInvalidQueryInvalid query parameters.
P2021500 Internal Server ErrorDatabaseConfigErrorTable does not exist (configuration error).
P2024503 Service UnavailableDatabaseTimeoutDatabase connection timeout.
P2034409 ConflictConcurrentUpdateSerializable transaction conflict (Postgres 40001). The transaction did not commit; the request is safe to retry.
P2000, P2020400 Bad RequestValueOutOfRangeA value is out of the allowed range or too long.
(other)500 Internal Server ErrorDatabaseErrorAn unmapped database error.

P2034 (ConcurrentUpdate, 409) is the expected “loser” outcome of a Serializable transaction under contention (checkout settlement, marketplace purchase, etc.), and your request did not take effect. If you receive it, you can safely resend the request as-is.

Handling errors on the client

const res = await fetch('/api/v1/display/orders', { method: 'POST', /* … */ });
if (!res.ok) {
  const err = await res.json(); // ErrorResponse
  switch (err.errorCode) {
    case 'ENTITLEMENT_REQUIRED': {
      // 403 — err.actionable.offer is the product that unlocks the missing
      // entitlement, priced for THIS tenant today.
      // reason === 'lapsed' → "Renew", otherwise → "Buy".
      const { offer, reason, licenseRequired } = err.actionable ?? {};
      showPurchasePrompt({ offer, reason, licenseRequired });
      break;
    }
    case 'QUOTA_EXCEEDED':
      // 402 — credits exhausted. Deep-link to err.details.offerCode.
      break;
    case 'LICENSE_REQUIRED':
    case 'ADDON_ALREADY_OWNED':
    case 'ADDON_ALREADY_GRANTED':
    case 'ADDON_REQUIRES_DEPENDENCY':
    case 'ADDON_LIMIT_REDUNDANT':
    case 'ADDON_MAX_QUANTITY':
      // 409 — the cart needs fixing; nothing was charged.
      showToast(err.message);
      break;
    case undefined:
      // No errorCode → branch on statusCode (e.g. 409 ConcurrentUpdate → retry)
      break;
    default:
      showToast(err.message);
  }
}