Error handling
Handle errors, configure retries, and understand error codes.
The SDK uses a tuple-based error pattern inspired by Go's error
handling. Every request() call returns [error, null] on failure or
[null, data] on success — no try/catch needed for normal error flow.
The error tuple
const [error, data] = await account.request("GET /users/me", {})
if (error) {
// error is OfApiError
console.error(error.code) // "rate_limited" | "unauthorized" | ...
console.error(error.status) // HTTP status code
console.error(error.message) // Human-readable description
return
}
// data is fully typed — no null check needed
console.log(data.name)This pattern makes it impossible to accidentally use the response without checking for errors first.
Error codes
The code field on OfApiError is one of these values:
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | Invalid or missing API key |
forbidden | 403 | Valid key but insufficient access |
not_found | 404 | Route or resource doesn't exist |
rate_limited | 429 | Too many requests — retried automatically |
server_error | 5xx | Upstream server error — retried automatically |
network_error | — | Connection failed — retried automatically |
unknown | — | Unexpected error |
Automatic retries
GET requests are retried automatically on 429, 5xx, and network
errors. The SDK uses exponential backoff with jitter.
Default retry configuration:
Prop
Type
Override retries per-client or per-request:
const client = new OfApiClient({
apiKey: process.env.BFL_API_KEY,
retry: { retries: 5, baseDelay: 2000 },
})Only GET requests are retried by default. POST, PUT, and DELETE requests are not retried to avoid duplicate side effects.
Using fetch() with try/catch
If you prefer the throw pattern, use fetch() instead of request():
import { OfApiError } from "@betterfans/link-sdk"
try {
const me = await account.fetch("GET /users/me", {})
console.log(me.name)
} catch (error) {
if (error instanceof OfApiError) {
switch (error.code) {
case "rate_limited":
console.log("Rate limited, try again later")
break
case "unauthorized":
console.log("Check your API key")
break
default:
console.error("Request failed:", error.message)
}
}
}Checking error types
OfApiError is exported from the SDK for instanceof checks:
import { OfApiError } from "@betterfans/link-sdk"
const [error, data] = await account.request("GET /users/me", {})
if (error) {
if (error.status === 429) {
// Rate limited — back off
}
if (error.code === "not_found") {
// Resource doesn't exist
}
}Batch requests
Send multiple API requests in a single call with typed results.
WebSocket EventBus
Subscribe to real-time OnlyFans events — new messages, tips, subscribers, online status — across all managed accounts over a single WebSocket connection. Part of the BetterFans Link SDK, the infrastructure behind OFManager.