HTTP & Retry
Timeouts, automatic retries with exponential backoff, and network resilience.
MarzbanSDK is built on top of Axios and uses axios-retry to handle transient network failures automatically, without any extra code in your application.
Timeout
The default timeout is 30 000 ms (30 seconds) per request. Override it in the config:
const sdk = await createMarzbanSDK({
// ...
timeout: 10_000, // 10 seconds
})Pass timeout: 0 to disable the timeout entirely (wait forever). This is not recommended for production.
Automatic retries
When a request fails, the SDK retries it automatically using exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 s |
| 2nd retry | 2 s |
| 3rd retry | 4 s |
| 4th retry | 8 s |
| … | … (capped at 30 s) |
The default is 3 retries (4 total attempts). Change it:
const sdk = await createMarzbanSDK({
// ...
retries: 5, // 5 retries, 6 total attempts
})Set retries: 0 to disable retries:
const sdk = await createMarzbanSDK({
// ...
retries: 0,
})What gets retried
Only safe methods — GET, HEAD, OPTIONS — are retried, and only for:
- Network errors (e.g. a dropped connection with no response)
- 5xx and 429 responses
The method check always comes first: POST, PUT, PATCH, and DELETE are
never retried, not even on a network error — re-sending a request whose
outcome is unknown could duplicate a write (e.g. restartCore).
The one exception is the login request, which is retried on network errors since re-issuing a token has no side effects.
Other 4xx responses are never retried.
401 re-auth retry
The SDK has a separate, built-in mechanism for 401 responses:
- A
401 Unauthorizedresponse triggerssdk.authorize(). - After a fresh token is obtained, the original request is retried once with the new token.
- If re-auth fails, the error is propagated to the caller.
This happens transparently — your code never sees the 401.
WebSocket reconnects
WebSocket log streams don't use retries at all — they have their own reconnect policy: exponential backoff with jitter, bounded by a time budget (10 minutes by default) instead of an attempt count.
const stream = await sdk.logs.connectByCore({
onMessage: (data) => console.log(data),
onError: (error) => console.error('Stream ended', error.code, error.message),
})If the connection drops after opening, the SDK re-authenticates and reconnects automatically until it succeeds or the budget runs out. A failed first connect rejects connect*() immediately by default (reconnect.initial opts into retrying that too). See Reconnect & Stream Lifecycle for the full policy, including the config.reconnect SDK-wide default and per-call overrides.
Multiple SDK instances
Each MarzbanSDK instance has its own isolated Axios instance. Retry counters, tokens, and connections from one instance never affect another.
const sdkA = await createMarzbanSDK({ baseUrl: 'https://server-a.com', ... })
const sdkB = await createMarzbanSDK({ baseUrl: 'https://server-b.com', ... })
// Independent connections, tokens, and retry stateCleanup
Call sdk.destroy() to release everything the SDK owns — active WebSocket connections, webhook listeners, the stored token — and enter a terminal, idempotent shutdown state:
await sdk.destroy()This is particularly important in long-running processes or tests where you create multiple SDK instances. See Lifecycle & shutdown for the full destroy() contract, including which operations reject afterward.