ofapisv1
quickstart

OnlyFans API with Rust

Calling the OnlyFans API from Rust needs nothing exotic: ofapis is a plain REST API with Bearer auth, so reqwest on top of tokio is all you need — an async Client, serde for JSON, no generated bindings. Every request goes to https://api.ofapis.com/api/public/v1. This quickstart takes you from token to a sent message.

ofapis is an independent, unofficial API for OnlyFans, not affiliated with OnlyFans. Accounts are connected once in the dashboard and the session is kept alive server-side, so your Rust code only handles your ofapis_sk_... token.

1. Get a token

Create a key in the dashboard (start free) and read it from the environment via std::env::var, never a literal in source. See authentication.

2. Setup with cargo

# Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

3. Verify the token — GET /me

Build one Client with the Bearer header baked into its default headers, so every request carries it:

use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use serde::Deserialize;

const BASE: &str = "https://api.ofapis.com/api/public/v1";

#[derive(Deserialize)]
struct Me { username: String }

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let token = std::env::var("OFAPIS_KEY")?;

    let mut headers = HeaderMap::new();
    let mut auth = HeaderValue::from_str(&format!("Bearer {token}"))?;
    auth.set_sensitive(true);
    headers.insert(AUTHORIZATION, auth);

    let client = reqwest::Client::builder()
        .default_headers(headers)
        .build()?;

    let res = client.get(format!("{BASE}/me")).send().await?;
    if !res.status().is_success() {
        return Err(format!("GET /me failed: {} {}", res.status(), res.text().await?).into());
    }
    let me: Me = res.json().await?;
    println!("connected as {}", me.username);
    Ok(())
}

4. List chats

#[derive(Deserialize)]
struct Chat { id: String }
#[derive(Deserialize)]
struct Chats { data: Vec<Chat> }

let chats: Chats = client.get(format!("{BASE}/chats?limit=50"))
    .send().await?
    .json().await?;
for c in chats.data {
    println!("{}", c.id);
}

5. Send a message

use serde_json::json;

let res = client.post(format!("{BASE}/chats/123/messages"))
    .json(&json!({ "text": "Thanks for subscribing!" }))
    .send().await?;

match res.status().as_u16() {
    200 => println!("delivered — 1 credit spent"),
    401 => return Err("bad or revoked token".into()),
    429 => eprintln!("rate limited — back off and retry"),
    s   => return Err(format!("send failed: {s} {}", res.text().await?).into()),
}

A 200 means delivered and one credit spent. Failed calls — an expired session, a 429, or an upstream 5xx — are never charged, so retrying after a failure costs nothing.

Notes on the client

reqwest::Client is cheap to clone and holds a connection pool internally — build it once and share it (clone into tasks) rather than constructing one per request. Set .timeout(Duration::from_secs(10)) on the builder for production, and mark the auth header sensitive (shown above) so it is redacted from debug logs.

Agencies: scope by account

Managing several creators? Address each by id under scoped routes, e.g. client.get(format!("{BASE}/accounts/42/chats")). See agency automation.

FAQ

Do I need an SDK to call the OnlyFans API from Rust?

No. reqwest with serde covers everything. If you want typed models generated for you, run an OpenAPI generator against the spec linked in the docs.

How do I handle rate limits in Rust?

Match on HTTP 429 and retry after a short backoff — crates like backoff or tokio-retry help. Plan ceilings run from 60 rpm on Free to 500 rpm on Pro — see pricing.

Does a failed request cost a credit?

No. Billing is metered per successful call: one 2xx equals one credit. Connection errors, 429s and upstream 5xx responses are not charged.

Should I reuse the reqwest Client?

Yes. It wraps a connection pool, so clone one shared Client across tasks instead of building a new one per request to reuse sockets and TLS sessions.

Next steps

Related guides

All guides
Start free — 250 credits, no card
Generate a token and make your first call in minutes.
Get started