Responding to RFIs
Find, read, answer, and submit a request for information on a compliance review.
This guide walks through answering a request for information (RFI) on a compliance review. For what an RFI is and how it fits into the review lifecycle, see Requests for Information (RFIs).
Step 1: Learn that an RFI exists
Subscribe to the compliance_review_status_changed webhook event (category COMPLIANCE_REVIEW). When currentStatus.type is additionalInformationRequested, the status carries the rfiId:
{
"type": "compliance_review_status_changed",
"organizationId": "0c1c0f50-7a89-4e3a-9b1a-1d4ce4d6f001",
"complianceReviewId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"previousStatus": {
"type": "inReview",
"submittedAt": "2026-02-24T12:00:00.000Z"
},
"currentStatus": {
"type": "additionalInformationRequested",
"rfiId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
},
"updatedAt": "2026-02-25T09:15:00.000Z"
}You can also subscribe to the RFI webhook category. It has two events:
| Event | When it fires |
|---|---|
rfi_created | A reviewer raises an RFI. |
rfi_status_changed | The RFI changes status. For example, it moves to submissionValidationsFailed, submitted or cancelled. |
{
"type": "rfi_status_changed",
"organizationId": "0c1c0f50-7a89-4e3a-9b1a-1d4ce4d6f001",
"rfiId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"origin": {
"type": "complianceReview",
"complianceReviewId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"statusChangeDetails": {
"previousStatus": "validatingSubmission",
"currentStatus": "submissionValidationsFailed"
},
"updatedAt": "2026-02-25T10:05:00.000Z"
}rfi_created carries the same organizationId, rfiId and origin fields, plus createdAt.
If you poll instead of subscribing, the same rfiId appears on the review's status from GET /compliance/{organizationId}/reviews/{complianceReviewId}:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"tier": "STANDARD",
"type": "individual",
"status": {
"type": "additionalInformationRequested",
"rfiId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
},
"createdAt": "2026-02-20T08:00:00.000Z",
"requiredAttestations": [
{ "attestationType": "individualPersonalInfo" },
{ "attestationType": "individualContactInfo" },
{ "attestationType": "individualResidentialAddress" },
{ "attestationType": "individualIdentityDocument" },
{ "attestationType": "individualFinancialInfo" },
{ "attestationType": "individualTaxInfo" }
],
"uploadedAttestations": [
{ "id": "c4a1f0d2-8b3e-4f27-9a05-1e6d3b7c8f42", "attestationType": "individualPersonalInfo", "validationStatus": { "type": "approved" } },
{ "id": "1d7b5e93-0c62-4a18-b3df-9e4a2c6f5017", "attestationType": "individualContactInfo", "validationStatus": { "type": "approved" } },
{ "id": "8f2c4a61-7d09-4b53-a8e1-5c3b0d9f6e24", "attestationType": "individualResidentialAddress", "validationStatus": { "type": "approved" } },
{ "id": "3a6e8b70-2f14-4c95-9d0a-7b1e5c8f2043", "attestationType": "individualIdentityDocument", "validationStatus": { "type": "approved" } },
{ "id": "5b9d1c48-6e27-4f03-8a52-0d4c7e1b9f36", "attestationType": "individualFinancialInfo", "validationStatus": { "type": "approved" } },
{ "id": "9e4f7a25-3b81-4d60-95c7-2f8a6b0e1d54", "attestationType": "individualTaxInfo", "validationStatus": { "type": "approved" } }
]
}The review carries the rfiId only while status.type is additionalInformationRequested. requiredAttestations and uploadedAttestations describe the review as a whole, and every required type is already uploaded — the review could not have reached a reviewer otherwise. They do not tell you what the reviewer asks for now. Read the RFI for that.
Step 2: Read the RFI
GET /compliance/{organizationId}/rfis/{rfiId}
The RFI lists only the outstanding items.
curl --request GET \
--url "https://api.muralpay.com/api/compliance/$ORGANIZATION_ID/rfis/$RFI_ID" \
--header 'accept: application/json' \
--header "authorization: Bearer $MURAL_API_KEY"The response is a discriminated union on type. Only the business variant has associatedPersons.
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"type": "individualComplianceReview",
"complianceReviewId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": { "type": "outstanding" },
"createdAt": "2026-02-25T09:15:00.000Z",
"updatedAt": "2026-02-25T09:15:00.000Z",
"attestations": [
{
"attestationType": "individualContactInfo",
"requestedScope": {
"type": "fields",
"details": [],
"fields": [
{
"field": "phoneNumber",
"details": [
{
"code": "INCONSISTENT_DATA",
"description": "The number is unreachable. Please provide a mobile number."
}
]
}
]
}
}
]
}{
"id": "3f1a9c22-55b8-4e01-9a7d-2c4f6b8e1d03",
"type": "businessComplianceReview",
"complianceReviewId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": { "type": "outstanding" },
"createdAt": "2026-02-25T09:15:00.000Z",
"updatedAt": "2026-02-25T09:15:00.000Z",
"attestations": [
{
"attestationType": "businessProofOfAddressDocuments",
"requestedScope": {
"type": "wholeAttestation",
"details": [
{
"code": "EXPIRED_DOCUMENT",
"description": "The uploaded proof of address is older than 90 days."
}
]
}
}
],
"associatedPersons": [
{
"associatedPersonId": "550e8400-e29b-41d4-a716-446655440001",
"attestations": [
{
"attestationType": "associatedPersonIdentityDocument",
"requestedScope": {
"type": "wholeAttestation",
"details": [
{
"code": "POOR_QUALITY",
"description": "The document image is too dark to read."
}
]
}
}
]
}
]
}You can submit only when
attestationsandassociatedPersonsare both empty.
How much to resupply
requestedScope.type tells you how much of an attestation to send back.
requestedScope.type | What to send |
|---|---|
wholeAttestation | The entire attestation, as if uploading it for the first time. Previous values are not kept. |
fields | At least the fields named in fields[]. Extra fields are accepted. If you omit a named field, the upload fails. |
Why the reviewer asked
Each details entry carries a machine-readable code and the reviewer's own description. Show description to your user, and use code to drive your logic.
code | Meaning |
|---|---|
INCORRECT_DOCUMENT | The wrong document was supplied. |
POOR_QUALITY | Unreadable — blurred, cropped, or too dark. |
MISSING_INFORMATION | Required information was absent. |
INCONSISTENT_DATA | Conflicts with other supplied data. |
EXPIRED_DOCUMENT | Past its validity date. |
ADDRESS_NOT_FOUND | The address could not be verified. |
ADDRESS_SUGGESTED_CORRECTION | A corrected address is suggested. |
OTHER | See description. |
Step 3: Upload the requested attestations
PUT /compliance/{organizationId}/rfis/{rfiId}/attestations
Review-level and associated-person attestations share one list. Associated-person entries carry their own associatedPersonId; review-level entries do not. The endpoint accepts only the types the RFI named. Any other type returns a 400.
Each attestation carries the same fields it carries on a compliance review. See Individual KYC Requirements or Business KYC Requirements for the fields of each type. Attestations that reference files still go through the Uploading Documents for an Attestation flow first; send the returned documentId.
If a business RFI requests businessAssociatedPerson, create the person and upload all of their required attestations first, then add them to the review with { "type": "businessAssociatedPerson", "associatedPersonId": "<id>" }, one entry per person.
curl --request PUT \
--url "https://api.muralpay.com/api/compliance/$ORGANIZATION_ID/rfis/$RFI_ID/attestations" \
--header 'accept: application/json' \
--header "authorization: Bearer $MURAL_API_KEY" \
--header 'content-type: application/json' \
--data '{
"attestations": [
{
"type": "individualContactInfo",
"phoneNumber": "+14155551234"
},
{
"type": "associatedPersonIdentityDocument",
"associatedPersonId": "550e8400-e29b-41d4-a716-446655440001",
"governmentId": {
"type": "passport",
"countryCode": "US",
"passportNumber": "A12345678",
"passportDocumentId": "b7e2d1c4-3f5a-4c8e-9d16-2a7b8c9e0f31"
}
}
]
}'A review-level type can appear one time per request, and an associated-person type one time for each person. A repeated type returns a 422. You can upload as many times as you need before submitting.
The response reports what this request stored, and separates the two kinds. It is not the merged view of the attestation, and it does not list what is still outstanding. Read the RFI again for that information.
{
"reviewAttestations": [
{
"id": "c4a1f0d2-8b3e-4f27-9a05-1e6d3b7c8f42",
"attestationType": "individualContactInfo",
"createdAt": "2026-02-25T10:02:00.000Z",
"validationStatus": { "type": "pending" }
}
],
"associatedPersons": []
}validationStatus.type is pending, approved, or failed. Before submitting, check the attestations the RFI requested:
pendingblocks submission. Wait, then submit again.failedwith anyBLOCKINGfailure blocks submission. Upload the attestation again with corrected data.failedwith onlyWARNINGfailures can be submitted withacknowledgeWarnings: true.
A blocked submit returns 412.
Step 4: Submit the response
POST /compliance/{organizationId}/rfis/{rfiId}/submit
Submitting validates your response. The request body is optional; send { "acknowledgeWarnings": true } to proceed past WARNING failures.
curl --request POST \
--url "https://api.muralpay.com/api/compliance/$ORGANIZATION_ID/rfis/$RFI_ID/submit" \
--header 'accept: application/json' \
--header "authorization: Bearer $MURAL_API_KEY"Request for Information statuses
status.type | Meaning | Can you still act? |
|---|---|---|
outstanding | The reviewer is waiting. Upload, then submit. | Yes |
validatingSubmission | Validations are running against your response. | No |
submissionValidationsFailed | One or more validations failed. failures lists them. | Yes — correct the failing validations and submit again |
submitted | RFI is with our compliance team. submittedAt records the time. | No |
cancelled | The RFI was cancelled. reason and cancelledAt explain. | No |
failures lists what to correct and why:
{
"type": "submissionValidationsFailed",
"failures": [
{
"severity": "WARNING",
"failureDescription": "Tax ID type CPF is not valid for country US. Allowed types: SSN, ITIN.",
"failureInfo": {
"validationCode": "TAX_ID_COUNTRY_PAIR",
"taxIdType": "CPF",
"country": "US"
}
}
]
}Use failureInfo.validationCode to drive your logic. The top-level validationCode field is deprecated.
You must correct a BLOCKING failure and upload again. You can bypass a WARNING failure: submit again with acknowledgeWarnings: true.
Cancellation
Do not continue with a cancelled RFI. Read the review again to see what changed:
reason | Meaning |
|---|---|
WITHDRAWN_BY_REVIEWER | The reviewer no longer needs the information. |
SUBJECT_RESET | A KYB reset reverted the review to draft. |
SUPERSEDED | A newer RFI replaced this one. Read the review for the current rfiId. |
Updated 22 minutes ago