← Blog

Claude API Ruby SDK Integration Tutorial

2026-10-22 · 5 min read · SubToAPI Team

Is there an official Claude API Ruby SDK?

No — Anthropic does not maintain an official Ruby SDK. The official SDKs are Python and TypeScript. If you're building with Ruby (Rails, Sinatra, a background job system, a CLI tool), you integrate directly against the REST API using Net::HTTP, Faraday, or HTTParty, or you use one of the community-maintained gems that wrap the same endpoints.

This isn't actually a limitation. Claude's API is a plain JSON-over-HTTPS interface, so a Ruby client is maybe 30 lines of code. This tutorial walks through building that client from scratch, handling streaming and tool calls, and then shows a faster path if you'd rather not maintain auth and retry logic yourself.

Setting up authentication

You'll need an API key from the Anthropic console. Store it as an environment variable, never in source control:

export ANTHROPIC_API_KEY="sk-ant-..."

Every request needs three headers: x-api-key, anthropic-version, and content-type: application/json.

A minimal Ruby client with Net::HTTP

No gems required — the standard library is enough for basic usage:

require 'net/http'
require 'json'
require 'uri'

def claude_message(prompt, model: "claude-3-5-sonnet-20241022")
  uri = URI("https://api.anthropic.com/v1/messages")
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request["x-api-key"] = ENV["ANTHROPIC_API_KEY"]
  request["anthropic-version"] = "2023-06-01"
  request["content-type"] = "application/json"

  request.body = {
    model: model,
    max_tokens: 1024,
    messages: [{ role: "user", content: prompt }]
  }.to_json

  response = http.request(request)
  JSON.parse(response.body)
end

result = claude_message("Explain Ruby's garbage collector in two sentences.")
puts result.dig("content", 0, "text")

This works fine for scripts and small services. For anything production-grade, you'll want retry logic, connection pooling, and better error handling — which is where Faraday helps.

A cleaner client with Faraday

require 'faraday'
require 'json'

class ClaudeClient
  BASE_URL = "https://api.anthropic.com/v1"

  def initialize(api_key: ENV["ANTHROPIC_API_KEY"])
    @conn = Faraday.new(url: BASE_URL) do |f|
      f.request :json
      f.response :json
      f.adapter Faraday.default_adapter
    end
    @api_key = api_key
  end

  def messages(model:, messages:, max_tokens: 1024, system: nil)
    body = { model: model, max_tokens: max_tokens, messages: messages }
    body[:system] = system if system

    response = @conn.post("/messages") do |req|
      req.headers["x-api-key"] = @api_key
      req.headers["anthropic-version"] = "2023-06-01"
      req.body = body
    end

    raise "Claude API error #{response.status}: #{response.body}" unless response.success?
    response.body
  end
end

client = ClaudeClient.new
result = client.messages(
  model: "claude-3-5-sonnet-20241022",
  messages: [{ role: "user", content: "Summarize this commit message: fix null pointer in cache eviction" }]
)
puts result.dig("content", 0, "text")

This gives you a reusable class you can inject into Rails services, Sidekiq jobs, or Rake tasks.

Handling streaming responses

Claude supports server-sent events for streaming. With Net::HTTP, you read the response body incrementally rather than waiting for the full payload:

require 'net/http'
require 'json'

uri = URI("https://api.anthropic.com/v1/messages")
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  request = Net::HTTP::Post.new(uri)
  request["x-api-key"] = ENV["ANTHROPIC_API_KEY"]
  request["anthropic-version"] = "2023-06-01"
  request["content-type"] = "application/json"
  request.body = {
    model: "claude-3-5-sonnet-20241022",
    max_tokens: 512,
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about Ruby threads." }]
  }.to_json

  http.request(request) do |response|
    response.read_body do |chunk|
      chunk.each_line do |line|
        next unless line.start_with?("data:")
        data = line.sub("data:", "").strip
        next if data == "[DONE]"
        parsed = JSON.parse(data) rescue nil
        delta = parsed&.dig("delta", "text")
        print delta if delta
      end
    end
  end
end

This pattern is a bit fiddly to get right across SSE chunk boundaries — partial JSON lines can get split across TCP packets. If you're building a user-facing streaming UI, test this under real network conditions, not just localhost.

Tool use (function calling) in Ruby

Tool definitions are just JSON schema objects in the request body:

tools = [
  {
    name: "get_weather",
    description: "Get current weather for a city",
    input_schema: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"]
    }
  }
]

result = client.messages(
  model: "claude-3-5-sonnet-20241022",
  messages: [{ role: "user", content: "What's the weather in Lisbon?" }],
  max_tokens: 512
)

(Pass tools: through your messages method the same way you'd pass system:.) When Claude wants to call a tool, the response content includes a tool_use block with the tool name and parsed input — your Ruby code executes the actual function, then sends the result back in a follow-up message with role: "tool_result".

Where a raw REST integration starts to hurt

Building your own Ruby client is fine for prototypes. It gets more work once you need:

SubToAPI sits in front of your existing Claude access and gives you issuable sub_live_... application keys, usage metadata per key, and streaming and tool support on the same request shapes shown above — you just point your Ruby client at https://api.subtoapi.app/v1/messages instead:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-3-5-sonnet-20241022",
    "max_tokens": 512,
    "messages": [{"role": "user", "content": "Summarize this PR description."}]
  }'

Your Faraday or Net::HTTP client needs no structural changes — just the base URL, the header name, and the key format. Check the quickstart for the exact request/response shape, and /docs/streaming and /docs/tools for Ruby-compatible streaming and tool-call examples. Plans start at Solo €9/month, with Team and Scale tiers for shared keys and seat management — see /pricing, or /signup to get a key and test it against your existing Ruby code in a few minutes.

Questions

Does Anthropic plan to release an official Ruby SDK? There's no announced timeline. The REST API is stable and well-documented, so most Ruby teams build a thin wrapper (as shown above) rather than wait.

Should I use a community Ruby gem instead of writing my own client? Community gems can save time, but check maintenance status and version pinning before relying on one in production — a 40-line Faraday wrapper you control is often safer than an unmaintained gem.

Can I use this same Ruby code with SubToAPI instead of Anthropic directly? Yes. Swap the base URL to https://api.subtoapi.app/v1 and the header to Authorization: Bearer $SUBTOAPI_KEY — the request and response JSON structure for messages, streaming, and tools stays the same, see /docs/messages.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →