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.

  1. The bank asks for a newer proof of address. The account stays pending.
  2. You upload one PDF with PATCH. The application goes back to processing.
  3. 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.

Reference

Was this page helpful?