Error Codes
The PayDirect API uses standard HTTP status codes and returns errors in a consistent JSON format.
Error Response Format
All errors follow a consistent JSON structure
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": "Invalid token symbol. Must be USDC, ETH, or ADAO."
}The error field contains a human-readable description of the problem. Some responses may include additional fields for context.
HTTP Status Codes
| Status | Name | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully (payments, webhooks, keys) |
| 400 | Bad Request | Missing or invalid request parameters |
| 401 | Unauthorized | Missing, invalid, or revoked API key |
| 403 | Forbidden | Valid key but insufficient permissions (e.g. accessing another workspace) |
| 404 | Not Found | Payment, webhook, or key not found (or not owned by this workspace) |
| 409 | Conflict | Idempotency key already used — existing resource returned |
| 429 | Too Many Requests | Rate limit exceeded. Check Retry-After header. |
| 500 | Internal Server Error | Unexpected error. Retry with exponential backoff. |
Common Validation Errors
Frequent 400 errors and how to fix them
| Error Message | Endpoint | Fix |
|---|---|---|
"tokenSymbol, amount, and merchantWallet are required" | POST /payments | Include all three required fields in the request body |
"Invalid token symbol" | POST /payments | Use uppercase: USDC, ETH, or ADAO |
"Amount must be a positive number" | POST /payments | Amount must parse as a number greater than zero |
"Invalid merchant wallet address" | POST /payments | Must be a valid Ethereum address: 0x + 40 hex chars |
"Payment cannot be cancelled" | POST /payments/:id/cancel | Only pending payments can be cancelled |
"Invalid webhook URL" | POST /webhooks | URL must be a valid, parseable URL |
"Invalid event type" | POST /webhooks | Events must be from: payment.created, payment.detected, payment.confirmed, payment.forwarded, payment.failed, payment.expired |
Rate Limit Errors (429)
When the rate limit is exceeded, the response includes headers to help you retry appropriately:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708100182
Retry-After: 42
{
"error": "Rate limit exceeded"
}| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window (100 or 1000) |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Retry-After | Seconds to wait before retrying |
// Recommended retry logic
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("retry-after") || "5");
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
// Retry the request
}Need More Help?
- Troubleshooting Guide for step-by-step solutions to common issues
- API Reference for exact endpoint specifications
- Dashboard Logs to inspect recent API requests and responses
