Ruby SDK
The official Ruby client for the FoPost API, with no runtime dependencies.
fopost is the official Ruby gem. It needs Ruby 3.1 or newer and has no runtime dependencies: it talks over net/http from the standard library, so it drops into any app without a version conflict.
bundle add fopostThis is a 0.x release. The surface is still settling and a minor version may break something. Pin an exact version if that matters to you.
Published on RubyGems. Source and issues: github.com/fopost/fopost-ruby. MIT licensed.
Quick start
require 'fopost'
client = Fopost.new(api_key: 'fp_...') # or set FOPOST_API_KEY
workspace = client.workspaces.list.first
accounts = client.accounts.list(workspace_id: workspace.id)
post = client.posts.create(
workspace_id: workspace.id,
content: 'Hello from Ruby',
accounts: accounts.map(&:id)
)
client.posts.publish(post.id)accounts takes account ids, the Fopost::SocialAccount objects themselves, or hashes with an id. publish returns once delivery is queued, not once it is live. Read client.posts.deliveries or subscribe to webhooks for the result.
Threads and scheduling
content takes a string for a single block, or an array for a thread:
client.posts.create(
workspace_id: workspace.id,
content: [
'First post in the thread',
{
'text' => 'Second one, with an image',
'media' => [{ 'type' => 'image', 'name' => 'chart.png', 'url' => 'https://.../chart.png' }]
}
],
accounts: accounts.map(&:id)
)status is "draft" or "scheduled", and a scheduled post needs schedule_at, which accepts a Time, a DateTime, or an ISO 8601 string. To send something out now, create it and call publish.
client.posts.create(
workspace_id: workspace.id,
status: 'scheduled',
schedule_at: Time.utc(2026, 9, 1, 10, 0),
content: 'Scheduled with the SDK',
accounts: [accounts.first.id]
)Posts
client.posts.get(post_id)
client.posts.update(post_id, title: 'Renamed') # partial: only what you pass is sent
client.posts.delete(post_id)
client.posts.publish(post_id) # queue delivery to every targeted account
client.posts.cancel(post_id) # cancel what has not gone out yet
client.posts.retry(post_id) # retry only the deliveries that failed
client.posts.preflight(post_id) # per-account blockers, without publishing
client.posts.deliveries(post_id).each { |d| puts "#{d.platform}: #{d.status}" }update is a partial update, so passing nil clears a field and leaving an argument out leaves it alone:
client.posts.update(post_id, schedule_at: nil) # sends {"schedule_at": null}
client.posts.update(post_id, title: 'Renamed') # sends {"title": "Renamed"}Pagination
posts.list returns one page, which is Enumerable over its items. posts.each walks every page for you.
page = client.posts.list(workspace_id: workspace.id, status: 'published', per_page: 50)
puts "#{page.meta.total} published posts"
# Every post, one page fetched at a time
client.posts.each(workspace_id: workspace.id) { |post| puts post.id }
# Or page by page, when you want the meta
client.posts.each_page(workspace_id: workspace.id) do |p|
puts "#{p.meta.current_page} (#{p.size} posts)"
endBoth walkers return an Enumerator when called without a block, so client.posts.each(...).lazy.first(10) works.
AI features
balance = client.ai.credits
puts "#{balance.credits_remaining} of #{balance.credits_total} credits left"
result = client.ai.generate_caption(
current_caption: 'shipping a new feature',
platforms: %w[twitter linkedin]
)
puts result.captionOnly credits and generate_caption work with an API key today. rewrite and
repurpose_url reach endpoints that require a dashboard session and will answer
401. Use the composer for those until a later release.
Configuration
Fopost.new(
api_key: 'fp_...', # or FOPOST_API_KEY
base_url: 'https://api.fopost.com/v1', # override for a dev server
timeout: 30.0, # seconds
max_retries: 3, # total attempts on a 429
transport: MyTransport.new # bring your own HTTP stack
)A 429 is retried automatically, waiting for the interval the API asks for in Retry-After (capped at 60 seconds). max_retries counts total attempts, so the default of 3 means two retries.
Anything the client does not wrap yet is still reachable, with the same auth, retries, and error handling:
client.request(:get, '/analytics/overview', params: { 'workspace_id' => workspace.id })
client.request(:post, '/webhooks', json: { 'url' => 'https://example.com/hook', 'events' => ['post.published'] })Errors
Every non-2xx response raises. All of them are rescuable as Fopost::Error.
| Status | Class |
|---|---|
| 400, 422 | Fopost::ValidationError |
| 401 | Fopost::AuthenticationError |
| 402 | Fopost::PaymentRequiredError |
| 403 | Fopost::PermissionDeniedError |
| 404 | Fopost::NotFoundError |
| 429 | Fopost::RateLimitError |
| anything else | Fopost::Error |
begin
client.posts.publish(post_id)
rescue Fopost::PaymentRequiredError => e
warn "Upgrade at #{e.upgrade_url}"
rescue Fopost::Error => e
warn "#{e.status} #{e.code}: #{e.message}"
ende.body holds the decoded response, and Fopost::ValidationError#errors carries per-field messages when the API sends them.
Testing your integration
The default transport is net/http. Anything that responds to call(method:, url:, headers:, body:) and returns a Fopost::HTTP::Response can replace it, which is useful for a shared connection pool, custom instrumentation, or stubbing the network in tests.
class LoggingTransport
include Fopost::HTTP::Transport
def initialize(inner) = @inner = inner
def call(method:, url:, headers:, body:)
warn "#{method} #{url}"
@inner.call(method: method, url: url, headers: headers, body: body)
end
end
client = Fopost.new(transport: LoggingTransport.new(Fopost::HTTP::NetHTTPTransport.new))Models
Responses come back as small model objects with snake_case readers. Fields the SDK does not model yet stay reachable, so a field added server-side never breaks an older client:
post.raw['someNewField'] # the decoded body, exactly as sent
post['some_new_field'] # by either spellingNext
Related documentation
- SDKs & Integrations
Official FoPost clients for TypeScript, Python, PHP, Ruby, Go, Rust, Java, .NET, Swift, Kotlin, Dart, and Elixir, framework integrations from Laravel to Next.js, and tooling for the CLI, CI, Terraform, and the automation platforms.
- SDKs Overview
Every official FoPost client, what it covers, and how to pick one.
- TypeScript SDK
The official TypeScript and Node.js client for the FoPost API.
- Python SDK
The official Python client for the FoPost API.
- PHP SDK
The official PHP client for the FoPost API, with no framework and no HTTP library.
- Go SDK
The official Go client for the FoPost API.
- Rust SDK
The official Rust client for the FoPost API, async and built on reqwest.
- Java SDK
The official Java client for the FoPost API, on the JDK's own HTTP client.