nodestash

Error Handling

Handle API errors with typed error classes

The SDK maps every API error to a specific TypeScript error class so you can catch and handle errors precisely.

Error Class Hierarchy

Error (native)
└── NodeStashError           — Base SDK error (network, timeout)
    └── ApiError             — API returned an error response
        ├── AuthenticationError  — 401 (invalid/missing key)
        ├── NotFoundError        — 404 (resource not found)
        ├── ValidationError      — 400/422 (validation failed)
        └── RateLimitError       — 429 (rate limited)

Importing Error Classes

import {
  NodeStash,
  NodeStashError,
  ApiError,
  AuthenticationError,
  NotFoundError,
  ValidationError,
  RateLimitError,
} from '@nodestash/sdk'

Catching Specific Errors

const client = new NodeStash({ apiKey: 'nds_live_...' })

try {
  const contact = await client.contacts.create({
    email: 'jane@acme.com',
    first_name: 'Jane',
  })
} catch (error) {
  if (error instanceof ValidationError) {
    // Field-level validation errors
    console.log('Validation failed:', error.message)
    for (const detail of error.details) {
      console.log(`  ${detail.field}: ${detail.message}`)
    }
  } else if (error instanceof AuthenticationError) {
    // Invalid or expired API key
    console.log('Authentication failed:', error.message)
  } else if (error instanceof NotFoundError) {
    // Resource doesn't exist
    console.log('Not found:', error.message)
  } else if (error instanceof RateLimitError) {
    // All retries exhausted
    console.log(`Rate limited. Retry after ${error.retryAfter}s`)
  } else if (error instanceof ApiError) {
    // Catch-all for other API errors
    console.log(`API error ${error.statusCode}: ${error.code}`)
  } else if (error instanceof NodeStashError) {
    // Network errors, timeouts
    console.log('SDK error:', error.message)
  }
}

Always catch specific error classes before the parent class. instanceof checks follow the class hierarchy — ValidationError also matches ApiError and NodeStashError.

Error Properties

ApiError

PropertyTypeDescription
messagestringHuman-readable error message
codestringMachine-readable error code
statusCodenumberHTTP status code
requestIdstring | undefinedRequest ID for debugging
detailsErrorDetail[]Field-level error details

AuthenticationError

PropertyTypeValue
statusCodenumber401
codestring'INVALID_API_KEY'
requestIdstring | undefinedRequest ID

NotFoundError

PropertyTypeValue
statusCodenumber404
codestring'NOT_FOUND'
requestIdstring | undefinedRequest ID

ValidationError

PropertyTypeDescription
statusCodenumber422
codestring'VALIDATION_ERROR'
detailsErrorDetail[]Field-level errors
requestIdstring | undefinedRequest ID
interface ErrorDetail {
  field?: string
  message: string
}

RateLimitError

PropertyTypeDescription
statusCodenumber429
codestring'RATE_LIMITED'
retryAfternumber | undefinedSeconds to wait
requestIdstring | undefinedRequest ID

Automatic Retries

The SDK automatically retries certain errors:

Error TypeRetried?Strategy
429 Rate LimitedYesRespects Retry-After, then exponential backoff
5xx Server ErrorYesExponential backoff (max 2 retries)
Network/TimeoutYesExponential backoff (max 2 retries)
400 ValidationNoThrown immediately
401 AuthNoThrown immediately
404 Not FoundNoThrown immediately

Backoff Schedule

AttemptDelay
1st retry1,000 ms
2nd retry2,000 ms
3rd retry4,000 ms
Maximum10,000 ms

Configure retries when initializing the client:

const client = new NodeStash({
  apiKey: 'nds_live_...',
  maxRetries: 5,     // default: 3
  timeout: 60_000,   // default: 30_000ms
})

Practical Patterns

Safe Get (Return null instead of throwing)

async function getContactOrNull(id: string) {
  try {
    return await client.contacts.get(id)
  } catch (error) {
    if (error instanceof NotFoundError) {
      return null
    }
    throw error
  }
}

const contact = await getContactOrNull('ct_maybe_exists')

Handle Duplicates

async function createOrGetContact(email: string, name: string) {
  try {
    return await client.contacts.create({
      email,
      first_name: name,
    })
  } catch (error) {
    if (error instanceof ApiError && error.code === 'CONFLICT') {
      // Contact with this email already exists — find it
      const { data } = await client.contacts.list({ email })
      return data[0]
    }
    throw error
  }
}

Log Request IDs

try {
  await client.contacts.update('ct_abc123', { email: 'new@email.com' })
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`API Error [${error.requestId}]: ${error.code} — ${error.message}`)
    // Share this request ID with support for debugging
  }
}

On this page