HTTP client gem with concurrent execution, circuit breaker, request flow pipelines, and JWT authentication. Built on Faraday.
| Need | Faraday alone | api-client |
|---|---|---|
| Sequential HTTP | ✓ | ✓ |
| Batch/concurrent | manual Typhoeus | auto-detected |
| Circuit breaker | manual Stoplight | built-in |
| Request pipelines | — | fetch → then → fan_out → collect |
| JWT auth | manual | 4-phase key rotation |
| SSRF prevention | — | UriPolicy with blocklist |
ore add api-client -github belt/api-client
ore add typhoeus faraday-typhoeus async async-http concurrent-ruby jwt fiddleore installIn Gemfile add:
gem"api-client",github: "belt/api-client"gem"typhoeus"# optional: HTTP/2 adaptergem"faraday-typhoeus"# optional: Faraday integration for typhoeusgem"async"# optional: fiber-based adaptergem"async-http"# optional: async HTTP transportgem"concurrent-ruby"# optional: thread pool adaptergem"jwt"# optional: JWT authenticationgem"fiddle"# optional: native RSS measurementbundleSee doc/onboarding.md for the full guide.
client=ApiClient.new(url: "https://api.example.com")# Simpleresponse=client.get("/users/1")# Concurrent (auto-selects Typhoeus > Async > Concurrent)responses=client.concurrent([{method: :get,path: "/users/1"},{method: :get,path: "/users/2"},])# Pipelineposts=client.request_flow.fetch(:get,"/users/123").then{ |r| JSON.parse(r.body)["post_ids"]}.fan_out{ |id| {method: :get,path: "/posts/#{id}"}}.collectSee doc/architecture.md for full system diagrams, data flow, and component responsibilities.
ApiClient::Base
├── Connection (Faraday + middleware + pool)
├── Orchestrators
│ ├── Sequential
│ └── Batch (auto-detected backend)
├── Adapters
│ ├── Typhoeus (HTTP/2 via libcurl Hydra)
│ ├── Async (fiber-based)
│ └── Concurrent (thread pool)
├── Processing
│ ├── RactorProcessor (CPU parallelism)
│ ├── AsyncProcessor (fork isolation)
│ └── ConcurrentProcessor (thread pool)
├── RequestFlow (chained pipelines)
├── Streaming::FanOutExecutor (backpressure + retry)
├── Circuit (Stoplight integration)
├── JWT::Authenticator (Bearer injection)
└── Hooks (ActiveSupport::Notifications, 30+ events)
ApiClient.configuredo |c|
c.service_uri="https://api.example.com"c.open_timeout=5c.read_timeout=30c.adapter=:typhoeus# or :async, :concurrentc.on_error=:raise# or :collect, :skipendAuto-detection priority: Typhoeus → Async → Concurrent. Per-adapter guides in doc/onboarding/.
| Adapter | Mechanism | Best for |
|---|---|---|
| Typhoeus | libcurl Hydra, HTTP/2 | High-throughput I/O |
| Async | Ruby fibers | Lightweight concurrency |
| Concurrent | Thread pool | CPU-bound post-processing |
authenticator=ApiClient::JWT::Authenticator.new(jwks_uri: "https://auth.example.com/.well-known/jwks.json",audience: "api.example.com",)client=ApiClient.new(url: "https://api.example.com")do |c|
c.jwt_authenticator=authenticatorendKeyStore supports 4-phase rotation: active → retiring → retired → revoked. See doc/auth/jwt.md for full configuration and key management.
| Processor | Isolation | Use case |
|---|---|---|
| Ractor | Process-level (Ractor::Port) | CPU-bound transforms |
| Async | Fork | Untrusted workloads |
| Concurrent | Thread pool | I/O-bound fan-out |
16 canonical clients in lib/api_client/examples/ covering
the 4×4 adapter × processor matrix. Each has a matching spec.
Run all: bundle exec rake examples:metrics
See doc/example-metrics.md for benchmark data.
bundle install
bundle exec rspec
bundle exec rake quality # rubycritic, reek, flay, flog# Install pre-push hook (CI parity gate on main)
git config core.hooksPath .githooks- Ruby ≥ 3.2
- Faraday ≥ 2.0
- ActiveSupport ≥ 6.0
| Gem | Enables |
|---|---|
| typhoeus | HTTP/2 adapter via libcurl Hydra |
| faraday-typhoeus | Faraday integration for typhoeus |
| async, async-http | Fiber-based concurrent adapter |
| concurrent-ruby | Thread pool adapter |
| jwt | JWT authentication and key rotation |
| fiddle | Native RSS measurement via FFI |
Apache-2.0