A complete, production-grade Elixir client for the Moov API.

This package wraps every resource in Moov's API reference - Moov accounts, sources, money movement, account tools, enrichment, and authentication - behind small, documented, @spec'd modules: Moov.Accounts, Moov.Transfers, Moov.Wallets, Moov.BankAccounts, Moov.Cards, Moov.Disputes, Moov.Invoices, and 25 more. See the module list in the sidebar, grouped the same way as https://docs.moov.io/api/.

Installation

def deps do
  [{:moov, "~> 0.1"}]
end

Quick start

client = Moov.Client.new(
  public_key: System.fetch_env!("MOOV_PUBLIC_KEY"),
  private_key: System.fetch_env!("MOOV_PRIVATE_KEY"),
  api_version: "v2026.04.00"
)

{:ok, account} =
  Moov.Accounts.create(client, %{
    account_type: "individual",
    profile: %{individual: %{name: %{first_name: "Ada", last_name: "Lovelace"}, email: "ada@example.com"}}
  })

{:ok, bank_account} =
  Moov.BankAccounts.link(client, account["accountID"], %{
    holder_name: "Ada Lovelace",
    holder_type: "individual",
    routing_number: "021000021",
    account_number: "123456789",
    bank_account_type: "checking"
  })

{:ok, transfer} =
  Moov.Transfers.create(client, account["accountID"], %{
    source: %{payment_method_id: source_payment_method_id},
    destination: %{payment_method_id: bank_account["paymentMethodID"]},
    amount: %{currency: "USD", value: 2500}
  })

Every function returns {:ok, result} or {:error, %Moov.Error{}} - see Moov.Error for how to pattern-match on failure types, and unwrap!/1 below if you'd rather raise.

Design philosophy

Response bodies are returned as plain maps with their original camelCase string keys, exactly as Moov sends them (account["accountID"], not a rigid struct) - request bodies, on the other hand, accept idiomatic snake_case keys and are camelCased automatically (see Moov.CaseConverter). This keeps the library forward-compatible with new fields Moov adds to responses over time, while staying pleasant to write Elixir against. Errors, retries, idempotency, and webhook verification are still fully typed and structured (Moov.Error, Moov.Webhook.Event) since those are exactly the places where stringly-typed data would bite you.

Raising instead of pattern matching

Moov.Accounts.get(client, account_id) |> Moov.unwrap!()

is equivalent to:

case Moov.Accounts.get(client, account_id) do
  {:ok, account} -> account
  {:error, error} -> raise error
end

Summary

Functions

Unwraps an {:ok, result} tuple to result, or raises the Moov.Error inside an {:error, error} tuple.

Functions

unwrap!(arg)

@spec unwrap!({:ok, term()} | {:error, Moov.Error.t()}) :: term()

Unwraps an {:ok, result} tuple to result, or raises the Moov.Error inside an {:error, error} tuple.

Works with the return value of any function in this library - pipe into it instead of writing case/with everywhere you're comfortable letting failures crash the calling process.

Examples

iex> Moov.unwrap!({:ok, %{"accountID" => "acc_123"}})
%{"accountID" => "acc_123"}

iex> Moov.unwrap!({:error, %Moov.Error{type: :not_found, message: "not found"}})
** (Moov.Error) Moov API error (not_found): not found