Unblock an application
Find out what the bank is waiting for on a pending v2 account, then send the documents or data in one PATCH, or send the customer to the bank's link.
Find out what the bank is waiting for on a pending v2 account. Then send the documents or data in one PATCH, or send the customer to the bank's link.
API version: v2.
Who it is for. Any developer whose new account is still pending with requires_user_action: true, or who received applications.accounts.blocked.
Status. Available.
Money example
Ada's client cannot pay her 1,200.00 USD invoice until her account is active.
- The bank asks for a newer proof of address. The account stays
pending. - You upload one PDF with PATCH. The application goes back to
processing. - The bank approves the application, the account becomes
active, and the client can send the 1,200.00 USD.
Before you start
You have the account_id from the create response, or the customer_id.
Find out what is required
1. Find the application
Read application_id from GET /v2/accounts/{account_id}, or list the customer's blocked applications:
curl --request GET \
--url 'https://api.gravv.xyz/v2/accounts/applications?customer_id=0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33&status=blocked' \
--header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v2/accounts/applications?customer_id=0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33&status=blocked", {
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/v2/accounts/applications?customer_id=0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33&status=blocked",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
Page through the list with limit and offset. Its count is the size of the current page, not a total. The status filter is lower case, and a misspelled status returns [].
2. Read what is asked
curl --request GET \
--url https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6 \
--header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6", {
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/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
{
"data": {
"application_id": "5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6",
"customer_id": "0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33",
"customer_type": "individual",
"status": "blocked",
"parties": [{"role": "primary", "status": "declared", "first_name": "Ada", "last_name": "Okafor"}],
"created_at": "2026-10-13T09:12:44.512031Z",
"updated_at": "2026-10-13T09:20:02.118734Z",
"status_reason": "awaiting_customer_document",
"status_message": "The bank is waiting on documents for this customer. Upload every type listed in required_documents in ONE PATCH /v2/accounts/applications/{application_id} call — separate calls make the bank re-review what it already holds.",
"required_documents": ["proof_of_address"],
"requires_user_action": true
},
"error": null
}
Branch on status_reason. Never branch on status_message, because its text can change.
status_reason |
Who acts | What to do |
|---|---|---|
awaiting_customer_document |
You | PATCH every type in required_documents, in one call |
awaiting_customer_data |
You | PATCH every field in required_fields, as metadata keys |
tos_pending |
The customer | Open required_action_url and accept the bank's terms. The application resumes on its own. The API has no call for terms |
kyc_pending |
The customer | Open required_action_url, the bank's own verification page. If it is not there yet, read the application again later |
provider_review, awaiting_activation_details, provider_unavailable, provisioning_retrying |
Nobody | Wait |
manual_review_requested |
Gravv | Gravv holds the application for review and sends no webhook. Contact support with account_id and application_id |
verification_rejected, verification_declined, review_declined |
Nobody | The bank said no, and the same details get the same answer. Try another option |
region_not_supported |
Nobody | Choose another bank_code |
identity_document_mismatch, information_expired, request_cancelled |
You | Open a new account with corrected data |
settlement_wallet_unavailable |
You | Open a new account on another network |
An application's status is one of processing, blocked, manual_review, approved, rejected, expired, cancelled.
Steps
3. Send what was asked, in one PATCH
PATCH takes no Idempotency-Key, so do not retry it without checking. After a timeout, read the application first. Each document needs filename here, and can be at most 2 MB. In sandbox, keep the files of one PATCH under 500 KB together.
curl --request PATCH \
--url https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6 \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '
{
"documents": [
{
"document_type": "proof_of_address",
"issuing_country": "GB",
"filename": "council-tax-2026.pdf",
"content": "<base64 of the document>"
}
]
}
'
const response = await fetch("https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6", {
method: "PATCH",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"documents": [
{
"document_type": "proof_of_address",
"issuing_country": "GB",
"filename": "council-tax-2026.pdf",
"content": "<base64 of the document>"
}
]
}),
});
console.log(response.status, await response.json());
import os
import requests
response = requests.patch(
"https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
"Content-Type": "application/json",
},
json={
"documents": [
{
"document_type": "proof_of_address",
"issuing_country": "GB",
"filename": "council-tax-2026.pdf",
"content": "<base64 of the document>",
},
],
},
)
print(response.status_code, response.json())
For missing data, send metadata instead:
curl --request PATCH \
--url https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6 \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--data '
{
"metadata": {
"tax_id_number": "123456789"
}
}
'
const response = await fetch("https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6", {
method: "PATCH",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"metadata": {
"tax_id_number": "123456789"
}
}),
});
console.log(response.status, await response.json());
import os
import requests
response = requests.patch(
"https://api.gravv.xyz/v2/accounts/applications/5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
"Content-Type": "application/json",
},
json={
"metadata": {
"tax_id_number": "123456789",
},
},
)
print(response.status_code, response.json())
On a business application, an identity document names its person with party_id, taken from parties[].id on the application. It also carries number and expiration_date.
The API returns the application. Its status is processing if you sent everything the bank asked for, or still blocked if you did not. The response carries no status_reason or required_* fields, so read the application again.
4. Wait for the account
Wait for applications.accounts.completed, or read GET /v2/accounts/{account_id} until status is active.
What can go wrong
| Response | Why | Fix |
|---|---|---|
400 application is not editable |
The application is processing, approved or rejected |
PATCH only a blocked application, or a manual_review application for documents |
400 Tax ID Number was not requested for this application |
You sent a field the bank did not ask for | Send only required_fields and required_documents |
400 document type "articles_of_incorporation" was not requested for this application |
You sent a document the bank did not ask for | Send only required_fields and required_documents |
400 missing document filename |
The document has no filename |
Add it |
400 missing document issuing country |
The document has no issuing_country |
Add it |
400 Party ID is required for a business party document |
A business ID has no party_id |
Add it |
400 Type is no longer accepted; send Document Type … |
You sent the old document keys | Send document_type and document_type_side |
400 invalid request payload: unknown field "tos_accepted" |
The API has no terms field | Send the customer to required_action_url |
404 Resource not found |
The id is wrong, or belongs to another environment | Check the id and the key |
Still blocked after the PATCH |
You sent only part of what the bank asked for | Read the application again and send the rest in one call |
| A second block of the same type never arrives as a webhook | Gravv can drop repeat events of one type for one application | Always read the application again after you act |
| No progress for 7 days on a customer block | The block expires. After three expiries, Gravv reviews the application | Act on each block before it expires, and contact support |
Webhooks you will receive
Gravv sends applications.accounts.blocked, with metadata.block_reason, metadata.action_url and metadata.additional_requirements (fields[], documents[]). Then it sends applications.accounts.completed, or applications.accounts.failed with metadata.fail_reason. Deduplicate events on the body's event_id. Do not use X-Event-ID, because it is the application id and is the same on every event. See Account application events.
Testing in sandbox
For the wallet-backed options, blocks come from the banks' test systems, and you cannot force one. The PATCH flow is the same in sandbox.