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
StatusNameDescription
200OKRequest succeeded
201CreatedResource created successfully (payments, webhooks, keys)
400Bad RequestMissing or invalid request parameters
401UnauthorizedMissing, invalid, or revoked API key
403ForbiddenValid key but insufficient permissions (e.g. accessing another workspace)
404Not FoundPayment, webhook, or key not found (or not owned by this workspace)
409ConflictIdempotency key already used — existing resource returned
429Too Many RequestsRate limit exceeded. Check Retry-After header.
500Internal Server ErrorUnexpected error. Retry with exponential backoff.
Common Validation Errors
Frequent 400 errors and how to fix them
Error MessageEndpointFix
"tokenSymbol, amount, and merchantWallet are required"POST /paymentsInclude all three required fields in the request body
"Invalid token symbol"POST /paymentsUse uppercase: USDC, ETH, or ADAO
"Amount must be a positive number"POST /paymentsAmount must parse as a number greater than zero
"Invalid merchant wallet address"POST /paymentsMust be a valid Ethereum address: 0x + 40 hex chars
"Payment cannot be cancelled"POST /payments/:id/cancelOnly pending payments can be cancelled
"Invalid webhook URL"POST /webhooksURL must be a valid, parseable URL
"Invalid event type"POST /webhooksEvents 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"
}
HeaderDescription
X-RateLimit-LimitMaximum requests per window (100 or 1000)
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds 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?