> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morapay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Consultant-grade errors with safeMessage, displayMessage, uiHint, and requestId.

Morapay errors are designed as an **on-demand consultant**, not just a stack trace. Every failed Merchant API response includes enough context for developers, support engineers, and customer-facing UI.

## Error envelope

```json theme={null}
{
  "success": false,
  "error": "Invalid payload. amount failed platform minimum check.",
  "code": "links.amount.below_minimum",
  "safeMessage": "This amount is below the minimum allowed for mobile money checkout in this region.",
  "displayMessage": "The transaction amount must be at least 1.00 GHS.",
  "why": "Mobile network operators enforce a platform floor transaction limit.",
  "howToFix": "Validate cart totals before calling the payment links API.",
  "docUrl": "https://docs.morapay.io/errors/codes/links-amount-below-minimum",
  "uiHint": {
    "severity": "warning",
    "placement": "inline",
    "actionText": "Update amount"
  },
  "requestId": "req_1717483920_abc",
  "details": {}
}
```

## Field guide

| Field                                   | Audience      | Use                                                   |
| --------------------------------------- | ------------- | ----------------------------------------------------- |
| `error` / `err.message`                 | Developers    | Server logs, Sentry; may include field paths          |
| `safeMessage` / `err.safeMessage`       | Customer UI   | Sanitized copy for any surface                        |
| `displayMessage` / `err.displayMessage` | UI components | Intent-driven display text                            |
| `uiHint` / `err.uiHint`                 | UI components | `{ severity, placement, actionText? }`                |
| `code` / `err.code`                     | Programmatic  | Semantic namespace `domain.resource.reason`           |
| `requestId` / `err.requestId`           | Support       | Correlate with API logs (`Morapay-Request-Id` header) |
| `docUrl` / `err.docUrl`                 | Self-serve    | Deep link to error code documentation                 |
| `details` / `err.details`               | Validation    | Field-level breakdown                                 |

## SDK usage

```typescript theme={null}
import {
  Morapay,
  MorapayInvalidRequestError,
  MorapayAuthenticationError,
  MorapayErrorCode,
  MorapayUi,
  isMorapayError,
} from "@morapay/sdk";

try {
  await morapay.products.link("prod_123", { amount: 0.1, currency: "GHS" });
} catch (err) {
  if (err instanceof MorapayInvalidRequestError) {
    showInlineError(err.displayMessage);
    console.error(err.format());
  } else if (err instanceof MorapayAuthenticationError) {
    alertOps(err.requestId);
  } else if (isMorapayError(err) && err.code === MorapayErrorCode.PayoutsMomoInsufficientBalance) {
    MorapayUi.toast(err);
  }
}
```

### Error code dictionary

Import `MorapayErrorCode` for IDE autocomplete instead of string literals. Typos fail at compile time when you compare against the dictionary:

```typescript theme={null}
import { MorapayErrorCode, isMorapayError, isMorapayErrorCode } from "@morapay/sdk";

catch (err) {
  if (isMorapayError(err)) {
    switch (err.code) {
      case MorapayErrorCode.LinksAmountBelowMinimum:
        showInlineError(err.displayMessage);
        break;
      case MorapayErrorCode.AuthKeysExpired:
        redirectToKeyRotation();
        break;
      default:
        if (isMorapayErrorCode(err.code)) {
          console.error(err.format());
        }
    }
  }
}
```

### Error subclasses

| Class                        | Typical HTTP       |
| ---------------------------- | ------------------ |
| `MorapayAuthenticationError` | 401                |
| `MorapayPermissionError`     | 403                |
| `MorapayNotFoundError`       | 404                |
| `MorapayConflictError`       | 409                |
| `MorapayInvalidRequestError` | 400, 422           |
| `MorapayRateLimitError`      | 429                |
| `MorapayApiError`            | 5xx                |
| `MorapayConnectionError`     | Network / non-JSON |

## Semantic codes

Codes follow `domain.resource.reason`:

```
auth.signature.mismatch
links.amount.below_minimum
payouts.momo.insufficient_balance
```

### Authentication

