Skip to main content

Error Handling

This guide covers how to handle errors from the AgentGate API effectively.

Error Response Format

All API errors follow a consistent format:
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": {
      // Additional context
    }
  }
}

Error Categories

Client Errors (4xx)

Errors caused by the request. Fix the request before retrying.
StatusCodeDescription
400INVALID_REQUESTRequest validation failed
401INVALID_API_KEYAPI key invalid or missing
402INSUFFICIENT_CREDITSNot enough credits
403INSUFFICIENT_PERMISSIONSAPI key lacks required scope
404RESOURCE_NOT_FOUNDResource doesn’t exist
429RATE_LIMITEDToo many requests

Server Errors (5xx)

Errors on AgentGate’s side. Safe to retry with backoff.
StatusCodeDescription
500INTERNAL_ERRORUnexpected server error
503SERVICE_UNAVAILABLETemporary unavailability

Handling Specific Errors

Authentication Errors (401)

try {
  await client.workOrders.create(/* ... */);
} catch (error) {
  if (error.status === 401) {
    if (error.code === 'INVALID_API_KEY') {
      // API key is wrong or revoked
      logger.error('Invalid API key - check configuration');
      throw new ConfigurationError('Invalid API key');
    }
    if (error.code === 'MISSING_API_KEY') {
      // API key not provided
      logger.error('API key not configured');
      throw new ConfigurationError('API key required');
    }
  }
  throw error;
}

Insufficient Credits (402)

try {
  await client.workOrders.create(/* ... */);
} catch (error) {
  if (error.status === 402 && error.code === 'INSUFFICIENT_CREDITS') {
    const { required, available } = error.details;

    // Option 1: Notify user
    await notifyUser(`Insufficient credits: need ${required}, have ${available}`);

    // Option 2: Queue for later
    await queueForLater(workOrder);

    return { status: 'insufficient_credits', required, available };
  }
  throw error;
}

Rate Limiting (429)

async function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T> {
  const maxRetries = 3;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429) {
        const retryAfter = error.headers?.['retry-after'] || Math.pow(2, attempt);
        logger.warn(`Rate limited, waiting ${retryAfter}s`);
        await sleep(retryAfter * 1000);
        continue;
      }
      throw error;
    }
  }

  throw new Error('Max retries exceeded');
}

// Usage
const workOrder = await withRateLimitRetry(() =>
  client.workOrders.create(/* ... */)
);

Validation Errors (400)

try {
  await client.workOrders.create({
    taskPrompt: '', // Invalid: empty
    workspaceSource: { type: 'git' } // Invalid: missing repository
  });
} catch (error) {
  if (error.status === 400 && error.code === 'INVALID_REQUEST') {
    const { errors } = error.details;

    // errors might be:
    // [
    //   { field: 'taskPrompt', message: 'must not be empty' },
    //   { field: 'workspaceSource.repository', message: 'is required' }
    // ]

    for (const err of errors) {
      logger.error(`Validation error: ${err.field} - ${err.message}`);
    }

    throw new ValidationError('Invalid work order', errors);
  }
  throw error;
}

Server Errors (5xx)

async function withRetry<T>(
  fn: () => Promise<T>,
  options = { maxRetries: 3, baseDelay: 1000 }
): Promise<T> {
  const { maxRetries, baseDelay } = options;
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      // Only retry 5xx errors
      if (error.status >= 500) {
        const delay = baseDelay * Math.pow(2, attempt);
        logger.warn(`Server error, retrying in ${delay}ms`, { attempt, error });
        await sleep(delay);
        continue;
      }

      throw error;
    }
  }

  throw lastError;
}

SDK Error Types

The TypeScript SDK provides typed error classes:
import {
  AgentGateError,
  AuthenticationError,
  AuthorizationError,
  ValidationError,
  PaymentError,
  NotFoundError,
  RateLimitError,
  ServerError
} from '@agentgate/sdk';

try {
  await client.workOrders.create(/* ... */);
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Handle auth issues
  } else if (error instanceof PaymentError) {
    // Handle credit issues
  } else if (error instanceof RateLimitError) {
    // Handle rate limiting
  } else if (error instanceof ServerError) {
    // Handle server errors
  } else if (error instanceof AgentGateError) {
    // Handle other API errors
  } else {
    // Handle non-API errors (network, etc.)
  }
}

Run Failures

Run failures are different from API errors. The API call succeeds, but the run itself fails.

Checking Run Status

const run = await client.runs.get(runId);

if (run.status === 'failed') {
  const { error } = run;

  switch (error.code) {
    case 'MAX_ITERATIONS_REACHED':
      // Task didn't converge
      logger.warn('Run did not converge', { runId, iterations: run.iterations });
      break;

    case 'VERIFICATION_FAILED':
      // Verification never passed
      logger.warn('Verification failed', { runId, details: error.details });
      break;

    case 'WORKSPACE_ERROR':
      // Problem with workspace setup
      logger.error('Workspace error', { runId, details: error.details });
      break;

    default:
      logger.error('Run failed', { runId, error });
  }
}

Webhook Error Handling

app.post('/webhooks/agentgate', async (req, res) => {
  res.sendStatus(200);

  const event = req.body;

  if (event.type === 'run.failed') {
    const { runId, error, tenantContext } = event.data;

    // Log for debugging
    logger.error('Run failed', { runId, error });

    // Notify affected user
    if (tenantContext?.tenantUserId) {
      await notifyUser(tenantContext.tenantUserId, {
        type: 'run_failed',
        runId,
        message: error.message
      });
    }

    // Potentially retry
    if (error.code === 'MAX_ITERATIONS_REACHED') {
      // Maybe try with more iterations or refined prompt
      await retryWithAdjustments(event.data);
    }
  }
});

Error Monitoring

Logging Best Practices

// Include correlation IDs
const requestId = generateId();

try {
  logger.info('Creating work order', { requestId });
  const workOrder = await client.workOrders.create(/* ... */);
  logger.info('Work order created', { requestId, runId: workOrder.runId });
} catch (error) {
  logger.error('Failed to create work order', {
    requestId,
    error: {
      code: error.code,
      message: error.message,
      status: error.status
    }
  });
  throw error;
}

Metrics to Track

  • Error rate by type (4xx vs 5xx)
  • Rate limit hits
  • Run failure rate by error code
  • Retry success rate