Stay within transfer limits

Check a sender's headroom before you send, and answer a transfer that paused for a limit, for screening or for sender details.

Use this recipe to check a sender's headroom before you send, and to answer a transfer that paused for a limit, for screening or for sender details.

Who it is for. A remittance platform whose business runs an enforced limits policy, with daily, weekly and monthly caps in USD per sender.

Status. Available if your business has a limits policy.

Money example

  1. Sender Maria Lopez has a weekly cap of 2,500.00 USD and has sent 2,200.00 USD this week.
  2. You send 1,500.00 USD by ACH. That makes 3,700 USD this week, which is over the 2,500 USD cap. Gravv pauses the transfer instead of refusing it, with breached_window weekly and remaining_usd 300.00.
  3. You can resume the transfer with stronger sender verification, if requirements offers it. You can also wait for Gravv to review it, send 300.00 USD or less instead, or let the pause expire after 72 hours. If the pause expires, Gravv refunds the fee.

Before you start

Find out whether your business has a limits policy. GET /v1/customers/{customer_id}/limits returns mode off, shadow or enforce. In shadow mode, no transfer ever pauses. A 404 not found: no limits policy applies means there is no policy.

Find out what is required

Headroom before you send

curl --request GET \
 --url https://api.gravv.xyz/v1/customers/7c9e6679-7425-40de-944b-e07fc1f90ae7/limits \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/customers/7c9e6679-7425-40de-944b-e07fc1f90ae7/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/7c9e6679-7425-40de-944b-e07fc1f90ae7/limits",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())

The smallest remaining_usd across windows is the most this sender can send now. cap_usd null means that window has no cap. Weeks are ISO weeks, Monday to Sunday, in UTC. Transfer limits explains every field in the response. When you send on behalf of end senders, read GET /v1/limits/remitters/{remitter_id} instead.

The paused response

The paused response carries pause_reason, breached_window, remaining_usd, requirements[] and expires_at. pause_reason is one of per_transfer, daily, weekly, monthly, annual, annual_requires_approval, screening or sender_details_required.

Steps

1. Send, and read transfer_status

If the amount fits within the caps, send as usual. If the HTTP 200 response shows transfer_status paused, go to step 2.

{
  "data": {
    "amount": "1500",
    "amount_currency": "USD",
    "reference": "d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f",
    "date_created": "2026-10-13T12:00:00.000Z",
    "transfer_status": "paused",
    "transaction_id": "e3d5f7a9-1b2c-4d4e-8f60-8a9b0c1d2e3f",
    "local_amount": null,
    "local_currency": null,
    "fee": "1.5",
    "fee_currency": "USD",
    "requires_approval": false,
    "status": "awaiting_remitter_information",
    "park_reason": "remitter_information",
    "pause_reason": "weekly",
    "requirements": [
      {
        "type": "remitter_verification",
        "tier": "T2",
        "levels": ["enhanced"],
        "description": "resume with remitter_verification at one of these levels"
      }
    ],
    "breached_window": "weekly",
    "remaining_usd": "300.00",
    "expires_at": "2026-10-16T12:00:00Z"
  },
  "error": null
}

2. Answer what requirements asks for

remitter_verification. This applies to businesses that verify their own senders. Resume with a verification at one of the listed levels. Send only checks you ran.

curl --request POST \
 --url https://api.gravv.xyz/v1/transfer/d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f/resume \
 --header 'Api-Key: <API_KEY>' \
 --header 'Content-Type: application/json' \
 --header 'Idempotency-Key: 7ee1ac93-8eec-4f46-95e0-fbc08703469f' \
 --data '
{
"remitter_verification": {
"remitter_id": "rem-maria-lopez-01",
"level": "enhanced",
"provider": "acme-kyc",
"provider_reference": "chk_558120",
"verified_at": "2026-10-13T11:58:00Z",
"screening": {
  "sanctions": "clear",
  "pep": "clear",
  "adverse_media": "clear"
}
}
}
'
const response = await fetch("https://api.gravv.xyz/v1/transfer/d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f/resume", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "7ee1ac93-8eec-4f46-95e0-fbc08703469f",
},
body: JSON.stringify({
"remitter_verification": {
  "remitter_id": "rem-maria-lopez-01",
  "level": "enhanced",
  "provider": "acme-kyc",
  "provider_reference": "chk_558120",
  "verified_at": "2026-10-13T11:58:00Z",
  "screening": {
    "sanctions": "clear",
    "pep": "clear",
    "adverse_media": "clear"
  }
}
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/transfer/d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f/resume",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Content-Type": "application/json",
    "Idempotency-Key": "7ee1ac93-8eec-4f46-95e0-fbc08703469f",
},
json={
    "remitter_verification": {
        "remitter_id": "rem-maria-lopez-01",
        "level": "enhanced",
        "provider": "acme-kyc",
        "provider_reference": "chk_558120",
        "verified_at": "2026-10-13T11:58:00Z",
        "screening": {
            "sanctions": "clear",
            "pep": "clear",
            "adverse_media": "clear",
        },
    },
},
)
print(response.status_code, response.json())
{
  "data": {
    "transfer_id": "d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f",
    "status": "approved_awaiting_execution",
    "transfer_status": "pending",
    "resumed": true
  },
  "error": null
}

