---
title: "Check what a sender can still send"
description: "Read a sender's caps and what is left in each window before a big payout, so a transfer does not pause on payday."
---
This recipe reads a sender's caps and how much is left in each limit window, so you can check a large payout before you send it. It is for an operations analyst at a US remittance app that verifies its own senders (remitters) and wants no transfers paused on payday.

**Status.** Available when your business has a limits policy.

## Money example

Maria wants to send 1,500 USD today.

1. Her limits are 1,000 USD per transfer and 3,000 USD a day, with 0 used today.
2. 1,500 USD is over the per-transfer cap, so Gravv would create the transfer `paused` instead of refusing it.
3. A higher verification tier lifts the cap. If you verify her to that tier first and then send, the transfer does not pause.

## Before you start

- Your business has a limits policy. Without one, both reads return 404 and no Gravv limits apply.
- The sender's id. For your customers, this is the customer id. For senders you verify yourself, it is the `remitter_id` you send in `additional_information.remitter_verification`.

## Find out what is required

The limits response tells you how Gravv checks the sender and how much they can still send. Read these fields:

| Field | Meaning |
| :-- | :-- |
| `mode` | `enforce`: a breach pauses the transfer. `shadow`: Gravv only records a breach. `off`: no checks |
| `blocked` | `true`: Gravv refuses every transfer with 422 `ERROR_CODE_SUBJECT_BLOCKED` |
| `windows.<window>.cap_usd`, `used_usd`, `remaining_usd` | USD strings for `per_transfer`, `daily`, `weekly`, `monthly` and `annual` (the last 12 months) |
| `cap_usd` `null` | No cap in that window, but only when `caps_basis` is `tier` |
| `usage_available` `false` | Gravv could not read usage, so `used_usd` and `remaining_usd` are `null` |
| `next_tier`, `candidate_tiers` | The caps a higher tier allows |

## Steps

### 1. Read a remitter's limits

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/limits/remitters/rem-20431 \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/limits/remitters/rem-20431", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/limits/remitters/rem-20431",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "subject_type": "remitter",
    "subject_id": "rem-20431",
    "tier_code": "",
    "tier_assignment": "per_transfer_attestation",
    "caps_basis": "floor",
    "mode": "enforce",
    "blocked": false,
    "usage_available": true,
    "as_of": "2026-10-13T09:15:22Z",
    "windows": {
      "per_transfer": { "cap_usd": "1000.00", "used_usd": null, "remaining_usd": null },
      "daily": { "cap_usd": "3000.00", "used_usd": "0.00", "remaining_usd": "3000.00" },
      "weekly": { "cap_usd": null, "used_usd": "0.00", "remaining_usd": null },
      "monthly": { "cap_usd": "10000.00", "used_usd": "0.00", "remaining_usd": "10000.00" },
      "annual": { "cap_usd": "0.00", "used_usd": "0.00", "remaining_usd": "0.00", "requires_approval": true }
    },
    "next_tier": null,
    "candidate_tiers": [
      {
        "tier_code": "STARTER",
        "rank": 0,
        "tier_caps": { "per_transfer": "1000", "daily": "3000", "weekly": null, "monthly": "10000", "annual": null, "annual_requires_approval": true },
        "caps": { "per_transfer": "1000", "daily": "3000", "weekly": null, "monthly": "10000", "annual": null, "annual_requires_approval": true }
      }
    ]
  },
  "error": null
}
```

1,500 is more than the `per_transfer` cap of 1,000, so the transfer would pause with `pause_reason` `per_transfer`. Tier codes such as `STARTER` come from your business's limits policy. Read them from this response and never hard-code them. Gravv accepts any string as a remitter id. For an id it has never seen, Gravv uses the policy's default tier.

### 2. Read a customer's limits

The response has the same shape as in step 1, with `subject_type` set to `customer`.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/customers/5be6128e-18c2-4797-a142-ea7d17be3111/limits \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/5be6128e-18c2-4797-a142-ea7d17be3111/limits", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/customers/5be6128e-18c2-4797-a142-ea7d17be3111/limits",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

### 3. Compare before you send

Convert the planned amount to USD and compare it with the smallest `remaining_usd` and with the `per_transfer` cap. If a transfer would pause, plan to verify that sender first. Do not split a payment to get under a cap.

If a transfer has already paused, [Stay within transfer limits](/recipes/bank-payouts/stay-within-transfer-limits) shows how to respond to it.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 404 `not found: no limits policy applies` | Your business has no limits policy | No Gravv limits apply, so there is nothing to check |
| 404 `not found: subject` | The customer id is not in your business | Fix the id |
| 400 `subject_id "x" is not a customer id` | The id on the customer route is not a UUID | Use the customer's UUID |
| 404 with the plain-text body `404 page not found` | An empty id, as in `/v1/limits/remitters/` | Add the id |
| 503 `ERROR_CODE_LIMITS_UNAVAILABLE` | Gravv cannot read limits right now | Retry, and wait longer before each new attempt |
| A remitter shows no usage although they sent today | Usage counts only transfers that carried `remitter_verification` with exactly this `remitter_id` (case matters) | Send the same id every time |
| An annual cap of `"0.00"` with `requires_approval` `true` | An annual allowance needs Gravv's approval | Transfers past it pause for review |

## Webhooks you will receive

Reading limits sends no webhook. If a transfer later breaches a cap, Gravv sends `transfer.requires_information`. Its `limit_details` object holds `breached_window`, `cap_usd`, `used_usd`, `remaining_usd` and `requirements`.

## Testing in sandbox

Your sandbox business needs a sandbox limits policy, and Gravv sets it up. If the sandbox policy is in `shadow` mode, both reads return 200, but Gravv only records a breach and never pauses the transfer. To see a pause, ask support to put the policy in `enforce` mode.

## Reference

- [Customer limits](/api-reference/limits/get-v1-customers-customer-id-limits)
- [Remitter limits](/api-reference/limits/get-v1-limits-remitters-remitter-id)
- [Transfer limits](/platform/transfers/transfer-limits)
