# Errors

> Learn how Qualy handles error codes, messages and more.

Qualy uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the `2xx` range indicate success. Codes in the `4xx` range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a charge failed, etc.). Codes in the `5xx` range indicate an error with Qualy’s servers.

---

## HTTP status code summary

| Number | Error | Description |
| --- | --- | --- |
| 200 | Ok | Everything worked as expected. |
| 400 | Bad Request | The request was unacceptable, often due to missing a required parameter. |
| 401 | Unauthorized | No valid API key provided. |
| 403 | Forbidden | The API key doesn’t have permissions to perform the request. |
| 404 | Not Found | The requested resource doesn’t exist. |
| 409 | Conflict | The request conflicts with another request. |
| 422 | Unprocessable Entity | The request could not be processed. This is returned when an idempotency key is reused with a different request body. See [Idempotency](/docs/idempotency.md). |
| 429 | Too Many Requests | You have exceeded the rate limit. The response includes a `Retry-After` header indicating how many seconds to wait before retrying. See [Rate limiting](/docs/fees-limits.md#rate-limiting). |
| 500, 502, 503, 504 | Server Errors | Something went wrong on Qualy's end. |
| 599 | Maintenance | Qualy is undergoing scheduled maintenance. The response includes a `Retry-After` header. Check [status page](https://qualyhq.statuspage.io/) for updates. |

## Maintenance mode

In rare cases, Qualy may enter maintenance mode for scheduled infrastructure work. When this happens, the API responds with HTTP status code `599` and includes a `Retry-After` header indicating the estimated time until the maintenance window ends. These windows are communicated in advance and you can monitor real-time status at [qualyhq.statuspage.io](https://qualyhq.statuspage.io/).

During maintenance:

* New payment attempts will fail.
* Dashboard login will be unavailable.
* Background operations (notifications, bank-related events) are queued and processed automatically once maintenance is over.
* Health checks, webhook subscriptions, and payment gateway callbacks continue to operate normally.

## Error codes

In addition to HTTP status codes, Qualy returns application-level error codes to help you identify the specific issue. Each error code follows the format `PREFIX-NUMBER`, where:

* **PREFIX** is a 3-letter module identifier (e.g., `PAY` for Payouts, `TXN` for Transactions)
* **NUMBER** is a numeric code unique within that module

For example, `PAY-1001` means "Payout not found" and `TXN-2008` means "You cannot refund more than the total amount". These codes remain stable and can be used to programmatically handle specific error scenarios, even if the human-readable message changes over time.

### Module prefixes

#### Payments & money movement

| Prefix | Module |
| --- | --- |
| `PIN` | Payment intents |
| `TXN` | Transactions |
| `PSP` | Payment splits |
| `BLK` | Bulk payment splits |
| `ORD` | Orders |
| `SUB` | Subscriptions |
| `RFI` | Refund intents |
| `PAY` | Payouts |
| `BNK` | Bank accounts |
| `TAX` | Tax |
| `FXX` | FX (foreign exchange) |
| `ATH` | Authorizations (direct-debit mandates) |
| `DUN` | Dunning |
| `WFL` | Approvals (workflow) |
| `BIL` | Billing |

#### Core resources

| Prefix | Module |
| --- | --- |
| `CON` | Contacts |
| `USR` | Users |
| `TEM` | Teams |
| `PRT` | Partnerships |
| `TNT` | Tenant |
| `FLD` | Custom fields |
| `SVC` | Services |

#### Platform & tooling

| Prefix | Module |
| --- | --- |
| `AUT` | Authentication |
| `IDM` | Idempotency |
| `REF` | Reference resolution (smart references) |
| `GTW` | Gateway |
| `NTF` | Notifications |
| `STG` | Storage |
| `SET` | Settings |
| `IMP` | Imports |
| `CMP` | Compliance |
| `DVL` | Developer |
| `GEN` | General |

#### Payment gateways & providers

| Prefix | Module |
| --- | --- |
| `ZAI` | Zai |
| `PGB` | PagBank |
| `ASA` | Asaas |
| `KLR` | Klarna |
| `NPY` | NexPay |
| `WSE` | Wise |
| `TRM` | TransferMate |
| `BLS` | BlueSnap |
| `XEX` | XE |

Other internal modules follow the same `PREFIX-NUMBER` convention; the prefix always identifies the module that produced the error.

## Error response

Error responses include the HTTP status code, a human-readable message, and when available, a specific error `code` and additional `details`:

```javascript
{
  "statusCode": 404,
  "code": "PAY-1001",
  "message": "Payout not found"
}
```

### Validation errors

When a request fails input validation (a missing required field, a wrong type, an invalid email), Qualy returns a `400` with `error: "Bad Request"` and a `message` that is an **array** of human-readable problems — one entry per failed field:

```javascript
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": [
    "email must be an email",
    "profile must be an object"
  ]
}
```

> **Warning — `message` can be a string or an array**
>
> Application errors return `message` as a single string, while validation errors return it as an array. If you surface error messages to users or logs, normalize both — for example `[].concat(body.message).join(', ')`.

Some errors may include a `details` field with additional context about the issue:

```javascript
{
  "statusCode": 400,
  "code": "BNK-6011",
  "message": "Country and currency combination not supported.",
  "details": {
    "country": "BR",
    "currency": "USD"
  }
}
```

If the request failed due to the lack of permissions, Qualy will return what roles are necessary to perform the requested operation.

```javascript
{
  "statusCode": 403,
  "message": "This action is forbidden because the user lacks necessary roles.",
  "data": {
    "roles": [
      "paymentIntents:edit:all",
      "paymentIntents:edit:me",
      "paymentIntents:edit:team"
  ]},
  "error": "Forbidden"
}
```
