Skip to main content

Errors

The AnySpend Platform API uses a consistent, structured error format across all endpoints. Errors follow the Stripe convention — a JSON object with an error key containing the type, code, human-readable message, and (where applicable) the parameter that caused the error.

Error response format

Every error response has this shape:
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_field",
    "message": "The 'name' field is required.",
    "param": "name"
  }
}
FieldTypeDescription
typestringThe category of error. Use this for broad error handling logic.
codestringA machine-readable error code. Use this for specific error handling.
messagestringA human-readable description of what went wrong. Safe to display to developers (but not end users).
paramstring or absentThe request parameter that caused the error, if applicable.

Error types

Error types group errors into broad categories. Use these for top-level error handling.
TypeHTTP statusDescription
invalid_request_error400The request was malformed or contained invalid parameters. Check the param field for which field caused the issue.
authentication_error401The API key is missing, invalid, expired, or revoked.
permission_error403The API key is valid but lacks the required permission level for the requested operation.
not_found_error404The requested resource does not exist, or does not belong to your organization.
rate_limit_error429You have exceeded the rate limit for your authentication tier.
idempotency_error409An idempotency key was reused with a different request body.
api_error500An unexpected internal error occurred on our side. These are rare and are automatically reported.

Error codes

Error codes provide specific, machine-readable identifiers for each error condition.

Request validation errors

CodeTypeHTTPDescription
missing_required_fieldinvalid_request_error400A required field was not included in the request body. The param field indicates which field is missing.
invalid_field_valueinvalid_request_error400A field was included but its value is invalid (wrong type, format, or out of range).
invalid_addressinvalid_request_error400An Ethereum address field is not a valid 0x + 40 hex character address.
no_updates_providedinvalid_request_error400A PATCH request was sent with no valid fields to update.
invalid_status_transitioninvalid_request_error400The requested status change is not allowed (e.g., completing an already-expired session).
duplicate_resourceinvalid_request_error409A resource with a conflicting unique field already exists.

Authentication and authorization errors

CodeTypeHTTPDescription
key_missingauthentication_error401No API key was found in the Authorization or X-API-Key header.
key_invalidauthentication_error401The provided API key does not match any key in the system.
key_expiredauthentication_error401The API key has passed its expires_at timestamp. Create a new key or extend the expiration.
key_revokedauthentication_error401The API key was explicitly revoked. Create a new key.
insufficient_permissionspermission_error403The API key does not have the permission level required for this operation (e.g., a read key attempting a POST).

Resource errors

CodeTypeHTTPDescription
resource_not_foundnot_found_error404The requested resource does not exist, or it belongs to a different organization.

Rate limiting and idempotency errors

CodeTypeHTTPDescription
rate_limit_exceededrate_limit_error429Too many requests in the current time window. The error message includes the retry delay.
idempotency_conflictidempotency_error409An Idempotency-Key header was reused with a different request body than the original request.

Server errors

CodeTypeHTTPDescription
internal_errorapi_error500An unexpected server error. These are automatically tracked. If this persists, contact support.

HTTP status codes

StatusMeaning
200Success — the resource was retrieved or updated
201Created — a new resource was successfully created
400Bad Request — the request was invalid
401Unauthorized — authentication failed
403Forbidden — the API key lacks the required permission
404Not Found — the resource does not exist
409Conflict — idempotency conflict or duplicate resource
429Too Many Requests — rate limit exceeded
500Internal Server Error — something went wrong on our end

Example error responses

Missing required field

curl -X POST https://platform-api.anyspend.com/api/v1/payment-links \
  -H "Authorization: Bearer asp_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{ "amount": "10000000" }'
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_field",
    "message": "The 'name' field is required.",
    "param": "name"
  }
}

Invalid Ethereum address

curl -X POST https://platform-api.anyspend.com/api/v1/payment-links \
  -H "Authorization: Bearer asp_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test Link",
    "token_address": "not-an-address",
    "chain_id": 8453,
    "recipient_address": "0xabc123..."
  }'
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_address",
    "message": "The 'token_address' field must be a valid Ethereum address (0x + 40 hex characters).",
    "param": "token_address"
  }
}

