MarzbanSDK
Advanced

Error Handling

Handle failures with MarzbanSDK's typed error hierarchy — every error extends `SdkError` with a machine-readable `code` and a matching type guard.

MarzbanSDK uses a typed error hierarchy. Every error extends , which extends Error — so standard try/catch works as expected. On top of that, each error class has a code property and a matching type guard for safe narrowing.

Error hierarchy

Error
└── SdkError
    ├── AuthError            (code: AUTH_FAILED)
    │   └── AuthTokenError   (code: AUTH_TOKEN_FAILED)
    ├── ConfigurationError   (code: CONFIG_INVALID)
    ├── HttpError            (code: NETWORK_HTTP_ERROR)
    └── WebhookError
        ├── WebhookSignatureError     (code: WEBHOOK_SIGNATURE_ERROR)
        ├── WebhookValidationError    (code: WEBHOOK_VALIDATION_ERROR)
        └── WebhookEnvironmentError  (code: WEBHOOK_ENVIRONMENT_ERROR)

SdkError base class

All SDK errors have these properties:

class SdkError extends Error {
  code: string      // machine-readable error code
  details?: unknown // extra context (Zod issues, original error, etc.)

  toJSON(): {
    name: string
    code: string
    message: string
    details: unknown
  }
}

Error codes reference

CodeClassWhen thrown
AUTH_FAILEDLogin request failed (wrong credentials, network error)
AUTH_TOKEN_FAILEDServer responded but returned no access_token
CONFIG_INVALIDConfig fails Zod schema validation
NETWORK_HTTP_ERRORHTTP request failed (4xx, 5xx, network timeout)
WEBHOOK_SIGNATURE_ERRORMissing signature or HMAC mismatch
WEBHOOK_VALIDATION_ERRORWebhook payload doesn't match expected schema
WEBHOOK_ENVIRONMENT_ERRORSignature verification called in a browser context

Secret redaction

details is safe to log or send to an external service as-is — passwords, tokens, Authorization headers, cookies and similar fields are stripped to [REDACTED] before the error is even constructed, wherever they appear in the object graph (including inside an already-JSON.stringify'd request body, e.g. a failed login's payload). The same redaction applies to whatever a caught error's trace prints through the built-in logger, so a raw secret never reaches your terminal or log files either.

try {
  await createMarzbanSDK({ baseUrl: '...', username: 'admin', password: 'hunter2' })
} catch (err) {
  if (isAuthError(err)) {
    console.error(err.details)
    // password/token fields inside are already "[REDACTED]" — safe to log
  }
}

This happens automatically — there's nothing to opt into, and no redaction helper to import.

Type guards

Import the guards to narrow errors without instanceof:

import {
  isAuthError,
  isAuthTokenError,
  isConfigurationError,
  isHttpError,
  isWebhookSignatureError,
  isWebhookValidationError,
  isWebhookEnvironmentError,
} from 'marzban-sdk'

Usage examples

Handling auth errors

import { createMarzbanSDK, isAuthError, isAuthTokenError } from 'marzban-sdk'

try {
  const sdk = await createMarzbanSDK({
    baseUrl: 'https://vpn.example.com',
    username: 'admin',
    password: 'wrong-password',
  })
} catch (err) {
  if (isAuthTokenError(err)) {
    // Server returned 200 but body had no token
    console.error('Token not found in response')
  } else if (isAuthError(err)) {
    // Covers AuthTokenError too — check specific first
    console.error('Authentication failed:', err.message)
  }
}

Handling HTTP errors

exposes the underlying HTTP response through typed getters, instead of reaching into .details (which carries the raw, redacted Axios error) yourself:

import { isHttpError } from 'marzban-sdk'

try {
  const user = await sdk.user.getUser('non-existent')
} catch (err) {
  if (isHttpError(err)) {
    console.error('HTTP status:', err.status)       // 404, or undefined if the request never got a response
    console.error('Status text:', err.statusText)    // "Not Found"
    console.error('Response body:', err.data)        // whatever the server returned
    console.error('Request:', err.method, err.url)    // "GET", "/api/user/non-existent"
  }
}

status/statusText/data/method/url are all undefined when there was no response to read them from — a network failure, a timeout, or a DNS error never reaches the server, so there's no status code to report. Check for undefined rather than assuming a response always exists.

Handling config errors

import { createMarzbanSDK, isConfigurationError } from 'marzban-sdk'

try {
  const sdk = await createMarzbanSDK({
    baseUrl: 'not-a-valid-url',
    username: '',
    password: 'secret',
  })
} catch (err) {
  if (isConfigurationError(err)) {
    console.error('Bad config:', err.message)
    console.error('Zod issues:', err.details)
  }
}

Catch-all with toJSON

import { SdkError } from 'marzban-sdk'

try {
  await sdk.user.getUsers()
} catch (err) {
  if (err instanceof SdkError) {
    console.error(JSON.stringify(err.toJSON(), null, 2))
    // {
    //   "name": "HttpError",
    //   "code": "NETWORK_HTTP_ERROR",
    //   "message": "HTTP request failed",
    //   "details": { ... }
    // }
  }
}

Best practices

  • Check specific subtypes before the parentisAuthTokenError before isAuthError, since extends .
  • Always re-throw unknown errors — only catch what you can handle; let the rest propagate.
  • Use toJSON() when logging errors to structured log systems — it serializes the full error context.
  • Webhook errors are server-side only is thrown if signature verification is called in a browser. Move webhook handling to a server route.

On this page