Skip to main content
Spending limits are how product and risk teams cap exposure per customer, per segment, or per portfolio without writing code. Common use cases: a daily ceiling on card spending for retail customers, a monthly cap on a specific MCC, a campaign-window limit for a marketing promotion. What changes in your operation: spending caps stop being constants hardcoded in config files or scattered across services. They become versioned data with a clear lifecycle (DRAFT → ACTIVE → INACTIVE), each new period starts counting from zero, and they’re audit-trailed every time a transaction would have pushed past one. Trade-off to be honest about: counters need to stay consistent across replicas and races. Tracer handles that transactionally — if a transaction is denied or sent to REVIEW, the counter rolls back. You give up “local clever logic in each service” and gain a single, consistent number.
Who is this guide for? Product managers configuring caps, risk teams reviewing exposure, compliance auditing what was denied, and developers integrating the validation call. The Limit types section assumes no API knowledge; the lifecycle and PATCH sections assume basic REST.
Spending limits in Tracer let you control transaction amounts by scope (account, portfolio, segment) and period (daily, weekly, monthly, custom, or per-transaction). Limits are evaluated in real-time alongside rules, in the same POST /v1/validations call.

Why use spending limits


  • Customer protection: Detect overspending and return DENY decisions for unauthorized large transactions
  • Risk management: Monitor exposure per account, segment, or portfolio
  • Flexible scoping: Apply limits at different granularity levels
  • Real-time tracking: Every decision reports how much of each cap it consumed
  • Period counting: Daily, weekly, and monthly limits start a new count at each period boundary
  • Time windows: Restrict limit enforcement to specific hours of the day
  • Custom periods: Define date-bound limits for campaigns, promotions, or compliance requirements
By the end of this guide, you will:
  • Understand limit types, time windows, and scoping options
  • Create and configure spending limits with period-based controls
  • Monitor limit usage in real-time
  • Manage the limit lifecycle

Core concepts


Understand the building blocks of spending limits.

Limit types

Tracer supports five types of spending limits:

Time windows

Time windows restrict when a limit is enforced during the day. When a transaction occurs outside the configured time window, the limit is skipped (not enforced) and the transaction is allowed to proceed without counting against that limit.
  • Format: HH:MM (24-hour, UTC)
  • Both fields required: If activeTimeStart is set, activeTimeEnd must also be set (and vice versa)
  • Half-open interval: Start is inclusive, end is exclusive [start, end)
  • Overnight windows supported: Setting activeTimeStart: "20:00" and activeTimeEnd: "06:00" creates a window from 8 PM to 6 AM UTC
Time windows can be applied to any limit type (DAILY, WEEKLY, MONTHLY, CUSTOM, or PER_TRANSACTION). If no time window is configured, the limit is active 24/7.
Example: Pix compliance A financial institution needs to enforce lower Pix transfer limits during nighttime hours (as recommended by BACEN):
  • limitType: DAILY
  • maxAmount: "1000.00"
  • activeTimeStart: "20:00"
  • activeTimeEnd: "06:00"
  • Scope: Pix transactions
Transactions between 20:00 and 06:00 UTC are checked against the R$ 1,000 limit. Transactions outside this window are not affected by this limit.

Custom periods

Custom periods define a date range during which a limit is active. This is useful for campaigns, promotions, seasonal events, or compliance requirements with specific date boundaries.
  • Required fields: customStartDate and customEndDate (only for CUSTOM type)
  • Half-open interval: Start is inclusive, end is exclusive [start, end)
  • Maximum duration: 5 years
  • Cannot be in the past: The customEndDate must not be entirely before the current date
The customStartDate and customEndDate fields are required for CUSTOM limits and forbidden for other limit types.
Example: Black Friday campaign A retailer wants to set a special spending limit for the Black Friday period:
  • limitType: CUSTOM
  • maxAmount: "100000.00"
  • customStartDate: "2026-11-25T00:00:00Z"
  • customEndDate: "2026-11-30T00:00:00Z"
  • Scope: CARD transactions in the retail segment
Usage accumulates across the whole window in a single count. From customEndDate onward, Tracer stops checking the limit.

Combining time windows and custom periods

Time windows and custom periods can be used together on CUSTOM limits. When combined, a transaction must be within both the custom period and the time window to be evaluated against the limit. For example, a CUSTOM limit with customStartDate Nov 25 to customEndDate Nov 30 and a time window of 09:00 to 18:00 would only enforce the limit during business hours within the Black Friday period.

