OnlyFans API with Ruby
ofapis is a plain REST API with Bearer auth, so you can call the OnlyFans API from Ruby with the standard-library Net::HTTP — no gem required. If you already use Faraday in your app, a variant is shown at the end. Every request goes to https://api.ofapis.com/api/public/v1.
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 Ruby code only ever deals with your own ofapis_sk_... token.
1. Get a token
Create a key in the dashboard (start free) and read it from the environment rather than committing it. See authentication.
2. Setup
net/http and json ship with Ruby, so no install is needed. For a project, pin them in a Gemfile so bundle exec is reproducible:
# Gemfile
source "https://rubygems.org"
gem "json" # stdlib, but explicit for bundler
gem "faraday" # optional, only if you prefer Faraday
Then bundle install.
3. Verify the token — GET /me
A tiny wrapper keeps the Authorization header on every call and reuses one connection:
require "net/http"
require "json"
BASE = URI("https://api.ofapis.com/api/public/v1")
TOKEN = ENV.fetch("OFAPIS_KEY")
$http = Net::HTTP.new(BASE.host, BASE.port).tap { |h| h.use_ssl = true }
def call(req)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
$http.request(req)
end
res = call(Net::HTTP::Get.new("#{BASE.path}/me"))
raise "GET /me failed: #{res.code} #{res.body}" unless res.code == "200"
me = JSON.parse(res.body)
puts "connected as #{me["username"]}"
4. List chats
res = call(Net::HTTP::Get.new("#{BASE.path}/chats?limit=50"))
data = JSON.parse(res.body).fetch("data", [])
data.each { |c| puts c["id"] }
5. Send a message
req = Net::HTTP::Post.new("#{BASE.path}/chats/123/messages")
req.body = { text: "Thanks for subscribing!" }.to_json
res = call(req)
case res.code
when "200" then puts "delivered — 1 credit spent"
when "401" then abort "bad or revoked token"
when "429" then warn "rate limited — back off and retry"
else abort "send failed: #{res.code} #{res.body}"
end
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.
Faraday variant
If your codebase already uses Faraday, build a connection once and lean on raise_error:
require "faraday"
conn = Faraday.new(url: "https://api.ofapis.com/api/public/v1") do |f|
f.request :json
f.response :json
f.response :raise_error
f.headers["Authorization"] = "Bearer #{ENV.fetch("OFAPIS_KEY")}"
end
puts conn.get("/me").body["username"]
conn.post("/chats/123/messages", { text: "Hi!" })
raise_error turns any non-2xx into a Faraday::Error you can rescue — pair it with a retry on Faraday::ServerError and 429.
Agencies: scope by account
Managing several creators? Address each by id under scoped routes, e.g. call(Net::HTTP::Get.new("#{BASE.path}/accounts/42/chats")). See agency automation.
FAQ
Is there an official Ruby gem for the OnlyFans API?
No. ofapis is a standard REST API, so Net::HTTP or Faraday is enough. You can also generate a client from the OpenAPI spec linked in the docs.
How do I handle rate limits in Ruby?
Watch for HTTP 429 and retry after a short backoff. 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. Network errors, 429s and upstream 5xx responses are not charged.
Should I reuse the Net::HTTP connection?
Yes. Create one Net::HTTP (or one Faraday connection) and reuse it so TLS handshakes and sockets are pooled instead of reopened per request.
Next steps
- Messaging API reference
- How to send OnlyFans messages via API
- Other languages: Python · Node · PHP