---
title: "Formats and limits"
description: "How the Gravv API writes amounts, dates, ids, country codes and statuses, and how large an uploaded file can be."
---
The Gravv API mixes a few formats. Most amounts are decimal strings, a few are in minor units, and card routes take numbers. This page lists the rules every route follows and the exceptions. Each endpoint's page has its own details.

**Example.** You pay out 2,500.00 USD. You send `"amount": "2500.00"`, a string. The transfer response shows `"fee": "1.5"`, with the trailing zero trimmed. The account's `ledger_balance.posted` reads `120000` with `scale` 2, which is 1,200.00. The `created_at` of the transaction reads `2026-10-13T09:15:02.481263Z`. Parse all three as written, never as floats.

## Amounts

| Where | Format | Example |
| :-- | :-- | :-- |
| Transfers, collections, FX quotes and card funding (requests) | A decimal string in major units, at most 2 decimal places. Transfers also accept a JSON number, but it can lose precision | `"2500.00"` |
| Card withdrawals (`amount`) and card limits (`card_limit`, `limit`) | A JSON number in US dollars. A string can fail with an internal error | `435.8` |
| Most response amounts: `amount`, `fee`, account and wallet `balance` | A decimal string in major units. The API may trim trailing zeros | `"1.5"`, `"250"` |
| `ledger_balance.posted` and `ledger_balance.projected` on accounts | **Minor units**, with `scale` | `120000` with `scale` 2 is 1,200.00 |
| Collections: `fee_cents`, `total_amount_cents` | **Minor units** (cents) | `150` is 1.50 |
| Webhook amounts | Decimal strings. Transfers trim zeros. Collections always show 2 decimals | `"100.5"`, `"1500.00"` |

- Never parse an amount as a floating-point number. Use a decimal type, or integers of minor units.
- Currencies are ISO 4217 codes in upper case (`USD`, `EUR`, `NGN`). Stablecoins are `USDC` and `USDT`.
- The API matches a settlement instruction's `currency` exactly, so always send it in upper case.

## Dates and times

- Timestamps are RFC 3339, usually in UTC (`Z`), with or without fractional seconds. Gravv sends both `2026-10-13T09:15:02Z` and `2026-10-13T09:15:02.481263Z`. Some fields carry an offset instead, such as a customer's `date_added` (`+01:00`). Parse with an RFC 3339 parser that accepts optional fractions and offsets, and compare times, not text.
- A webhook's `timestamp` is when Gravv sent the event, not always when the change happened. Its precision depends on the event.
- Dates without a time are `YYYY-MM-DD`: `date_of_birth`, `remitter_date_of_birth`, and the dates on KYC documents. Gravv does not check document dates on upload, but a review can reject a document with a wrong date.
- Date filters differ by route, and some lists have none. [Webhook history](/platform/webhooks/webhooks-overview) takes RFC 3339, `YYYY-MM-DD` or a date and time without a zone, and reads all of them as UTC. The [transactions list](/recipes/transactions/reconcile-a-day-of-movements) has no date filter, and the FX orders list ignores dates. Filter on your side.

## Ids and references

- Gravv ids are UUIDs: `customer_id`, `account_id`, `external_account_id`, `card_id`, `wallet_id`. A transfer's `source.id` and `customer_id` must be UUID version 4.
- Ids belong to one environment. A sandbox id is "not found" with a live key, and the other way round.
- A transfer has two ids: the `transaction_id` you read in transactions, and the `reference` you [resume](/platform/transfers/paused-transfers) with.
- `client_reference` is your own reference. Use one per payment and keep it unique.
- An on-chain deposit's `transaction_id` is `<network>:<hash>`.
- A webhook's `event_id`, in the body, is unique per event, so deduplicate on it. The `X-Event-ID` header is shared by every event of one change. See [Webhooks](/platform/webhooks/webhooks-overview#delivery-retries-and-idempotency).

## Codes and casing

| What | Format | Example |
| :-- | :-- | :-- |
| Countries on customers, payees and institutions | ISO 3166-1 alpha-2, upper case | `NG`, `US` |
| Countries in card KYC | ISO 3166-1 alpha-3, including on the selfie | `NGA` |
| Countries in customer KYC documents | Alpha-2 or alpha-3, upper case | `NG` or `NGA` |
| States and regions on v2 account forms | ISO 3166-2 | `US-CA`, `GB-LND` |
| Statuses, types and most other values | Lower case with underscores. Filters are case-sensitive, so `status=PENDING` matches nothing | `manual_review`, `setup_failed`, `paused` |
| On-chain deposit events (`inflow.onchain.*`) | `status` in upper case | `COMPLETED`, `FAILED` |
| Customer KYC document types and sides | Upper case | `PASSPORT`, `FRONT_SIDE` |
| Card KYC document types | Lower case | `passport`, `id_card` |
| Blockchain networks | Lower case | `polygon`, `stellar` |
| Phone numbers | E.164, with the country code | `+2348012345678` |

## Upload sizes

Files go in the JSON body as base64. The decoded file must be a JPEG, PNG or PDF, and Gravv checks the type from the file's content.

| Upload | Largest file in live |
| :-- | :-- |
| Customer KYC documents, [`POST /v1/customers/kyc/upload-document`](/api-reference/kyc/post-v1-customers-kyc-upload-document) | 10 MB |
| Card KYC documents, [`POST /v1/risk/upload-document`](/api-reference/card-kyc/post-v1-risk-upload-document) | 5 MB |
| Transfer supporting documents, [`POST /v1/transfer/documents`](/api-reference/transfer/post-v1-transfer-documents) | 700 KB |
| v2 account documents, on [create](/platform/accounts/open-an-account) and on [an application update](/platform/accounts/fix-a-blocked-application) | 2 MB each |

- **In sandbox, keep every file under 500 KB**, and the files of one request under 500 KB together. Sandbox refuses requests of about 600 KB or more with `413` `the uploaded file is too large — the maximum is 10MB`, whatever the route's own limit.
- Gravv does not deduplicate a request body over 1 MiB under its `Idempotency-Key`. Base64 encoding makes a file over about 770 KB that large. A retry of such an upload runs again instead of returning the first response. After a timeout, look for what the first call stored before you send it again. See [Idempotent requests](/getting-started/idempotent-requests#retry-behavior).
- Send standard base64 with no `data:` prefix. The v2 account routes also take URL-safe base64.
