A cash pool is a collection of payment accounts from one or more payment service providers that you want to manage as a single unit. Cash pools aggregate balances across multiple accounts, providing a unified view of your funds for financial reporting, treasury management, and reconciliation.
Cash pools can be created in two ways:
- Static pools: Explicitly specify which accounts to include
- Dynamic pools: Define criteria that automatically match accounts
Why cash pools?#
When managing funds across multiple financial institutions, you often need to view and analyze balances from different accounts together. For example, you might have:
- A Stripe account for card payments
- A PayPal account for alternative payments
- A bank account for direct transfers
Rather than querying each account individually, cash pools let you:
- View aggregated balances across all accounts in real-time
- Track historical balances at specific points in time
- Simplify financial reporting with consolidated balance views
- Enable reconciliation against your internal ledger
- Monitor liquidity across multiple financial institutions
- Automatically include new accounts that match your criteria (dynamic pools)
Pool structure#
A cash pool contains:
- ID: Unique UUID identifier
- Name: Human-readable name (must be unique)
- Created at: Timestamp when the pool was created
- Pool accounts: Array of account IDs included in the pool (for static pools)
- Query: JSON query defining account matching criteria (for dynamic pools)
Creating cash pools#
Cash pools are created via the Payments API using one of two approaches:
Static pools#
Specify exact accounts to include in the pool:
curl -X POST $FORMANCE_API_URL/api/payments/pools \
-H "Content-Type: application/json" \
-d '{
"name": "My Static Pool",
"accountIDs": [
"stripe:acc_1234567890",
"wise:account-uuid-here"
]
}'Dynamic pools#
Define criteria that automatically match accounts:
curl -X POST $FORMANCE_API_URL/api/payments/pools \
-H "Content-Type: application/json" \
-d '{
"name": "All USD Stripe Accounts",
"query": "{\"$match\": {\"connector_id\": \"stripe:connector-id\", \"default_asset\": \"USD\"}}"
}'Static pools require explicit account IDs, while dynamic pools use JSON query syntax. The two approaches are mutually exclusive - you cannot specify both accountIDs and query in the same request.
Query syntax#
Dynamic pools use the same query syntax as the List Accounts API. Supported query fields include:
- connector_id: Match accounts from specific connectors
- default_asset: Filter by currency/asset
- type: Account type (e.g.,
INTERNAL,EXTERNAL) - name: Account name matching
- psu_id: Payment service user ID
- metadata.{key}: Custom metadata fields
Query examples#
All EUR accounts across connectors:
{
"name": "EUR Accounts",
"query": "{\"$match\": {\"default_asset\": \"EUR\"}}"
}Stripe accounts only:
{
"name": "Stripe Pool",
"query": "{\"$match\": {\"connector_id\": \"stripe:connector-123\"}}"
}Internal accounts with specific metadata:
{
"name": "Business Accounts",
"query": "{\"$match\": {\"type\": \"INTERNAL\", \"metadata.category\": \"business\"}}"
}Static vs. Dynamic pools#
| Feature | Static Pools | Dynamic Pools |
|---|---|---|
| Account membership | Static - explicitly defined | Dynamic - automatically updated |
| New accounts | Must be manually added | Automatically included if they match criteria |
| Use case | Known, fixed set of accounts | Accounts that change frequently or match patterns |
| Management | Requires manual updates | Self-maintaining |
| Account operations | Can add/remove individual accounts | Cannot modify - accounts determined by query |
When to use static pools#
- Fixed account sets: When you have a specific, unchanging group of accounts
- Mixed criteria: When accounts don't follow a consistent pattern
- Fine-grained control: When you need to include/exclude specific accounts manually
When to use dynamic pools#
- Dynamic environments: When new accounts are frequently created
- Connector-based grouping: All accounts from a specific payment provider
- Currency segregation: Separate pools for different currencies
- Automated workflows: When pools need to be configured declaratively via API
Dynamic pools do not support manual account addition or removal. To modify membership, update the pool's query criteria.
Using cash pools#
Balance queries#
Pool balance endpoints aggregate balances from all accounts in the pool by asset:
Latest aggregated balances:
curl -X GET $FORMANCE_API_URL/api/payments/pools/<POOL_ID>/balances/latestHistorical balances at a specific timestamp:
curl -X GET $FORMANCE_API_URL/api/payments/pools/<POOL_ID>/balances?at=2024-01-15T23:59:59ZBalance aggregation logic:
- Static pools: Fetches balances from the specified account IDs
- Dynamic pools: Runs the query to find matching accounts, then fetches their balances
- Groups by asset/currency
- Sums amounts for each asset
- Returns array of aggregated balances
Dynamic pools resolve their account membership dynamically at query time, ensuring balances always reflect the current set of accounts matching your criteria.
Reconciliation policies#
Cash pool IDs are used in the paymentsPoolID field of reconciliation policies:
{
"name": "string",
"ledgerName": "string",
"ledgerQuery": "object",
"paymentsPoolID": "uuid-string"
}The reconciliation service compares ledger account balances against the aggregated cash pool balances. See Getting Started with Reconciliation for a complete workflow.