Driftstack driftstack docs

Error reference

reference. Every error response from the Driftstack API is an RFC 9457 Problem Details JSON document. The type URI uniquely identifies the error class; the status carries the HTTP status code; the detail field explains what went wrong in human-readable text.

{
  "type": "https://errors.driftstack.dev/rate-limited",
  "title": "Rate limit exceeded",
  "status": 429,
  "detail": "Rate limit for \"global\" exceeded for tier \"api_starter\".",
  "retry_after_seconds": 12
}

The full mapping below covers every problem-type the API can return, the matching SDK error class in each language, and whether isRetryable() (TS / Python) / IsRetryable() (Go) returns true for that class.

Mapping table

Problem-type URIHTTPTypeScriptPythonGoRetryable?
errors.driftstack.dev/bad-request400BadRequestErrorValidationErrorValidationErrorno
errors.driftstack.dev/validation-failed400ValidationErrorValidationErrorValidationErrorno
errors.driftstack.dev/unauthorized401AuthErrorAuthErrorAuthErrorno
errors.driftstack.dev/invalid-key401InvalidKeyErrorInvalidKeyErrorInvalidKeyErrorno
errors.driftstack.dev/revoked-key401RevokedKeyErrorRevokedKeyErrorRevokedKeyErrorno
errors.driftstack.dev/expired-key401ExpiredKeyErrorExpiredKeyErrorExpiredKeyErrorno
errors.driftstack.dev/forbidden403ForbiddenErrorForbiddenErrorForbiddenErrorno
errors.driftstack.dev/mfa-step-up-required403MfaStepUpRequiredErrorMfaStepUpRequiredErrorMfaStepUpRequiredErrorno
errors.driftstack.dev/email-not-verified403EmailNotVerifiedErrorEmailNotVerifiedErrorEmailNotVerifiedErrorno
errors.driftstack.dev/not-found404NotFoundErrorNotFoundErrorNotFoundErrorno
errors.driftstack.dev/conflict409ConflictErrorConflictErrorConflictErrorno
errors.driftstack.dev/email-already-registered409EmailAlreadyRegisteredErrorEmailAlreadyRegisteredErrorEmailAlreadyRegisteredErrorno
errors.driftstack.dev/invalid-credentials401InvalidCredentialsErrorInvalidCredentialsErrorInvalidCredentialsErrorno
errors.driftstack.dev/invalid-auth-token400InvalidAuthTokenErrorInvalidAuthTokenErrorInvalidAuthTokenErrorno
errors.driftstack.dev/legal-acceptance-required403ForbiddenErrorLegalAcceptanceRequiredErrorLegalAcceptanceRequiredErrorno
errors.driftstack.dev/rate-limited429RateLimitErrorRateLimitErrorRateLimitErroryes
errors.driftstack.dev/concurrency-limit429ConcurrencyLimitErrorConcurrencyLimitErrorConcurrencyLimitErrorno
errors.driftstack.dev/tier-limit429TierLimitErrorQuotaExceededErrorQuotaExceededErrorno
errors.driftstack.dev/session-destroyed410SessionDestroyedErrorSessionDestroyedErrorSessionDestroyedErrorno
errors.driftstack.dev/session-timeout504SessionDestroyedErrorSessionTimeoutErrorSessionTimeoutErrorno
errors.driftstack.dev/driver-error502DriverErrorDriverErrorDriverErrorno
errors.driftstack.dev/driver-not-integrated503DriverNotIntegratedErrorDriverErrorDriverErrorno
errors.driftstack.dev/feature-unavailable503FeatureUnavailableErrorFeatureUnavailableErrorFeatureUnavailableErrorno
errors.driftstack.dev/internal5xxInternalErrorInternalErrorInternalErroryes
(network failure / parse error)0TransportErrorTransportErrorTransportErroryes

When to retry

The SDKs all expose a public isRetryable(err) / is_retryable / IsRetryable predicate that returns true for the three retryable classes above. Use it from your own retry/backoff loop:

import { Driftstack, isRetryable } from '@driftstack/sdk';

for (let attempt = 0; attempt < 5; attempt++) {
  try {
    return await client.sessions.create({ archetype: '…' });
  } catch (err) {
    if (!isRetryable(err)) throw err;
    await sleepWithBackoff(attempt, err);
  }
}
from driftstack import Driftstack, is_retryable, RateLimitError

for attempt in range(5):
    try:
        return client.sessions.create(archetype="…")
    except Exception as err:
        if not is_retryable(err):
            raise
        wait = err.retry_after_seconds if isinstance(err, RateLimitError) else backoff(attempt)
        time.sleep(wait or backoff(attempt))
for attempt := 0; attempt < 5; attempt++ {
    sess, err := client.Sessions.Create(ctx, opts)
    if err == nil {
        return sess, nil
    }
    if !driftstack.IsRetryable(err) {
        return nil, err
    }
    var rl *driftstack.RateLimitError
    if errors.As(err, &rl) && rl.RetryAfterSeconds > 0 {
        time.Sleep(time.Duration(rl.RetryAfterSeconds) * time.Second)
    } else {
        time.Sleep(backoff(attempt))
    }
}

The built-in retry in each SDK does this automatically — see the SDK quickstarts for the no-config retry path. Use the isRetryable predicate when you have your own retry/circuit- breaker library and want to integrate Driftstack errors.

Why some 5xx aren’t retryable

DriverError (502) and DriverNotIntegrated (503) are not retryable because the underlying cause is structural (specific archetype unavailable, driver build issue) rather than transient. Retrying makes the same call against the same broken driver — no improvement. Surface the error to the user; the dashboard’s incident page (/trust/incidents) will reflect any active driver outage.

FeatureUnavailableError (503) means an endpoint requires infrastructure not configured in this deployment (e.g. avatar uploads when R2 isn’t wired). Retrying doesn’t change the deployment config; surface the error.

MfaStepUpRequiredError (403) means the customer needs to prove fresh MFA before the request will succeed. Retrying without an MFA prompt is the same as the first attempt; prompt the customer first.

Cross-references

  • API key scopes — when a request returns forbidden, the detail names the required scope.
  • Rate limits — when a request returns rate-limited, the Retry-After header carries the wait time.
  • SDK quickstarts — built-in retry configuration for each language.

Source of truth

The full problem-type list lives in packages/api-types/src/problem.ts (PROBLEM_TYPES). SDK error classes are mirrored at:

  • packages/sdk-typescript/src/errors.ts
  • packages/sdk-python/src/driftstack/errors.py
  • packages/sdk-go/errors.go

Any new problem-type lands via V-NNN slice that updates PROBLEM_TYPES, all three SDK error tables, this docs page, and the integration test that covers the new server-side route.