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

# Error handling

> Understanding and handling API errors

LinkXG uses standard HTTP status codes and consistent error response formats. This guide explains how errors are structured and how to handle them in your integration.

## Error response format

All error responses follow a consistent structure:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": {}
  }
}
```

| Field           | Description                   |
| --------------- | ----------------------------- |
| `success`       | Always `false` for errors     |
| `error.code`    | Machine-readable error code   |
| `error.message` | Human-readable description    |
| `error.details` | Additional context (optional) |

***

## HTTP status codes

| Status | Meaning               | When it occurs                                         |
| ------ | --------------------- | ------------------------------------------------------ |
| 400    | Bad Request           | Invalid input, malformed JSON, missing required fields |
| 401    | Unauthorized          | Missing or invalid authentication token                |
| 403    | Forbidden             | Valid token but insufficient permissions               |
| 404    | Not Found             | Resource does not exist or is not accessible           |
| 409    | Conflict              | Resource conflict (e.g., duplicate SKU)                |
| 422    | Unprocessable Entity  | Validation failed                                      |
| 429    | Too Many Requests     | Rate limit exceeded                                    |
| 500    | Internal Server Error | Server-side error                                      |
| 503    | Service Unavailable   | Temporary unavailability                               |

***

## Common error codes

### Authentication errors

| Code                   | Status | Description                         |
| ---------------------- | ------ | ----------------------------------- |
| `INVALID_CREDENTIALS`  | 401    | Email or password incorrect         |
| `TOKEN_EXPIRED`        | 401    | Access token has expired            |
| `TOKEN_INVALID`        | 401    | Token is malformed or revoked       |
| `REFRESH_TOKEN_REUSED` | 401    | Refresh token has already been used |
| `INSUFFICIENT_SCOPE`   | 403    | Token lacks required scope          |

### Permission errors

| Code                       | Status | Description                                     |
| -------------------------- | ------ | ----------------------------------------------- |
| `INSUFFICIENT_PERMISSIONS` | 403    | User role cannot perform this action            |
| `MISSING_CAPABILITY`       | 403    | Subscription tier does not include this feature |
| `LIMIT_EXCEEDED`           | 403    | Resource or rate limit reached                  |
| `TENANT_MISMATCH`          | 403    | Attempting to access another tenant's resources |

### Validation errors

| Code                     | Status | Description                          |
| ------------------------ | ------ | ------------------------------------ |
| `VALIDATION_ERROR`       | 422    | Input failed validation              |
| `DUPLICATE_SKU`          | 409    | SKU already exists in your portfolio |
| `INVALID_FORMAT`         | 400    | Field format is incorrect            |
| `REQUIRED_FIELD_MISSING` | 400    | Required field not provided          |

### Resource errors

| Code              | Status | Description                    |
| ----------------- | ------ | ------------------------------ |
| `NOT_FOUND`       | 404    | Resource does not exist        |
| `ALREADY_EXISTS`  | 409    | Resource already exists        |
| `RESOURCE_LOCKED` | 423    | Resource is locked for editing |

***

## Validation error details

Validation errors include field-level details:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "fields": [
        {
          "field": "sku",
          "message": "SKU must be unique within your company",
          "value": "WIDGET-001"
        },
        {
          "field": "price",
          "message": "Price must be a positive number",
          "value": -10
        }
      ]
    }
  }
}
```

Use the `field` value to highlight specific form fields or log the exact problem.

***

## Handling errors in code

### Basic error handling

```javascript theme={null}
async function createProduct(productData) {
  const response = await fetch('https://api.linkxg.com/api/v1/products', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(productData)
  });

  if (!response.ok) {
    const error = await response.json();
    
    switch (response.status) {
      case 401:
        // Token expired — refresh and retry
        await refreshToken();
        return createProduct(productData);
      
      case 403:
        if (error.error.code === 'MISSING_CAPABILITY') {
          throw new Error('This feature requires a plan upgrade');
        }
        throw new Error('Permission denied');
      
      case 409:
        if (error.error.code === 'DUPLICATE_SKU') {
          throw new Error(`SKU ${productData.sku} already exists`);
        }
        throw new Error('Conflict');
      
      case 422:
        // Validation error — return field details
        throw new ValidationError(error.error.details.fields);
      
      case 429:
        // Rate limited — wait and retry
        const retryAfter = response.headers.get('Retry-After') || 60;
        await sleep(retryAfter * 1000);
        return createProduct(productData);
      
      default:
        throw new Error(error.error.message);
    }
  }

  return response.json();
}
```

### Retry logic for transient errors

Some errors are transient and can be retried:

| Status                  | Retry? | Strategy                                  |
| ----------------------- | ------ | ----------------------------------------- |
| 429                     | Yes    | Wait for `Retry-After` duration           |
| 500                     | Yes    | Exponential backoff, max 3 retries        |
| 503                     | Yes    | Exponential backoff, max 3 retries        |
| 401                     | Maybe  | Refresh token, then retry once            |
| 400, 403, 404, 409, 422 | No     | These indicate a problem with the request |

```javascript theme={null}
async function requestWithRetry(url, options, maxRetries = 3) {
  const retryableStatuses = [429, 500, 503];
  let lastError;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.ok) {
        return response.json();
      }
      
      if (!retryableStatuses.includes(response.status)) {
        // Non-retryable error
        throw await response.json();
      }
      
      // Calculate backoff
      const retryAfter = response.headers.get('Retry-After');
      const delay = retryAfter 
        ? parseInt(retryAfter) * 1000 
        : Math.min(1000 * Math.pow(2, attempt), 30000);
      
      await sleep(delay);
      
    } catch (error) {
      lastError = error;
    }
  }
  
  throw lastError;
}
```

***

## Security considerations

**Do not expose error details to end users.** Internal error codes and stack traces should be logged, not displayed. Show generic messages to users.

**404 for access denied.** When a user requests a resource they cannot access (wrong tenant, insufficient permissions), the API returns 404 rather than 403 to avoid revealing that the resource exists.

**Rate limit errors do not confirm existence.** Rate limiting is applied before resource lookup, so a 429 does not indicate whether the requested resource exists.

***

## Logging recommendations

Log the following for debugging:

* Request URL and method
* Response status code
* Error code and message
* Request ID (from `X-Request-Id` header)
* Timestamp

Do not log:

* Authentication tokens
* Full request bodies containing sensitive data
* User passwords or secrets

***

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/developers/authentication">
    Set up API authentication
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    Browse the complete API documentation
  </Card>
</CardGroup>
