Anthropic Claude API Ruby Integration Example
Anthropic doesn't publish an official Ruby SDK for Claude, which is the main reason developers search for a working Ruby integration example. The good news is that the Claude API is a plain HTTPS JSON API, so integrating it in Ruby takes about 20 lines of code using the standard library or a lightweight HTTP client like Faraday. There's no need to wait for an official gem or wrap a Python client — you're just POSTing JSON to an endpoint and reading a JSON response.
This guide walks through a real, runnable Ruby integration: authentication, a basic message request, streaming responses, error handling, and where a proxy layer like SubToAPI fits in if you want a simpler key and billing model without changing your Ruby code.
Setting Up Your Environment
You need three things: a Ruby runtime (2.7+ is fine), an HTTP client, and an API key. For the HTTP client, net/http from the standard library works without any dependencies, but most teams prefer faraday or httparty for cleaner syntax. This guide shows both.
gem install faraday
Store your key as an environment variable rather than hardcoding it:
export ANTHROPIC_API_KEY="sk-ant-xxxxxxxx"
Basic Request with net/http
Here's a minimal, dependency-free integration using the standard library:
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.anthropic.com/v1/messages')
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-sonnet-4-5',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Explain what a Ruby module is in two sentences.' }
]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
puts result.dig('content', 0, 'text')
This is the entire integration. The key parts are the two required headers (x-api-key and anthropic-version), the JSON body shape (model, max_tokens, messages), and parsing the response — Claude returns content as an array of blocks, so you index into content[0]['text'] to get the message.
Cleaner Syntax with Faraday
If you're already using Faraday elsewhere in your app, the same call looks like this:
require 'faraday'
require 'json'
conn = Faraday.new(url: 'https://api.anthropic.com') do |f|
f.request :json
f.response :json
f.adapter Faraday.default_adapter
end
response = conn.post('/v1/messages') do |req|
req.headers['x-api-key'] = ENV['ANTHROPIC_API_KEY']
req.headers['anthropic-version'] = '2023-06-01'
req.body = {
model: 'claude-sonnet-4-5',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Summarize the plot of Hamlet in one sentence.' }]
}
end
puts response.body.dig('content', 0, 'text')
Faraday's f.request :json and f.response :json middleware handle serialization automatically, which removes the manual JSON.parse and .to_json calls from the net/http version.
Streaming Responses in Ruby
Streaming is where a Ruby integration gets more involved, because you need to read Server-Sent Events off the socket incrementally rather than waiting for the full response body. With net/http, you can do this by reading the response body in chunks:
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.anthropic.com/v1/messages')
Net::HTTP.start(uri.hostname, 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-sonnet-4-5',
max_tokens: 1024,
stream: true,
messages: [{ role: 'user', content: 'Write a short poem about Ruby.' }]
}.to_json
http.request(request) do |response|
response.read_body do |chunk|
chunk.each_line do |line|
next unless line.start_with?('data:')
payload = line.sub('data:', '').strip
next if payload.empty? || payload == '[DONE]'
event = JSON.parse(payload) rescue next
if event['type'] == 'content_block_delta'
print event.dig('delta', 'text')
end
end
end
end
end
This parses each SSE line, skips keep-alive noise, and prints text as it arrives — the same pattern you'd use for any streaming LLM API in Ruby.
Error Handling
Claude's API returns standard HTTP status codes: 401 for bad keys, 429 for rate limits, 400 for malformed requests, and 500/529 for server-side issues. A production integration should check the status code before parsing the body:
case response.code.to_i
when 200
JSON.parse(response.body)
when 401
raise 'Invalid API key'
when 429
raise 'Rate limited — back off and retry'
when 500..599
raise 'Server error — retry with backoff'
else
raise "Unexpected response: #{response.code} #{response.body}"
end
Wrap retries around 429 and 5xx responses with exponential backoff, and log the request-id header from Anthropic's response if you need to reference a specific call in support tickets.
Simplifying the Integration with SubToAPI
If you're building this integration to serve an app, dashboard, or internal tool, you may run into two friction points that aren't about Ruby at all: giving each service or teammate its own scoped key, and getting usage numbers per key without building your own logging layer. SubToAPI sits in front of Claude and issues sub_live_... application keys per project or team member, with usage metadata included in every response — the request shape and response format stay identical, so none of the Ruby code above changes except the base URL and key.
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello from Ruby"}]
}'
The quickstart and messages endpoint docs mirror the Anthropic API closely enough that you can point your existing Faraday or net/http client at SubToAPI with a one-line change. Plans start at €9/month on the Solo tier, with team seats on the Team and Scale plans — see pricing or start a free trial at signup.
questions
Is there an official Ruby gem for the Claude API? No. Anthropic maintains official SDKs for Python and TypeScript, but not Ruby. Ruby developers integrate directly over HTTP using net/http, faraday, or httparty, which is straightforward since the API is plain JSON over HTTPS.
What's the minimum code needed to call Claude from Ruby? About 15–20 lines: build a POST request to /v1/messages with x-api-key and anthropic-version headers, a JSON body containing model, max_tokens, and messages, then parse the JSON response and read content[0]['text'].
How do I handle streaming responses in Ruby without a dedicated SSE library? Use Net::HTTP with read_body to consume the response as it arrives, splitting on newlines, filtering lines starting with data:, and parsing each JSON payload individually — no external streaming gem is required.