An unofficial, complete Go 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.
- Zero required dependencies. Built entirely on the Go standard
library (
net/http,encoding/json,crypto/subtle,log/slog) — nothing togo getbeyond this module itself. - Hexagonal / DDD architecture.
domain/*packages declare each resource's data and behavior as aRepositoryinterface (the port);infrastructure/treasuryprimeapiimplements every one of them against Treasury Prime's REST API (the adapter);application/*contains business workflows that orchestrate several repositories at once (opening an account end to end, issuing-and-displaying a card). See Architecture below. - ~39 resource repositories, each with a typed entity struct and
typed
CreateParams/UpdateParams. - Idiomatic Go throughout: every method takes
context.Contextfirst; errors implementUnwrapforerrors.Is/errors.As; pagination is a lazy iterator in thebufio.Scannerstyle; configuration is functional options; constant-time webhook signature comparison viacrypto/subtle. - Pluggable HTTP transport via a one-method
Doerinterface that*http.Clientalready satisfies — bring your own instrumented client, or use the default.
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 — some lesser-used endpoints (noted in their doc comments) were implemented from documentation rather than against a live account.
go get github.com/iamkanishka/treasuryprime-goRequires Go 1.25 (uses generics extensively).
package main
import (
"context"
"log"
"os"
"github.com/iamkanishka/treasuryprime-go"
"github.com/iamkanishka/treasuryprime-go/domain/ach"
"github.com/iamkanishka/treasuryprime-go/domain/shared"
)
func main() {
client, err := treasuryprime.New(
treasuryprime.WithAPIKey(os.Getenv("TREASURY_PRIME_KEY_ID"), os.Getenv("TREASURY_PRIME_KEY_VALUE")),
treasuryprime.WithEnvironment(treasuryprime.EnvironmentSandbox),
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
page, err := client.Account.List(ctx, nil)
if err != nil {
log.Fatal(err)
}
for _, account := range page.Items {
log.Printf("%s: %s", account.ID, account.Status)
}
created, err := client.Ach.Create(ctx, ach.CreateParams{
AccountID: "acct_123456",
CounterpartyID: "cp_098765",
Amount: "100.00",
Direction: ach.DirectionCredit,
SecCode: ach.SECCodeCCD,
}, shared.WithIdempotencyKey(shared.NewIdempotencyKey()))
if err != nil {
log.Fatal(err)
}
log.Printf("created ACH transfer %s", created.ID)
}See examples/ for runnable programs covering basic usage,
webhooks, and pagination.
domain/ — the ports: entities + Repository interfaces, zero I/O
account/, ach/, wire/, card/, webhook/, shared/, ... (one package per resource)
application/ — business workflows spanning multiple repositories
accountopening/ — submit person/business + account application + deposit, in sequence
cardprovisioning/ — issue a card, then prep it for client-side display via Marqeta
infrastructure/ — the adapters: REST implementations of every domain Repository
transport/ — auth, retries, JSON, generic Resource[T] CRUD executor
treasuryprimeapi/ — one file per resource, implementing its domain.Repository
marqeta/ — direct Marqeta API calls (JS / UX Toolkit access tokens)
simulation/ — sandbox-only simulation endpoint
client.go, options.go — composition root: wires every adapter into one Client
Why this matters in practice, not just in theory:
- Testability without HTTP.
application/accountopeningis tested entirely with in-memory fakes implementing theRepositoryinterfaces — nohttptest.Server, no network, just plain structs (seeapplication/accountopening/service_test.go). - One place to swap transports. Everything routes through
transport.Doer; provide your own and every resource picks it up. - Resources are mechanical, not magical. Adding the 40th resource
means writing a domain package (entity + interface) and an
infrastructure package (a thin wrapper around the generic
transport.Resource[T]) — no reflection, no code generation.
first, err := client.Ach.List(ctx, url.Values{"status": {"pending"}})
it := client.Ach.Iterator(ctx, first)
for it.Next() {
a := it.Item()
fmt.Println(a.ID, a.Amount)
}
if err := it.Err(); err != nil {
log.Fatal(err)
}Pages are fetched lazily: stopping the loop early (a break, or running
out of for) never triggers an HTTP call for a page you didn't end up
needing.
Every method returns a *shared.Error (or wraps one) on failure. It
implements Unwrap, so it composes with errors.Is/errors.As:
created, err := client.Ach.Create(ctx, ach.CreateParams{})
if err != nil {
var apiErr *shared.Error
if errors.As(err, &apiErr) && apiErr.Type == shared.ErrorTypeAPI {
log.Printf("treasury prime rejected the request: %d %v", apiErr.StatusCode, apiErr.Body)
}
}shared.IsType(err, shared.ErrorTypeAPI) is a convenience wrapper around
the same errors.As check.
sig := webhook.Signature{BasicUser: "myapp", BasicSecret: webhookSecret}
func handler(w http.ResponseWriter, r *http.Request) {
if !sig.Valid(r.Header.Get("Authorization")) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
var payload map[string]any
json.NewDecoder(r.Body).Decode(&payload)
event, _ := webhook.ParseEvent(payload)
// Re-fetch via the matching typed repository, or via client.FetchRaw
// for a generic map[string]any if you'd rather not switch on EventType.
}_, err := client.Simulation.AchStatus(ctx, ach.ID, "settled")
_, err = client.Simulation.CardAuthRequest(ctx, card.ID, map[string]any{"amount": "25.00"})type myDoer struct{ inner *http.Client }
func (d *myDoer) Do(req *http.Request) (*http.Response, error) {
// add tracing, custom retry policy, etc.
return d.inner.Do(req)
}
client, err := treasuryprime.New(
treasuryprime.WithAPIKey(id, secret),
treasuryprime.WithDoer(&myDoer{inner: http.DefaultClient}),
)| Area | Repositories |
|---|---|
| Account opening | AccountApplication, BusinessApplication, PersonApplication, AdditionalPersonApplication, Deposit, Kyc, KycProduct, AccountProduct, AccountNumberReservation |
| Accounts & parties | Account, Business, Person, AccountLock, ReserveAccount, StatementConfig, Transaction (AverageBalance/DailyBalance/TaxDocument via Account) |
| Payments | Ach, Wire, Book, NetworkTransfer, FedNow, Check, CheckDeposit, Counterparty, IncomingAch, IncomingWire, InvoiceAccountNumber, ManualHold, Greendot, DepositSweep |
| Cards | Card, CardProduct, CardEvent, CardAuthLoopEndpoint, DigitalWalletToken, infrastructure/marqeta |
| Utilities | Document, File, RoutingNumber, Webhook |
| Testing | Simulation (sandbox only) |
go build ./...
go vet ./...
go test ./... -cover
gofmt -l . # should print nothing
staticcheck ./... # if installedMIT — see LICENSE.