Developer Docs

Overview

The Manza API lets you programmatically manage accounts and transfers, invoices and customers, payment links, and checkout sessions, and subscribe to signed webhooks. It follows RESTful conventions with JSON request and response bodies.

Getting started#

The path from “I want to try Manza” to first production transaction:

  1. Create a workspace — sign up and complete activation.
  2. Test in the sandbox — see the Sandbox & test cards guide.
  3. Generate an API key from Settings → Developer → API Keys.
  4. Make your first request to confirm the key works.
  5. Build against staging, then mint a production key and switch base URL.
request.sh
curl https://ma.manza.dev/api/entity \
  -H "Authorization: Bearer sk_live_..."
manza
manza login
manza entity get
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.entity.get();
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.entity.get
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.entity.get()
main.go
import manza "github.com/getmanza/manza-go"

client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))

entity, err := client.Entity.Get(ctx)
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let entity = client.entity().get()?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, entity} = Manza.Entity.get(client)
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

entity = client.entity.get
app.php
use Manza\Client;

$client = new Client(apiKey: getenv('MANZA_API_KEY'));

$entity = $client->entity->get();

Generating an API key#

To generate an API key:

  1. Open Settings → Developer → API Keys in the Manza dashboard.
  2. Click Create API Key.
  3. Name the key (e.g. “Production”, “Staging”, “My App”) and select the scopes it needs (see Scopes below).
  4. List the IP addresses the key may be used from (see IP allowlist below). This is required for a key with transfers:authorize and optional for any other key. Then confirm with your verification code.
  5. Copy the key immediately — it is shown only once. Store it in a secrets manager.
  6. Use it in requests as Authorization: Bearer <key>.

Environments#

Production handles real money. The sandbox has separate data, dashboards and API keys; a key from one environment does not authenticate in another. Build and test against the sandbox, then generate a production key and switch your base URL. Examples on these pages use the host you are currently viewing, with /api as the API prefix.

Authentication#

API keys authenticate requests via a Bearer token in the Authorization header. Keys use the sk_live_ prefix and are hashed with SHA-256 before storage — Manza never stores your key in plaintext.

Keep keys secret
Never expose keys in client-side code, public repos, or logs. If a key is compromised, revoke it immediately from the dashboard.
Server-side only
The API is meant to be called from your backend, not from browser JavaScript. It sends no CORS headers, so cross-origin requests are blocked.

IP allowlist#

A key can be locked to your servers' addresses. Set the list in Settings → Developer → API Keys, on the key's page: one IPv4 or IPv6 address or CIDR network per line, up to 20. A bare address means that one host (/32 or /128). A request with the key from any other address is refused with 403 ip_not_allowed, on every endpoint.

Only a legal representative or manager can set or change the list, and only in the dashboard with a verification code. The API cannot read or change it. Every change notifies the members who approve payments.

A key with transfers:authorize must have a list. Other keys may leave it empty, which allows any address. A network that covers every address (0.0.0.0/0 or ::/0) is refused: it would look like a restriction without being one, so leave the list empty instead. Use static egress addresses: a NAT gateway, a fixed proxy, or your hosting provider's published ranges.

Versioning#

The API uses date-based versioning via the Manza-Version header. If omitted, the current version is assumed. Breaking changes ship under a new version date with a migration guide; non-breaking changes (new fields, new endpoints) apply to all versions.

The original Zazu-Version header is still accepted and responses echo both. It is deprecated — pin new clients to Manza-Version.

Rate limiting#

The general limit is 120 requests per minute per API key. Transfer initiation (POST /api/payments) also has a stricter 10 requests per minute limit. Exceeding a limit returns 429 with error.type: "rate_limit_error". Back off before retrying; spread work over time rather than retrying in a tight loop.

Errors#

All errors follow a consistent envelope format:

