> ## 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/start-here/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/start-here/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 no longer live as 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 Tracer denies a transaction or sends it 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 Tracer 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). Tracer evaluates limits 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** Tracer enforces a limit during the day. When a transaction occurs outside the configured time window, Tracer **skips** the limit and does not enforce it. The transaction proceeds without counting against that limit.

* **Format**: `HH:MM` (24-hour, UTC)
* **Both fields required**: If you set `activeTimeStart`, you must also set `activeTimeEnd` (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>
  You can apply time windows to **any** limit type (DAILY, WEEKLY, MONTHLY, CUSTOM, or PER\_TRANSACTION). Without a time window, 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

Tracer checks transactions between 20:00 and 06:00 UTC against the R\$ 1,000 limit. This limit does not affect transactions outside this window.

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

You can use time windows and custom periods together on `CUSTOM` limits. Tracer then checks a transaction against the limit only when it falls within **both** the custom period **and** the time window.

For example, take a `CUSTOM` limit with `customStartDate` Nov 25 to `customEndDate` Nov 30 and a time window of `09:00` to `18:00`. Tracer enforces that limit only 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 you leave out works as a wildcard (matches any value). You must set at least one field. Tracer rejects empty scope objects (`{}`) 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.** A transaction can match multiple limits, for example an account-level and a segment-level limit. Tracer then checks **all** applicable limits independently in a single transaction. Tracer denies the transaction as soon as it exceeds any one of them.

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

Tracer keeps a counter 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 Tracer validates a transaction, it 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, Tracer **skips** the limit (it does not use the client-supplied `transactionTimestamp` here)
3. **Check custom period** - If the limit is `CUSTOM`, verify the **current server time** falls within `customStartDate`/`customEndDate`. If outside, Tracer **skips** the limit (again, it does not use `transactionTimestamp`)
4. **Calculate projected usage** - Add transaction amount to current usage
5. **Compare threshold** - Check if projected usage exceeds limit amount
6. **Return result** - If the transaction exceeds any applicable limit, 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 Tracer denies a transaction (by limits or rules) or flags it for review, it rolls back all counter increments 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

  Tracer reports skipped limits for transparency, but they do **not** participate in the DENY decision, and their counters do **not** increment. 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. That includes 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`. Tracer creates limits 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"`)
* **asset**: ISO 4217 asset 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. Tracer enforces uniqueness on the name **exactly as stored**, after it trims leading and trailing whitespace. 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/products/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 Tracer checked, and its ceiling
* **currentUsage**: the projected consumption of that cap's current period and matched scope if Tracer allows this transaction
* **attemptedAmount**: the amount checked against the cap
* **exceeded**: whether the attempted amount would push this cap past its ceiling. Tracer evaluates every cap, 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. Use it to review a limit's overall consumption rather than to answer how much a customer has left in the current period.

Tracer deletes a counter 90 days after its period ends (see [Usage tracking](#usage-tracking)). 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 `asset` fields are immutable. You cannot change them 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, Tracer denies subsequent transactions 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`. Tracer does not evaluate a limit in DRAFT or INACTIVE state. Also confirm the limit's scope actually matches the transaction (segment, transaction type, etc.).
  * **"My PATCH lowered the limit but transactions are still denied."** Lowering the limit does not clear the count. If the current period already consumed more than the new ceiling, Tracer denies subsequent transactions 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), see [Limit types](#limit-types) earlier in this guide. The same guide covers the optional time-window and custom-period fields and the full scope-field list. The [API reference](/en/reference/products/tracer/create-limit) has schema-level details.
