Responding to RFIs
When a reviewer needs more information to finish a compliance review, they raise a request for information (RFI). The review moves to additionalInformationRequested. It does not continue until you answer.
An RFI is its own resource, addressed by its own id. It lists the attestations the reviewer wants again, and how much of each one to send. Some attestations need a full resupply. Others need only the named fields.
| Step | Endpoint |
|---|---|
| Read the RFI | GET /compliance/{organizationId}/rfis/{rfiId} |
| Upload attestations | PUT /compliance/{organizationId}/rfis/{rfiId}/attestations |
| Submit your response | POST /compliance/{organizationId}/rfis/{rfiId}/submit |
The lifecycle
- A reviewer evaluates your compliance review. The review is in
inReview. - When the reviewer requires more information, the Compliance Review status is moved to
additionalInformationRequestedand a webhook event forcompliance_review_status_changedis emitted. This event contains anrfiId. - You get the RFI by the id, upload the necessary attestations, then submit the RFI.
- The RFI status then changes based on submission validations. If:
- They pass. The RFI moves to
submittedand the Compliance Review returns toinReview. - They fail. The RFI moves to
submissionValidationsFailedand the Compliance Review status does not change. Correct the data and submit again.
- They pass. The RFI moves to
You answer an RFI once. If the reviewer needs more information after your response, they will raise a new RFI and the Compliance Review reports a new rfiId.
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"
}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 Compliance Reviews or Business Compliance Reviews for the shape of each type. Attestations that reference files still go through the Document Uploads flow first; send the returned documentId.
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. 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. Validations must be in an approved state in order to submit the RFI.
Step 4: Submit the response
POST /compliance/{organizationId}/rfis/{rfiId}/submit
Submitting runs the submission validators against the RFI. The request body is optional.
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 | Withdrawn by reviewer. reason and cancelledAt explain. | No |
failures lists what attestations to correct and why:
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. |
Updated 1 day ago