# ExternalService
[](https://hex.pm/packages/external_service)
[](https://hexdocs.pm/external_service)
An Elixir library for safely calling external services and APIs with
customizable **retry logic**, the **circuit breaker** pattern, and optional
**rate limiting**. Calls can be synchronous, asynchronous background tasks, or
fanned out in parallel for MapReduce-style processing — all under the same
protection.
## Why?
Calling an external service is risky: the network hiccups, the service is briefly
overloaded, or it goes down entirely. `ExternalService` wraps those calls with
three complementary safety mechanisms:
- **Retries** smooth over *transient* failures by trying again with configurable
backoff and jitter.
- A **circuit breaker** protects against a *persistently* failing service: once
failures cross a threshold the breaker opens and calls fail fast instead of
piling up against a service that is already down.
- A **rate limiter** keeps you under the call quota the service imposes.
You wrap your call in a function, hand it to `ExternalService`, and all of the
above is applied on every call.
## Installation
Add `external_service` to your dependencies in `mix.exs`:
```elixir
def deps do
[{:external_service, "~> 2.0"}]
end
```
## Quick start
Define a module for the service, configuring it declaratively, and start it
under your supervisor:
```elixir
defmodule MyApp.Stripe do
use ExternalService,
circuit_breaker: [tolerate: 5, within: :timer.seconds(1), reset: :timer.seconds(5)],
rate_limit: [limit: 100, per: :timer.seconds(1)],
retry: [max_attempts: 5, backoff: :exponential, jitter: true]
def charge(params) do
call fn ->
case Stripe.charge(params) do
{:ok, result} -> {:ok, result}
{:error, %{status: status}} when status in 500..599 -> :retry
other -> other
end
end
end
end
```
```elixir
# In your supervision tree:
children = [MyApp.Stripe]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
```
Now call it from anywhere; retries, circuit breaking, and rate limiting are
applied automatically:
```elixir
case MyApp.Stripe.charge(%{amount: 1000, currency: "usd", source: token}) do
{:ok, result} ->
handle_success(result)
{:error, %ExternalService.RetriesExhausted{}} ->
{:error, :payment_unavailable}
{:error, %ExternalService.CircuitBreakerOpen{}} ->
{:error, :payment_unavailable}
{:error, reason} ->
{:error, reason}
end
```
Inside the function you pass to `call/1`, return `:retry` or `{:retry, reason}`
to ask for another attempt; any other value is treated as success and returned
as-is.
## Documentation
Full documentation is on [HexDocs](https://hexdocs.pm/external_service). Start
with these guides:
- **[Getting Started](guides/getting-started.md)** — your first reliable call.
- **[The Module Front Door](guides/the-front-door.md)** — everything `use
ExternalService` generates, plus supervision and overrides.
- **[Circuit Breakers](guides/circuit-breakers.md)** — how the breaker trips,
resets, and how to introspect it.
- **[Retries](guides/retries.md)** — backoff, jitter, attempt/time budgets, and
retrying on exceptions.
- **[Rate Limiting](guides/rate-limiting.md)** — staying under a quota.
- **[Error Handling](guides/error-handling.md)** — `call` vs `call!` and the
structured error types.
- **[Telemetry](guides/telemetry.md)** — observing calls, retries, and trips.
- **[Migrating to 2.0](guides/migrating-to-2.0.md)** — upgrading from 1.x.
## Upgrading from 1.x
Version 2.0 is a breaking modernization of the library. See the
[migration guide](guides/migrating-to-2.0.md) and the
[CHANGELOG](CHANGELOG.md) for the full list of changes.
## License
Released under the Apache 2.0 license. Originally sponsored by Ropig.