Skip to content

Ruby SDK

A comprehensive Ruby client library for the Splinterpic API. Compatible with Ruby 2.7+ and works seamlessly with Rails, Sinatra, and plain Ruby applications.

Add to your Gemfile:

# If we had a published gem (future)
gem 'splinterpic'

Create the SDK class in your project:

require 'net/http'
require 'json'
require 'uri'
class SplinterpicClient
class Error < StandardError; end
class RateLimitError < Error; end
class InvalidPromptError < Error; end
class QuotaExceededError < Error; end
attr_reader :base_url, :default_model
def initialize(config = {})
@base_url = config[:base_url] or raise ArgumentError, 'base_url is required'
@default_model = config[:default_model] || '@cf/black-forest-labs/flux-1-schnell'
end
def generate(options = {})
data = {
prompt: options[:prompt] || raise(ArgumentError, 'prompt is required'),
model: options[:model] || @default_model
}
data[:template_id] = options[:template_id] if options[:template_id]
data[:collection_id] = options[:collection_id] if options[:collection_id]
request(:post, '/api/generate', data)
end
def images(filters = {})
request(:get, '/api/images', nil, filters)
end
def image(image_id)
request(:get, "/api/images/#{image_id}")
end
def delete_image(image_id)
request(:delete, "/api/images/#{image_id}")
end
def models
request(:get, '/api/models')
end
def create_collection(name, description = nil)
data = { name: name }
data[:description] = description if description
request(:post, '/api/collections', data)
end
def collections
request(:get, '/api/collections')
end
private
def request(method, path, body = nil, query = nil)
uri = URI.join(@base_url, path)
uri.query = URI.encode_www_form(query) if query
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
request = case method
when :get then Net::HTTP::Get.new(uri)
when :post then Net::HTTP::Post.new(uri)
when :delete then Net::HTTP::Delete.new(uri)
else raise ArgumentError, "Unsupported method: #{method}"
end
request['Content-Type'] = 'application/json'
request.body = body.to_json if body
response = http.request(request)
handle_response(response)
end
def handle_response(response)
body = JSON.parse(response.body, symbolize_names: true)
case response.code.to_i
when 200..299
body
when 400
raise InvalidPromptError, body[:error] || 'Bad request'
when 402
raise QuotaExceededError, body[:error] || 'Quota exceeded'
when 429
raise RateLimitError, body[:error] || 'Rate limit exceeded'
else
raise Error, body[:error] || "HTTP #{response.code}"
end
rescue JSON::ParserError
raise Error, "Invalid JSON response: #{response.body}"
end
end
require_relative 'splinterpic_client'
# Initialize the client
client = SplinterpicClient.new(
base_url: 'https://splinterpic-worker.your-name.workers.dev',
default_model: '@cf/black-forest-labs/flux-1-schnell'
)
# Generate an image
begin
image = client.generate(
prompt: 'A serene mountain landscape at sunset'
)
puts "Image generated successfully!"
puts "ID: #{image[:id]}"
puts "URL: #{image[:r2_url]}"
rescue SplinterpicClient::Error => e
puts "Error: #{e.message}"
end

Initializer (config/initializers/splinterpic.rb)

