Skip to main content

Error Handling

RELIABILITYTechWriterErrorv3.2.0STABLE

Error handling

The SDK turns every non-2xx response into a typed exception that extends a single base class, TechWriterError. Each one carries a stable code, the original requestId, a retryable hint, and — for validation errors — a field-level breakdown you can pipe directly into your form.

The result: predictable error handling. Branch on the code, log the request ID, decide whether to retry. Transient failures are absorbed by the built-in retry policy before they reach your code, so the exceptions you do see are almost always actionable.

Pick a scenario in the panel on the right to see a realistic snippet — from basic try / catch to circuit breakers and conflict resolution. Every snippet is copy-pasteable into a Node.js project.

Error codes

The codes you'll meet most often, the HTTP status they ride on, and the recommended response.

CodeStatusActionWhat it means
unauthorized401FixAPI key missing, expired, or rotated. Refresh the credential and retry once.
forbidden403SurfaceAuthenticated, but the key lacks the scope. Surface to the caller — do not retry.
not_found404SurfaceResource ID does not exist or was soft-deleted. Treat as absent in your domain logic.
validation_failed422FixPayload broke a schema rule. err.details lists field, rule, and expected pattern.
conflict409RetryConcurrency conflict. Re-read the resource and retry with the new lastSeenVersion.
rate_limited429BackoffQuota exceeded. Honor the Retry-After header before retrying.
idempotency_replay409SurfaceSame Idempotency-Key reused with a different body. Generate a new key.
server_error500RetryTransient internal error. SDK retries with exponential backoff up to your retries cap.
service_unavailable503BackoffDependency degraded. Trip a circuit breaker if it persists for more than 30 seconds.
gateway_timeout504RetryUpstream took too long. Safe to retry idempotent reads; use idempotency keys for writes.
network_errorRetryConnection reset, DNS failure, or TLS error before any HTTP response was received.
request_abortedFatalCaller cancelled via AbortSignal or timeout. Stop retrying immediately.

Exception hierarchy

Every SDK error is a subclass of TechWriterError. Catch the base class for a single funnel, or specific subclasses for fine-grained control.

ClassExtendsNotes
TechWriterErrorErrorBase class for every SDK error. Carries code, status, requestId, retryable.
AuthenticationErrorTechWriterErrorRaised on 401. Inspect err.code for unauthorized, token_expired, or token_revoked.
PermissionErrorTechWriterErrorRaised on 403. err.requiredScope tells you which scope the key was missing.
NotFoundErrorTechWriterErrorRaised on 404. Use it as a clean signal for "absent" rather than a hard failure.
ValidationErrorTechWriterErrorRaised on 422. err.details is an array of { field, rule, expected }.
ConflictErrorTechWriterErrorRaised on 409. err.currentVersion holds the server's view of the version.
RateLimitErrorTechWriterErrorRaised on 429. err.retryAfter is in seconds; err.limit and err.remaining track the quota.
ServerErrorTechWriterErrorRaised on 5xx. Retryable by default — counts against your retries budget.
NetworkErrorTechWriterErrorRaised when no HTTP response was received. Always retryable.

Error object fields

The shape of every thrown error. Use these instead of parsing err.message.

FieldTypeDescription
codestringStable, machine-readable identifier. Branch your logic on this, not on err.message.
statusnumberHTTP status code. Null for network and abort errors.
messagestringHuman-readable summary. Safe to log; do not parse it programmatically.
requestIdstringUnique server-side ID. Always include this when contacting support.
retryablebooleanWhether the SDK considers this error safe to retry. False for 4xx and abort.
retryAfternumber?Seconds to wait before retrying. Set on 429 and some 503 responses.
detailsarray?Field-level breakdown for validation errors: { field, rule, expected }.
attemptnumberWhich retry attempt produced the final error. 1 means it failed on the first try.
causeError?Underlying cause — the original network error, JSON parse error, etc.

Retry behavior

What the SDK does on your behalf, by default. Override per-call with the retries and retryDelay options.

CodeAuto-retryCapWait strategyNotes
rate_limitedYes5Retry-After headerSDK pauses for the exact duration the server returned.
server_errorYes3Exponential, jitterBase 250 ms, doubling, full-jitter, capped at 8 s.
service_unavailableYes3Exponential, jitterTrip a circuit breaker if you see > 5 in 30 s.
gateway_timeoutYes2Exponential, jitterPair with idempotency keys when retrying writes.
network_errorYes3Exponential, jitterNo HTTP response received — always safe to retry.
conflictManualRe-read firstRead the latest version, re-apply the change, then retry.
validation_failedNoFix the payload — retrying without changes always fails.
unauthorizedOnce1After token refreshRefresh the credential first, then retry the original call.
forbiddenNoScope is missing — no amount of retrying will help.

Wire format

Every error response uses this envelope. The SDK parses it into the typed exception above; if you call the API directly, this is what you'll see.

{
  "error": {
    "code": "validation_failed",
    "message": "Field 'slug' must be lowercase kebab-case.",
    "requestId": "req_01HW8N4Z3R8C9X",
    "details": [
      {
        "field": "slug",
        "rule": "pattern",
        "expected": "^[a-z0-9-]+$"
      }
    ]
  }
}

Debugging checklist

Six steps from "something broke" to "ticket closed."

#StepWhat to do
1Capture the requestIdEvery error carries err.requestId. Log it before doing anything else — without it support cannot trace the call.
2Inspect err.code, not err.messageCodes are stable across versions; messages are not. Branch on the code and never on substrings of the message.
3Read err.details for 422sValidation errors include field, rule, and expected. Map them straight onto your form inputs for the cleanest UX.
4Check err.retryableA quick boolean for "should I bother retrying this." False on 4xx and aborts; true on 5xx, 429, and network.
5Replay with the request logPlug client.on('request') and client.on('response') into your logger to replay any failure offline.
6Escalate with the requestIdWhen you reach out to support@techwriter.io, include the requestId — answers come back in minutes, not days.

Where to go next

Was this section helpful?

Pattern — Catch & branch by code

import { TechWriterError } from ;

try {
  const project = await client.projects.get();
  return project;
} catch (err) {
  if (!(err instanceof TechWriterError)) throw err; "color: ">#8b949e">// network or bug

  switch (err.code) {
    case :
      "color: ">#8b949e">// refresh credential, then surface to caller
      throw err;
    case :
      return null; "color: ">#8b949e">// project was deleted, treat as absent
    case :
      "color: ">#8b949e">// SDK will already have retried; bubble up
      throw err;
    default:
      "color: ">#8b949e">// log + rethrow for the on-call to investigate
      logger.error({ requestId: err.requestId, code: err.code }, err.message);
      throw err;
  }
}

Pro tip

Always log err.requestId alongside err.code. With those two values, support can replay the exact request from server-side traces — without them, debugging a one-off 500 is a guessing game.

Stuck on an error?

Email support@techwriter.io with the requestId and the SDK version. Most tickets are resolved within one business hour.