Skip to content
Get started

Production Reliability

Caching behavior, retry handling, error patterns, timeouts, and operational decisions for running Tabstack dependably in production.

This guide covers what you need to know to run Tabstack dependably in production: caching behavior, when to bypass it, retry handling, error patterns, and the operational decisions that matter at scale.


Tabstack caches extraction results by URL. When you request the same URL again, the API may return a cached result rather than re-fetching the page. This is usually what you want: it’s faster and reduces cost.

What’s cached: The response for a given URL + endpoint combination. Effort level and schema are also factors; changing them may produce a fresh fetch.

How long results are cached: Cache TTL is not publicly documented. For time-sensitive data (pricing, inventory, live stats), always set nocache: true.

When to use nocache: true:

  • Pricing or availability data that changes frequently
  • Pages you know have updated since your last fetch
  • Debugging extraction issues (confirms the problem isn’t a stale cache)
  • Any data with a business requirement for freshness
// Price monitoring — always fresh
const result = await client.extract.json({
url: 'https://competitor.com/pricing',
nocache: true,
json_schema: { /* ... */ }
})

The SDK automatically retries failed requests twice with exponential backoff. The following errors are retried:

StatusError
408Request Timeout
409Conflict
429Rate Limit
500+Server errors
N/ANetwork / connection failures

Retries happen transparently. You don’t need to implement retry logic for these cases.

Disable retries if you’re running your own retry orchestration:

const client = new Tabstack({
apiKey: process.env.TABSTACK_API_KEY,
maxRetries: 0
})
// Or per-request:
await client.extract.json({ url, json_schema }, { maxRetries: 5 })

A 422 UnprocessableEntityError means the page was fetched but extraction failed. This is the most actionable error; you can often fix it:

import Tabstack, { UnprocessableEntityError } from '@tabstack/sdk'
try {
return await client.extract.json({ url, json_schema })
} catch (err) {
if (err instanceof UnprocessableEntityError) {
// Try with full rendering
return await client.extract.json({
url,
json_schema,
effort: 'max',
nocache: true
})
}
throw err
}

429 errors are auto-retried, but if you’re hitting them consistently you’re exceeding your plan’s rate limit. Use exponential backoff in your own queuing layer for bulk operations:

import { RateLimitError } from '@tabstack/sdk'
async function extractWithBackoff(
url: string,
schema: object,
attempt = 0
): Promise<unknown> {
try {
return await client.extract.json({ url, json_schema: schema })
} catch (err) {
if (err instanceof RateLimitError && attempt < 3) {
const delay = Math.pow(2, attempt) * 1000
await new Promise(r => setTimeout(r, delay))
return extractWithBackoff(url, schema, attempt + 1)
}
throw err
}
}

401 AuthenticationError in production usually means a key was rotated or the environment variable wasn’t set in the deployment. These are not retried automatically; they require intervention.

import { AuthenticationError } from '@tabstack/sdk'
if (err instanceof AuthenticationError) {
// Alert — this is a configuration problem, not a transient failure
alertOps('Tabstack API key invalid or missing')
}

Default timeout is 60 seconds per request. effort: 'max' on complex SPAs can approach this. Tune per endpoint:

// Longer timeout for heavy pages
const result = await client.extract.json(
{ url, json_schema, effort: 'max' },
{ timeout: 90_000 } // 90 seconds
)

For /automate and /research (streaming), the timeout governs the initial connection, not the stream duration. Long-running tasks may continue streaming well past 60 seconds.


Enable debug logging during development to see full request/response detail:

const client = new Tabstack({
apiKey: process.env.TABSTACK_API_KEY,
logLevel: 'debug' // 'debug' | 'info' | 'warn' | 'error' | 'off'
})

In production, warn (default) is appropriate: it surfaces retry events and errors without noise.

For Python, set TABSTACK_LOG=info in the environment.


For high-volume extraction jobs (many URLs), control concurrency to avoid rate limit errors:

import PLimit from 'p-limit'
const limit = PLimit(5) // 5 concurrent requests
const results = await Promise.all(
urls.map(url =>
limit(() => client.extract.json({ url, json_schema }))
)
)