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

# Sub Accounts

> SDK methods for sub-account lifecycle, delegation tokens, and scoped transfers.

# SDK Sub Accounts

The SDK exposes first-class methods for sub-account operations.

## Methods

```typescript theme={null}
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

```typescript theme={null}
const sub = await zendfi.createSubAccount({
  label: 'user_paschal_001',
  spend_limit_usdc: 500,
  access_mode: 'delegated',
  yield_enabled: false,
});
```

## List and Inspect

```typescript theme={null}
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.

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
await zendfi.freezeSubAccount(sub.id, { reason: 'fraud-review' });
```

## Unfreeze

```typescript theme={null}
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

```typescript theme={null}
await zendfi.drainSubAccount(sub.id, {
  token: 'Usdc',
  amount: 10,
  mode: 'live',
  passkey_signature,
});
```

## Withdraw to External Address

```typescript theme={null}
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)

```typescript theme={null}
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)

```typescript theme={null}
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

```typescript theme={null}
await zendfi.revokeSubAccountAutomationToken(automation.token_id);
```

## Mint Signing Grant (Session Auth)

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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,
});
```

## Browser-Mediated Signing Grant (Recommended)

```typescript theme={null}
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.

```typescript theme={null}
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

```typescript theme={null}
await zendfi.revokeSubAccountSigningGrant(grant.grant_id);
```

## Close

```typescript theme={null}
await zendfi.closeSubAccount(sub.id);
```

## Passkey Signature Shape

```typescript theme={null}
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`).
