PDF to EPUB Conversion in Ruby — toolkit.bot API Integration
This guide shows how to call the toolkit.bot REST API from Ruby to convert PDF files to EPUB. The API accepts a PDF via multipart upload, processes it asynchronously, and returns a download URL when the job is complete.
Net::HTTP — no dependencies
The standard library net/http handles multipart uploads with a bit of manual boundary construction:
require 'net/http'
require 'json'
API_BASE = 'https://toolkit.bot/api/v1'
API_KEY = ENV.fetch('TOOLKIT_API_KEY')
def convert_pdf_to_epub(pdf_path, output_path)
# Upload
uri = URI("#{API_BASE}/jobs")
boundary = "RubyBoundary#{Time.now.to_i}"
body = build_multipart(pdf_path, boundary)
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{API_KEY}"
req['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
req.body = body
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
raise "Upload failed: #{resp.body}" unless resp.is_a?(Net::HTTPSuccess)
job_id = JSON.parse(resp.body)['job_id']
puts "Job ID: #{job_id}"
# Poll
download_url = nil
60.times do
sleep 4
status_uri = URI("#{API_BASE}/jobs/#{job_id}")
status_req = Net::HTTP::Get.new(status_uri)
status_req['Authorization'] = "Bearer #{API_KEY}"
status_resp = Net::HTTP.start(status_uri.host, status_uri.port, use_ssl: true) do |h|
h.request(status_req)
end
data = JSON.parse(status_resp.body)
puts "Status: #{data['status']}"
if data['status'] == 'done'
download_url = data['download_url']
break
end
raise "Conversion failed: #{data.inspect}" if data['status'] == 'failed'
end
raise 'Timed out' unless download_url
# Download
dl_uri = URI(download_url)
dl_req = Net::HTTP::Get.new(dl_uri)
dl_req['Authorization'] = "Bearer #{API_KEY}"
epub = Net::HTTP.start(dl_uri.host, dl_uri.port, use_ssl: true) { |h| h.request(dl_req) }
File.binwrite(output_path, epub.body)
puts "Saved: #{output_path}"
end
def build_multipart(path, boundary)
filename = File.basename(path)
content = File.binread(path)
[
"--#{boundary}
",
"Content-Disposition: form-data; name="file"; filename="#{filename}"
",
"Content-Type: application/pdf
",
content,
"
--#{boundary}--
"
].join
end
convert_pdf_to_epub('document.pdf', 'document.epub')
Faraday example
If Faraday is already in your Gemfile, the multipart handling is much cleaner:
# Gemfile
gem 'faraday'
gem 'faraday-multipart'
require 'faraday'
require 'faraday/multipart'
require 'json'
API_BASE = 'https://toolkit.bot/api/v1'
API_KEY = ENV.fetch('TOOLKIT_API_KEY')
conn = Faraday.new(url: API_BASE) do |f|
f.request :multipart
f.request :url_encoded
f.adapter Faraday.default_adapter
end
# Upload
resp = conn.post('/jobs') do |req|
req.headers['Authorization'] = "Bearer #{API_KEY}"
req.body = { file: Faraday::UploadIO.new('document.pdf', 'application/pdf') }
end
job_id = JSON.parse(resp.body)['job_id']
puts "Job: #{job_id}"
# Poll
download_url = nil
60.times do
sleep 4
status = conn.get("/jobs/#{job_id}") do |req|
req.headers['Authorization'] = "Bearer #{API_KEY}"
end
data = JSON.parse(status.body)
break (download_url = data['download_url']) if data['status'] == 'done'
raise data.inspect if data['status'] == 'failed'
end
# Download
epub = Faraday.get(download_url, nil, 'Authorization' => "Bearer #{API_KEY}")
File.binwrite('document.epub', epub.body)
puts 'Done: document.epub'
Rails integration
In a Rails app, wrap the conversion in an ActiveJob to avoid blocking web requests:
# app/jobs/epub_conversion_job.rb
class EpubConversionJob < ApplicationJob
queue_as :default
def perform(document_id)
doc = Document.find(document_id)
conn = Faraday.new(url: 'https://toolkit.bot/api/v1') do |f|
f.request :multipart
f.adapter :net_http
end
# Upload
resp = conn.post('/jobs') do |req|
req.headers['Authorization'] = "Bearer #{ENV['TOOLKIT_API_KEY']}"
req.body = { file: Faraday::UploadIO.new(doc.pdf_path, 'application/pdf') }
end
job_id = JSON.parse(resp.body)['job_id']
# Poll (use retries via Sidekiq or manual loop)
60.times do
sleep 5
status_resp = conn.get("/jobs/#{job_id}") do |req|
req.headers['Authorization'] = "Bearer #{ENV['TOOLKIT_API_KEY']}"
end
data = JSON.parse(status_resp.body)
if data['status'] == 'done'
epub = Faraday.get(data['download_url'],
nil, 'Authorization' => "Bearer #{ENV['TOOLKIT_API_KEY']}")
doc.update!(epub_data: epub.body, epub_ready: true)
return
end
raise "failed" if data['status'] == 'failed'
end
end
end
Call it from a controller: EpubConversionJob.perform_later(document.id)
FAQ
Does this work with Ruby 2.x?
Yes. Net::HTTP and Faraday support Ruby 2.7+. The examples above use no Ruby 3-specific syntax.
How do I handle Faraday SSL errors?
Add f.ssl.verify = true in the connection block. If you see SSL errors in development, install the certifi gem or set ssl_verify_peer: false (never in production).
Is there a Ruby gem for toolkit.bot?
Not yet. The REST API is simple enough that the Faraday wrapper above is production-ready without a dedicated gem.
Can I use Typhoeus for parallel requests?
Yes. Use Typhoeus for parallel uploads in batch workflows — submit all jobs first, then poll them in a single loop. The API supports concurrent job submissions.
Sign up at toolkit.bot/api — free tier available, no credit card required.