Authentication failure

curl https://platform-api.anyspend.com/api/v1/payment-links \
  -H "Authorization: Bearer asp_live_expired_key..."
{
  "error": {
    "type": "authentication_error",
    "code": "key_expired",
    "message": "This API key has expired."
  }
}

Insufficient permissions

# A read-only key trying to create a resource
curl -X POST https://platform-api.anyspend.com/api/v1/payment-links \
  -H "Authorization: Bearer asp_live_readonly_key..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Test" }'
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_permissions",
    "message": "This API key does not have 'write' permission."
  }
}

Resource not found

curl https://platform-api.anyspend.com/api/v1/payment-links/pl_nonexistent \
  -H "Authorization: Bearer asp_live_abc123..."
{
  "error": {
    "type": "not_found_error",
    "code": "resource_not_found",
    "message": "No payment_link found with id 'pl_nonexistent'."
  }
}

Rate limit exceeded

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 42 seconds."
  }
}

Idempotency conflict

{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_conflict",
    "message": "An idempotency key was used with a different request body."
  }
}

Handling errors in code

TypeScript / JavaScript

async function createPaymentLink(data: PaymentLinkInput) {
  const response = await fetch(
    "https://platform-api.anyspend.com/api/v1/payment-links",
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.ANYSPEND_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    }
  );

  const body = await response.json();

  if (!response.ok) {
    const error = body.error;

    switch (error.type) {
      case "invalid_request_error":
        // Fix the request -- check error.param for the bad field
        console.error(`Invalid request: ${error.message} (field: ${error.param})`);
        break;

      case "authentication_error":
        // Check your API key -- it may be expired or revoked
        console.error(`Auth failed: ${error.message}`);
        break;

      case "permission_error":
        // Upgrade the API key permissions
        console.error(`Insufficient permissions: ${error.message}`);
        break;

      case "rate_limit_error":
        // Back off and retry
        console.warn(`Rate limited: ${error.message}`);
        await sleep(5000);
        return createPaymentLink(data); // retry
        break;

      case "not_found_error":
        console.error(`Not found: ${error.message}`);
        break;

      case "idempotency_error":
        // The idempotency key was reused with different data
        console.error(`Idempotency conflict: ${error.message}`);
        break;

      case "api_error":
        // Server error -- retry with exponential backoff
        console.error(`Server error: ${error.message}`);
        break;
    }

    throw new Error(`AnySpend API error: ${error.code} - ${error.message}`);
  }

  return body;
}

Python

import requests
import time

def create_payment_link(data: dict) -> dict:
    response = requests.post(
        "https://platform-api.anyspend.com/api/v1/payment-links",
        headers={
            "Authorization": f"Bearer {os.environ['ANYSPEND_API_KEY']}",
            "Content-Type": "application/json",
        },
        json=data,
    )

    body = response.json()

    if not response.ok:
        error = body["error"]
        error_type = error["type"]

        if error_type == "rate_limit_error":
            time.sleep(5)
            return create_payment_link(data)  # retry

        if error_type == "authentication_error":
            raise PermissionError(f"Auth failed: {error['message']}")

        if error_type == "invalid_request_error":
            raise ValueError(
                f"Bad request on field '{error.get('param', 'unknown')}': "
                f"{error['message']}"
            )

        raise Exception(f"AnySpend API error: {error['code']} - {error['message']}")

    return body

Best practices

Use error.type for broad control flow (e.g., retry on rate_limit_error, fail fast on authentication_error). Use error.code for specific handling within a type.
When error.param is present, you can map it directly to a form field to show inline validation errors in your UI.
rate_limit_error and api_error (500) are transient. Use exponential backoff when retrying:
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      const isRetryable =
        err.status === 429 || err.status === 500;

      if (!isRetryable || attempt === maxRetries) throw err;

      const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error("Unreachable");
}
Client errors like 400, 401, 403, and 404 will not resolve on retry. Fix the request or credentials before retrying.
Always log error.type, error.code, error.message, and error.param together. This gives you the full picture when debugging production issues.