Section titled “Initializer (config/initializers/splinterpic.rb)”
require 'splinterpic_client'
Rails.configuration.splinterpic = SplinterpicClient.new(
base_url: ENV['SPLINTERPIC_BASE_URL'],
default_model: ENV.fetch('SPLINTERPIC_DEFAULT_MODEL', '@cf/black-forest-labs/flux-1-schnell')
)
Terminal window
SPLINTERPIC_BASE_URL=https://splinterpic-worker.your-name.workers.dev
SPLINTERPIC_DEFAULT_MODEL=@cf/black-forest-labs/flux-1-schnell
class ImagesController < ApplicationController
before_action :set_client
def create
@image = @client.generate(
prompt: params[:prompt],
model: params[:model]
)
render json: { success: true, image: @image }
rescue SplinterpicClient::Error => e
render json: { success: false, error: e.message }, status: :unprocessable_entity
end
def index
@images = @client.images(
limit: params[:limit] || 20,
offset: params[:offset] || 0
)
render json: @images
end
def show
@image = @client.image(params[:id])
render json: @image
rescue SplinterpicClient::Error => e
render json: { error: 'Image not found' }, status: :not_found
end
def destroy
@client.delete_image(params[:id])
head :no_content
rescue SplinterpicClient::Error => e
render json: { error: e.message }, status: :unprocessable_entity
end
private
def set_client
@client = Rails.configuration.splinterpic
end
end
class GenerateImageJob < ApplicationJob
queue_as :default
retry_on SplinterpicClient::RateLimitError, wait: 5.seconds, attempts: 3
retry_on SplinterpicClient::Error, wait: 10.seconds, attempts: 2
def perform(prompt, user_id, options = {})
client = Rails.configuration.splinterpic
image = client.generate(
prompt: prompt,
model: options[:model]
)
# Store in database
Image.create!(
user_id: user_id,
external_id: image[:id],
prompt: prompt,
url: image[:r2_url],
model: image[:model],
cost: image[:cost]
)
# Notify user
ImageGeneratedNotification.with(image: image).deliver_later(User.find(user_id))
end
end
# Usage
GenerateImageJob.perform_later('A sunset over mountains', current_user.id)
class Image < ApplicationRecord
belongs_to :user
validates :prompt, presence: true, length: { maximum: 500 }
validates :external_id, presence: true, uniqueness: true
scope :recent, -> { order(created_at: :desc) }
scope :by_model, ->(model) { where(model: model) }
def regenerate
client = Rails.configuration.splinterpic
new_image = client.generate(
prompt: self.prompt,
model: self.model
)
update!(
external_id: new_image[:id],
url: new_image[:r2_url],
cost: new_image[:cost]
)
end
def self.batch_generate(prompts, user)
prompts.map do |prompt|
GenerateImageJob.perform_later(prompt, user.id)
end
end
end
require 'sinatra'
require 'sinatra/json'
require_relative 'splinterpic_client'
# Initialize client
configure do
set :splinterpic, SplinterpicClient.new(
base_url: ENV['SPLINTERPIC_BASE_URL']
)
end
# Routes
post '/api/generate' do
begin
payload = JSON.parse(request.body.read, symbolize_names: true)
image = settings.splinterpic.generate(
prompt: payload[:prompt],
model: payload[:model]
)
json success: true, image: image
rescue SplinterpicClient::Error => e
status 422
json success: false, error: e.message
end
end
get '/api/images' do
begin
images = settings.splinterpic.images(
limit: params[:limit],
offset: params[:offset]
)
json images
rescue SplinterpicClient::Error => e
status 500
json error: e.message
end
end
get '/api/images/:id' do
begin
image = settings.splinterpic.image(params[:id])
json image
rescue SplinterpicClient::Error => e
status 404
json error: 'Image not found'
end
end
require 'concurrent'
class BatchImageGenerator
def initialize(client)
@client = client
end
def generate_batch(prompts, max_threads: 5)
pool = Concurrent::FixedThreadPool.new(max_threads)
futures = []
prompts.each do |prompt|
futures << Concurrent::Future.execute(executor: pool) do
begin
@client.generate(prompt: prompt)
rescue StandardError => e
{ error: e.message, prompt: prompt }
end
end
end
# Wait for all to complete
results = futures.map(&:value)
pool.shutdown
pool.wait_for_termination
results
end
end
# Usage
client = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])
generator = BatchImageGenerator.new(client)
prompts = [
'Mountain landscape at sunset',
'Ocean waves on beach',
'City skyline at night'
]
results = generator.generate_batch(prompts)
results.each do |result|
if result[:error]
puts "Failed: #{result[:prompt]} - #{result[:error]}"
else
puts "Generated: #{result[:id]}"
end
end
class CollectionManager
def initialize(client)
@client = client
end
def create_campaign(name, prompts)
# Create collection
collection = @client.create_collection(
name,
"Marketing campaign images"
)
# Generate images in collection
images = prompts.map do |prompt|
@client.generate(
prompt: prompt,
collection_id: collection[:id]
)
end
{
collection: collection,
images: images
}
end
def export_collection(collection_id, format: :json)
images = @client.images(collection_id: collection_id)
case format
when :json
images.to_json
when :csv
require 'csv'
CSV.generate do |csv|
csv << ['ID', 'Prompt', 'Model', 'URL', 'Created At']
images[:images].each do |img|
csv << [img[:id], img[:prompt], img[:model], img[:r2_url], img[:created_at]]
end
end
end
end
end
# Usage
manager = CollectionManager.new(client)
campaign = manager.create_campaign('Q1 Social Media', [
'Product showcase on white background',
'Lifestyle shot with product in use'
])
puts "Created campaign with #{campaign[:images].size} images"
# Export to CSV
csv_data = manager.export_collection(campaign[:collection][:id], format: :csv)
File.write('campaign_export.csv', csv_data)
class RateLimitedClient
def initialize(client, requests_per_minute: 60)
@client = client
@requests_per_minute = requests_per_minute
@request_times = []
@mutex = Mutex.new
end
def generate(options)
wait_if_rate_limited
@client.generate(options)
end
private
def wait_if_rate_limited
@mutex.synchronize do
now = Time.now
# Remove timestamps older than 1 minute
@request_times.reject! { |t| now - t > 60 }
if @request_times.size >= @requests_per_minute
sleep_time = 60 - (now - @request_times.first)
sleep(sleep_time) if sleep_time > 0
@request_times.clear
end
@request_times << now
end
end
end
# Usage
client = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])
rate_limited_client = RateLimitedClient.new(client, requests_per_minute: 30)
# This will automatically rate limit requests
100.times do |i|
image = rate_limited_client.generate(prompt: "Test image #{i}")
puts "Generated: #{image[:id]}"
end
class RetryableClient
def initialize(client, max_retries: 3)
@client = client
@max_retries = max_retries
end
def generate(options)
retries = 0
begin
@client.generate(options)
rescue SplinterpicClient::RateLimitError => e
retries += 1
if retries <= @max_retries
wait_time = 2 ** retries # Exponential backoff: 2, 4, 8 seconds
puts "Rate limited, retrying in #{wait_time} seconds..."
sleep(wait_time)
retry
else
raise
end
rescue SplinterpicClient::Error => e
retries += 1
if retries <= @max_retries && e.message.include?('timeout')
wait_time = 2 ** retries
puts "Request timeout, retrying in #{wait_time} seconds..."
sleep(wait_time)
retry
else
raise
end
end
end
end
# Usage
client = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])
retryable = RetryableClient.new(client, max_retries: 3)
image = retryable.generate(prompt: 'Test')
require 'redis'
require 'json'
class CachedSplinterpicClient
def initialize(client, redis_url: ENV['REDIS_URL'])
@client = client
@redis = Redis.new(url: redis_url)
end
def models
cached = @redis.get('splinterpic:models')
if cached
JSON.parse(cached, symbolize_names: true)
else
models = @client.models
@redis.setex('splinterpic:models', 3600, models.to_json) # Cache for 1 hour
models
end
end
def image(image_id)
cache_key = "splinterpic:image:#{image_id}"
cached = @redis.get(cache_key)
if cached
JSON.parse(cached, symbolize_names: true)
else
image = @client.image(image_id)
@redis.setex(cache_key, 300, image.to_json) # Cache for 5 minutes
image
end
end
def generate(options)
# Don't cache generation requests
@client.generate(options)
end
end
# Usage
client = SplinterpicClient.new(base_url: ENV['SPLINTERPIC_BASE_URL'])
cached_client = CachedSplinterpicClient.new(client)
# First call hits API
models = cached_client.models
# Subsequent calls use cache
models = cached_client.models # Fast!
require 'rspec'
require 'webmock/rspec'
RSpec.describe SplinterpicClient do
let(:base_url) { 'https://splinterpic-worker.test.workers.dev' }
let(:client) { described_class.new(base_url: base_url) }
describe '#generate' do
it 'generates an image successfully' do
stub_request(:post, "#{base_url}/api/generate")
.with(
body: {
prompt: 'Test prompt',
model: '@cf/black-forest-labs/flux-1-schnell'
}.to_json
)
.to_return(
status: 200,
body: {
id: 'img_123',
prompt: 'Test prompt',
r2_url: 'https://example.com/image.png'
}.to_json
)
result = client.generate(prompt: 'Test prompt')
expect(result[:id]).to eq('img_123')
expect(result[:prompt]).to eq('Test prompt')
end
it 'raises error on invalid prompt' do
stub_request(:post, "#{base_url}/api/generate")
.to_return(
status: 400,
body: { error: 'Invalid prompt' }.to_json
)
expect {
client.generate(prompt: '')
}.to raise_error(SplinterpicClient::InvalidPromptError)
end
end
describe '#models' do
it 'fetches available models' do
stub_request(:get, "#{base_url}/api/models")
.to_return(
status: 200,
body: {
models: [
{ id: 'model1', name: 'Model 1' }
]
}.to_json
)
result = client.models
expect(result[:models]).to be_an(Array)
expect(result[:models].size).to eq(1)
end
end
end
require 'minitest/autorun'
require 'webmock/minitest'
class SplinterpicClientTest < Minitest::Test
def setup
@base_url = 'https://splinterpic-worker.test.workers.dev'
@client = SplinterpicClient.new(base_url: @base_url)
end
def test_generate_success
stub_request(:post, "#{@base_url}/api/generate")
.to_return(
status: 200,
body: { id: 'img_123', prompt: 'Test' }.to_json
)
result = @client.generate(prompt: 'Test')
assert_equal 'img_123', result[:id]
end
def test_rate_limit_error
stub_request(:post, "#{@base_url}/api/generate")
.to_return(
status: 429,
body: { error: 'Rate limit exceeded' }.to_json
)
assert_raises(SplinterpicClient::RateLimitError) do
@client.generate(prompt: 'Test')
end
end
end
# Good
client = SplinterpicClient.new(
base_url: ENV.fetch('SPLINTERPIC_BASE_URL')
)
# Bad - Never hardcode URLs
client = SplinterpicClient.new(
base_url: 'https://splinterpic-worker.workers.dev'
)
begin
image = client.generate(prompt: prompt)
rescue SplinterpicClient::RateLimitError => e
# Wait and retry
sleep 5
retry
rescue SplinterpicClient::QuotaExceededError => e
# Notify admin
AdminMailer.quota_exceeded.deliver_now
raise
rescue SplinterpicClient::InvalidPromptError => e
# Show user-friendly message
flash[:error] = "Please provide a valid prompt"
rescue SplinterpicClient::Error => e
# Log and notify
Rails.logger.error "Splinterpic error: #{e.message}"
raise
end
# Good - non-blocking
GenerateImageJob.perform_later(prompt, user_id)
# Bad - blocks request
image = client.generate(prompt: prompt)
class SplinterpicClient
def initialize(config = {})
@logger = config[:logger] || Logger.new(STDOUT)
# ... rest of initialization
end
private
def request(method, path, body = nil, query = nil)
@logger.info "Splinterpic API: #{method.upcase} #{path}"
start_time = Time.now
response = # ... make request
duration = ((Time.now - start_time) * 1000).round
@logger.info "Splinterpic API: #{response.code} (#{duration}ms)"
handle_response(response)
end
end