REST API

Transfers API

Initiate single transfers programmatically. The API creates a transfer draft and hands it to your workspace's approval flow — a manager or legal representative reviews and approves it in the Zazu app. The API never moves money on its own: an API key alone cannot execute a transfer.

How it works#

  1. POST /api/payments validates the payload and creates a draft in requested status. The workspace's managers and legal representatives are notified and the draft appears in their approval queue.
  2. An approver reviews the draft in the app and authorizes it there (protected by their own two-factor step). Execution then follows the same path as any web-initiated transfer — settlement, audit trail, and webhooks behave identically.
  3. Poll GET /api/payments/:id (or subscribe to the transfer.executed webhook) to follow the lifecycle: requestedprocessingcompleted / failed.

The recipient is either a saved beneficiary (beneficiary_id, see Beneficiaries) or one of your own accounts (destination_account_id) for internal moves. Scheduled, recurring, and bulk transfers are not available via the API.

Feature-flagged
API transfers require the release_api_transfers feature flag on your workspace. Contact support to enable it.
Rate limit
Transfer initiation has its own limit of 10 requests per minute per API key, on top of the global 120/minute.

Required scopes#

EndpointScope
Initiate a transfertransfers:write
Get transfer drafttransfers:read
List / Get beneficiariesbeneficiaries:read

The member who created the API key must also hold the payments:create permission — the draft is created on their behalf and they appear as the requester in the approval queue and the audit trail.

Transfer draft object#

While approval is outstanding the status is requested and transfer is null. Once approved and executed, the draft carries the transfer's id and status:

payment.json
{
  "id": "01964a3b-1a2b-7000-8000-feed00000042",
  "status": "completed",
  "amount": "1500.00",
  "currency_code": "ZAR",
  "payment_reference": "INV-000042",
  "account_id": "01964a3b-0000-7000-8000-ac6000000a01",
  "beneficiary_id": "01964a3b-5e6f-7000-8000-beef00000001",
  "external_account_id": "01964a3b-7a8b-7000-8000-beef00000002",
  "destination_account_id": null,
  "transfer": {
    "id": "01964a3b-3c4d-7000-8000-feed00000043",
    "status": "accepted"
  },
  "created_at": "2026-07-16T09:00:00Z",
  "updated_at": "2026-07-16T10:41:30Z"
}

List beneficiaries#

GET /api/beneficiaries

Read-only directory of your saved recipients, used to resolve the beneficiary_id for a transfer. Each beneficiary embeds its bank accounts; when a transfer names only the beneficiary, the account marked default is used. Beneficiaries are created and managed in the dashboard. Cursor pagination as elsewhere (limit 1–100, default 25). GET /api/beneficiaries/:id fetches one.

request.sh
curl "https://zazu.africa/api/beneficiaries" \
  -H "Authorization: Bearer sk_live_..."
zazu
zazu beneficiaries list
app.js
import { Zazu } from "@getzazu/sdk";
const zazu = new Zazu({ apiKey: process.env.ZAZU_API_KEY });

await zazu.beneficiaries.list();
app.rb
require "zazu"
zazu = Zazu.new(api_key: ENV["ZAZU_API_KEY"])

zazu.beneficiaries.list
app.py
from zazu_sdk import Zazu
client = Zazu(api_key="sk_live_...")

client.beneficiaries.list()
main.go
import zazu "github.com/getzazu/zazu-go"

client, _ := zazu.New(zazu.WithAPIKey(os.Getenv("ZAZU_API_KEY")))

page, err := client.Beneficiaries.List(ctx, zazu.ListParams{})
main.rs
let client = zazu_sdk::Client::builder()
    .api_key(std::env::var("ZAZU_API_KEY")?)
    .build()?;

let page = client.beneficiaries().list(Default::default())?;
app.exs
{:ok, client} = Zazu.new(api_key: System.fetch_env!("ZAZU_API_KEY"))

{:ok, page} = Zazu.Beneficiaries.list(client)
app.cr
require "zazu"

client = Zazu::Client.new # reads ZAZU_API_KEY

page = client.beneficiaries.list
app.php
use Zazu\Client;

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

