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
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request succeeded. Response body contains the requested data. |
| 201 | Created | Resource created successfully (e.g., new borrow position, supply position). |
| 400 | Bad Request | Malformed request body, missing required fields, or invalid parameter types. |
| 401 | Unauthorized | Missing, invalid, or expired JWT token in the Authorization header. |
| 403 | Forbidden | Valid token but insufficient permissions (e.g., non-admin accessing admin routes). |
| 404 | Not Found | The requested resource does not exist (e.g., unknown pool ID, position ID). |
| 409 | Conflict | State conflict (e.g., duplicate supply to the same pool, position already liquidated). |
| 422 | Unprocessable Entity | Validation passed but business logic rejected the request (e.g., health factor too low). |
| 429 | Too Many Requests | Rate limit exceeded. Check Retry-After header for backoff duration. |
| 500 | Internal Server Error | Unexpected 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": {
"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:
| Domain | Error Code | HTTP Status | Description |
|---|---|---|---|
| Auth | AUTH_INVALID_CREDENTIALS | 401 | Email or password is incorrect |
| Auth | AUTH_TOKEN_EXPIRED | 401 | JWT token has expired |
| Auth | AUTH_INSUFFICIENT_PERMISSIONS | 403 | User lacks required role |
| Pool | POOL_NOT_FOUND | 404 | Pool ID does not exist |
| Pool | POOL_PAUSED | 422 | Pool is temporarily paused by governance |
| Supply | SUPPLY_EXCEEDS_CAP | 422 | Amount exceeds pool supply cap |
| Supply | SUPPLY_INSUFFICIENT_BALANCE | 422 | Wallet balance too low for requested supply |
| Borrow | BORROW_HEALTH_FACTOR_TOO_LOW | 422 | Borrow would reduce health factor below 1.0 |
| Borrow | BORROW_EXCEEDS_CAPACITY | 422 | Amount exceeds available borrowing power |
| Borrow | BORROW_NO_COLLATERAL | 422 | No collateral positions found for borrower |
| Credit | CREDIT_TIER_INSUFFICIENT | 422 | Credit tier does not qualify for this pool |
| Oracle | ORACLE_PRICE_STALE | 422 | Price feed data is too old for safe operation |
| Canton | CANTON_SUBMISSION_FAILED | 500 | DAML command submission rejected by Canton |
| Canton | CANTON_CONTRACT_NOT_FOUND | 404 | Referenced contract ID not active on ledger |
| Canton | CANTON_AUTHORIZATION_FAILED | 403 | Signing party not authorized for this choice |
| Rate | RATE_LIMIT_EXCEEDED | 429 | Too many requests in the current window |
| Validation | VALIDATION_FAILED | 400 | Zod 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:
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:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed",
"details": {
"issues": [
{
"path": ["amount"],
"message": "Expected string, received number"
},
{
"path": ["poolId"],
"message": "Required"
}
]
}
}
}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 Group | Window | Max Requests |
|---|---|---|
| Read endpoints (GET) | 1 minute | 300 |
| Write endpoints (POST/PUT/DELETE) | 1 minute | 60 |
| Auth endpoints | 15 minutes | 10 |
| Admin endpoints | 1 minute | 30 |
When a rate limit is exceeded, the API returns HTTP 429 with headers indicating the limit state:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1709118660
Retry-After: 42Canton-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.