Scopes

Scopes define which transactions a limit applies to. Unlike rules, every limit must have at least one scope object — limits cannot be global. Within a single scope object, the supported fields are:
  • segmentId - Apply to transactions from a specific segment
  • portfolioId - Apply to transactions from a specific portfolio
  • accountId - Apply to transactions from a specific account
  • merchantId - Apply to transactions to a specific merchant
  • transactionType - Apply to specific transaction types (CARD, WIRE, PIX, CRYPTO)
  • subType - Apply to a specific transaction subtype (e.g., debit, credit)
Matching semantics:
  • Within one scope object: fields combine with AND. A field that is not specified is treated as a wildcard (matches any value). At least one field must be set — empty scope objects ({}) are rejected with error code 0009.
  • Across multiple scope objects on the same limit: they combine with OR. The limit applies if any scope object matches the transaction.
No hierarchy between limits. When a transaction matches multiple limits (for example, both an account-level and a segment-level limit), Tracer checks all applicable limits independently in a single transaction. The transaction is denied as soon as any one of them is exceeded.

Usage tracking

For DAILY, WEEKLY, MONTHLY, and CUSTOM limits, Tracer keeps one usage counter per limit, per matched scope, per period. The validation decision reports that counter — see Read consumption. A counter is kept for 90 days after its period ends, then a background worker deletes it.

How limits work


Tracer evaluates limits during every validation request.

Limit check flow

When a transaction is validated, Tracer checks all applicable limits:
How Tracer checks all applicable spending limits during a validation request and updates their usage counters

Figure 1. How spending limits work

  1. Find limits - Query all active limits matching the transaction scope
  2. Check time window - If the limit has a time window configured, verify the current server time falls within activeTimeStart/activeTimeEnd. If outside, the limit is skipped (the client-supplied transactionTimestamp is not used here)
  3. Check custom period - If the limit is CUSTOM, verify the current server time falls within customStartDate/customEndDate. If outside, the limit is skipped (again, transactionTimestamp is not used)
  4. Calculate projected usage - Add transaction amount to current usage
  5. Compare threshold - Check if projected usage exceeds limit amount
  6. Return result - If any applicable limit is exceeded — or any DENY rule matches — Tracer returns a DENY decision (your system should then block the transaction)
Limit checks and counter increments are transactional. If a transaction is denied (by limits or rules) or flagged for review, all counter increments are rolled back atomically. This prevents limit leakage from partial operations.
When a limit is skipped during evaluation, limitUsageDetails[i] includes skipped: true and a skipReason field with one of two values:
  • "outside_time_window" — current server time is outside the limit’s activeTimeStart/activeTimeEnd window
  • "outside_custom_period" — current server time is outside the limit’s customStartDate/customEndDate range
Skipped limits are reported for transparency but do not participate in the DENY decision, and their counters are not incremented. The window check uses server time, not the client-supplied transactionTimestamp, to prevent timestamp-manipulation attacks.
Why server time instead of transactionTimestamp. The client can set transactionTimestamp to whatever they want — including a value crafted to fall inside an active window when the real transaction would fall outside it. If Tracer trusted the client clock for time-window enforcement, anyone with access to the payload could bypass off-hours limits. Pinning the window check to Tracer’s own clock removes that attack surface. The downside is that small clock drift between Tracer pods can cause edge-case skips around the window boundary; in practice, Tracer’s NTP-synced clocks keep this in single-digit milliseconds.

Example scenario

A corporate segment has a daily limit of R$ 50,000 ("50000.00") for CARD transactions. If current usage is R45,000andanewtransactionofR 45,000 and a new transaction of R 8,000 arrives:
  • Projected usage: R45,000+R 45,000 + R 8,000 = R$ 53,000
  • Limit: R$ 50,000
  • Result: Tracer returns DENY decision (your system should block the transaction)

Create a limit


Create limits using POST /v1/limits. Limits are created in DRAFT status by default. A limit requires:
  • name: A descriptive name (e.g., “Daily Corporate Card Limit”)
  • limitType: DAILY, WEEKLY, MONTHLY, CUSTOM, or PER_TRANSACTION
  • maxAmount: Maximum amount as a decimal value (e.g., "50000.00")
  • currency: ISO 4217 currency code (e.g., BRL, USD)
  • scopes: At least one scope to define which transactions it applies to
