Raise a customer's limits with KYC

Read a customer's tier, caps and headroom, and lift a limits pause by completing their KYC and resuming the transfer.

This recipe reads a customer's tier, caps and headroom, and lifts a limits pause by completing the customer's KYC and resuming the transfer.

Who it is for. A remittance app whose user Kofi tries to send 800.00 USD. The app's support team must tell him why the transfer paused and what will lift the pause.

Status. Available when your business's limits policy is set to enforce. In off and shadow mode nothing pauses.

Money example

On the standard limits, with your policy set to enforce:

  1. Kofi has no completed KYC, so his tier is none, with caps of 500.00 USD a day, 2,500.00 a week and 5,000.00 a month.
  2. He already sent 200.00 USD today and now sends 800.00 USD. Together that is 1,000.00 USD, which is over the 500.00 daily cap. Gravv creates the transfer paused instead of refusing it. Gravv pays nothing out, and remaining_usd shows 300.00.
  3. Kofi completes KYC. His tier becomes basic, with a cap of 50,000.00 USD a day, a week and a month.
  4. You resume the paused transfer, and Gravv sends the 800.00 USD.

Before you start

  • The customer exists.
  • Your business may have its own policy instead of the standard one. Read the tier, the caps and next_tier from the API. Never hard-code them.

Find out what is required

curl --request GET \
 --url https://api.gravv.xyz/v1/customers/9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13/limits \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/customers/9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13/limits", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/customers/9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13/limits",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
{
  "data": {
    "subject_type": "customer",
    "subject_id": "9d2f4b6a-1c3e-4a5b-8d7f-2e4a6c8e0b13",
    "tier_code": "none",
    "tier_assignment": "kyc",
    "caps_basis": "tier",
    "mode": "enforce",
    "blocked": false,
    "usage_available": true,
    "as_of": "2026-10-13T12:00:00Z",
    "windows": {
      "per_transfer": {"cap_usd": null, "used_usd": null, "remaining_usd": null},
      "daily": {"cap_usd": "500.00", "used_usd": "200.00", "remaining_usd": "300.00"},
      "weekly": {"cap_usd": "2500.00", "used_usd": "200.00", "remaining_usd": "2300.00"},
      "monthly": {"cap_usd": "5000.00", "used_usd": "200.00", "remaining_usd": "4800.00"},
      "annual": {"cap_usd": null, "used_usd": "200.00", "remaining_usd": null, "requires_approval": false}
    },
    "next_tier": {
      "tier_code": "basic",
      "caps": {"per_transfer": null, "daily": "50000", "weekly": "50000", "monthly": "50000", "annual": null, "annual_requires_approval": false}
    },
    "candidate_tiers": null
  },
  "error": null
}
  • tier_code is the current tier. tier_assignment kyc means the tier comes from KYC. operator means Gravv set it by hand.
  • windows holds the cap, the amount used and the amount remaining for each window. A null cap means no cap for that window.
  • Every amount is in USD, whatever the account currency. Days, weeks and months reset on UTC boundaries (weeks are ISO weeks). The year is a rolling 12 months.
  • next_tier holds the caps the customer gets one tier up.

The standard tiers:

Tier How a customer gets it Daily Weekly Monthly
none No completed KYC 500.00 2,500.00 5,000.00
basic basic KYC completed, for people, associated persons and businesses 50,000.00 50,000.00 50,000.00
intermediate Not yet available. No API starts this level

Steps

1. Check before a large payout

Read the limits as above. If the amount is more than any remaining_usd, expect a pause.

2. Recognize the pause

The transfer request returns HTTP 200 with transfer_status paused, pause_reason daily, breached_window daily and remaining_usd 300.00. For a tier that comes from KYC, requirements lists only admin_review. You cannot send any field to prove more.

3. Lift the tier, then resume

Complete the customer's KYC (hosted page or your own documents). Then resume with an empty body and a new key. The resume runs the limit check again with the new tier.

curl --request POST \
 --url https://api.gravv.xyz/v1/transfer/c7d8e9f0-a1b2-4c3d-8e4f-5a6b7c8d9e0f/resume \
 --header 'Api-Key: <API_KEY>' \
 --header 'Idempotency-Key: f3fdb3bd-b779-4b8a-9d2e-84ce1c579cdb' \
 --header 'Content-Type: application/json' \
 --data '
{}
'
const response = await fetch("https://api.gravv.xyz/v1/transfer/c7d8e9f0-a1b2-4c3d-8e4f-5a6b7c8d9e0f/resume", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Idempotency-Key": "f3fdb3bd-b779-4b8a-9d2e-84ce1c579cdb",
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/transfer/c7d8e9f0-a1b2-4c3d-8e4f-5a6b7c8d9e0f/resume",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Idempotency-Key": "f3fdb3bd-b779-4b8a-9d2e-84ce1c579cdb",
    "Content-Type": "application/json",
},
json={},
)
print(response.status_code, response.json())

resumed: true means the transfer continues. resumed: false lists what is still missing.

Other choices

  • Wait for Gravv to release it (transfer.resumed with reason released), or let it expire. On expiry, Gravv fails the transfer, refunds the fee and sends transfer.status.failed with reason limit_pause_expired. Pauses expire after 72 hours by default, and expires_at gives the exact time.
  • Send less, within remaining_usd.

For more on pauses, including businesses that verify their own senders, see Stay within transfer limits.

What can go wrong

Response Why Fix
404 not found: no limits policy applies Your business has no limits policy Nothing is limited. Contact support if you expected limits
404 not found: subject The customer is unknown, or belongs to another business Check the id and environment
400 subject_id "abc" is not a customer id The id is not a UUID Send the customer id
503 limits are temporarily unavailable, please retry (ERROR_CODE_LIMITS_UNAVAILABLE) The limits store is unavailable Retry
usage_available: false, with used_usd and remaining_usd null Gravv could not read usage, but the caps are still right Retry for usage
422 transfers are not permitted for this sender (ERROR_CODE_SUBJECT_BLOCKED) Compliance blocked the customer, and blocked is true Contact support
410 this paused transfer has expired and can no longer be resumed; please initiate a new transfer The pause passed expires_at Start a new transfer
The resume returns resumed: false KYC is not completed yet, or the amount is still over the cap Check the KYC status and the limits again

Webhooks you will receive

You receive transfer.requires_information, with event_data.reason set to the window and limit_details holding breached_window, cap_usd, used_usd, remaining_usd, amount_usd, tier and requirements. Then you receive transfer.resumed, or transfer.status.failed with reason limit_pause_expired or limit_pause_cancelled. See Transfer events.

Testing in sandbox

The limits read works in sandbox. A sandbox business can carry its own policy, so the figures, the tier name and tier_assignment (which can also be default) differ from the standard ones. Read them from the call. Whether transfers pause depends on your sandbox business's mode. Ask support to set shadow or enforce for testing.

Reference

Was this page helpful?