manza-elixir

Elixir SDK for the Manza API.

# mix.exs
def deps do
[
{:manza, github: "getmanza/manza-elixir"}
]
end
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, entity} = Manza.Entity.get(client)
{:ok, page} = Manza.Accounts.list(client)
for account <- page.data do
IO.puts("#{account["id"]} #{account["name"]}")
end
# Initiate a transfer — it lands in your workspace's in-app approval
# queue; the API never executes a transfer itself.
{:ok, draft} =
Manza.TransferDrafts.create(client, %{
"account_id" => account_id,
"beneficiary_id" => beneficiary_id,
"amount" => "150.00",
"payment_reference" => "INV-000042"
})

Hosts

The default base URL is production Morocco, https://ma.manza.finance. For South Africa pass base_url: "https://za.manza.finance" (or set MANZA_BASE_URL). The replay cassettes are recorded against https://ma.manza.dev.

Environment variables

MANZA_API_KEY, MANZA_BASE_URL and MANZA_API_VERSION are read when the matching option is not passed. The old ZAZU_API_KEY, ZAZU_BASE_URL and ZAZU_API_VERSION names still work for all of 1.x as a fallback and log a one-time deprecation warning per variable.

Resources

Module Calls
Manza.Entity get/1
Manza.Accounts list/2, get/2, list_transactions/3, get_transaction/3
Manza.Customers list/2, get/2, create/2, update/3, delete/2
Manza.Invoices list, get, create, update, send, mark as paid, cancel, credit note, delete, payment link
Manza.PaymentLinks list/2, get/2, create/2, cancel/2
Manza.CheckoutSessions create/2, get/2
Manza.WebhookEndpoints CRUD, enable/disable, test, regenerate secret
Manza.Beneficiaries list/2, get/2, create/2, list_external_accounts/3, get_external_account/3, create_external_account/3
Manza.PayeeTrustRequests create/2 (external_account_ids), get/2
Manza.TransferDrafts create/2, get/2, authorize/4, decline/4

Machine-authorized transfers

A transfer draft inside your machine-authorization envelope (trusted payee, within limits) is sent to your enrolled transfer authorizer as a payment.authorization_requested webhook carrying the authorization_id and a one-time nonce. Answer it with a different API key than the one that created the draft (scope transfers:authorize). Manza.TransferAuthorization holds the pure signing functions:

payee = Manza.TransferAuthorization.payee_for(external_account_id: draft["external_account_id"])
# Build the input from your own record of the transfer, not the webhook's
# `signature_input`. `amount` must be the API's decimal string, e.g. "2500.0".
input =
Manza.TransferAuthorization.signature_input(
draft["id"], nonce, draft["amount"], draft["currency_code"],
draft["account_id"], payee, draft["client_reference"]
)
signature = Manza.TransferAuthorization.sign(signing_secret, input)
{:ok, _} = Manza.TransferDrafts.authorize(authorizer_client, draft["id"], authorization_id, signature)
# or: Manza.TransferDrafts.decline(authorizer_client, draft["id"], authorization_id, "reason")

A blank signature returns {:error, %Manza.ConfigurationError{}} without calling the API (the server counts a missing signature as a failed attempt). create/2 accepts an optional client_reference (unique per entity, at most 128 characters); a duplicate returns {:error, %Manza.Error{kind: :conflict}} whose payment_id names the existing draft.

Response shape

Response bodies are returned as-is from the API — snake_case string-keyed maps in response.body, no struct mapping. The same shape ships across every Manza SDK (Ruby, TypeScript, Python, Go, ...) so the cassette contract is one-to-one.

List endpoints return a Manza.Page (data, has_more, next_cursor); Manza.Page.next/1 fetches the following page (nil on the last one). Page size is capped at 100 records.

Errors

Non-2xx responses come back as {:error, %Manza.Error{}} with status, kind (:authentication, :forbidden, :not_found, :validation for 400 and 422, :conflict for 409, :rate_limit, :server, :api), the API's type/message/param, and the request_id. Rate limits carry retry_after; conflicts carry payment_id. Transport failures are {:error, %Manza.ConnectionError{}}; invalid config is {:error, %Manza.ConfigurationError{}}.

Tests

Tests replay the canonical cassettes recorded by manza-ruby. The cassettes are downloaded from the Ruby SDK's release tarball and served from a Bypass server. Same interactions, same assertions, every language.

scripts/fetch-cassettes.sh
mix test

The SDK family