If the transfer is still over a cap, the response is resumed: false with new requirements.

screening_clear. Resume with a remitter_verification whose screening values are all clear.

admin_review. Only Gravv can release the transfer. Contact support with the reference, or wait for the next window to start.

Sender details. Some payout routes need the sender's identity in remitter_full_name, remitter_date_of_birth, remitter_address and remitter_country. Resume with additional_information:

curl --request POST \
 --url https://api.gravv.xyz/v1/transfer/d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f/resume \
 --header 'Api-Key: <API_KEY>' \
 --header 'Content-Type: application/json' \
 --header 'Idempotency-Key: 17273857-9c44-4272-91e8-49bd4214e6f1' \
 --data '
{
"additional_information": {
"remitter_full_name": "Maria Lopez",
"remitter_date_of_birth": "1988-04-17",
"remitter_country": "US",
"remitter_address": {
  "address_line1": "55 Water Street",
  "city": "Brooklyn",
  "postal_code": "11201",
  "country": "US"
}
}
}
'
const response = await fetch("https://api.gravv.xyz/v1/transfer/d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f/resume", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "17273857-9c44-4272-91e8-49bd4214e6f1",
},
body: JSON.stringify({
"additional_information": {
  "remitter_full_name": "Maria Lopez",
  "remitter_date_of_birth": "1988-04-17",
  "remitter_country": "US",
  "remitter_address": {
    "address_line1": "55 Water Street",
    "city": "Brooklyn",
    "postal_code": "11201",
    "country": "US"
  }
}
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/transfer/d2c4e6f8-0a1b-4c3d-9e5f-7a8b9c0d1e2f/resume",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Content-Type": "application/json",
    "Idempotency-Key": "17273857-9c44-4272-91e8-49bd4214e6f1",
},
json={
    "additional_information": {
        "remitter_full_name": "Maria Lopez",
        "remitter_date_of_birth": "1988-04-17",
        "remitter_country": "US",
        "remitter_address": {
            "address_line1": "55 Water Street",
            "city": "Brooklyn",
            "postal_code": "11201",
            "country": "US",
        },
    },
},
)
print(response.status_code, response.json())

customer_email. Add an email to the customer with PUT /v1/customers/{customer_id}, then resume with {}.

If you do nothing. At expires_at, the transfer fails with reason limit_pause_expired (or sender_details_expired), and Gravv refunds the fee.

Use a new Idempotency-Key for every resume attempt.

What can go wrong

Response Why Fix
200 transfer_status paused, pause_reason weekly The weekly cap is reached Follow step 2, or send at most remaining_usd
422 transfers are not permitted for this sender The sender is blocked Contact support. Do not retry
503 transfer limits are temporarily unavailable, please retry Gravv cannot check limits right now Retry with backoff
400 remitter_verification.remitter_id is required The verification has no sender id Add remitter_id
400 the remitter information does not match the sender this transfer was paused for; resume with the same remitter_id, or initiate a new transfer The resume has a different remitter_id Use the same remitter_id, or send a new transfer
400 remitter_date_of_birth must be a date as YYYY-MM-DD The date has the wrong format, or is in the future Send 1988-04-17
200 resumed: false with new requirements The transfer is still over a cap, or a detail is still missing Send what is listed
410 ERROR_CODE_TRANSFER_PAUSE_EXPIRED or ERROR_CODE_TRANSFER_PAUSE_CANCELLED The pause has ended Send a new transfer
409 ERROR_CODE_TRANSFER_IN_PROGRESS with Retry-After: 1 Gravv is still creating a transfer with the same client_reference Wait a second and retry
404 not found: subject on the limits read The customer is not one of yours Check the id

Webhooks you will receive

  1. transfer.requires_information, with event_data.status paused, reason (the pause reason) and limit_details (breached_window, cap_usd, used_usd, remaining_usd, amount_usd, requirements).
  2. After a resume, transfer.resumed, then the usual transfer events.
  3. If the pause expires or is cancelled, transfer.status.failed with reason limit_pause_expired, limit_pause_cancelled, sender_details_expired or sender_details_cancelled.

Testing in sandbox

A new sandbox customer's policy is in shadow mode. You cannot produce a limit pause there, and usage does not change. The limits read works, and a non-UUID id returns 400 subject_id "not-a-uuid" is not a customer id. Contact support for a test policy if you need to test a pause.

Reference

Was this page helpful?