Read account requirements
Use GET /v2/accounts/requirements to get the fields and documents a bank needs, and turn the form into a create request.
API version: v2.
Each bank asks for different data. GET /v2/accounts/requirements returns the form for one option. The form lists the fields, the documents and, for a business, the people (parties) to declare. Read it before you open the account, so the create call succeeds the first time.
A money example. Ada's 1,200.00 USD invoice needs a USD account. The default USD option for a GB individual asks for 13 fields (names, date of birth, email, phone, gender, citizenship, tax residence and a full address) and 2 documents (an identity document and a proof of address). You collect those once, and the account opens without a second round.
Ask for the form
Always send customer_type, currency, bank_code and country. The create call picks the bank from the country inside your data, while this call picks it from the country parameter. Unless you send both bank_code and country, you can get the form of a different bank. Send customer_id too, to see what is already on file:
curl --request GET \
--url 'https://api.gravv.xyz/v2/accounts/requirements?customer_type=individual¤cy=USD&bank_code=<bank_code>&country=GB&customer_id=0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33' \
--header 'Api-Key: <API_KEY>'
Here is the response, trimmed to 3 fields and 2 suggestions (checked in sandbox):
{
"data": {
"documents": [
{"new_document_fields": ["document_type", "content", "issuing_country", "number", "issued_date", "expiration_date"], "required": true, "scope": "root", "types": ["national_id", "passport", "drivers_license", "permanent_resident_card", "employment_authorization", "other"]},
{"new_document_fields": ["document_type", "content", "issuing_country"], "required": true, "scope": "root", "types": ["proof_of_address"]}
],
"fields": [
{"group": "identity", "is_predefined": true, "label": "First Name", "name": "first_name", "required": true, "scope": "root", "type": "string", "write_only": false},
{"enum": "Gender", "group": "identity", "is_predefined": true, "label": "Gender", "name": "gender", "options": ["female", "male"], "required": true, "scope": "root", "type": "enum", "write_only": false},
{"applies_when": {"field": "citizenship_country", "op": "eq", "value": "US"}, "group": "us_tax", "is_predefined": false, "label": "Tax ID Number", "name": "tax_id_number", "required": true, "scope": "root", "type": "string", "write_only": true}
],
"has_existing_ref": false,
"max_parties": null,
"min_parties": null,
"suggestions": {
"documents": {"passport": {"message": "already on file for this customer; upload a new one to override it", "value": "passport"}},
"fields": {"first_name": {"message": "on file for this customer; send this field to override it", "value": "Ada"}}
},
"token_options": [{"asset": "usdc", "network": "polygon"}, {"asset": "usdc", "network": "ethereum"}, {"asset": "usdc", "network": "solana"}]
},
"error": null
}
Turn the form into a request
| The form says | Put it in the create body |
|---|---|
a field with scope root |
data.root.<name>. A dotted name is nested: permanent_address.city becomes "permanent_address": {"city": "…"} |
a field with scope party[*] |
every entry of data.parties[] (directors, owners, signers of a business) |
new_party_fields |
the fields for a person who is not on the customer record (no id) |
a document with scope root |
one entry in data.documents[], of any one of the listed types |
a document with scope party[*] |
that party's own documents[] |
new_document_fields |
the keys a new upload carries: document_type, content (base64; JPEG, PNG or PDF; at most 2 MB, and in sandbox under 500 KB for all of one request's files), issuing_country, and number, issued_date, expiration_date for identity documents |
required: true and no condition |
always send it |
applies_when |
send it only when the condition is true for this customer, for example only for US citizens |
options |
the only accepted values |
write_only: true |
a secret, such as a tax number. The API never returns it |
is_predefined: true, or the field is in suggestions.fields |
already on the customer record. Leave it out and Gravv fills it, or send it to override |
suggestions.documents.<type> |
a document of that type is on file |
token_options |
the networks you can send as network |
min_parties, max_parties |
how many people a business must declare. null means none needed |
has_existing_ref: true |
the bank already knows this customer, so send no data.root |
existing_application |
an application is already open at this bank, so fix that one instead |
These parts of the form are not yet live: a document with a required_when condition, a metadata object on a document, a predefined object, and the autofill_tax_form question. They exist only on a build that has not reached production. In production, every document carries required (true, or false when the bank fills a tax form itself). Sandbox already returns the new shape for some forms.
Rules the form does not show
- Top level, not in
data.root. Sendcustomer_id,customer_type,currency,account_type,category,label,bank_code,networkandwallet_idat the top level. The API refusesnetworkorwallet_idinsidedata.root. - Always send
customer_typeand the routing country. Send the routing country indata.root, ascitizenship_countryfor a person orincorporation_countryfor a business. Gravv chooses the bank from your request before it fills in any missing fields. - Filled in for you. If you leave them out, Gravv fills in names,
date_of_birth,email, phone,gender,legal_name,registration_number,business_description,business_typeandindustry. A filled-incitizenship_countryortax_residence_countryis the customer's address country, so send them when they differ. Gravv does not fill in the address, so sendpermanent_addressyourself. - Documents on file. If you send no documents, Gravv attaches the customer's documents on file. If you send even one, Gravv uses only what you send. To reuse a file, send
{"id": "<document id>"}instead ofcontent. - Registration type.
registration_typedefaults fromcustomer_type. Send it only for a joint or minor account. - Gender.
genderacceptsfemaleormaleon these forms. If you created the customer withother, the form suggestsotherbut refuses it. Ask the customer which value to send. - Old field names. Gravv still maps old names such as
doborssn. Send the name the form gives. Sending both returnsalias_conflict.
Errors
| Response | Why | Fix |
|---|---|---|
400 Customer Type is required (and 1 other field; see details) with details.customer_type and details.currency |
Both are required | Send them |
400 no account option is available for the requested combination (…), with UNSUPPORTED_COMBINATION in details |
No option serves the values you sent. Also returned for an unknown bank_code, or a bank_code that does not serve that country or currency |
Copy bank_code, currency and country from one option in capabilities |
400 invalid request payload: unknown field "limit" |
Unknown query parameter | Send only the documented ones |
| A business form for an individual customer | The requirements call does not check customer_type against the record |
Send the customer's own type. The create call refuses a mismatch |
No suggestions although you sent customer_id |
The customer lookup failed | Retry. The form itself is correct |
Next
Open the account with the data you collected.