Skip to main content

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).