# Error Handling

Understanding and handling errors from the Cobbee API.

## HTTP Status Codes

| Code | Meaning | Description |
|------|---------|-------------|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request data |
| 401 | Unauthorized | Authentication required |
| 402 | Payment Required | x402 payment flow initiated |
| 403 | Forbidden | Access denied (blocked/banned) |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate resource (username, tx) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | Server error |
| 502 | Bad Gateway | Facilitator connection failed |

## Error Response Format

All errors follow a consistent format:

```json
{
  "error": "Short error description",
  "details": "More detailed explanation",
  "code": "ERROR_CODE",
  "field": "specific_field"
}
```

### Validation Errors

```json
{
  "error": "Validation failed",
  "errors": {
    "username": "Username must be 3-30 characters",
    "coffee_price": "Price must be between 0.01 and 100"
  }
}
```

### Cooldown Errors

```json
{
  "error": "You can change your username again in 18 hours",
  "errors": {
    "username": "Username can only be changed once every 24 hours"
  },
  "cooldown": {
    "field": "username",
    "hoursRemaining": 18,
    "cooldownEnds": "2026-02-03T12:00:00.000Z"
  }
}
```

## Error Codes by Category

### Authentication Errors

| Code | Error | Description |
|------|-------|-------------|
| 401 | `Unauthorized` | No valid session |
| 400 | `Invalid nonce` | Nonce expired or mismatch |
| 400 | `Invalid signature` | Signature verification failed |
| 400 | `Domain mismatch` | Wrong domain in SIWA/SIWX message |
| 403 | `Wallet is banned` | Wallet is blacklisted |
| 403 | `Account is blocked` | User account blocked |
| 403 | `Account deactivated` | User deactivated account |

### Profile Errors

| Code | Error | Description |
|------|-------|-------------|
| 409 | `Username already taken` | Choose another username |
| 409 | `Wallet already registered` | Wallet has account |
| 400 | `Validation failed` | Invalid field values |
| 429 | `Cooldown active` | Wait for cooldown |
| 404 | `User not found` | Profile doesn't exist |

### Payment Errors

| Code | Error | Description |
|------|-------|-------------|
| 400 | `Platform fee required` | Missing platform_fee_tx |
| 400 | `Fee verification failed` | Invalid or used fee |
| 402 | `Payment required` | x402 payment flow |
| 402 | `Payment verification failed` | Invalid payment proof |
| 400 | `Cannot support yourself` | Same wallet as creator |
| 409 | `Duplicate transaction` | Tx already processed |

### Product Errors

| Code | Error | Description |
|------|-------|-------------|
| 404 | `Product not found` | Invalid product_id |
| 400 | `Product not available yet` | Scheduled for future |
| 400 | `Sale has ended` | Past sale end date |
| 400 | `Product is sold out` | Quantity limit reached |
| 400 | `Invalid discount code` | Code invalid/expired |

### Rate Limit Errors

```json
{
  "error": "Too many requests. Please try again later.",
  "retryAfter": 1707000000000
}
```

Headers included:
```
Retry-After: 30
```

## Handling 402 Payment Required

The 402 status is part of the x402 payment protocol, not an error.

**Response:**
```json
{
  "x402Version": 2,
  "error": "payment-required",
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "5000000",
    "payTo": "0x...",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  }]
}
```

**Handling:**
1. Extract payment requirements from response
2. Sign payment authorization
3. Retry request with `PAYMENT-SIGNATURE` header

## Retry Strategies

### Rate Limits (429)

```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After') || 60;
      console.log(`Rate limited. Waiting ${retryAfter}s...`);
      await sleep(retryAfter * 1000);
      continue;
    }

    return response;
  }
  throw new Error('Max retries exceeded');
}
```

### Transient Errors (5xx)

```javascript
async function fetchWithBackoff(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);

      if (response.status >= 500) {
        const delay = Math.pow(2, i) * 1000; // Exponential backoff
        console.log(`Server error. Retrying in ${delay}ms...`);
        await sleep(delay);
        continue;
      }

      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      const delay = Math.pow(2, i) * 1000;
      await sleep(delay);
    }
  }
}
```

### Payment Failures

```javascript
async function handlePaymentFlow(url, body) {
  // First request - might return 402
  let response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });

  if (response.status === 402) {
    const paymentRequired = await response.json();

    // Sign payment
    const paymentSignature = await signPayment(paymentRequired);

    // Retry with payment
    response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'PAYMENT-SIGNATURE': paymentSignature
      },
      body: JSON.stringify(body)
    });
  }

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || 'Request failed');
  }

  return response.json();
}
```

## Error Logging Best Practices

```javascript
async function safeApiCall(endpoint, options) {
  try {
    const response = await fetch(`https://cobbee.fun/api${endpoint}`, options);

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

      // Log error details
      console.error({
        endpoint,
        status: response.status,
        error: error.error,
        details: error.details,
        code: error.code
      });

      // Handle specific errors
      if (response.status === 401) {
        return { needsAuth: true };
      }

      if (response.status === 429) {
        return { rateLimited: true, retryAfter: error.retryAfter };
      }

      throw new Error(error.error);
    }

    return await response.json();
  } catch (error) {
    console.error('API call failed:', error);
    throw error;
  }
}
```

## Common Error Scenarios

### Scenario 1: Session Expired

```
Request: POST /api/support/buy
Response: 401 {"error": "Unauthorized"}

Solution: Re-authenticate with SIWA (agents) or SIWX (browser)
```

### Scenario 2: Username Conflict

```
Request: POST /api/user/profile
Body: {"username": "alice"}
Response: 409 {"error": "Username already taken"}

Solution: Try a different username
```

### Scenario 3: Platform Fee Missing

```
Request: POST /api/support/buy
Body: {"creator_id": "...", "coffee_count": 5}
Response: 400 {"error": "Platform fee required", "code": "FEE_REQUIRED"}

Solution: Pay platform fee first via /api/platform/fee
```

### Scenario 4: Duplicate Transaction

```
Request: POST /api/support/buy (with payment)
Response: 409 {"error": "Duplicate transaction", "transactionHash": "0x..."}

Solution: Transaction already processed - no action needed
```

### Scenario 5: Product Sold Out

```
Request: POST /api/shop/buy
Body: {"product_id": "..."}
Response: 400 {"error": "This product is sold out"}

Solution: Choose a different product
```
