Error Codes

The Dualis Finance API uses standard HTTP status codes combined with structured error responses. This page documents every status code, the custom error code format, common errors with troubleshooting steps, and rate limiting behavior.

HTTP Status Codes

CodeStatusDescription
200OKRequest succeeded. Response body contains the requested data.
201CreatedResource created successfully (e.g., new borrow position, supply position).
400Bad RequestMalformed request body, missing required fields, or invalid parameter types.
401UnauthorizedMissing, invalid, or expired JWT token in the Authorization header.
403ForbiddenValid token but insufficient permissions (e.g., non-admin accessing admin routes).
404Not FoundThe requested resource does not exist (e.g., unknown pool ID, position ID).
409ConflictState conflict (e.g., duplicate supply to the same pool, position already liquidated).
422Unprocessable EntityValidation passed but business logic rejected the request (e.g., health factor too low).
429Too Many RequestsRate limit exceeded. Check Retry-After header for backoff duration.
500Internal Server ErrorUnexpected server error. Includes a correlation ID for debugging.

Error Response Format

All error responses follow a consistent JSON structure. The code field uses a namespaced format that identifies both the domain and the specific error:

Error Response
{
  "error": {
    "code": "BORROW_HEALTH_FACTOR_TOO_LOW",
    "message": "Borrow would reduce health factor below 1.0",
    "details": {
      "currentHealthFactor": 1.82,
      "projectedHealthFactor": 0.91,
      "minimumRequired": 1.0
    },
    "correlationId": "req_7f3a2b1c",
    "timestamp": "2026-02-28T10:30:00Z"
  }
}

Custom Error Code Format

Error codes follow the pattern DOMAIN_SPECIFIC_ERROR using uppercase snake_case. The domain prefix maps to the API endpoint group:

DomainError CodeHTTP StatusDescription
AuthAUTH_INVALID_CREDENTIALS401Email or password is incorrect
AuthAUTH_TOKEN_EXPIRED401JWT token has expired
AuthAUTH_INSUFFICIENT_PERMISSIONS403User lacks required role
PoolPOOL_NOT_FOUND404Pool ID does not exist
PoolPOOL_PAUSED422Pool is temporarily paused by governance
SupplySUPPLY_EXCEEDS_CAP422Amount exceeds pool supply cap
SupplySUPPLY_INSUFFICIENT_BALANCE422Wallet balance too low for requested supply
BorrowBORROW_HEALTH_FACTOR_TOO_LOW422Borrow would reduce health factor below 1.0
BorrowBORROW_EXCEEDS_CAPACITY422Amount exceeds available borrowing power
BorrowBORROW_NO_COLLATERAL422No collateral positions found for borrower
CreditCREDIT_TIER_INSUFFICIENT422Credit tier does not qualify for this pool
OracleORACLE_PRICE_STALE422Price feed data is too old for safe operation
CantonCANTON_SUBMISSION_FAILED500DAML command submission rejected by Canton
CantonCANTON_CONTRACT_NOT_FOUND404Referenced contract ID not active on ledger
CantonCANTON_AUTHORIZATION_FAILED403Signing party not authorized for this choice
RateRATE_LIMIT_EXCEEDED429Too many requests in the current window
ValidationVALIDATION_FAILED400Zod schema validation failed

Common Errors and Troubleshooting

AUTH_TOKEN_EXPIRED

JWT tokens expire after 24 hours. Use the refresh endpoint to obtain a new token without re-entering credentials:

Terminal
curl -X POST http://localhost:4000/v1/auth/refresh \
  -H "Authorization: Bearer <expired-token>"

BORROW_HEALTH_FACTOR_TOO_LOW

The requested borrow would push the health factor below the minimum threshold of 1.0. To resolve this:

  • Reduce the borrow amount
  • Add more collateral via POST /v1/borrow/add-collateral
  • Repay existing debt to free up borrowing capacity

CANTON_SUBMISSION_FAILED

The DAML command was rejected by the Canton participant. Check the details field for the raw Canton error. Common causes include stale contract IDs (the contract was already archived) and insufficient party authorization. The API's error mapper (packages/api/src/canton/error-mapper.ts) translates raw Canton errors into the structured format shown above.

VALIDATION_FAILED

Request body did not pass Zod 4 schema validation. The details field contains the specific validation errors:

Validation Error
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Request validation failed",
    "details": {
      "issues": [
        {
          "path": ["amount"],
          "message": "Expected string, received number"
        },
        {
          "path": ["poolId"],
          "message": "Required"
        }
      ]
    }
  }
}
Correlation ID
Every error response includes a correlationId. When reporting issues, include this ID so the team can trace the request through API logs, BullMQ job history, and Canton transaction records.

Rate Limiting

The API enforces rate limits per authenticated user. Limits vary by endpoint group:

Endpoint GroupWindowMax Requests
Read endpoints (GET)1 minute300
Write endpoints (POST/PUT/DELETE)1 minute60
Auth endpoints15 minutes10
Admin endpoints1 minute30

When a rate limit is exceeded, the API returns HTTP 429 with headers indicating the limit state:

Rate Limit Headers
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1709118660
Retry-After: 42
Backoff Strategy
Implement exponential backoff when receiving 429 responses. Repeatedly hitting rate limits without backing off may result in temporary IP-level blocking.

Canton-Specific Errors

Errors originating from the Canton ledger are mapped through the error mapper before reaching the client. The original Canton error is preserved in the details.cantonError field for debugging purposes. Canton errors typically indicate contract-level issues: archived contracts, unauthorized parties, or DAML runtime exceptions. These map to either 403, 404, or 500 HTTP status codes depending on the root cause.