Skip to main content
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:
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": {}
  }
}
FieldDescription
successAlways false for errors
error.codeMachine-readable error code
error.messageHuman-readable description
error.detailsAdditional context (optional)

HTTP status codes

StatusMeaningWhen it occurs
400Bad RequestInvalid input, malformed JSON, missing required fields
401UnauthorizedMissing or invalid authentication token
403ForbiddenValid token but insufficient permissions
404Not FoundResource does not exist or is not accessible
409ConflictResource conflict (e.g., duplicate SKU)
422Unprocessable EntityValidation failed
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error
503Service UnavailableTemporary unavailability

Common error codes

Authentication errors

CodeStatusDescription
INVALID_CREDENTIALS401Email or password incorrect
TOKEN_EXPIRED401Access token has expired
TOKEN_INVALID401Token is malformed or revoked
REFRESH_TOKEN_REUSED401Refresh token has already been used
INSUFFICIENT_SCOPE403Token lacks required scope

Permission errors

CodeStatusDescription
INSUFFICIENT_PERMISSIONS403User role cannot perform this action
MISSING_CAPABILITY403Subscription tier does not include this feature
LIMIT_EXCEEDED403Resource or rate limit reached
TENANT_MISMATCH403Attempting to access another tenant’s resources

Validation errors

CodeStatusDescription
VALIDATION_ERROR422Input failed validation
DUPLICATE_SKU409SKU already exists in your portfolio
INVALID_FORMAT400Field format is incorrect
REQUIRED_FIELD_MISSING400Required field not provided

Resource errors

CodeStatusDescription
NOT_FOUND404Resource does not exist
ALREADY_EXISTS409Resource already exists
RESOURCE_LOCKED423Resource is locked for editing

Validation error details

Validation errors include field-level details:
{
  "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

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:
StatusRetry?Strategy
429YesWait for Retry-After duration
500YesExponential backoff, max 3 retries
503YesExponential backoff, max 3 retries
401MaybeRefresh token, then retry once
400, 403, 404, 409, 422NoThese indicate a problem with the request
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