Skip to main content
GraphQL errors follow the standard GraphQL error format with additional extensions for error codes.

Error Format

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

Common Error Codes

CodeHTTP StatusDescription
NOT_FOUND404Resource not found
UNAUTHORIZED401Invalid or missing API key
FORBIDDEN403Insufficient permissions
BAD_REQUEST400Invalid query or variables
INTERNAL_ERROR500Server error

Preview-Specific Error Codes

CodeDescription
INVALID_PREVIEW_TOKENInvalid or expired preview token
PREVIEW_NOT_FOUNDPreview resource not found or deleted
VERSION_NOT_FOUNDComponent version not found
MISSING_PREVIEW_TOKENPreview token required but not provided

Error Examples

Resource Not Found

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

Unauthorized

{
  "errors": [
    {
      "message": "Invalid or missing API key",
      "extensions": {
        "code": "UNAUTHORIZED"
      }
    }
  ]
}

Invalid Preview Token

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

Version Not Found

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

Validation Error

{
  "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

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

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

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

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