Limits
Money Service limits are split into two categories: technical limits (enforced by the database schema, immutable) and business limits (defined by the tenant's Billing plan, cached in Valkey).
Technical Limits (Immutable)
| Limit | Value | Enforcement |
|---|---|---|
| Amount type | Positive BIGINT integer (cents) | PostgreSQL schema |
| Minimum amount | 1 cent (amount > 0) | Application layer + DB CHECK |
| Zero amount | Not allowed → 400 Bad Request | Application layer |
| Negative amount | Not allowed → 400 Bad Request | Application layer |
| Negative balance | Impossible | PostgreSQL CHECK (balance >= 0) |
| BIGINT max | 9,223,372,036,854,775,807 cents ≈ $92 quadrillion | PostgreSQL data type |
The BIGINT ceiling is a technical boundary, not a business limit.
No tenant will realistically approach it in the lifetime of the
platform. It provides multi-decade headroom without a schema
migration.
Business Limits (Billing Plan)
Business limits are defined per tenant in the Billing Service and can be adjusted by the Platform Owner:
| Limit | Default value | Env variable |
|---|---|---|
| Max single transaction amount | $100 000 (10 000 000 cents) | Configurable per tenant plan |
| Max wallet balance | $1 000 000 (100 000 000 cents) | Configurable per tenant plan |
These are defaults for a standard plan. The Billing Service can configure higher limits for enterprise tenants.
Limits Caching (Valkey)
The Money Service does not query the Billing Service on every transaction. Limits are fetched once and cached in Valkey:
Valkey key: billing:plan_limits:{tenantId}
Value: { "maxTxAmount": 10000000, "maxBalance": 100000000 }
TTL: 5 minutes
Cache Resolution Flow
Transaction request arrives:
1. Check Valkey: billing:plan_limits:{tenantId}
┌─ HIT: use cached limits (may be up to 5 min stale)
│ → execute transaction
│
└─ MISS: gRPC call to Billing Service
┌─ Billing OK: cache result in Valkey (TTL 5 min)
│ → execute transaction
│
└─ Billing DOWN: FAIL-CLOSED
→ 503 Service Unavailable
Transaction is NOT executed
Fail-Closed Behavior
When Billing is unreachable and the cache is cold (miss):
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
{
"type": "https://api.septemcore.com/problems/service-unavailable",
"status": 503,
"detail": "Cannot verify transaction limits: Billing Service is unavailable.",
"code": "LIMITS_UNAVAILABLE"
}
Why fail-closed: Financial safety takes precedence over availability. Executing a transaction without verifying limits could violate tenant plan constraints, compliance rules, or fraud controls. The 5-minute Valkey TTL ensures this path is hit only in genuine outage scenarios — not during normal spikes.
| Billing status | Cache status | Result |
|---|---|---|
| Online | Hit (fresh ≤ 5 min) | ✅ Use cache |
| Online | Miss | ✅ Fetch from Billing, cache, proceed |
| Down | Hit (stale but present) | ✅ Use stale cache (safe) |
| Down | Miss | ❌ 503 Service Unavailable (fail-closed) |
Limit Violations
When a transaction exceeds a business limit, the operation is rejected before any database write:
POST https://api.septemcore.com/v1/wallets/01j9paw1t000000000000000/credit
Idempotency-Key: ...
Content-Type: application/json
{ "amount": 15000000 }
Response 422 Unprocessable Entity:
{
"type": "https://api.septemcore.com/problems/limit-exceeded",
"status": 422,
"detail": "Transaction amount 15000000 cents exceeds the plan limit of 10000000 cents ($100,000).",
"code": "LIMIT_EXCEEDED",
"limit": "maxTxAmount",
"value": 15000000,
"max": 10000000
}
Balance limit violation (credit would push balance above the plan maximum):
{
"type": "https://api.septemcore.com/problems/limit-exceeded",
"status": 422,
"code": "LIMIT_EXCEEDED",
"limit": "maxBalance",
"detail": "This credit would push the wallet balance to 105000000 cents, exceeding the plan limit of 100000000 cents ($1,000,000)."
}
Insufficient Funds
When a debit or hold amount exceeds available:
{
"type": "https://api.septemcore.com/problems/insufficient-funds",
"status": 400,
"detail": "Insufficient funds: available balance is 3000 cents, requested 5000 cents.",
"code": "INSUFFICIENT_FUNDS",
"available": 3000,
"requested": 5000
}
Error Reference
| Scenario | HTTP | Code |
|---|---|---|
amount is zero | 400 | INVALID_AMOUNT |
amount is negative | 400 | INVALID_AMOUNT |
| Transaction amount exceeds plan max | 422 | LIMIT_EXCEEDED (limit: maxTxAmount) |
| Credit would exceed max balance | 422 | LIMIT_EXCEEDED (limit: maxBalance) |
Insufficient available | 400 | INSUFFICIENT_FUNDS |
| Billing down, cache cold | 503 | LIMITS_UNAVAILABLE |