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
Sub-accounts are merchant-owned child wallets with dedicated MPC keys. They let you isolate balances, scope delegated access, and route splits/offramp flows by sub-account.
Sub-accounts are created programmatically via API, SDK, or CLI. The dashboard intentionally provides a read-only watchtower view.
Authentication
All sub-account endpoints are protected merchant endpoints.
- Use
Authorization: Bearer zfi_test_... or Authorization: Bearer zfi_live_...
- Dashboard sessions also work for merchant UI usage.
Create Sub-Account
Immutable label for this sub-account. Must be unique per merchant.
Optional configured spend limit (USDC).
access_mode
string
default:"delegated"
delegated or merchant_managed.
Yield toggle metadata for this sub-account.
Example
curl -X POST https://api.zendfi.tech/api/v1/subaccounts \
-H "Authorization: Bearer zfi_test_your_key" \
-H "Content-Type: application/json" \
-d '{
"label": "user_paschal_001",
"spend_limit_usdc": 500,
"access_mode": "delegated",
"yield_enabled": false
}'
Response
{
"id": "sa_7b1w9j2k4m8p",
"merchant_id": "e9c1c4dc-7aa5-4ad5-90af-6e7035f4503d",
"wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"label": "user_paschal_001",
"status": "active",
"spend_limit_usdc": 500,
"access_mode": "delegated",
"session_key": null,
"yield_enabled": false,
"created_at": "2026-03-26T18:00:00Z"
}
List Sub-Accounts
Returns all sub-accounts under the authenticated merchant.
Get Sub-Account
GET /api/v1/subaccounts/{id}
id accepts either the UUID or external ID (for example sa_xxxxx).
Get Sub-Account Balance
GET /api/v1/subaccounts/{id}/balance
Response
{
"subaccount_id": "sa_7b1w9j2k4m8p",
"wallet_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"usdc_balance": 125.42,
"sol_balance": 0.0192,
"accrued_yield": 0,
"yield_enabled": false,
"status": "active"
}
Get Merchant Sub-Account TTL Policy
GET /api/v1/subaccounts/ttl-policy
Returns effective TTL ceilings, merchant overrides, and platform hard caps.
Response
{
"merchant_id": "e9c1c4dc-7aa5-4ad5-90af-6e7035f4503d",
"policy": {
"effective": {
"signing_grant_max_ttl_seconds": 1209600,
"automation_token_max_ttl_seconds": 1209600,
"child_delegation_max_ttl_seconds": 259200
},
"merchant_overrides": {
"signing_grant_max_ttl_seconds": 1209600,
"automation_token_max_ttl_seconds": 1209600,
"child_delegation_max_ttl_seconds": 259200
},
"platform_hard_caps": {
"signing_grant_max_ttl_seconds": 7776000,
"automation_token_max_ttl_seconds": 7776000,
"child_delegation_max_ttl_seconds": 7776000
}
}
}
Update Merchant Sub-Account TTL Policy
POST /api/v1/subaccounts/ttl-policy
Updates merchant-specific TTL ceilings. Each provided value must be within the platform hard-cap range.
Body
{
"signing_grant_max_ttl_seconds": 1209600,
"automation_token_max_ttl_seconds": 1209600,
"child_delegation_max_ttl_seconds": 259200
}
Response shape matches GET /api/v1/subaccounts/ttl-policy.
Mint Delegation Token
POST /api/v1/subaccounts/{id}/session-key
deposit_only, withdraw_only, spend_only, read_only, or full_access.
Optional spend cap enforced for this token.
Token lifetime in seconds.
Optional destination allowlist.
If true, token auto-revokes after first successful authorized use.
Optional policy version UUID attached to this token.
Optional actor label for audit attribution.
Optional actor public key for attribution.
Optional structured actor metadata.
Response
{
"token_id": "8fe55f3b-7bd5-4768-a6df-6b28dbad1e61",
"subaccount_id": "sa_7b1w9j2k4m8p",
"scope": "withdraw_only",
"expires_at": "2026-03-26T18:15:00Z",
"spend_limit_usdc": 50,
"delegation_token": "satk_xxxxx"
}
delegation_token is returned once. Treat it like a secret and never log it.
Mint Child Delegation Token
POST /api/v1/subaccounts/{id}/session-key/child
Mints an attenuated child token from an existing parent delegation token.
expires_in_seconds must be within the merchant effective child-delegation TTL ceiling and cannot exceed parent token expiry.
Body
{
"parent_delegation_token": "satk_parent_xxxxx",
"scope": "withdraw_only",
"spend_limit_usdc": 25,
"expires_in_seconds": 900,
"whitelist": ["7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"],
"single_use": true,
"policy_version_id": "a6e9db6f-e264-4f9d-a4c7-7f94b18f0f34",
"agent_label": "risk-bot-v2",
"agent_public_key": "ed25519:...",
"agent_metadata": {
"workflow": "vendor-payout"
}
}
Response
{
"token_id": "d5a8012b-95ca-4e94-a0b6-e4674df22e4e",
"parent_token_id": "8fe55f3b-7bd5-4768-a6df-6b28dbad1e61",
"subaccount_id": "sa_7b1w9j2k4m8p",
"scope": "withdraw_only",
"expires_at": "2026-03-26T18:15:00Z",
"spend_limit_usdc": 25,
"delegation_depth": 1,
"delegation_token": "satk_child_xxxxx"
}
Freeze Sub-Account
POST /api/v1/subaccounts/{id}/freeze
Optional body:
{ "reason": "fraud-review" }
Freezing revokes active delegation tokens and blocks sub-account activity.
Unfreeze Sub-Account
POST /api/v1/subaccounts/{id}/unfreeze
Optional body:
{ "reason": "manual-review-cleared" }
Guardrails:
- Only sub-accounts currently in
frozen status can be unfrozen.
closed sub-accounts cannot be unfrozen.
- Previously revoked delegation tokens are not reactivated. Mint new token(s) if needed.
Drain Sub-Account
POST /api/v1/subaccounts/{id}/drain
Drains funds from the sub-account wallet back to the merchant wallet.
Body
{
"token": "Usdc",
"amount": 25,
"mode": "live",
"passkey_signature": {
"credential_id": "...",
"authenticator_data": [1,2,3],
"signature": [4,5,6],
"client_data_json": [7,8,9]
}
}
Withdraw From Sub-Account
POST /api/v1/subaccounts/{id}/withdraw
Body
{
"to_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"amount": 10,
"token": "Usdc",
"mode": "live",
"delegation_token": "satk_xxxxx",
"signing_grant": "ssgt_xxxxx",
"execution_intent_id": "89d52313-1cea-4313-80a4-23afc543287e"
}
signing_grant and passkey_signature are mutually exclusive.
passkey_signature remains available as an interactive fallback and is no longer required for headless external-withdraw flows.
Withdraw Sub-Account To Bank (One-Shot)
POST /api/v1/subaccounts/{id}/withdraw-bank
Creates a PAJ offramp order and automatically completes OTP verification using the same proxy-email automation model used by split bank withdrawals.
Body
{
"amount_usdc": 25,
"bank_id": "GTB",
"account_number": "0123456789",
"mode": "live",
"signing_grant": "ssgt_xxxxx",
"automation_token": "saatk_xxxxx",
"delegation_token": "satk_xxxxx",
"execution_intent_id": "89d52313-1cea-4313-80a4-23afc543287e"
}
bank_id accepts a bank identifier value: PAJ bank id, bank code, or bank name.
Response
{
"success": true,
"subaccount_id": "sa_7b1w9j2k4m8p",
"order_id": "17d8564c-6807-4f58-b8f0-c4338fb310b4",
"paj_order_id": "67f1d2f54d9f553abf9a2310",
"paj_deposit_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"bank_account_number": "0123456789",
"bank_account_name": "Paschal Okafor",
"amount_usdc": 25,
"fiat_amount": 38500,
"exchange_rate": 1540,
"fee": 0.25,
"status": "PAID",
"transaction_signature": "3k8...xyz"
}
automation_token and delegation_token are mutually exclusive.
signing_grant and passkey_signature are mutually exclusive.
For API-key initiated bank withdrawals, provide:
- one policy token (
automation_token or delegation_token)
- one signing authorization (
signing_grant)
passkey_signature remains available as an interactive fallback and is no longer required for headless flows.
Mint Sub-Account Automation Token
POST /api/v1/merchants/me/subaccounts/automation-tokens
Requires merchant dashboard session auth. Use this endpoint to mint bounded headless automation credentials for withdraw-bank.
ttl_seconds must be within the merchant effective automation-token TTL ceiling.
Body
{
"sub_account_id": "sa_7b1w9j2k4m8p",
"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",
"agent_public_key": "ed25519:...",
"agent_metadata": {
"pipeline": "nightly-settlement"
}
}
Response
{
"token_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
"automation_token": "saatk_xxxxx",
"merchant_id": "e9c1c4dc-7aa5-4ad5-90af-6e7035f4503d",
"sub_account_id": "sa_7b1w9j2k4m8p",
"expires_at": "2026-03-26T20:00:00Z",
"max_uses": 25,
"total_limit_usdc": 500,
"per_tx_limit_usdc": 50,
"allowed_bank_ids": ["GTB"],
"allowed_account_numbers": ["0123456789"],
"mode": "live",
"created_at": "2026-03-26T19:00:00Z"
}
automation_token is returned once. Treat it as a secret and never log it.
Mint Sub-Account Signing Grant
POST /api/v1/merchants/me/subaccounts/signing-grants
Requires merchant dashboard session auth. Use this endpoint to perform one interactive passkey approval and mint bounded headless signing credentials for withdraw-bank.
ttl_seconds must be within the merchant effective signing-grant TTL ceiling.
Body
{
"sub_account_id": "sa_7b1w9j2k4m8p",
"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",
"agent_public_key": "ed25519:...",
"agent_metadata": {
"team": "ops"
},
"passkey_signature": {
"credential_id": "...",
"authenticator_data": [1,2,3],
"signature": [4,5,6],
"client_data_json": [7,8,9]
}
}
Response
{
"grant_id": "2f8fad5b-d9cb-469f-a165-70867728950e",
"signing_grant": "ssgt_xxxxx",
"merchant_id": "e9c1c4dc-7aa5-4ad5-90af-6e7035f4503d",
"sub_account_id": "sa_7b1w9j2k4m8p",
"expires_at": "2026-03-26T20:00:00Z",
"max_uses": 25,
"total_limit_usdc": 500,
"per_tx_limit_usdc": 50,
"allowed_bank_ids": ["GTB"],
"allowed_account_numbers": ["0123456789"],
"mode": "live",
"created_at": "2026-03-26T19:00:00Z"
}
signing_grant is returned once. Treat it as a secret and never log it.
Start Signing Grant Browser Intent
POST /api/v1/subaccounts/signing-grants/browser-intents/start
Recommended API-key flow for CLI/SDK parity with merchant dashboard UX. This creates a short-lived browser approval intent and returns a one-time approval URL.
ttl_seconds must be within the merchant effective signing-grant TTL ceiling.
Body
{
"sub_account_id": "sa_7b1w9j2k4m8p",
"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"
}
Response
{
"intent_id": "f8df2a11-4f6b-4dbf-b5fb-d03ddcf3f2e3",
"intent_token": "sgit_xxxxx",
"approval_url": "https://api.zendfi.tech/api/v1/subaccounts/signing-grants/approval?intent_id=f8df2a11-4f6b-4dbf-b5fb-d03ddcf3f2e3&token=sgit_xxxxx",
"expires_at": "2026-03-27T12:00:00Z"
}
intent_token is returned once. Treat it as a secret and never log it.
Poll Signing Grant Browser Intent
POST /api/v1/subaccounts/signing-grants/browser-intents/poll
Polls intent status. On approval, this returns the minted signing_grant exactly once and marks the intent consumed.
Body
{
"intent_id": "f8df2a11-4f6b-4dbf-b5fb-d03ddcf3f2e3",
"intent_token": "sgit_xxxxx"
}
Response (approved)
{
"status": "approved",
"completed": true,
"expires_at": "2026-03-27T12:00:00Z",
"grant": {
"grant_id": "2f8fad5b-d9cb-469f-a165-70867728950e",
"signing_grant": "ssgt_xxxxx",
"merchant_id": "e9c1c4dc-7aa5-4ad5-90af-6e7035f4503d",
"sub_account_id": "sa_7b1w9j2k4m8p",
"expires_at": "2026-03-26T20:00:00Z",
"max_uses": 25,
"total_limit_usdc": 500,
"per_tx_limit_usdc": 50,
"allowed_bank_ids": ["GTB"],
"allowed_account_numbers": ["0123456789"],
"mode": "live",
"created_at": "2026-03-26T19:00:00Z"
}
}
Revoke Sub-Account Signing Grant
POST /api/v1/merchants/me/subaccounts/signing-grants/{grant_id}/revoke
Immediately revokes the signing grant.
Response
{
"success": true,
"grant_id": "2f8fad5b-d9cb-469f-a165-70867728950e",
"status": "revoked"
}
Revoke Sub-Account Automation Token
POST /api/v1/merchants/me/subaccounts/automation-tokens/{token_id}/revoke
Immediately revokes the automation token.
Response
{
"success": true,
"token_id": "0f8fad5b-d9cb-469f-a165-70867728950e",
"status": "revoked"
}
This endpoint is API-key compatible and requires no manual OTP submission in your client. OTP handling is automated server-side via proxy email and IMAP monitor.
Create Policy Version
POST /api/v1/merchants/me/subaccounts/policies
Creates a versioned policy document that can be attached to delegation tokens, automation tokens, signing grants, triggers, intents, and balance rules.
Body
{
"sub_account_id": "sa_7b1w9j2k4m8p",
"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"
}
}
Dry Run Policy
POST /api/v1/merchants/me/subaccounts/policies/dry-run
Evaluates a policy document without persisting it.
Get Policy
GET /api/v1/merchants/me/subaccounts/policies/{policy_id}
Create Webhook Trigger Subscription
POST /api/v1/merchants/me/subaccounts/webhook-triggers
Supported trigger types:
balance_below
balance_above
threshold_crossed
funds_arrival
daily_withdrawal_above
List Webhook Trigger Subscriptions
GET /api/v1/merchants/me/subaccounts/webhook-triggers
Create Execution Intent
POST /api/v1/merchants/me/subaccounts/execution-intents
Creates a maker-checker style execution gate.
Approve Execution Intent
POST /api/v1/merchants/me/subaccounts/execution-intents/{intent_id}/approve
Release Execution Intent by Signal
POST /api/v1/subaccounts/execution-intents/release
Releases a pending or approved intent using its one-time signal token.
Create Balance Rule
POST /api/v1/merchants/me/subaccounts/balance-rules
Creates an automated balance action rule (topup_below or drain_above).
Close Sub-Account
DELETE /api/v1/subaccounts/{id}
Closes the sub-account, deactivates its wallet, and revokes associated delegation tokens.
Webhook Coverage
Sub-account flows emit:
- Withdrawal events:
WithdrawalInitiated, WithdrawalFailed, WithdrawalCompleted
- Lifecycle events:
SubAccountCreated, SubAccountDelegationTokenMinted, SubAccountFrozen, SubAccountUnfrozen, SubAccountClosed
- Reactive controls:
SubAccountBalanceLow, SubAccountBalanceHigh, SubAccountThresholdCrossed
- Execution gates:
SubAccountExecutionGatePending, SubAccountExecutionGateReleased
See Webhooks for payload structure.