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. |
| 503 | Service Unavailable | Workspace paused by circuit breaker. Payouts disabled until resumed. See code: CIRCUIT_BREAKER_OPEN. |
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 |
Circuit Breaker Errors (503)
Workspace automatically paused after consecutive payout failures
When a workspace accumulates 3 consecutive payout failures, PayDirect activates the circuit breaker and pauses all payouts for that workspace. Error responses include a structured code field:
| Code | Status | Description |
|---|---|---|
| CIRCUIT_BREAKER_OPEN | 503 | Workspace is paused. No payout attempted. Resume via API or wait 30 minutes. |
| INSUFFICIENT_GAS | 400 | Workspace wallet lacks ETH for gas. Send ETH or wait for auto-funding. |
| PAYOUT_FAILED | 500 | On-chain transaction failed. Check balance and destination address. |
// Handling circuit breaker in your agent
const res = await fetch(url + "/api/v1/payouts", { method: "POST", ... });
const data = await res.json();
if (data.code === "CIRCUIT_BREAKER_OPEN") {
// Workspace is paused — stop scheduling payouts
console.error("Payouts paused:", data.error);
// Option 1: Wait for auto-resume (30 min)
// Option 2: Fix the issue, then POST /api/v1/workspaces/{id}/resume
}
if (data.failStreak > 0) {
console.warn(`Fail streak: ${data.failStreak}, remaining: ${data.remainingBeforePause}`);
}See Payouts API > Circuit Breaker for the full resume flow and agent integration guide.
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