| Code                                                                                 | HTTP |
| ------------------------------------------------------------------------------------ | ---- |
| [`auth.unauthorized`](/errors/codes/auth-unauthorized)                               | 401  |
| [`auth.forbidden`](/errors/codes/auth-forbidden)                                     | 403  |
| [`auth.keys.invalid`](/errors/codes/auth-keys-invalid)                               | 401  |
| [`auth.keys.revoked`](/errors/codes/auth-keys-revoked)                               | 401  |
| [`auth.keys.expired`](/errors/codes/auth-keys-expired)                               | 401  |
| [`auth.keys.legacy_unsupported`](/errors/codes/auth-keys-legacy-unsupported)         | 401  |
| [`auth.keys.secret_in_header`](/errors/codes/auth-keys-secret-in-header)             | 401  |
| [`auth.keys.origin_not_allowed`](/errors/codes/auth-keys-origin-not-allowed)         | 403  |
| [`auth.signature.mismatch`](/errors/codes/auth-signature-mismatch)                   | 401  |
| [`auth.signature.timestamp_expired`](/errors/codes/auth-signature-timestamp-expired) | 401  |
| [`auth.signature.incomplete`](/errors/codes/auth-signature-incomplete)               | 401  |
| [`auth.signature.missing`](/errors/codes/auth-signature-missing)                     | 401  |
| [`auth.signature.invalid_timestamp`](/errors/codes/auth-signature-invalid-timestamp) | 401  |
| [`auth.signature.invalid_format`](/errors/codes/auth-signature-invalid-format)       | 401  |

### KYB and permissions

| Code                                           | HTTP |
| ---------------------------------------------- | ---- |
| [`kyb.forbidden`](/errors/codes/kyb-forbidden) | 403  |
| [`kyb.blocked`](/errors/codes/kyb-blocked)     | 403  |

### Payment links

| Code                                                                     | HTTP |
| ------------------------------------------------------------------------ | ---- |
| [`links.amount.below_minimum`](/errors/codes/links-amount-below-minimum) | 422  |
| [`links.slug.duplicate`](/errors/codes/links-slug-duplicate)             | 409  |

### Payouts and treasury

| Code                                                                                   | HTTP |
| -------------------------------------------------------------------------------------- | ---- |
| [`payouts.momo.insufficient_balance`](/errors/codes/payouts-momo-insufficient-balance) | 400  |
| [`payouts.network.carrier_down`](/errors/codes/payouts-network-carrier-down)           | 503  |
| [`payouts.clearing.refund_failed`](/errors/codes/payouts-clearing-refund-failed)       | 500  |

### Crypto checkout

| Code                                                                 | HTTP |
| -------------------------------------------------------------------- | ---- |
| [`crypto.chain.unsupported`](/errors/codes/crypto-chain-unsupported) | 422  |
| [`crypto.slippage.exceeded`](/errors/codes/crypto-slippage-exceeded) | 422  |

### Resources and validation

| Code                                                                   | HTTP |
| ---------------------------------------------------------------------- | ---- |
| [`resource.not_found`](/errors/codes/resource-not-found)               | 404  |
| [`resource.conflict`](/errors/codes/resource-conflict)                 | 409  |
| [`request.invalid`](/errors/codes/request-invalid)                     | 400  |
| [`request.validation_failed`](/errors/codes/request-validation-failed) | 422  |

### Platform and connectivity

| Code                                                                             | HTTP |
| -------------------------------------------------------------------------------- | ---- |
| [`api.rate_limited`](/errors/codes/api-rate-limited)                             | 429  |
| [`api.service_unavailable`](/errors/codes/api-service-unavailable)               | 503  |
| [`api.internal_error`](/errors/codes/api-internal-error)                         | 500  |
| [`api.bad_gateway`](/errors/codes/api-bad-gateway)                               | 502  |
| [`api.gateway_timeout`](/errors/codes/api-gateway-timeout)                       | 504  |
| [`api.connection_failed`](/errors/codes/api-connection-failed)                   | 502  |
| [`api.proxy.core_unconfigured`](/errors/codes/api-proxy-core-unconfigured)       | 503  |
| [`api.proxy.signing_unconfigured`](/errors/codes/api-proxy-signing-unconfigured) | 503  |
| [`api.proxy.core_unreachable`](/errors/codes/api-proxy-core-unreachable)         | 502  |

## Next step

[Get help](/support/get-help): how to reach support with the right context.