error.json
{
  "error": {
    "type": "validation_error",
    "message": "Customer not found",
    "param": "customer_id"
  }
}
TypeHTTPDescription
invalid_request_error400Malformed JSON or missing required parameters
invalid_version400Unsupported Manza-Version header
authentication_error401Missing or invalid API key
entity_inactive403Entity has not completed activation
forbidden_error403API access or feature not enabled
insufficient_scope403API key lacks required scope
ip_not_allowed403API key used from an address outside its IP allowlist
not_found_error404Resource does not exist
conflict409Resource conflict, such as an existing credit note
state_error422Invalid state transition
validation_error422Invalid request parameters
rate_limit_error429Too many requests
api_error500Internal server error

Pagination#

List endpoints use cursor-based pagination, ordered by ID descending. Supply limit (default 25, clamped to 1–100) and an optional cursor. Treat the cursor as opaque; an invalid cursor returns 400 invalid_request_error.

GET /api/accounts?limit=10

The response contains data (the current page), has_more and next_cursor. When has_more is true, pass next_cursor in the next request:

GET /api/accounts?limit=10&cursor=PASTE_NEXT_CURSOR_HERE

Continue until has_more is false; next_cursor is then null. Send your Bearer token with every request.

last-page.json
{
  "data": [],
  "has_more": false,
  "next_cursor": null
}

Scopes#

Choose scopes when creating an API key. Existing keys do not gain new scopes when another product is released. If a request returns 403 insufficient_scope, create a new key with the required permissions, update your integration and revoke the old key when it is no longer used.

ScopePermission
entity:readRetrieve your entity
accounts:readRead accounts and their transactions
transfers:readRetrieve an API-created payment
transfers:writeSubmit a payment into the dashboard approval flow
transfers:authorizeAuthorize or decline transfer authorization challenges; never on the key that created the transfer
beneficiaries:readRead beneficiaries and their bank accounts
beneficiaries:writeCreate beneficiaries and their bank accounts
beneficiaries:request_trustAsk approvers to trust payees for machine-approved transfers
bulk_payments:readRegistered scope; no public REST bulk-payment endpoint
bulk_payments:writeRegistered scope; no public REST bulk-payment endpoint
invoices:readList and retrieve invoices
invoices:writeCreate, update, delete invoices and create credit notes
invoices:sendSend, mark paid and cancel invoices
invoices:auto_approveAutomatically approve and send API-created invoices, bypassing approval rules
customers:readList and retrieve customers
customers:writeCreate, update and delete customers
payment_links:readList and retrieve hosted payment URLs
payment_links:writeCreate and cancel hosted payment URLs, including invoice links
checkout_sessions:readRetrieve hosted checkout records
checkout_sessions:writeCreate hosted checkout records
webhook_endpoints:readList and retrieve webhook endpoints
webhook_endpoints:writeCreate, update, delete, test, enable or disable endpoints and rotate secrets

This catalogue lists registered permissions; product availability is checked separately. invoices:auto_approve supplements invoices:write; it does not grant invoice creation by itself.

Entity#

GET /api/entity returns the entity associated with your API key. Required scope: entity:read. Use the authenticated request in Getting started above. A successful response is 200 OK:

{
  "id": "01964a3b-0000-7000-8000-000000000001",
  "name": "Acme Corp",
  "created_at": "2026-01-15T10:30:00Z"
}

id is the entity identifier, name its name, and created_at an ISO 8601 timestamp.

Feature access#

API access must be enabled for your entity, and the entity must have completed activation. Product access and key scopes are checked independently: granting a scope does not enable a product. An unavailable feature returns 403 forbidden_error; incomplete activation returns 403 entity_inactive.

Access requirementApplies to
API accessAll API endpoints
InvoicingInvoice and customer endpoints
Card collectionPayment-link and checkout-session endpoints
Transfer initiationPayment creation and retrieval, and beneficiary and bank-account creation and lookup

Contact support if the product you need is unavailable. Public docs follow the deployment's released products; your entity can still have additional access restrictions.