cURL to Ruby Converter (Net::HTTP and Faraday)

Paste the curl command from an API doc and read it back as Ruby - a Net::HTTP call that needs no gems, and the Faraday equivalent for the connection your Rails app already has.

All tools

Your curl command

Ruby for this request

Net::HTTP (standard library, no gems)

uri = URI("https://api.example.com/v1/posts")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"

request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer rb_live_9f2"
request.body = "{\"title\":\"Release notes\",\"published\":true}"

response = http.request(request)

Faraday

conn = Faraday.new(url: "https://api.example.com")

response = conn.post("/v1/posts") do |req|
  req.headers["Content-Type"] = "application/json"
  req.headers["Authorization"] = "Bearer rb_live_9f2"
  req.body = "{\"title\":\"Release notes\",\"published\":true}"
end

Everything runs in your browser. Nothing is uploaded.

What each curl flag becomes in Ruby

The conversion reads the flags an API doc usually ships and nothing else. -X sets the verb, which picks the request class - Net::HTTP::Get, Post, Put, Patch, Delete or Head - and the matching Faraday method; with no -X the verb is GET, or POST as soon as there is a body, exactly as curl decides it. Each -H becomes a request["Name"] = "value" line, or a req.headers["Name"] assignment inside the Faraday block, with the name and the value split on the first colon. Every -d, --data-raw and --data-binary is concatenated with an ampersand the way curl concatenates them, and the result is assigned to the body verbatim - no re-encoding, so a JSON payload stays the JSON you pasted. -u becomes request.basic_auth("user", "pass") and f.request :authorization, :basic on the Faraday connection, rather than a hand-built header. -b becomes a Cookie header, -I sets HEAD, and the flags that only change what curl prints - -s, -v, -i, --compressed - are dropped, because they describe a terminal rather than a request. Anything left over is listed under the snippets instead of being guessed at.

Net::HTTP or Faraday in a Rails app

Net::HTTP is in the standard library, so the first snippet runs in a fresh Rails app with nothing added to the Gemfile. That is the whole argument for it: one call to a webhook, a health probe, a script that runs once a night. The cost shows up on the second and third call - you are assigning headers by hand, checking the response class yourself, and parsing the body at every call site. Faraday earns its place when the calls become a client. A connection is built once, with a base URL, the auth and the default headers on it, and the per-request block only carries what actually changes. Middleware then moves the repetitive parts out of the call site - JSON request and response encoding, retries with backoff, redirects, instrumentation - and because Faraday keeps the adapter separate you can swap Net::HTTP for something else without touching the calls. In practice a Rails app tends to start with the first snippet and move to the second the day a second endpoint appears on the same host.

Redirects, timeouts and TLS in Ruby

Three defaults differ from curl's and all three bite in production. Net::HTTP does not follow redirects, so where curl -L quietly lands on the new URL, Ruby hands you a Net::HTTPRedirection and nothing else happens; Faraday behaves the same until faraday-follow_redirects is in the stack. Timeouts are the second: Net::HTTP will wait 60 seconds to open a connection and 60 more to read, which is long enough to hold a web request or a job worker hostage, so set http.open_timeout and http.read_timeout - or Faraday's options.timeout and options.open_timeout - to something a caller can live with. TLS is the third, and the one worth leaving alone: http.use_ssl is set from the scheme above, the certificate is verified against the system store, and curl -k has no place in a deployed app. If a call can be slow or can fail, it belongs in an Active Job with a retry policy rather than in a controller action, so a request that times out costs a queued job rather than a user's page.

Questions Rails developers ask about curl and Ruby

For a single call, Net::HTTP is enough and adds no dependency - it ships with Ruby, and the first snippet is the whole thing. Reach for Faraday when the calls turn into a client: a base URL and auth configured once, JSON encoding and decoding as middleware, retries and instrumentation in one place instead of at every call site. Faraday also keeps the adapter pluggable, so the calls do not change if the transport does. What you gain is not speed, it is that the second, third and tenth endpoint on the same host cost almost nothing to add.

Set it yourself, because both defaults are too generous. With Net::HTTP assign http.open_timeout and http.read_timeout in seconds before the request, and rescue Net::OpenTimeout and Net::ReadTimeout around it. With Faraday the same two live on the connection, as conn.options.timeout and conn.options.open_timeout, or in the request: Faraday.new(url: base, request: { timeout: 5, open_timeout: 2 }). curl's -m and --connect-timeout mean the same thing, but this page leaves them out of the snippet rather than guessing which of the two Ruby timeouts you meant.

The snippet assigns the body as a string, which is what curl sent and what the API expects, so a JSON payload only needs the Content-Type header that is already there; build it with JSON.generate or to_json when the body is a Hash rather than a pasted literal. On the way back, Net::HTTP gives you response.body as a string, so JSON.parse(response.body) is the second line every time. Faraday can do both for you: add the json request and response middleware to the connection and you pass a Hash to req.body and read response.body back as one.

Net::HTTP does not raise on a 4xx or a 5xx - it returns them, so a failed call looks like a successful one until you look. Check response.code, or the class, with response.is_a?(Net::HTTPSuccess) as the usual guard, and remember that a 301 or a 302 arrives here too because nothing followed it. Faraday returns a response object with status and success?, and raises on error statuses only when the raise_error middleware is in the stack, which is the shape most clients settle on. Either way, network failures are separate exceptions - Errno::ECONNREFUSED, the timeout classes, OpenSSL::SSL::SSLError - and they need rescuing whatever you do about status codes.

Not in a controller action if you can help it. An external call adds its latency, and its timeouts, to your own response, and one slow third party is enough to occupy every Puma thread. Put it behind an Active Job so a slow or failing call costs a retry rather than a request, wrap it in a small client object rather than calling Net::HTTP from a model, and keep the credentials in Rails credentials rather than in the string the snippet shows. In tests, stub the call at the HTTP boundary - WebMock or VCR - so the suite never depends on somebody else's uptime.

Start creating your next app now