Errors & idempotency
One error envelope, stable codes, and idempotency that actually protects money.
Not live yet — api.baynoy.com does not resolve, so the examples below will not run today. These shapes are settled: design against them now and nothing changes when keys go live.
One error envelope
Every non-2xx response carries {"error": {"code", "message", "details?"}}. Branch on code — it is a stable identifier. Never branch on message: messages are for humans and they change.
try {
await baynoy.payouts.create({ amount: 250000, currency: "EUR", beneficiary: "ben_123" },
{ idempotencyKey: key });
} catch (err) {
switch (err.code) {
case "insufficient_funds": return showTopUp();
case "beneficiary_cooldown": return showCooldown(err.details.availableAt);
case "idempotency_key_reused": return; // already done — do nothing
case "rate_limited": return retryAfter(err.details.retryAfterMs);
default: throw err; // unknown codes are bugs, not flows
}
}Idempotency is not optional on money
Financial POSTs require an Idempotency-Key scoped to your account and endpoint. The same key with the same body returns the first result; the same key with a different body is a conflict. Generate the key before the first attempt and reuse it for every retry of that same intent.
curl https://api.baynoy.com/v1/payouts \\
-H "Authorization: Bearer bk_test_..." \\
-H "Idempotency-Key: 7c1f0b6e-..." \\
-d amount=250000 -d currency=EUR -d beneficiary=ben_123
# same key + same body -> the original result, no second payout
# same key + other body -> 409 idempotency_key_reusedRetries and rate limits
Retry 5xx and rate_limited with exponential backoff and jitter; never retry a 4xx other than rate_limited without changing something. Rate-limit responses carry the wait time in details — respect it rather than guessing.