Documentation Index
Fetch the complete documentation index at: https://docs.zendfi.tech/llms.txt
Use this file to discover all available pages before exploring further.
SDK Sub Accounts
The SDK exposes first-class methods for sub-account operations.
Methods
zendfi.createSubAccount(...)
zendfi.listSubAccounts()
zendfi.getSubAccount(...)
zendfi.getSubAccountBalance(...)
zendfi.getSubAccountTtlPolicy()
zendfi.updateSubAccountTtlPolicy(...)
zendfi.mintSubAccountDelegationToken(...)
zendfi.mintSubAccountChildDelegationToken(...)
zendfi.freezeSubAccount(...)
zendfi.unfreezeSubAccount(...)
zendfi.drainSubAccount(...)
zendfi.withdrawFromSubAccount(...)
zendfi.withdrawSubAccountToBank(...)
zendfi.createSubAccountAutomationToken(...)
zendfi.revokeSubAccountAutomationToken(...)
zendfi.createSubAccountSigningGrant(...)
zendfi.startSubAccountSigningGrantBrowserIntent(...)
zendfi.pollSubAccountSigningGrantBrowserIntent(...)
zendfi.revokeSubAccountSigningGrant(...)
zendfi.createSubAccountPolicy(...)
zendfi.dryRunSubAccountPolicy(...)
zendfi.getSubAccountPolicy(...)
zendfi.createSubAccountWebhookTriggerSubscription(...)
zendfi.listSubAccountWebhookTriggerSubscriptions()
zendfi.createSubAccountExecutionIntent(...)
zendfi.approveSubAccountExecutionIntent(...)
zendfi.releaseSubAccountExecutionIntentBySignal(...)
zendfi.createSubAccountBalanceRule(...)
zendfi.closeSubAccount(...)
Create
const sub = await zendfi.createSubAccount({
label: 'user_paschal_001',
spend_limit_usdc: 500,
access_mode: 'delegated',
yield_enabled: false,
});
List and Inspect
const subaccounts = await zendfi.listSubAccounts();
const sub = await zendfi.getSubAccount(subaccounts[0].id);
const balance = await zendfi.getSubAccountBalance(sub.id);
Merchant TTL Policy
Use merchant TTL policy to control max TTL ceilings for signing grants, automation tokens, and child delegation tokens.
const ttlPolicy = await zendfi.getSubAccountTtlPolicy();
await zendfi.updateSubAccountTtlPolicy({
signing_grant_max_ttl_seconds: 60 * 60 * 24 * 14,
automation_token_max_ttl_seconds: 60 * 60 * 24 * 14,
child_delegation_max_ttl_seconds: 60 * 60 * 24 * 3,
});
All TTL-bearing endpoints validate against the merchant’s effective policy and platform hard caps.
Mint Delegation Token
const token = await zendfi.mintSubAccountDelegationToken(sub.id, {
scope: 'withdraw_only',
spend_limit_usdc: 50,
expires_in_seconds: 900,
whitelist: ['7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU'],
single_use: true,
policy_version_id: 'a6e9db6f-e264-4f9d-a4c7-7f94b18f0f34',
agent_label: 'risk-bot-v2',
});
Mint Child Delegation Token
const child = await zendfi.mintSubAccountChildDelegationToken(sub.id, {
parent_delegation_token: token.delegation_token,
scope: 'withdraw_only',
spend_limit_usdc: 25,
expires_in_seconds: 900,
single_use: true,
policy_version_id: 'a6e9db6f-e264-4f9d-a4c7-7f94b18f0f34',
});
Freeze
await zendfi.freezeSubAccount(sub.id, { reason: 'fraud-review' });
Unfreeze
await zendfi.unfreezeSubAccount(sub.id, { reason: 'manual-review-cleared' });
Unfreeze guardrails: only frozen sub-accounts can be unfrozen, and previously revoked delegation tokens remain revoked.
Drain to Merchant Wallet
await zendfi.drainSubAccount(sub.id, {
token: 'Usdc',
amount: 10,
mode: 'live',
passkey_signature,
});
Withdraw to External Address
await zendfi.withdrawFromSubAccount(sub.id, {
to_address: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
amount: 5,
token: 'Usdc',
mode: 'live',
delegation_token: token.delegation_token,
signing_grant: 'ssgt_xxxxx',
execution_intent_id: '89d52313-1cea-4313-80a4-23afc543287e',
});
signing_grant and passkey_signature are mutually exclusive.
passkey_signature is now optional for withdrawFromSubAccount and should only be used as an interactive fallback.
Withdraw to Bank (One-Shot)
await zendfi.withdrawSubAccountToBank(sub.id, {
amount_usdc: 25,
bank_id: 'GTB',
account_number: '0123456789',
mode: 'live',
signing_grant: 'ssgt_xxxxx',
automation_token: 'saatk_xxxxx',
delegation_token: token.delegation_token,
execution_intent_id: '89d52313-1cea-4313-80a4-23afc543287e',
});
bank_id accepts a bank identifier value (PAJ bank id, bank code, or bank name).
This method mirrors split bank-withdraw proxy-email OTP automation, so no manual OTP entry is required in your integration.
automation_token and delegation_token are mutually exclusive.
signing_grant and passkey_signature are mutually exclusive.
passkey_signature is now optional for withdrawSubAccountToBank and should only be used as an interactive fallback.
Mint Automation Token (Session Auth)
const automation = await zendfi.createSubAccountAutomationToken({
sub_account_id: sub.id,
ttl_seconds: 3600,
max_uses: 25,
total_limit_usdc: 500,
per_tx_limit_usdc: 50,
allowed_bank_ids: ['GTB'],
allowed_account_numbers: ['0123456789'],
mode: 'live',
policy_version_id: 'a6e9db6f-e264-4f9d-a4c7-7f94b18f0f34',
parent_token_id: 'c4fab649-f4f0-438f-8dc8-9737a82c59d3',
agent_label: 'reconciliation-agent',
});
Revoke Automation Token
await zendfi.revokeSubAccountAutomationToken(automation.token_id);
Mint Signing Grant (Session Auth)
const grant = await zendfi.createSubAccountSigningGrant({
sub_account_id: sub.id,
ttl_seconds: 3600,
max_uses: 25,
total_limit_usdc: 500,
per_tx_limit_usdc: 50,
allowed_bank_ids: ['GTB'],
allowed_account_numbers: ['0123456789'],
mode: 'live',
policy_version_id: 'a6e9db6f-e264-4f9d-a4c7-7f94b18f0f34',
parent_grant_id: 'e9ae9bc0-e640-4a2b-a7e8-d85b93b1a8a0',
active_days_utc: [1, 2, 3, 4, 5],
active_start_time_utc: '09:00',
active_end_time_utc: '18:00',
auto_renew: true,
agent_label: 'ops-approver',
passkey_signature,
});
Policies
const policy = await zendfi.createSubAccountPolicy({
sub_account_id: sub.id,
policy_type: 'signing_grant',
status: 'active',
policy_json: {
max_per_tx_usdc: 100,
max_per_day_usdc: 500,
allowed_modes: ['live'],
allowed_weekdays_utc: [1, 2, 3, 4, 5],
active_start_utc: '09:00',
active_end_utc: '18:00',
},
});
const dryRun = await zendfi.dryRunSubAccountPolicy({
policy_json: policy.policy_json,
amount_usdc: 25,
mode: 'live',
sub_account_id: sub.id,
daily_spend_usdc: 100,
});
const fetched = await zendfi.getSubAccountPolicy(policy.policy_id);
Webhook Triggers
await zendfi.createSubAccountWebhookTriggerSubscription({
sub_account_id: sub.id,
trigger_type: 'balance_below',
threshold_value_usdc: 20,
cooldown_seconds: 300,
});
const triggers = await zendfi.listSubAccountWebhookTriggerSubscriptions();
Execution Intents
const intent = await zendfi.createSubAccountExecutionIntent({
sub_account_id: sub.id,
intent_type: 'withdraw_to_bank',
requires_signal_type: 'webhook_ack',
payload: {
bank_id: 'GTB',
account_number: '0123456789',
},
expires_in_seconds: 900,
});
await zendfi.approveSubAccountExecutionIntent(intent.intent_id, { approve: true });
if (intent.signal_token) {
await zendfi.releaseSubAccountExecutionIntentBySignal({ signal_token: intent.signal_token });
}
Balance Rules
await zendfi.createSubAccountBalanceRule({
sub_account_id: sub.id,
rule_name: 'keep-hot-wallet-funded',
rule_type: 'topup_below',
threshold_usdc: 50,
action_amount_usdc: 100,
max_actions_per_day: 6,
cooldown_seconds: 300,
});
import open from 'open';
const intent = await zendfi.startSubAccountSigningGrantBrowserIntent({
sub_account_id: sub.id,
ttl_seconds: 3600,
max_uses: 25,
total_limit_usdc: 500,
per_tx_limit_usdc: 50,
mode: 'live',
});
await open(intent.approval_url);
let signingGrant: string | undefined;
for (let i = 0; i < 180; i += 1) {
const poll = await zendfi.pollSubAccountSigningGrantBrowserIntent({
intent_id: intent.intent_id,
intent_token: intent.intent_token,
});
if (poll.completed) {
if (poll.status !== 'approved' || !poll.grant) {
throw new Error(poll.error || `intent ended in ${poll.status}`);
}
signingGrant = poll.grant.signing_grant;
break;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
if (!signingGrant) {
throw new Error('Timed out waiting for passkey approval');
}
Use this signingGrant in withdrawFromSubAccount or withdrawSubAccountToBank.
Frontend Browser Example
Use this pattern in a dashboard/web app where a user clicks a button to approve with passkey.
async function mintSigningGrantInBrowser(subAccountId: string) {
const intent = await zendfi.startSubAccountSigningGrantBrowserIntent({
sub_account_id: subAccountId,
ttl_seconds: 3600,
max_uses: 25,
total_limit_usdc: 500,
per_tx_limit_usdc: 50,
mode: 'live',
});
// Must run in direct user interaction context to avoid popup blocking.
window.open(intent.approval_url, '_blank', 'noopener,noreferrer');
for (let i = 0; i < 180; i += 1) {
const poll = await zendfi.pollSubAccountSigningGrantBrowserIntent({
intent_id: intent.intent_id,
intent_token: intent.intent_token,
});
if (poll.completed) {
if (poll.status !== 'approved' || !poll.grant) {
throw new Error(poll.error || `intent ended in ${poll.status}`);
}
return poll.grant.signing_grant;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
throw new Error('Timed out waiting for passkey approval');
}
Revoke Signing Grant
await zendfi.revokeSubAccountSigningGrant(grant.grant_id);
Close
await zendfi.closeSubAccount(sub.id);
Passkey Signature Shape
type PasskeySignaturePayload = {
credential_id: string;
authenticator_data: number[];
signature: number[];
client_data_json: number[];
};
Use this payload for sensitive transfer methods (drainSubAccount, withdrawFromSubAccount) and signing-grant minting (createSubAccountSigningGrant).