HTTP Client
Making requests
Use the typed request methods to call any OnlyFans API route.
The SDK provides two patterns for making requests: a tuple pattern
that returns [error, data] for explicit error handling, and a
throw pattern that throws on failure.
The tuple pattern: request()
request() returns a tuple of [error, null] or [null, data]. This
makes error handling explicit without try/catch:
const account = client.for("123456789")
const [error, data] = await account.request("GET /users/me", {})
if (error) {
console.error(error.code, error.status, error.message)
} else {
console.log(data.name) // fully typed as UserFull
}The first argument is always a route string in the format
"METHOD /path". TypeScript narrows the parameters and response
based on this string.
The throw pattern: fetch()
If you prefer exceptions, fetch() has the same signature but throws
an OfApiError on failure:
try {
const me = await client.fetch("GET /users/me", {
auth: { onlyfansUserId: "123456789" },
})
console.log(me.name)
} catch (error) {
if (error instanceof OfApiError) {
console.error(error.code, error.status)
}
}Verb shortcuts
For ergonomics, the client exposes .GET(), .POST(), .PUT(), and
.DELETE() methods. These are sugar over request() with the verb
extracted from the path:
const [error, chats] = await client.GET("/chats", {
auth: { onlyfansUserId: "123456789" },
query: { limit: 20 },
})const [error, message] = await client.POST("/chats/:id/messages", {
auth: { onlyfansUserId: "123456789" },
pathParams: { id: "98765" },
body: { text: markdownToHtml("Hello!") },
})const [error, updated] = await client.PUT("/messages/queue/:id", {
auth: { onlyfansUserId: "123456789" },
pathParams: { id: "55555" },
body: { text: markdownToHtml("Updated message content") },
})const [error] = await client.DELETE("/messages/queue/:id", {
auth: { onlyfansUserId: "123456789" },
pathParams: { id: "55555" },
})Request options
Every request accepts these options:
| Option | Type | Description |
|---|---|---|
auth | { onlyfansUserId: string } | The OnlyFans account to act as. Required unless using a scoped client. |
query | RouteQuery<R> | Query parameters. Shape varies by route. TypeScript enforces the correct fields. |
pathParams | RoutePathParams<R> | Path parameters for routes with :id segments. TypeScript requires all params. |
body | RouteBody<R> | Request body for POST/PUT/PATCH routes. |
Route string format
Routes follow the pattern "VERB /path" exactly as they appear in the
OnlyFans API. Path parameters use :name syntax:
// No parameters
"GET /users/me"
// Path parameter
"GET /users/:id"
"DELETE /messages/:id"
// Multiple path parameters
"POST /lists/:id/users/:id"TypeScript infers the required pathParams, query, and body from
the route string. If you misspell a route, you get a compile error,
not a runtime 404.
Type inference
You can extract input and output types for any route using the
ApiRoutes interface:
import type { ApiRoutes } from "@betterfans/link-sdk"
type ChatsQuery = ApiRoutes["GET /chats"]["query"]
// { limit?: number; offset?: number; order?: string; ... }
type ChatsResponse = ApiRoutes["GET /chats"]["response"]
// { list: ChatListItem[]; hasMore: boolean; ... }
type SendBody = ApiRoutes["POST /chats/:id/messages"]["body"]
// { text?: string; mediaFiles?: ...; price?: number; ... }This is useful for building typed wrappers or passing data between functions without losing type information.