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

# Errors

> Error handling for the AnySpend Platform API

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:

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_field",
    "message": "The 'name' field is required.",
    "param": "name"
  }
}
```

| Field     | Type               | Description                                                                                         |
| --------- | ------------------ | --------------------------------------------------------------------------------------------------- |
| `type`    | `string`           | The category of error. Use this for broad error handling logic.                                     |
| `code`    | `string`           | A machine-readable error code. Use this for specific error handling.                                |
| `message` | `string`           | A human-readable description of what went wrong. Safe to display to developers (but not end users). |
| `param`   | `string` or absent | The request parameter that caused the error, if applicable.                                         |

## Error types

Error types group errors into broad categories. Use these for top-level error handling.

| Type                    | HTTP status | Description                                                                                                          |
| ----------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `invalid_request_error` | `400`       | The request was malformed or contained invalid parameters. Check the `param` field for which field caused the issue. |
| `authentication_error`  | `401`       | The API key is missing, invalid, expired, or revoked.                                                                |
| `permission_error`      | `403`       | The API key is valid but lacks the required permission level for the requested operation.                            |
| `not_found_error`       | `404`       | The requested resource does not exist, or does not belong to your organization.                                      |
| `rate_limit_error`      | `429`       | You have exceeded the rate limit for your authentication tier.                                                       |
| `idempotency_error`     | `409`       | An idempotency key was reused with a different request body.                                                         |
| `api_error`             | `500`       | An 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

| Code                        | Type                    | HTTP  | Description                                                                                                |
| --------------------------- | ----------------------- | ----- | ---------------------------------------------------------------------------------------------------------- |
| `missing_required_field`    | `invalid_request_error` | `400` | A required field was not included in the request body. The `param` field indicates which field is missing. |
| `invalid_field_value`       | `invalid_request_error` | `400` | A field was included but its value is invalid (wrong type, format, or out of range).                       |
| `invalid_address`           | `invalid_request_error` | `400` | An Ethereum address field is not a valid `0x` + 40 hex character address.                                  |
| `no_updates_provided`       | `invalid_request_error` | `400` | A `PATCH` request was sent with no valid fields to update.                                                 |
| `invalid_status_transition` | `invalid_request_error` | `400` | The requested status change is not allowed (e.g., completing an already-expired session).                  |
| `duplicate_resource`        | `invalid_request_error` | `409` | A resource with a conflicting unique field already exists.                                                 |

### Authentication and authorization errors

| Code                       | Type                   | HTTP  | Description                                                                                                          |
| -------------------------- | ---------------------- | ----- | -------------------------------------------------------------------------------------------------------------------- |
| `key_missing`              | `authentication_error` | `401` | No API key was found in the `Authorization` or `X-API-Key` header.                                                   |
| `key_invalid`              | `authentication_error` | `401` | The provided API key does not match any key in the system.                                                           |
| `key_expired`              | `authentication_error` | `401` | The API key has passed its `expires_at` timestamp. Create a new key or extend the expiration.                        |
| `key_revoked`              | `authentication_error` | `401` | The API key was explicitly revoked. Create a new key.                                                                |
| `insufficient_permissions` | `permission_error`     | `403` | The API key does not have the permission level required for this operation (e.g., a `read` key attempting a `POST`). |

### Resource errors

| Code                 | Type              | HTTP  | Description                                                                       |
| -------------------- | ----------------- | ----- | --------------------------------------------------------------------------------- |
| `resource_not_found` | `not_found_error` | `404` | The requested resource does not exist, or it belongs to a different organization. |

### Rate limiting and idempotency errors

| Code                   | Type                | HTTP  | Description                                                                                     |
| ---------------------- | ------------------- | ----- | ----------------------------------------------------------------------------------------------- |
| `rate_limit_exceeded`  | `rate_limit_error`  | `429` | Too many requests in the current time window. The error message includes the retry delay.       |
| `idempotency_conflict` | `idempotency_error` | `409` | An `Idempotency-Key` header was reused with a different request body than the original request. |

### Server errors

| Code             | Type        | HTTP  | Description                                                                                     |
| ---------------- | ----------- | ----- | ----------------------------------------------------------------------------------------------- |
| `internal_error` | `api_error` | `500` | An unexpected server error. These are automatically tracked. If this persists, contact support. |

## HTTP status codes

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

## Example error responses

### Missing required field

```bash theme={null}
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" }'
```

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_required_field",
    "message": "The 'name' field is required.",
    "param": "name"
  }
}
```

### Invalid Ethereum address

```bash theme={null}
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..."
  }'
```

```json theme={null}
{
  "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

```bash theme={null}
curl https://platform-api.anyspend.com/api/v1/payment-links \
  -H "Authorization: Bearer asp_live_expired_key..."
```

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "key_expired",
    "message": "This API key has expired."
  }
}
```

### Insufficient permissions

```bash theme={null}
# 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" }'
```

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_permissions",
    "message": "This API key does not have 'write' permission."
  }
}
```

### Resource not found

```bash theme={null}
curl https://platform-api.anyspend.com/api/v1/payment-links/pl_nonexistent \
  -H "Authorization: Bearer asp_live_abc123..."
```

```json theme={null}
{
  "error": {
    "type": "not_found_error",
    "code": "resource_not_found",
    "message": "No payment_link found with id 'pl_nonexistent'."
  }
}
```

### Rate limit exceeded

```json theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 42 seconds."
  }
}
```

### Idempotency conflict

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

## Handling errors in code

### TypeScript / JavaScript

```typescript theme={null}
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

```python theme={null}
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

<AccordionGroup>
  <Accordion title="Always check the error type first">
    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.
  </Accordion>

  <Accordion title="Use the param field to build form validation">
    When `error.param` is present, you can map it directly to a form field to show inline validation errors in your UI.
  </Accordion>

  <Accordion title="Implement retry logic for transient errors">
    `rate_limit_error` and `api_error` (500) are transient. Use exponential backoff when retrying:

    ```typescript theme={null}
    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");
    }
    ```
  </Accordion>

  <Accordion title="Never retry 4xx errors (except 429)">
    Client errors like `400`, `401`, `403`, and `404` will not resolve on retry. Fix the request or credentials before retrying.
  </Accordion>

  <Accordion title="Log the full error object for debugging">
    Always log `error.type`, `error.code`, `error.message`, and `error.param` together. This gives you the full picture when debugging production issues.
  </Accordion>
</AccordionGroup>
