Plaidly Docs

Wallet and Tenant Limits

Per-merchant and per-agent limit model with tenant isolation checks

Wallet and Tenant Limits

Use this page for BDT-314 on the M5 Mainnet Safety milestone.

The goal is to make spending and wallet access predictable without letting one merchant or agent read or mutate another tenant's data.

Identity scope

Every request must resolve to one merchant scope.

Scope keyWhat it controlsNotes
merchant_idMerchant-owned sessions, wallets, webhooks, spend policies, and exportsPrimary tenant boundary.
api_keyServer-to-server access for the merchantMust map to exactly one merchant.
agent_idAgent-owned approvals, budget checks, and automation tracesMust be bound to the same merchant scope.
wallet_idWallet read/write operationsWallets inherit the merchant scope that created them.
session_idPayment session reads/writesSessions must never cross tenant scope.

Limit types

Plaidly enforces the same limit model across dashboard, API, and agent workflows.

LimitWhat it boundsCommon use
Session amountMaximum value for a single payment sessionPrevents oversized deposits or test payments that exceed policy.
Daily volumeTotal value a merchant or agent can move in a dayCaps aggregate exposure.
Spend amountMaximum value for an outbound spend decisionStops a single approval from draining the budget.
Failed attemptsCount of failed sensitive actions within a windowDetects brute force, misconfiguration, or repeated abuse.

Limits are evaluated before the write is committed. If a request would cross the boundary, reject it and record the failure in the audit log.

The same mainnet gate applies to the current outbound spend-attempt path: sandbox merchants stay testnet-only, and a merchant must be KYC-verified before payment sessions, payout requests, or agent-wallet spend attempts can use mainnet.

Agent spend policy evaluation

The current agent-wallet spend-policy evaluator matches active rules on these fields:

  • merchant_id
  • agent_wallet_id
  • optional api_domain
  • optional chain
  • optional network
  • optional token_symbol
  • optional min_amount_subunits / max_amount_subunits
  • optional window_start / window_end

Decision precedence is explicit:

  1. deny
  2. require_approval
  3. allow

Within the same decision class, the highest priority wins; ties fall back to the oldest created_at.

If no policy matches, the evaluator currently defaults to allow.

Every evaluation writes both:

  • an agent_wallet_spend_decision_logs row
  • an audit event named agent_wallet.spend_policy_decided

POST /v1/agent-wallets/{agent_wallet_id}/spend-attempts invokes this evaluator before any ledger or broadcast side effect. Denied attempts return denied and do not create ledger rows. Allowed attempts create a pending spent ledger entry and return pending; require_approval attempts return approval_required.

Tenant isolation checks

Apply these checks to every sensitive route:

  1. The credential or agent must belong to the same merchant_id as the target object.
  2. The target wallet_id, session_id, or policy id must resolve to that same merchant scope.
  3. Read paths and write paths use the same ownership check.
  4. List and export paths must filter by the caller's merchant scope before applying paging or date filters.
  5. Admin or support overrides must still write an audit event with the actor identity and reason.

Wallet-specific rules

  • Wallet creation, rotation, funding, and sweep activity inherit merchant scope.
  • Settlement wallets stay hidden outside the owning merchant and support-only surfaces.
  • Wallet identifiers can appear in logs and audit records, but secrets and private keys do not.
  • If a wallet is reassigned or rotated, the old and new wallet states must remain distinguishable in the audit trail.

Failure behavior

When a limit or isolation check fails:

OutcomeExpected behavior
Standard merchant requestReturn a safe validation or authorization error.
Agent-driven requestSurface the same denial to the agent and the dashboard.
Export or list requestReturn only data from the caller's own merchant scope.
Repeated boundary probingEscalate to the abuse monitoring runbook.

Treat mainnet_kyc_required and sandbox_mainnet_blocked as policy gates, not abuse by themselves. They become an abuse signal only when the same actor keeps probing after the denial.

Do not leak whether another tenant exists. The caller should see that the request is not allowed, not that a different merchant owns the object.

On this page