# Ruby Social Media API SDK

The official Ruby SDK. Ruby 3.0+, zero runtime dependencies (Net::HTTP, OpenSSL,
JSON), plain-Hash responses.

## Install

```bash
gem install omnisocials
```

Or in your Gemfile:

```ruby
gem "omnisocials"
```

## Authentication

```ruby
require "omnisocials"

client = OmniSocials::Client.new(api_key: "omsk_live_...")
# or: export OMNISOCIALS_API_KEY=omsk_live_... and just OmniSocials::Client.new
```

## Create a post

```ruby
post = client.posts.create(
  content: "Big announcement coming Friday",
  channels: ["instagram", "facebook", "linkedin"],
  scheduled_at: "2026-08-01T09:00:00Z",
  media_urls: ["https://example.com/teaser.jpg"]
)
```

Any `media_urls` (or `media_ids`) entry can also be a Hash carrying per-media
alt text (max 1500 chars), delivered to Mastodon, Bluesky, X (photos/GIFs), and
Pinterest:

```ruby
media_urls: [{ "url" => "https://example.com/teaser.jpg", "alt" => "Red sneaker on a white background" }]
```

Publish immediately:

```ruby
post = client.posts.create_and_publish(
  content: "We are live!",
  channels: ["x", "bluesky"]
)
```

## Upload media

```ruby
media = client.media.upload_from_url(
  url: "https://example.com/promo.mp4",
  name: "promo-august",
  folder: "Campaigns"
)
media_id = media["data"]["id"]
```

Local files: `client.media.upload(file: "/path/to/image.jpg", name: "hero-shot")`
(`file` accepts a path String/Pathname, an IO, or a raw binary String). Base64:
`client.media.upload_from_base64(data: b64_string, mime_type: "image/png")`.

## Analytics

```ruby
# One post
stats = client.analytics.post("123")

# Up to 100 posts in one request (avoids rate limit trouble when syncing)
bulk = client.analytics.posts(["123", "124", "125"])

# Workspace overview
overview = client.analytics.overview(period: "30d")

# Account-level stats (followers etc.)
accounts = client.analytics.accounts(platform: "instagram")
```

## Verify webhooks

Rails controller:

```ruby
class OmnisocialsWebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    payload = request.body.read  # raw body, exactly as received
    signature = request.headers["X-OmniSocials-Signature"]

    begin
      event = OmniSocials::Webhooks.verify(
        payload: payload,
        signature: signature,
        secret: ENV.fetch("OMNISOCIALS_WEBHOOK_SECRET"),
        tolerance: 300
      )
    rescue OmniSocials::WebhookVerificationError
      return head :bad_request
    end

    if event["type"] == "post.published"
      event["data"]["targets"].each do |target|
        Rails.logger.info "#{target["platform"]} #{target["status"]} #{target["native_post_id"]}"
      end
    end

    head :ok
  end
end
```

## Errors

The base class is `OmniSocials::Error`. `OmniSocials::APIError` covers HTTP errors
with subclasses `AuthenticationError` (401), `PermissionDeniedError` (403),
`NotFoundError` (404), `ValidationError` (400/422), `RateLimitError` (429, with
`#retry_after`), and `ServerError` (5xx), plus `APIConnectionError` and
`WebhookVerificationError`. Each carries `status`, `code`, `message`, and `body`.

```ruby
begin
  client.posts.get("does-not-exist")
rescue OmniSocials::NotFoundError => e
  puts "Gone: #{e.code} #{e.message}"
rescue OmniSocials::RateLimitError => e
  puts "Slow down, retry in #{e.retry_after}s"
rescue OmniSocials::ValidationError => e
  puts "Bad request: #{e.body}"
rescue OmniSocials::AuthenticationError
  puts "Check your API key"
rescue OmniSocials::APIConnectionError
  puts "Network problem"
rescue OmniSocials::APIError => e
  puts "API error: #{e.status} #{e.message}"
end
```

## Configuration

```ruby
client = OmniSocials::Client.new(
  api_key: "omsk_live_...",
  timeout: 60,        # seconds
  max_retries: 5,     # retries after the first attempt
  base_url: "https://api.omnisocials.com/v1"  # override for testing
)
```

## Notes

- A fresh connection is used per request; there is nothing to close
- All responses are plain Hashes with string keys
- Omitting `folder_id`/`parent_id` leaves a value unchanged; passing `nil` explicitly moves to root/top level
- Source: [github.com/OmniSocials/omnisocials-ruby](https://github.com/OmniSocials/omnisocials-ruby)
