TreasuryPrime

An unofficial, complete Elixir client for the Treasury Prime banking API (the Ledger product): bank accounts, ACH, wires, book transfers, FedNow, debit cards, account opening / KYC, check deposit (RDC), check issuing, Green Dot cash loads, webhooks, and sandbox simulations.

This is a community-built client and is not affiliated with or endorsed by Treasury Prime, Inc. Always cross-check field names and behavior against the official API reference for your integration — Treasury Prime's API surface evolves, and some lesser-used endpoints in this library (noted in their @moduledocs) were implemented from documentation rather than against a live account.

Installation

def deps do
[
{:treasury_prime, "~> 1.0.0"}
]
end

Quick start

client =
TreasuryPrime.new(
api_key_id: System.fetch_env!("TREASURY_PRIME_KEY_ID"),
api_key_value: System.fetch_env!("TREASURY_PRIME_KEY_VALUE"),
environment: :sandbox # or :production
)
{:ok, page} = TreasuryPrime.Account.list(client)
Enum.each(page.data, &IO.inspect/1)
{:ok, account} = TreasuryPrime.Account.get(client, "acct_123456")
{:ok, ach} =
TreasuryPrime.Ach.create(
client,
%{
account_id: "acct_123456",
counterparty_id: "cp_098765",
amount: "100.00",
direction: "credit",
sec_code: "ccd"
},
idempotency_key: TreasuryPrime.Idempotency.generate_key()
)

Prefer not to thread a client through every call? Configure one in config.exs / runtime.exs and pull it from TreasuryPrime.Config instead:

# config/runtime.exs
config :treasury_prime,
api_key_id: System.fetch_env!("TREASURY_PRIME_KEY_ID"),
api_key_value: System.fetch_env!("TREASURY_PRIME_KEY_VALUE"),
environment: :sandbox
client = TreasuryPrime.Config.client()
TreasuryPrime.Account.list(client)

Pagination

{:ok, page} = TreasuryPrime.Ach.list(client, %{status: "pending"})
page.data #=> [%TreasuryPrime.Ach{}, ...]
# One page at a time:
{:ok, next_page} = TreasuryPrime.Page.next(page)
# Or lazily over everything:
client
|> TreasuryPrime.Ach.list!(%{status: "pending"})
|> TreasuryPrime.Page.stream()
|> Stream.filter(&(&1.amount == "100.00"))
|> Enum.take(10)

Errors

Every function returns {:ok, result} | {:error, %TreasuryPrime.Error{}}, or has a ! variant that raises the same TreasuryPrime.Error struct (which implements the Exception behaviour) instead.

case TreasuryPrime.Ach.create(client, %{}) do
{:ok, ach} ->
ach
{:error, %TreasuryPrime.Error{type: :api_error, status: 400, body: body}} ->
Logger.error("ACH creation failed: #{inspect(body)}")
{:error, %TreasuryPrime.Error{type: :network_error}} = error ->
Logger.error(Exception.message(elem(error, 1)))
end

Webhooks

{:ok, webhook} =
TreasuryPrime.Webhook.create(client, %{
event: "ach.update",
url: "https://example.application.com/notify",
basic_user: "myapp",
basic_secret: System.fetch_env!("TREASURY_PRIME_WEBHOOK_SECRET")
})

In your webhook controller:

def create(conn, params) do
header = conn |> Plug.Conn.get_req_header("authorization") |> List.first()
if TreasuryPrime.WebhookSignature.valid?(header, "myapp", webhook_secret()) do
event = TreasuryPrime.WebhookEvent.parse!(params)
{:ok, fresh_object} = TreasuryPrime.WebhookEvent.fetch(event, client)
MyApp.Webhooks.handle(event.event, fresh_object)
send_resp(conn, 200, "")
else
send_resp(conn, 401, "")
end
end

Sandbox testing

{:ok, _} = TreasuryPrime.Testing.Simulation.ach_status(client, ach.id, "settled")
{:ok, _} = TreasuryPrime.Testing.Simulation.card_auth_request(client, card.id, %{amount: "25.00"})

Using a different HTTP transport

defmodule MyApp.ReqHTTPClient do
@behaviour TreasuryPrime.HTTPClient
@impl true
def request(method, url, headers, body, opts) do
case Req.request(method: method, url: url, headers: headers, body: body, retry: false) do
{:ok, resp} -> {:ok, %{status: resp.status, headers: resp.headers, body: resp.body}}
{:error, reason} -> {:error, reason}
end
end
end
client = TreasuryPrime.new(api_key_id: "...", api_key_value: "...", http_client: MyApp.ReqHTTPClient)

Resource coverage

AreaModules
Account openingAccountApplication, BusinessApplication, PersonApplication, AdditionalPersonApplication, Deposit, Kyc, KycProduct, AccountProduct, AccountNumberReservation
Accounts & partiesAccount, Business, Person, AccountLock, AverageBalance, DailyBalance, Transaction, ReserveAccount, StatementConfig, TaxDocument
PaymentsAch, Wire, Book, NetworkTransfer, FedNow, Check, CheckDeposit, Counterparty, IncomingAch, IncomingWire, InvoiceAccountNumber, ManualHold, Greendot, DepositSweep
CardsCard, CardProduct, CardEvent, CardAuthLoopEndpoint, DigitalWalletToken, Marqeta.JS, Marqeta.UXToolkit
UtilitiesDocument, File, RoutingNumber, Webhook, WebhookEvent, WebhookSignature
TestingTesting.Simulation

Development

mix deps.get
mix test
mix format
mix credo
mix dialyzer

License

MIT — see LICENSE.