Optional fields:
  • activeTimeStart: Start of the daily time window in HH:MM format (e.g., "09:00")
  • activeTimeEnd: End of the daily time window in HH:MM format (e.g., "17:00")
  • customStartDate: Start date for CUSTOM limits (ISO 8601 timestamp, required for CUSTOM)
  • customEndDate: End date for CUSTOM limits (ISO 8601 timestamp, required for CUSTOM)
Limit names must be globally unique across all non-deleted limits — unlike rule names, which are unique only within their scope context. Uniqueness is enforced on the name exactly as stored, after leading and trailing whitespace is trimmed: the comparison is case-sensitive and does not collapse whitespace inside the name, so Daily Card Limit and daily card limit are two distinct, both-acceptable limits. Deleting a limit frees its name for reuse. A collision returns 409 Conflict with error code 0442.
For complete payload structure and field details, see the API reference.

List and query limits


Query limits for management and auditing using GET /v1/limits.

Query parameters

Get a specific limit

Use GET /v1/limits/{id} to retrieve the full limit definition including scopes and current status.

Read consumption


From the decision

Every POST /v1/validations response carries limitUsageDetails, with one entry per limit Tracer checked. Each entry reports:
  • limitId and limitAmount — which cap was checked, and its ceiling
  • currentUsage — the projected consumption of that cap’s current period and matched scope if this transaction is allowed
  • attemptedAmount — the amount checked against the cap
  • exceeded — whether the attempted amount would push this cap past its ceiling; every cap is evaluated, so more than one entry can carry exceeded: true, and any of them produces the DENY

From the limit

GET /v1/limits/{id}/usage reports a cumulative total. Its currentUsage adds up the usage counters recorded for the limit, across periods and scopes, so use it to review a limit’s overall consumption rather than to answer how much a customer has left in the current period. A counter is deleted 90 days after its period ends (see Usage tracking), so on a long-running limit this total covers only the periods still retained, not the limit’s full lifetime.

Update a limit


Update limits using PATCH /v1/limits/{id}. The limitType and currency fields are immutable and cannot be changed after creation.
Changing the limit amount does not clear the current count. If you reduce a limit below what the current period already consumed, subsequent transactions are denied until the next period starts.

Limit lifecycle


Limits follow the same lifecycle as rules:
Lifecycle of rules and limits in Tracer, showing the status transitions a definition moves through from creation to active enforcement

Figure 2. Spending limits lifecycle

States

Transitions


Best practices


Recommendations for effective limit management.

Naming

  • Be descriptive - Include the scope and type in the name
  • Use consistent patterns - e.g., “Daily Limit”

Scope design

  • Start broad, refine as needed - Begin with segment-level limits, add account-level for exceptions
  • Avoid overlapping scopes - Multiple limits on the same scope can cause confusion
  • Use transaction types - Different payment methods may need different limits

Time window design

  • Use for regulatory compliance - BACEN nighttime Pix limits are a common use case
  • Consider timezone impact - Time windows use UTC; account for your users’ local timezone offset
  • Combine with custom periods - Use time windows inside custom periods for precise campaign controls

Monitoring

  • Read the decision payload - limitUsageDetails shows how much of each cap every transaction consumed
  • Review denied transactions - High denial rates may indicate limits are too restrictive
  • Adjust seasonally - Consider temporary limit increases during high-spending periods or use CUSTOM limits for specific date ranges
Common pitfalls when working with limits:
  • “My customer is reporting overspend — they should have hit the limit.” Check whether the limit is ACTIVE. A limit in DRAFT or INACTIVE state is not evaluated. Also confirm the limit’s scope actually matches the transaction (segment, transaction type, etc.).
  • “My PATCH lowered the limit but transactions are still being denied.” Lowering the limit does not clear the count. If the current period already consumed more than the new ceiling, subsequent transactions are denied until the next period starts.
  • “I tried to delete an ACTIVE limit and it was rejected.” The deletion comes back 422 with code 0363. Send POST /v1/limits/{id}/deactivate first, then DELETE /v1/limits/{id}. This is intentional: it prevents accidentally removing a live enforcement.
  • GET /v1/limits/{id}/usage reports more than the customer spent this period.” That endpoint totals the usage counters recorded for the limit, across periods and scopes. For the current period, read limitUsageDetails on the validation response.

Quick reference


Key endpoints and configuration options.

Endpoints

For limit type definitions (DAILY, WEEKLY, MONTHLY, CUSTOM, PER_TRANSACTION), the optional time-window and custom-period fields, and the full scope-field list, see Limit types earlier in this guide and the API reference for schema-level details.