Skip to main content

Error Handling

The Metabind API uses standard HTTP status codes and returns detailed error objects to help you understand and handle errors effectively.

Error Response Format

All error responses follow a consistent format:
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable message",
    "details": {
      "field": "Additional error context"
    }
  }
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable error description
detailsobjectAdditional context (optional)

HTTP Status Codes

Success Codes

CodeDescription
200 OKRequest succeeded
201 CreatedResource created successfully
204 No ContentRequest succeeded with no response body

Client Error Codes

CodeDescription
400 Bad RequestInvalid request syntax or parameters
401 UnauthorizedMissing or invalid authentication
403 ForbiddenValid auth but insufficient permissions
404 Not FoundResource does not exist
409 ConflictResource conflict (e.g., duplicate name)
422 Unprocessable EntityValidation failed
429 Too Many RequestsRate limit exceeded

Server Error Codes

CodeDescription
500 Internal Server ErrorUnexpected server error
502 Bad GatewayUpstream service error
503 Service UnavailableService temporarily unavailable

Common Error Codes

Validation Errors

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Request validation failed",
    "details": {
      "name": "Name is required",
      "email": "Invalid email format"
    }
  }
}

Resource Not Found

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Content not found",
    "details": {
      "resourceType": "content",
      "resourceId": "cont123"
    }
  }
}

Duplicate Resource

{
  "error": {
    "code": "ALREADY_EXISTS",
    "message": "A component with this name already exists",
    "details": {
      "field": "name",
      "value": "ProductCard"
    }
  }
}

Invalid Status Transition

{
  "error": {
    "code": "INVALID_STATUS_TRANSITION",
    "message": "Cannot publish deleted content",
    "details": {
      "currentStatus": "deleted",
      "requestedStatus": "published"
    }
  }
}

Resource In Use

{
  "error": {
    "code": "RESOURCE_IN_USE",
    "message": "Cannot delete component referenced by packages",
    "details": {
      "referencedBy": ["package-1.0.0", "package-1.1.0"]
    }
  }
}

Package Dependency Errors

{
  "error": {
    "code": "INVALID_PACKAGE_DEPENDENCY",
    "message": "Incompatible package versions",
    "details": {
      "conflicts": [
        {
          "package": "core",
          "required": { "by": "[email protected]", "version": "1.0.0" },
          "provided": { "by": "[email protected]", "version": "1.2.0" }
        }
      ]
    }
  }
}

Circular Dependency

{
  "error": {
    "code": "CIRCULAR_DEPENDENCY",
    "message": "Package dependency cycle detected",
    "details": {
      "cycle": ["[email protected]", "[email protected]", "[email protected]"]
    }
  }
}

Rate Limiting

When you exceed the rate limit, you’ll receive a 429 Too Many Requests response:
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "details": {
      "retryAfter": 60
    }
  }
}
The retryAfter field indicates the number of seconds to wait before retrying.

Rate Limit Headers

Rate limit information is included in response headers:
HeaderDescription
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when the limit resets

Handling Errors

JavaScript Example

async function fetchContent(id) {
  try {
    const response = await fetch(
      `https://api.metabind.ai/v1/organizations/org123/projects/proj456/content/${id}`,
      {
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        }
      }
    );

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

      switch (response.status) {
        case 401:
          throw new Error('Authentication required');
        case 403:
          throw new Error('Permission denied');
        case 404:
          throw new Error(`Content ${id} not found`);
        case 429:
          const retryAfter = error.error.details?.retryAfter || 60;
          throw new Error(`Rate limited. Retry after ${retryAfter}s`);
        default:
          throw new Error(error.error.message);
      }
    }

    return await response.json();
  } catch (error) {
    console.error('API Error:', error.message);
    throw error;
  }
}

Retry Strategy

For transient errors (5xx, 429), implement exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.status === 429 || response.status >= 500) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      return response;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
    }
  }
}

Best Practices

Use the code field for programmatic error handling, not the message field which may change.
The details object contains valuable debugging information. Log it for troubleshooting.
Implement exponential backoff and respect the retryAfter value.
Validate data client-side before making API calls to avoid unnecessary 400/422 errors.