Developers
A small API that does one job well.
Create people and payout batches, follow every payment, and hear about results by signed webhook. Start in test mode with fake money.

Quick start
Your first payout in three calls.
Create credentials on the website (Developers, choose Test), then:
POST /oauth/token
grant_type=client_credentials
client_id=tqz_test_1a2b3c4d
client_secret=…
→ { "access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "balance.read payouts.write" }POST /api/v1/sandbox/fund
Authorization: Bearer <token>
{ "amount": 5000 }
→ { "success": true,
"result": { "available": 5000.00 } }POST /api/v1/payout-batches
Authorization: Bearer <token>
Idempotency-Key: payroll-2026-10
{ "name": "October salaries",
"items": [{ "beneficiaryId": "…",
"amount": 1850.00 }] }
POST /api/v1/payout-batches/{id}/submitIn your language
The same call, in the language you use.
Pick a language and copy it. Swap in your own credentials and people.
# 1. Trade your credentials for a token
curl -X POST https://api.tranzaq.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=tqz_test_… -d client_secret=…
# 2. Create a batch (safe to retry)
curl -X POST https://api.tranzaq.com/api/v1/payout-batches \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: payroll-2026-10" \
-H "Content-Type: application/json" \
-d '{ "name": "October salaries",
"items": [{ "beneficiaryId": "…", "amount": 1850.00 }] }'const base = "https://api.tranzaq.com";
const tokenResponse = await fetch(`${base}/oauth/token`, {
method: "POST",
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.TRANZAQ_CLIENT_ID,
client_secret: process.env.TRANZAQ_CLIENT_SECRET,
}),
});
const { access_token } = await tokenResponse.json();
const response = await fetch(`${base}/api/v1/payout-batches`, {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Idempotency-Key": "payroll-2026-10",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "October salaries",
items: [{ beneficiaryId: "…", amount: 1850.0 }],
}),
});
console.log(await response.json());using var http = new HttpClient { BaseAddress = new Uri("https://api.tranzaq.com") };
var tokenResponse = await http.PostAsync("/oauth/token", new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = Environment.GetEnvironmentVariable("TRANZAQ_CLIENT_ID")!,
["client_secret"] = Environment.GetEnvironmentVariable("TRANZAQ_CLIENT_SECRET")!,
}));
var token = (await tokenResponse.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("access_token").GetString();
var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/payout-batches")
{
Content = JsonContent.Create(new
{
name = "October salaries",
items = new[] { new { beneficiaryId = "…", amount = 1850.00m } },
}),
};
request.Headers.Authorization = new("Bearer", token);
request.Headers.Add("Idempotency-Key", "payroll-2026-10");
var response = await http.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());import os
import requests
base = "https://api.tranzaq.com"
token = requests.post(f"{base}/oauth/token", data={
"grant_type": "client_credentials",
"client_id": os.environ["TRANZAQ_CLIENT_ID"],
"client_secret": os.environ["TRANZAQ_CLIENT_SECRET"],
}).json()["access_token"]
response = requests.post(
f"{base}/api/v1/payout-batches",
headers={"Authorization": f"Bearer {token}", "Idempotency-Key": "payroll-2026-10"},
json={"name": "October salaries", "items": [{"beneficiaryId": "…", "amount": 1850.00}]},
)
print(response.json())Authentication
OAuth 2.0, with scopes you control.
Your server signs in with a client ID and secret and receives a token that lasts an hour. Credentials carry only the scopes you choose.
- Client credentials flow: no user sign-in, made for servers
- Ask for fewer scopes than the credentials have, never more
- Revoke credentials and every token already issued stops working at once
- Calls are limited per credentials, so one busy customer never affects another
| Scope | Allows |
|---|---|
| balance.read | Read the balance |
| beneficiaries.read | List and read people |
| beneficiaries.write | Add people |
| payouts.read | List and read payout batches |
| payouts.write | Create, submit and cancel batches |
| sandbox.fund | Test mode only: add fake money |
Test mode
Build with fake money. Go live when you are ready.
Test and live are completely separate: test credentials only ever touch a separate database with test money.
- Test credentials are available the moment you sign up, before your business is approved
- Client IDs show their mode: tqz_test_… or tqz_live_…
- Add fake money with the sandbox funding call
- Payouts really run through the whole flow, using a test bank
- Live credentials unlock when your business has been approved
Test mode
tqz_test_1a2b3c4d
Fake money · separate database
Live mode
tqz_live_5e6f7a8b
Real money · after approval
Webhooks
Hear about every result, safely.
Register an address and we send a signed message when something happens. Failed deliveries are retried, and every attempt is logged.
- payout.batch.approved
- payout.batch.rejected
- payout.batch.cancelled
- payout.batch.completed
- payout.item.succeeded
- payout.item.failed
- funding.credited
const expected = crypto
.createHmac("sha256", signingSecret)
.update(timestamp + "." + rawBody)
.digest("hex");
// X-Tranzaq-Signature must equal `expected`.
// Reject messages with an old X-Tranzaq-Timestamp.Built to be safe
Retries never pay twice.
Creating people and payout batches needs an Idempotency-Key. Send the same key with the same request and you get the first answer back, with nothing created twice.
Same key, same request
Returns the original answer, marked as a replay.
Same key, different request
Refused, so a key can never be reused by mistake.
A rejected request
Does not use up its key, so you can fix it and send it again.
Get started
Start building today.
Create test credentials and make your first payout in minutes.
