Skip to main content

Error Handling

Use instanceof checks against specific error subclasses to handle different failure modes. Order your checks from most specific to least specific, with the base TalonicError as the final catch-all.

Comprehensive catch pattern
import {
  TalonicAuthError,
  TalonicNotFoundError,
  TalonicValidationError,
  TalonicRateLimitError,
  TalonicServerError,
  TalonicNetworkError,
  TalonicTimeoutError,
  TalonicError,
} from '@talonic/node'

try {
  const result = await talonic.extract({
    file_path: './invoice.pdf',
    schema_id: 'sch_abc123',
  })
} catch (err) {
  if (err instanceof TalonicAuthError) {
    console.error('Authentication failed — check your API key')
  } else if (err instanceof TalonicNotFoundError) {
    console.error(`Resource not found: ${err.message}`)
  } else if (err instanceof TalonicValidationError) {
    console.error(`Invalid request: ${err.code} — ${err.message}`)
  } else if (err instanceof TalonicRateLimitError) {
    console.error(`Rate limited. Resets at ${err.rateLimit.resetAt.toISOString()}`)
  } else if (err instanceof TalonicServerError) {
    console.error(`Server error (retries exhausted): ${err.code}`)
  } else if (err instanceof TalonicTimeoutError) {
    console.error(`Timed out after ${err.timeoutMs}ms`)
  } else if (err instanceof TalonicNetworkError) {
    console.error(`Network failure: ${err.message}`)
  } else if (err instanceof TalonicError) {
    console.error(`Talonic error: ${err.code} (status ${err.status})`)
  }
}

TalonicRateLimitError includes a rateLimit object with limit, remaining, and resetAt (Date) for scheduling retries. All errors include requestId for support debugging. The TalonicTimeoutError includes timeoutMs with the configured timeout value that was exceeded, and TalonicNetworkError preserves the original error as cause for inspecting DNS or TCP failures.

The retryable property on every error tells you whether the SDK considers the failure worth retrying. When you catch an error after retries are exhausted, checking retryable helps decide whether to queue the request for later or surface it to the user immediately. Errors with retryable: true that still threw mean the SDK exhausted all maxRetries attempts; errors with retryable: false were thrown on the first attempt without any retry.

Structured error logging
import { TalonicError } from '@talonic/node'

async function extractWithLogging(filePath: string, schemaId: string) {
  try {
    return await talonic.extract({ file_path: filePath, schema_id: schemaId })
  } catch (err) {
    if (err instanceof TalonicError) {
      // Structured log entry safe for metrics and alerting
      const logEntry = {
        level: 'error',
        service: 'extraction-pipeline',
        error_code: err.code,        // 'invalid_schema', 'rate_limited', etc.
        http_status: err.status,     // 422, 429, 500, or 0 for transport errors
        request_id: err.requestId,   // server trace ID
        retryable: err.retryable,    // was this retried?
        error_class: err.name,       // 'TalonicValidationError', etc.
        message: err.message,
        file: filePath,
        schema_id: schemaId,
      }
      console.error(JSON.stringify(logEntry))
    }
    throw err
  }
}

For logging and observability, capture err.code, err.status, and err.requestId on every failure. The code is a machine-readable string like "invalid_schema" or "rate_limited" that is safe to use in metrics and alerting rules. The err.name property gives you the class name (e.g. 'TalonicValidationError') which is useful for grouping errors by category in your monitoring dashboard.

Rate-limit-aware retry wrapper
import { TalonicRateLimitError, TalonicError } from '@talonic/node'

// Retry wrapper that waits for rate limit reset
async function extractWithRateLimitRetry<T>(
  fn: () => Promise<T>,
  maxAttempts = 2,
): Promise<T> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (err) {
      if (err instanceof TalonicRateLimitError && attempt < maxAttempts - 1) {
        const waitMs = err.rateLimit.resetAt.getTime() - Date.now()
        if (waitMs > 0 && waitMs < 120_000) {
          console.log(`Rate limited. Waiting ${Math.ceil(waitMs / 1000)}s...`)
          await new Promise(r => setTimeout(r, waitMs + 100))
          continue
        }
      }
      throw err
    }
  }
  throw new Error('Unreachable')
}

// Usage
const result = await extractWithRateLimitRetry(() =>
  talonic.extract({ file_path: './doc.pdf', schema_id: 'sch_abc123' })
)

When building extraction pipelines that process many documents, consider wrapping your calls in a function that distinguishes between recoverable and terminal errors. Errors with retryable: true (rate limits, server errors, network issues, timeouts) may succeed if retried later. Errors with retryable: false (auth, validation, not found) indicate a problem with the request itself and should be logged and skipped. This pattern is especially useful for batch processing where you want to continue with remaining documents rather than failing the entire batch.

Remember that TalonicRateLimitError and TalonicServerError are only thrown after all retry attempts are exhausted. If you catch them, the SDK has already retried maxRetries times.

Frequently asked questions

How do I handle rate limits in the Talonic SDK?+
Catch TalonicRateLimitError and read err.rateLimit.resetAt to know when to retry. The SDK retries rate-limited requests automatically up to maxRetries times first, respecting the X-RateLimit-Reset header. The error is only thrown after all retries are exhausted.
What is the retryable property on errors?+
It indicates whether the SDK considers the failure worth retrying. Errors thrown after retries are exhausted still carry retryable: true, meaning they could succeed on a future attempt. Use this to decide whether to queue the request for later processing or surface it to the user immediately.
How should I log Talonic errors?+
Capture err.code (machine-readable string), err.status (HTTP status or 0), err.requestId (server-side trace ID), err.name (class name), and err.message. The code field is stable and safe for metrics and alerting rules. Include requestId in support tickets for server-side debugging.
Should I order instanceof checks from specific to general?+
Yes. Always check the most specific error subclass first (e.g. TalonicRateLimitError before TalonicError). Since all errors extend TalonicError, placing the base class check first would catch everything and prevent the more specific handlers from running.
How do I handle errors in batch processing pipelines?+
Check err.retryable to distinguish recoverable from terminal errors. Errors with retryable: true (rate limits, server errors, network issues) may succeed later. Errors with retryable: false (auth, validation, not found) indicate a problem with the request itself. Log terminal errors and continue processing remaining documents rather than failing the entire batch.