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:

EventWhen it fires
rfi_createdA reviewer raises an RFI.
rfi_status_changedThe 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."
              }
            ]
          }
        ]
      }
    }
  ]
}

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 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:

  • pending blocks submission. Wait, then submit again.
  • failed with any BLOCKING failure blocks submission. Upload the attestation again with corrected data.
  • failed with only WARNING failures can be submitted with acknowledgeWarnings: 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.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
cancelledThe 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:

reasonMeaning
WITHDRAWN_BY_REVIEWERThe reviewer no longer needs the information.
SUBJECT_RESETA KYB reset reverted the review to draft.
SUPERSEDEDA newer RFI replaced this one. Read the review for the current rfiId.

Did this page help you?