n4nAI

Switching a Rails app from OpenAI to n4n

Step-by-step guide to switch a Rails app from OpenAI to n4n: repoint the API base, map models, and verify with tests. Includes runnable Ruby and curl snippets.

n4n Team3 min read665 words

Audio narration

Coming soon — every post will get a voice note here.

Most Rails apps talk to OpenAI through the ruby-openai gem or a hand-rolled HTTP client. To switch rails app openai to n4n, you repoint the base URL, swap your API key, and reconsider model identifiers—the gateway speaks the OpenAI chat completions shape, so the migration is mostly mechanical. The payoff is broader model access and gateway-level fallback without rewriting response handling.

Step 1: Inventory your OpenAI calls

Before changing anything, find every place that constructs a client or hits the OpenAI host.

grep -rn "OpenAI::Client" app/ lib/ config/
grep -rn "api.openai.com" app/ lib/ config/ || true

A typical Rails service looks like this:

class Summarizer
  def self.run(text)
    client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])
    client.chat(parameters: {
      model: "gpt-4o",
      messages: [{ role: "user", content: "Summarize: #{text}" }]
    })
  end
end

If you see OpenAI::Client.new scattered across controllers, stop. The first move is to centralize the client so the switch rails app openai to n4n touches one file, not twenty.

Step 2: Extract a single client factory

Create app/services/llm_client.rb and route all traffic through it:

class LlmClient
  def self.instance
    @instance ||= OpenAI::Client.new
  end
end

Replace direct instantiations:

# before
client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"])
# after
client = LlmClient.instance

This isolation pays off when you later change configuration or add request instrumentation.

Step 3: Store credentials outside code

Use Rails encrypted credentials or environment variables. I prefer ENV for CI parity and twelve-factor discipline.

export N4N_API_KEY="sk-..."
export OPENAI_API_KEY="sk-..." # keep until cutover verified

For encrypted credentials, edit the file:

rails credentials:edit --environment production

Add:

n4n:
  api_key: <%= ENV["N4N_API_KEY"] %>

Reference it in code with Rails.application.credentials.n4n[:api_key]. Never hardcode keys in initializers.

Step 4: Repoint the client to the n4n endpoint

The ruby-openai gem accepts a uri_base configuration. n4n.ai exposes one OpenAI-compatible endpoint that addresses 240+ models, so you only change the host and token.

# config/initializers/openai.rb
OpenAI.configure do |config|
  config.access_token = Rails.application.credentials.n4n[:api_key]
  config.uri_base    = "https://api.n4n.ai/v1"
  config.request_timeout = 30
end

If you used a custom Faraday middleware instead of the gem, set the connection URL to the same base. No need to rewrite request serialization—the JSON contract matches OpenAI’s /v1/chat/completions.

Step 5: Map model identifiers and routing directives

OpenAI model strings like gpt-4o become provider-qualified names on the gateway. The switch rails app openai to n4n lets you pin a specific backend or let the gateway route.

response = LlmClient.instance.chat(parameters: {
  model: "openai/gpt-4o",
  messages: [{ role: "user", content: "Explain idempotency keys" }],
  temperature: 0.1
})

n4n honors client routing directives; if you need to force a provider or region, pass the documented header. It also forwards provider cache-control hints, so any Cache-Control header you previously sent to OpenAI still reaches the upstream. The response includes a usage object with prompt_tokens and completion_tokens—the gateway meters per-token usage, so log it:

Rails.logger.info("llm_usage=#{response['usage'].to_json}")

Step 6: Preserve streaming and timeout behavior

Streaming works identically. Set stream: true and pass a block.

client = LlmClient.instance
client.chat(parameters: {
  model: "openai/gpt-4o",
  messages: [{ role: "user", content: "Stream a haiku" }],
  stream: true
}) do |chunk, _bytes|
  delta = chunk.dig("choices", 0, "delta", "content")
  print delta if delta
end

Bump request_timeout if your previous OpenAI calls used a longer window; gateway-level fallback may take extra seconds when a provider is degraded, and you don’t want mid-stream disconnects.

Step 7: Write a smoke test

Add a minimal test that hits the real endpoint in a staging environment. Use WebMock only for unit tests; for the smoke test, allow network.

# spec/services/llm_client_spec.rb
RSpec.describe LlmClient do
  it "returns a completion from n4n" do
    VCR.turned_off do
      WebMock.allow_net_connect!
      resp = described_class.instance.chat(parameters: {
        model: "openai/gpt-4o",
        messages: [{ role: "user", content: "Say pong" }]
      })
      expect(resp.dig("choices", 0, "message", "content")).to include("pong")
      expect(resp["usage"]).to have_key("total_tokens")
    end
  end
end

Run it against staging where N4N_API_KEY is set. This verifies the switch rails app openai to n4n didn’t break auth, body shape, or usage accounting.

Step 8: Verify with a direct curl

Before flipping deploy, call the endpoint directly to confirm token and model.

curl -s https://api.n4n.ai/v1/chat/completions \
  -H "Authorization: Bearer $N4N_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-4o","messages":[{"role":"user","content":"ping"}]}'

Expect a JSON object with id, object, choices. If you see 401, the key is wrong. 404 means the model string is unrecognized. A 200 with valid choices confirms the gateway path.

Step 9: Remove OpenAI-specific redundancy

Once traffic flows, delete your manual retry/backoff loops. Because n4n.ai performs automatic fallback when a provider is rate-limited or degraded, client-side exponential backoff on 429s is largely redundant. Keep a thin timeout guard, but drop the multi-provider switch logic you maintained for OpenAI outages.

Also remove the OPENAI_API_KEY from production env if no code references it. Grep again:

grep -rn "OPENAI_API_KEY" app/ config/ lib/ | grep -v "N4N"

If empty, you are done.

Step 10: Instrument token metering

Per-token metering is only useful if you ship it to your metrics pipeline. Extract usage in a wrapper:

class LlmClient
  def self.chat(parameters)
    resp = instance.chat(parameters: parameters)
    Statsd.count("llm.tokens", resp.dig("usage", "total_tokens").to_i)
    resp
  end
end

This gives you cost visibility after the switch rails app openai to n4n, since the gateway returns standard usage fields.

Verification checklist

  • All OpenAI::Client references go through LlmClient
  • uri_base points to https://api.n4n.ai/v1
  • Smoke test passes in staging with real key
  • curl returns valid chat completion with usage
  • Old OpenAI key removed from runtime
  • Manual retry loops deleted

The switch rails app openai to n4n is complete. Your Rails code stays on the OpenAI client interface while gaining a 240+ model catalog and gateway-handled resilience.

Tagsrailsn4nmigrationopenai-api

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All ruby on rails llm integration posts →