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.

The lifecycle

  1. A reviewer evaluates your compliance review. The review is ininReview.
  2. When the reviewer requires more information, the Compliance Review status is moved to additionalInformationRequested and a webhook event for compliance_review_status_changed is emitted. This event contains an rfiId.
  3. You get the RFI by the id, upload the necessary attestations, then submit the RFI.
  4. The RFI status then changes based on submission validations. If:
    • They pass. The RFI moves to submitted and the Compliance Review returns to inReview.
    • They fail. The RFI moves to submissionValidationsFailed and the Compliance Review status does not change. Correct the data and submit again.

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."
              }
            ]
          }
        ]
      }
    }
  ]
}
📘

You can submit only when attestations and associatedPersons are both empty.

How much to resupply

requestedScope.type tells you how much of an attestation to send back.

requestedScope.typeWhat to send
wholeAttestationThe entire attestation, as if uploading it for the first time. Previous values are not kept.
fieldsAt 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.

codeMeaning
INCORRECT_DOCUMENTThe wrong document was supplied.
POOR_QUALITYUnreadable — blurred, cropped, or too dark.
MISSING_INFORMATIONRequired information was absent.
INCONSISTENT_DATAConflicts with other supplied data.
EXPIRED_DOCUMENTPast its validity date.
ADDRESS_NOT_FOUNDThe address could not be verified.
ADDRESS_SUGGESTED_CORRECTIONA corrected address is suggested.
OTHERSee 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.typeMeaningCan you still act?
outstandingThe reviewer is waiting. Upload, then submit.Yes
validatingSubmissionValidations are running against your response.No
submissionValidationsFailedOne or more validations failed. failures lists them.Yes — correct the failing validations and submit again
submittedRFI is with our compliance team. submittedAt records the time.No
cancelledWithdrawn 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:

reasonMeaning
WITHDRAWN_BY_REVIEWERThe reviewer no longer needs the information.
SUBJECT_RESETA KYB reset reverted the review to draft.

Did this page help you?