> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Spending limits

> Configure Tracer spending limits by account, portfolio, or segment with daily, weekly, monthly, and custom periods, time windows, and lifecycle controls.

export const GMetadata = ({children}) => <Tooltip headline="Metadata" tip="Additional key-value information attached to entities like accounts or transactions — such as external IDs, reference numbers, or department codes." cta="See glossary" href="/en/glossary">
    {children}
  </Tooltip>;

export const GAuditTrail = ({children}) => <Tooltip headline="Audit trail" tip="A chronological, immutable record of every action and transaction in the system — essential for regulatory compliance and dispute resolution." cta="See glossary" href="/en/glossary">
    {children}
  </Tooltip>;

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.

<Tip>
  **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.
</Tip>

**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:

| Type              | Description                                     | Period counting                                                                            |
| ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `DAILY`           | Maximum amount per day                          | A new count starts each calendar day at 00:00 UTC                                          |
| `WEEKLY`          | Maximum amount per week                         | A new count starts each ISO week, Monday at 00:00 UTC                                      |
| `MONTHLY`         | Maximum amount per month                        | A new count starts on the 1st of the month at 00:00 UTC                                    |
| `CUSTOM`          | Maximum amount within a user-defined date range | One count for the whole range; from `customEndDate` onward Tracer stops checking the limit |
| `PER_TRANSACTION` | Maximum amount per single transaction           | No count is kept; each transaction is checked on its own                                   |

### 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

<Note>
  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.
</Note>

**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

<Warning>
  The `customStartDate` and `customEndDate` fields are **required** for `CUSTOM` limits and **forbidden** for other limit types.
</Warning>

**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](#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:

<Frame caption="Figure 1. How spending limits work">
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/en/d2/how-limits-works.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=edfe5fa47c5158e9f86f97d7dcc9326b" alt="How Tracer checks all applicable spending limits during a validation request and updates their usage counters" width="1137" height="284" data-path="images/en/d2/how-limits-works.svg" />
</Frame>

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)

<Note>
  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.
</Note>

<Note>
  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.
</Note>

<Info>
  **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.
</Info>

### Example scenario

A corporate segment has a daily limit of R\$ 50,000 (`"50000.00"`) for CARD transactions.

If current usage is R$ 45,000 and a new transaction of R$ 8,000 arrives:

* Projected usage: 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)

<Note>
  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`.
</Note>

For complete payload structure and field details, see the [API reference](/en/reference/tracer/create-limit).

***

## List and query limits

***

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

### Query parameters

| Parameter          | Type    | Description                                                             |
| ------------------ | ------- | ----------------------------------------------------------------------- |
| `name`             | string  | Filter by name (case-insensitive partial match)                         |
| `status`           | string  | Filter by status (DRAFT, ACTIVE, INACTIVE)                              |
| `limit_type`       | string  | Filter by limit type (DAILY, WEEKLY, MONTHLY, CUSTOM, PER\_TRANSACTION) |
| `account_id`       | string  | Filter by scope: account ID                                             |
| `segment_id`       | string  | Filter by scope: segment ID                                             |
| `portfolio_id`     | string  | Filter by scope: portfolio ID                                           |
| `merchant_id`      | string  | Filter by scope: merchant ID                                            |
| `transaction_type` | string  | Filter by scope: transaction type (CARD, WIRE, PIX, CRYPTO)             |
| `sub_type`         | string  | Filter by scope: subtype (e.g., debit, credit)                          |
| `limit`            | integer | Items per page (default: 10, max: 100)                                  |
| `cursor`           | string  | Pagination cursor                                                       |
| `sort_by`          | string  | Sort field: `created_at`, `updated_at`, `name`, `max_amount`            |
| `sort_order`       | string  | Sort direction: `ASC`, `DESC` (default: DESC)                           |

### 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](#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.

<Warning>
  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.
</Warning>

***

## Limit lifecycle

***

Limits follow the same lifecycle as rules:

<Frame caption="Figure 2. Spending limits lifecycle">
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/en/d2/rules-limits-lifecycle-tracer.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=0e9996cc0609ad9f3b8d0ee10feb3a56" alt="Lifecycle of rules and limits in Tracer, showing the status transitions a definition moves through from creation to active enforcement" width="531" height="1050" data-path="images/en/d2/rules-limits-lifecycle-tracer.svg" />
</Frame>

### States

| State      | Description                                                                                    |
| ---------- | ---------------------------------------------------------------------------------------------- |
| `DRAFT`    | Limit created but not active; can be modified freely                                           |
| `ACTIVE`   | Limit is checked during validations                                                            |
| `INACTIVE` | Limit is not checked; preserved for <GAuditTrail>audit trail</GAuditTrail>; can be reactivated |
| `DELETED`  | Permanently removed; does not appear in listings                                               |

### Transitions

| Operation  | From            | To       | Description                                      |
| ---------- | --------------- | -------- | ------------------------------------------------ |
| Create     | -               | DRAFT    | Limits are created in DRAFT status by default    |
| Activate   | DRAFT, INACTIVE | ACTIVE   | Start checking this limit                        |
| Deactivate | ACTIVE          | INACTIVE | Stop checking this limit                         |
| Draft      | INACTIVE        | DRAFT    | Return to draft for editing                      |
| Delete     | DRAFT, INACTIVE | DELETED  | Permanently remove (cannot delete ACTIVE limits) |

***

## Best practices

***

Recommendations for effective limit management.

### Naming

* **Be descriptive** - Include the scope and type in the name
* **Use consistent patterns** - e.g., "Daily {Segment} {Type} Limit"

| Less clear  | More clear                       |
| ----------- | -------------------------------- |
| `Limit 1`   | `Daily Corporate Card Limit`     |
| `VIP limit` | `Monthly VIP Pix Limit`          |
| `BF promo`  | `Custom Black Friday Card 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

<Warning>
  **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.
</Warning>

***

## Quick reference

***

Key endpoints and configuration options.

### Endpoints

| Operation        | Method | Endpoint                     |
| ---------------- | ------ | ---------------------------- |
| Create limit     | POST   | `/v1/limits`                 |
| List limits      | GET    | `/v1/limits`                 |
| Get limit        | GET    | `/v1/limits/{id}`            |
| Update limit     | PATCH  | `/v1/limits/{id}`            |
| Activate limit   | POST   | `/v1/limits/{id}/activate`   |
| Deactivate limit | POST   | `/v1/limits/{id}/deactivate` |
| Draft limit      | POST   | `/v1/limits/{id}/draft`      |
| Delete limit     | DELETE | `/v1/limits/{id}`            |
| Get usage        | GET    | `/v1/limits/{id}/usage`      |

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](#limit-types) earlier in this guide and the [API reference](/en/reference/tracer/create-limit) for schema-level details.