$page = $client->beneficiaries->list();
response.json
{
  "data": [
    {
      "id": "01964a3b-5e6f-7000-8000-beef00000001",
      "name": "Acme Supplies Ltd",
      "beneficiary_type": "company",
      "email": "billing@acme.example",
      "phone_number": null,
      "external_accounts": [
        {
          "id": "01964a3b-7a8b-7000-8000-beef00000002",
          "name": "Acme Main",
          "account_number": "62000003592",
          "bank_identifier": "410105",
          "currency_code": "ZAR",
          "default": true
        }
      ],
      "created_at": "2026-05-02T10:15:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Initiate a transfer#

POST /api/payments

Required: account_id, amount, and exactly one of beneficiary_id or destination_account_id. Optional: external_account_id (a specific bank account of the beneficiary), currency_code (must match the source account), payment_reference, and internal_notes. A successful response is 201 with the draft in requested status — approval now happens in the app.

request.sh
curl -X POST "https://zazu.africa/api/payments" \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "01964a3b-0000-7000-8000-ac6000000a01",
    "beneficiary_id": "01964a3b-5e6f-7000-8000-beef00000001",
    "amount": "1500.00",
    "payment_reference": "INV-000042"
  }'
zazu
zazu transfers create --account-id 01964a3b-0000-7000-8000-ac6000000a01 \
  --beneficiary-id 01964a3b-5e6f-7000-8000-beef00000001 --amount 1500.00 --payment-reference "INV-000042"
app.js
import { Zazu } from "@getzazu/sdk";
const zazu = new Zazu({ apiKey: process.env.ZAZU_API_KEY });

await zazu.payments.create({
  accountId: "01964a3b-0000-7000-8000-ac6000000a01",
  beneficiaryId: "01964a3b-5e6f-7000-8000-beef00000001",
  amount: "1500.00",
  paymentReference: "INV-000042",
});
app.rb
require "zazu"
zazu = Zazu.new(api_key: ENV["ZAZU_API_KEY"])

zazu.payments.create(
  account_id: "01964a3b-0000-7000-8000-ac6000000a01",
  beneficiary_id: "01964a3b-5e6f-7000-8000-beef00000001",
  amount: "1500.00",
  payment_reference: "INV-000042"
)
app.py
from zazu_sdk import Zazu
client = Zazu(api_key="sk_live_...")

client.payments.create(
  account_id="01964a3b-0000-7000-8000-ac6000000a01",
  beneficiary_id="01964a3b-5e6f-7000-8000-beef00000001",
  amount="1500.00",
  payment_reference="INV-000042",
)
main.go
import zazu "github.com/getzazu/zazu-go"

client, _ := zazu.New(zazu.WithAPIKey(os.Getenv("ZAZU_API_KEY")))

draft, err := client.Payments.Create(ctx, zazu.Attributes{
    "account_id":        "01964a3b-0000-7000-8000-ac6000000a01",
    "beneficiary_id":    "01964a3b-5e6f-7000-8000-beef00000001",
    "amount":            "1500.00",
    "payment_reference": "INV-000042",
})
main.rs
let client = zazu_sdk::Client::builder()
    .api_key(std::env::var("ZAZU_API_KEY")?)
    .build()?;

let draft = client.payments().create(&serde_json::json!({
    "account_id": "01964a3b-0000-7000-8000-ac6000000a01",
    "beneficiary_id": "01964a3b-5e6f-7000-8000-beef00000001",
    "amount": "1500.00",
    "payment_reference": "INV-000042",
}))?;
app.exs
{:ok, client} = Zazu.new(api_key: System.fetch_env!("ZAZU_API_KEY"))

{:ok, draft} =
  Zazu.Payments.create(client, %{
    "account_id" => "01964a3b-0000-7000-8000-ac6000000a01",
    "beneficiary_id" => "01964a3b-5e6f-7000-8000-beef00000001",
    "amount" => "1500.00",
    "payment_reference" => "INV-000042"
  })
app.cr
require "zazu"

client = Zazu::Client.new # reads ZAZU_API_KEY

draft = client.payments.create(
  account_id: "01964a3b-0000-7000-8000-ac6000000a01",
  beneficiary_id: "01964a3b-5e6f-7000-8000-beef00000001",
  amount: "1500.00",
  payment_reference: "INV-000042"
)
app.php
use Zazu\Client;

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

$draft = $client->payments->create([
    'account_id' => '01964a3b-0000-7000-8000-ac6000000a01',
    'beneficiary_id' => '01964a3b-5e6f-7000-8000-beef00000001',
    'amount' => '1500.00',
    'payment_reference' => 'INV-000042',
]);
response.json
{
  "id": "01964a3b-1a2b-7000-8000-feed00000042",
  "status": "requested",
  "amount": "1500.00",
  "currency_code": "ZAR",
  "payment_reference": "INV-000042",
  "account_id": "01964a3b-0000-7000-8000-ac6000000a01",
  "beneficiary_id": "01964a3b-5e6f-7000-8000-beef00000001",
  "external_account_id": "01964a3b-7a8b-7000-8000-beef00000002",
  "destination_account_id": null,
  "transfer": null,
  "created_at": "2026-07-16T09:00:00Z",
  "updated_at": "2026-07-16T09:00:00Z"
}

Get a transfer draft#

GET /api/payments/:id

Poll a draft's status while it moves through approval and execution. Only drafts created through the API are visible here — transfers initiated in the dashboard are not.

request.sh
curl "https://zazu.africa/api/payments/01964a3b-1a2b-7000-8000-feed00000042" \
  -H "Authorization: Bearer sk_live_..."
zazu
zazu transfers get 01964a3b-1a2b-7000-8000-feed00000042
app.js
import { Zazu } from "@getzazu/sdk";
const zazu = new Zazu({ apiKey: process.env.ZAZU_API_KEY });

await zazu.payments.retrieve("01964a3b-1a2b-7000-8000-feed00000042");
app.rb
require "zazu"
zazu = Zazu.new(api_key: ENV["ZAZU_API_KEY"])

zazu.payments.retrieve("01964a3b-1a2b-7000-8000-feed00000042")
app.py
from zazu_sdk import Zazu
client = Zazu(api_key="sk_live_...")

client.payments.retrieve("01964a3b-1a2b-7000-8000-feed00000042")
main.go
import zazu "github.com/getzazu/zazu-go"

client, _ := zazu.New(zazu.WithAPIKey(os.Getenv("ZAZU_API_KEY")))

draft, err := client.Payments.Get(ctx, "01964a3b-1a2b-7000-8000-feed00000042")
main.rs
let client = zazu_sdk::Client::builder()
    .api_key(std::env::var("ZAZU_API_KEY")?)
    .build()?;

let draft = client.payments().get("01964a3b-1a2b-7000-8000-feed00000042")?;
app.exs
{:ok, client} = Zazu.new(api_key: System.fetch_env!("ZAZU_API_KEY"))

{:ok, draft} = Zazu.Payments.get(client, "01964a3b-1a2b-7000-8000-feed00000042")
app.cr
require "zazu"

client = Zazu::Client.new # reads ZAZU_API_KEY

draft = client.payments.get("01964a3b-1a2b-7000-8000-feed00000042")
app.php
use Zazu\Client;

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

$draft = $client->payments->get('01964a3b-1a2b-7000-8000-feed00000042');
response.json
{
  "id": "01964a3b-1a2b-7000-8000-feed00000042",
  "status": "completed",
  "amount": "1500.00",
  "currency_code": "ZAR",
  "payment_reference": "INV-000042",
  "account_id": "01964a3b-0000-7000-8000-ac6000000a01",
  "beneficiary_id": "01964a3b-5e6f-7000-8000-beef00000001",
  "external_account_id": "01964a3b-7a8b-7000-8000-beef00000002",
  "destination_account_id": null,
  "transfer": {
    "id": "01964a3b-3c4d-7000-8000-feed00000043",
    "status": "accepted"
  },
  "created_at": "2026-07-16T09:00:00Z",
  "updated_at": "2026-07-16T10:41:30Z"
}

Error responses#

ScenarioStatusTypeParam
Unknown account / beneficiary / destination422validation_erroraccount_id
Both or neither recipient field given422validation_errorbeneficiary_id
Amount ≤ 0 or not a number422validation_erroramount
Amount exceeds available balance422insufficient_funds
Key's creator may not create transfer drafts403forbidden_error
API transfers not enabled403forbidden_error
Initiation rate limit exceeded429rate_limit_error