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

# Error Handling

> GraphQL error codes and handling

GraphQL errors follow the standard GraphQL error format with additional `extensions` for error codes.

## Error Format

```json theme={null}
{
  "errors": [
    {
      "message": "Human-readable error message",
      "extensions": {
        "code": "ERROR_CODE",
        "additionalInfo": "..."
      },
      "path": ["queryName", "fieldName"]
    }
  ],
  "data": {
    "queryName": null
  }
}
```

## Common Error Codes

| Code             | HTTP Status | Description                |
| ---------------- | ----------- | -------------------------- |
| `NOT_FOUND`      | 404         | Resource not found         |
| `UNAUTHORIZED`   | 401         | Invalid or missing API key |
| `FORBIDDEN`      | 403         | Insufficient permissions   |
| `BAD_REQUEST`    | 400         | Invalid query or variables |
| `INTERNAL_ERROR` | 500         | Server error               |

## Preview-Specific Error Codes

| Code                    | Description                             |
| ----------------------- | --------------------------------------- |
| `INVALID_PREVIEW_TOKEN` | Invalid or expired preview token        |
| `PREVIEW_NOT_FOUND`     | Preview resource not found or deleted   |
| `VERSION_NOT_FOUND`     | Component version not found             |
| `MISSING_PREVIEW_TOKEN` | Preview token required but not provided |

## Error Examples

### Resource Not Found

```json theme={null}
{
  "errors": [
    {
      "message": "Content not found",
      "extensions": {
        "code": "NOT_FOUND",
        "id": "cont999"
      },
      "path": ["content"]
    }
  ],
  "data": {
    "content": null
  }
}
```

### Unauthorized

```json theme={null}
{
  "errors": [
    {
      "message": "Invalid or missing API key",
      "extensions": {
        "code": "UNAUTHORIZED"
      }
    }
  ]
}
```

### Invalid Preview Token

```json theme={null}
{
  "errors": [
    {
      "message": "Invalid or expired preview token",
      "extensions": {
        "code": "INVALID_PREVIEW_TOKEN"
      },
      "path": ["preview"]
    }
  ],
  "data": { "preview": null }
}
```

### Version Not Found

```json theme={null}
{
  "errors": [
    {
      "message": "Component version not found",
      "extensions": {
        "code": "VERSION_NOT_FOUND",
        "version": 5
      },
      "path": ["preview"]
    }
  ],
  "data": { "preview": null }
}
```

### Validation Error

```json theme={null}
{
  "errors": [
    {
      "message": "Variable \"$id\" of required type \"ID!\" was not provided.",
      "extensions": {
        "code": "BAD_REQUEST"
      },
      "locations": [{ "line": 1, "column": 7 }]
    }
  ]
}
```

## Client-Side Error Handling

### JavaScript

```javascript theme={null}
async function fetchContent(id) {
  const response = await fetch('https://api.metabind.ai/graphql', {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: GET_CONTENT_QUERY,
      variables: { id }
    })
  });

  const { data, errors } = await response.json();

  if (errors) {
    for (const error of errors) {
      switch (error.extensions?.code) {
        case 'NOT_FOUND':
          console.warn(`Content ${id} not found`);
          return null;
        case 'UNAUTHORIZED':
          throw new Error('Invalid API key');
        default:
          console.error('GraphQL error:', error.message);
      }
    }
  }

  return data?.content;
}
```

### Apollo Client

```javascript theme={null}
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { onError } from '@apollo/client/link/error';

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    for (const { message, extensions, path } of graphQLErrors) {
      switch (extensions?.code) {
        case 'UNAUTHORIZED':
          // Redirect to login or refresh API key
          handleUnauthorized();
          break;
        case 'NOT_FOUND':
          console.warn(`Resource not found at ${path?.join('.')}`);
          break;
        case 'INVALID_PREVIEW_TOKEN':
          // Preview token expired
          handleExpiredPreview();
          break;
        default:
          console.error(`GraphQL error: ${message}`);
      }
    }
  }

  if (networkError) {
    console.error(`Network error: ${networkError}`);
  }
});

const client = new ApolloClient({
  link: errorLink.concat(httpLink),
  cache: new InMemoryCache()
});
```

### React Hook

```javascript theme={null}
function useContent(id) {
  const { data, loading, error } = useQuery(GET_CONTENT, {
    variables: { id },
    errorPolicy: 'all'  // Return partial data with errors
  });

  if (error) {
    const graphQLError = error.graphQLErrors?.[0];

    if (graphQLError?.extensions?.code === 'NOT_FOUND') {
      return { content: null, loading: false, notFound: true };
    }

    throw error;  // Re-throw unexpected errors
  }

  return { content: data?.content, loading, notFound: false };
}
```

## Subscription Error Handling

```javascript theme={null}
import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://api.metabind.ai/graphql',
  connectionParams: {
    headers: { 'x-api-key': API_KEY }
  },
  on: {
    error: (error) => {
      console.error('WebSocket error:', error);
    },
    closed: () => {
      console.log('WebSocket closed');
    }
  },
  retryAttempts: 5,
  shouldRetry: () => true
});

client.subscribe({
  query: CONTENT_UPDATED,
  variables: { id: 'cont123' }
}, {
  next: (data) => {
    if (data.errors) {
      for (const error of data.errors) {
        console.error('Subscription error:', error.message);
      }
      return;
    }
    handleUpdate(data.data.contentUpdated);
  },
  error: (err) => {
    console.error('Subscription error:', err);
  },
  complete: () => {
    console.log('Subscription complete');
  }
});
```

## Best Practices

1. **Always check for errors**: GraphQL can return partial data with errors
2. **Use error codes**: Check `extensions.code` for programmatic error handling
3. **Handle NOT\_FOUND gracefully**: Resources may be deleted or unpublished
4. **Implement retry logic**: Network errors may be transient
5. **Log errors**: Include path and extensions for debugging
6. **Differentiate preview errors**: Preview tokens can expire independently of API keys
