> ## Documentation Index
> Fetch the complete documentation index at: https://dev.enterprise.moonpay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Information Requests (RFIs)

> Read open information requests for your customers, answer the questions in the latest round, and upload the documents MoonPay asked for.

## Overview

An information request (RFI) is MoonPay Enterprise asking for more detail about one of your customers. An RFI carries questions, a request for documents, or both. You collect the answers from your customer, submit them through the API, and MoonPay reviews and closes the RFI.

There are three types:

* `probing_questions`: questions about the customer or their activity
* `source_of_funds`: questions about specific transactions. Always tied to a transaction
* `liveness`: a new selfie check. The RFI carries a `liveness_url` to send your customer to. It takes no answers and no follow-up rounds, so a response to it returns `400`

<Warning>
  Some RFIs also move the customer back to `IdentificationRequired`. That pauses the customer, and funds sent to an active Autoramp are held until the RFI is resolved. Treat an RFI on a live customer as blocking, not informational.

  Held funds can be returned to the sender, but only manually. [Contact the MoonPay team](/support) to arrange it.
</Warning>

Not every RFI does this. A customer can keep transacting with an open RFI, so read the customer's status rather than assuming the RFI paused them.

<Info>
  RFIs do not emit webhooks. Poll `GET /api/rfis` to pick up new requests.
</Info>

## Rounds

Every RFI has at least one round. Round 1 is created with the RFI. If your answers don't settle the question, MoonPay sends a follow-up round with new questions on the same RFI.

Only the latest round accepts a response. The write endpoints take a `round_id` and return `409 Conflict` if that round is not the latest one, so read the RFI first and use the round with the highest `round_number`. Rounds come back sorted by `round_number` ascending.

## Statuses

| Status | Description |
| - | - |
| `waiting_on_response` | The latest round is open. You owe a response. |
| `needs_review` | Your response is in. MoonPay is reviewing it. |
| `closed` | MoonPay resolved the RFI. |

Submitting a response moves an RFI from `waiting_on_response` to `needs_review`. A follow-up round moves it back to `waiting_on_response`.

<Note>
  The API does not return how an RFI was resolved. A `closed` RFI tells you MoonPay is done with it, not whether the response passed.
</Note>

## How it works

<Steps>
  <Step title="Poll for open RFIs">
    `GET /api/rfis`

    Returns open RFIs grouped by customer, oldest first.
  </Step>

  <Step title="Read the latest round">
    Take the round with the highest `round_number`. Its `questions` are what you need to answer, and `documents_requested` tells you whether documents are also needed.
  </Step>

  <Step title="Upload documents, if requested">
    `POST /api/rfis/{rfi_id}/rounds/{round_id}/documents`

    Collect the files from your customer and upload each one. Upload at least one before you submit the response.
  </Step>

  <Step title="Submit the response">
    `POST /api/rfis/{rfi_id}/rounds/{round_id}/response`

    Answer every required question in one call. The RFI moves to `needs_review`.
  </Step>

  <Step title="Wait for MoonPay">
    MoonPay either closes the RFI or sends a follow-up round. Keep polling to find out which.
  </Step>
</Steps>

## Answer an RFI in the dashboard

Every RFI also shows up in the Partner Dashboard, so your team can answer one without an integration. The dashboard calls the same endpoints documented below, so an RFI answered there reaches `needs_review` exactly as an API response does.

### 1. Open the RFI queue

**Compliance** > **RFIs**: every customer with an open request, how many requests each one has, and the age of the oldest. The status column separates what you owe a response on from what is already back with MoonPay.

<Frame quality="high">
  <img src="https://mintcdn.com/moonpayiron/U-hpPwCsALA8cBD_/images/clips/respond-to-rfi/01-rfi-queue.png?fit=max&auto=format&n=U-hpPwCsALA8cBD_&q=85&s=423b5f45c078f6f1508ed6bf64dbbbf4" alt="Compliance page on the RFIs tab, listing three customers with request counts and needs action or submitted badges" width="1600" height="1000" data-path="images/clips/respond-to-rfi/01-rfi-queue.png" />
</Frame>

### 2. Read the request

Click the customer's name. **Questions · Round 1** lists what was asked, with a **Needs action** badge while a response is outstanding. The copy button copies the questions to your clipboard to forward to your customer.

Required questions carry an asterisk, and each answer box matches the question type: free text, or Yes/No with an optional details field.

<Frame quality="high">
  <img src="https://mintcdn.com/moonpayiron/U-hpPwCsALA8cBD_/images/clips/respond-to-rfi/02-information-requests.png?fit=max&auto=format&n=U-hpPwCsALA8cBD_&q=85&s=6cb619c949e3b0859b4b1765ae7327ca" alt="Information requests sheet showing the customer record and Questions Round 1 with four unanswered questions" width="1600" height="1000" data-path="images/clips/respond-to-rfi/02-information-requests.png" />
</Frame>

### 3. Submit the response

**Submit response** stays disabled until every required question has an answer. When documents were requested, the card also shows a file upload, and submitting is blocked until at least one file is uploaded.

After submitting, the card turns read-only: a **Submitted** badge, your answers echoed under each question, a Responded timestamp, and **Copy Q\&A** for the whole exchange. The customer's row drops its needs-action badge.

<Frame quality="high">
  <img src="https://mintcdn.com/moonpayiron/U-hpPwCsALA8cBD_/images/clips/respond-to-rfi/04-response-submitted.png?fit=max&auto=format&n=U-hpPwCsALA8cBD_&q=85&s=0f906d42ead6cabefc76ddef76268e55" alt="The same sheet after submitting, read-only with a Submitted badge, the answers echoed under each question, and a Response submitted toast" width="1600" height="1000" data-path="images/clips/respond-to-rfi/04-response-submitted.png" />
</Frame>

<Note>
  A follow-up round arrives as a new **Questions · Round 2** card on the same RFI, and the customer's row goes back to needs action. Answer it the same way.
</Note>

## List open RFIs

`GET /api/rfis`

By default, returns RFIs in `waiting_on_response` or `needs_review`, grouped by customer. Customers with at least one RFI still waiting on a response come first, then the customer whose oldest RFI was sent earliest. Closed RFIs are not included unless you pass `state=completed`, which returns only closed RFIs, most recently closed first.

### Query parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `state` | string | No | `open` (default) or `completed` |
| `search_term` | string | No | Matches customer name, customer email, or an exact customer ID. Max 256 characters |
| `cursor` | string (UUID) | No | Customer ID from the previous response's `cursor`. Fetches the next page |
| `prev_cursor` | string (UUID) | No | Customer ID from the previous response's `prev_cursor`. Fetches the previous page |
| `page_size` | integer | No | Customers per page. Default `10`, max `100` |

<CodeGroup>
  ```bash theme={null}
  curl -X GET "https://api.sandbox.iron.xyz/api/rfis?page_size=25" \
    -H "X-API-Key: <your-api-key>"
  ```

  ```typescript theme={null}
  const response = await fetch(
    "https://api.sandbox.iron.xyz/api/rfis?page_size=25",
    { headers: { "X-API-Key": "<your-api-key>" } },
  );
  ```

  ```python theme={null}
  response = requests.get(
      "https://api.sandbox.iron.xyz/api/rfis",
      params={"page_size": 25},
      headers={"X-API-Key": "<your-api-key>"},
  )
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "items": [
    {
      "customer_id": "9c1f0a3e-5d4b-4f20-9a6e-3b71c8f2d014",
      "customer_name": "Alina Vogt",
      "customer_email": "alina.vogt@example.com",
      "customer_type": "Person",
      "oldest_sent_at": "2026-09-12T09:14:02Z",
      "rfis": [
        {
          "id": "7f3a2b18-64c9-4f8d-8c31-b0d5e9a12f77",
          "customer_id": "9c1f0a3e-5d4b-4f20-9a6e-3b71c8f2d014",
          "transaction_id": null,
          "rfi_type": "probing_questions",
          "status": "waiting_on_response",
          "rounds": [
            {
              "id": "c58e1d47-2a90-4b6f-9f03-7e2c4a8b1d55",
              "round_number": 1,
              "questions": [
                {
                  "id": "employment_status",
                  "prompt": "What is the customer's current employment status?",
                  "answer_type": "text",
                  "required": true
                },
                {
                  "id": "trades_for_third_party",
                  "prompt": "Is the customer transacting on behalf of anyone else?",
                  "answer_type": "yes_no",
                  "required": true
                }
              ],
              "answers": null,
              "documents_requested": true,
              "document_instructions": "Upload the last three months of bank statements.",
              "document_upload_url": null,
              "document_link_opened_at": null,
              "documents": [],
              "sent_at": "2026-09-12T09:14:02Z",
              "response_received_at": null
            }
          ],
          "partner_note": null,
          "sent_at": "2026-09-12T09:14:02Z",
          "response_received_at": null,
          "closed_at": null,
          "created_at": "2026-09-12T09:14:02Z",
          "updated_at": "2026-09-12T09:14:02Z"
        }
      ]
    }
  ],
  "cursor": null
}
```

`cursor` is the customer ID to pass as `cursor` for the next page. It is `null` on the last page.

## List a customer's RFIs

`GET /api/customers/{customer_id}/rfis`

Returns the open RFIs for one customer as a flat array, newest first. Closed RFIs are not included here. Fetch a closed RFI by ID instead.

<CodeGroup>
  ```bash theme={null}
  curl -X GET "https://api.sandbox.iron.xyz/api/customers/9c1f0a3e-5d4b-4f20-9a6e-3b71c8f2d014/rfis" \
    -H "X-API-Key: <your-api-key>"
  ```
</CodeGroup>

## Get an RFI

`GET /api/rfis/{rfi_id}`

Returns one RFI in any status, including closed.

<CodeGroup>
  ```bash theme={null}
  curl -X GET "https://api.sandbox.iron.xyz/api/rfis/7f3a2b18-64c9-4f8d-8c31-b0d5e9a12f77" \
    -H "X-API-Key: <your-api-key>"
  ```
</CodeGroup>

### Response

<ResponseField name="id" type="string (UUID)" required>
  The RFI ID.
</ResponseField>

<ResponseField name="customer_id" type="string (UUID)" required>
  The customer the RFI is about.
</ResponseField>

<ResponseField name="transaction_id" type="string (UUID)">
  The transaction under review. Always set for `source_of_funds`, `null` otherwise.
</ResponseField>

<ResponseField name="rfi_type" type="string" required>
  `probing_questions`, `source_of_funds`, or `liveness`.
</ResponseField>

<ResponseField name="status" type="string" required>
  `waiting_on_response`, `needs_review`, or `closed`.
</ResponseField>

<ResponseField name="liveness_url" type="string">
  The verification link to send your customer on a `liveness` RFI. Omitted on every other RFI type, and on a `liveness` RFI until the link is ready.
</ResponseField>

<ResponseField name="rounds" type="array" required>
  One entry per round, sorted by `round_number` ascending.

  <Expandable title="properties">
    <ResponseField name="id" type="string (UUID)" required>
      The round ID. Pass it as `round_id` when you respond.
    </ResponseField>

    <ResponseField name="round_number" type="integer" required>
      Starts at 1 and increments per follow-up.
    </ResponseField>

    <ResponseField name="questions" type="array" required>
      <Expandable title="properties">
        <ResponseField name="id" type="string" required>
          Question ID. Use it as `question_id` in your answer. Unique within the round.
        </ResponseField>

        <ResponseField name="prompt" type="string" required>
          The question to put to your customer.
        </ResponseField>

        <ResponseField name="answer_type" type="string" required>
          `text` or `yes_no`. Determines which answer field to send.
        </ResponseField>

        <ResponseField name="required" type="boolean" required>
          When `true`, the response is rejected unless this question is answered.
        </ResponseField>

        <ResponseField name="transaction_id" type="string (UUID)">
          The transaction this question asks about. Present on `source_of_funds` questions, omitted otherwise.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="answers" type="array">
      The answers you submitted for this round. `null` until you respond.
    </ResponseField>

    <ResponseField name="documents_requested" type="boolean" required>
      When `true`, the round needs documents as well as answers.
    </ResponseField>

    <ResponseField name="document_instructions" type="string">
      What the documents need to show.
    </ResponseField>

    <ResponseField name="document_upload_url" type="string">
      External upload link on legacy rounds created before documents were uploaded through the API. `null` on new rounds: upload files with [Upload a document](#upload-a-document) instead.
    </ResponseField>

    <ResponseField name="document_link_opened_at" type="string (ISO 8601)">
      When the legacy upload link was first recorded as opened. `null` until then.
    </ResponseField>

    <ResponseField name="documents" type="array" required>
      Files uploaded for this round. Each entry has `id`, `file_name`, `content_type`, `size_bytes`, and `uploaded_at`. Empty until you upload one.
    </ResponseField>

    <ResponseField name="sent_at" type="string (ISO 8601)" required>
      When the round was sent.
    </ResponseField>

    <ResponseField name="response_received_at" type="string (ISO 8601)">
      When your response for this round arrived. `null` until then.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="sent_at" type="string (ISO 8601)" required>
  When the latest round was sent. A follow-up round resets it.
</ResponseField>

<ResponseField name="response_received_at" type="string (ISO 8601)">
  When your response to the latest round arrived. `null` until you respond, and reset to `null` when a follow-up round is sent.
</ResponseField>

<ResponseField name="closed_at" type="string (ISO 8601)">
  When MoonPay closed the RFI. `null` while it is open.
</ResponseField>

<ResponseField name="partner_note" type="string">
  A note from MoonPay about a closed RFI. Set only on some closed RFIs, `null` otherwise.
</ResponseField>

<ResponseField name="created_at" type="string (ISO 8601)" required>
  When the RFI was created.
</ResponseField>

<ResponseField name="updated_at" type="string (ISO 8601)" required>
  When the RFI last changed.
</ResponseField>

## Upload a document

`POST /api/rfis/{rfi_id}/rounds/{round_id}/documents`

Uploads one file for a round that has `documents_requested: true`. Send it as `multipart/form-data` in a `file` field. Each file must be a PDF, PNG, or JPEG of at most 10 MB, and a round holds at most 20 files. Call it once per file.

<CodeGroup>
  ```bash theme={null}
  curl -X POST "https://api.sandbox.iron.xyz/api/rfis/7f3a2b18-64c9-4f8d-8c31-b0d5e9a12f77/rounds/c58e1d47-2a90-4b6f-9f03-7e2c4a8b1d55/documents" \
    -H "X-API-Key: <your-api-key>" \
    -H "Idempotency-Key: <unique-request-id>" \
    -F "file=@bank-statement.pdf;type=application/pdf"
  ```
</CodeGroup>

Returns `200` with the uploaded document:

```json theme={null}
{
  "id": "0192a6f4-1c2e-7b3d-9a41-5e8f0c7d2b13",
  "file_name": "bank-statement.pdf",
  "content_type": "application/pdf",
  "size_bytes": 284113,
  "uploaded_at": "2026-09-13T10:02:45Z"
}
```

A file of the wrong type or over 10 MB returns `400` with a plain-text body:

```bash theme={null}
< HTTP/2 400
< content-type: text/plain; charset=utf-8
<
upload a PDF, PNG or JPEG file of at most 10 MB
```

<Note>
  This `POST` requires an `Idempotency-Key` header. See [Idempotency](/idempotency) for retry behavior.
</Note>

## Record that the upload link was opened

`POST /api/rfis/{rfi_id}/rounds/{round_id}/document-link-opened`

Applies only to legacy rounds that carry a `document_upload_url`. Records that the link was opened, and stamps `document_link_opened_at`. Call it once, when you hand the link to your customer.

On a legacy round, an API key can submit a response with `documents_uploaded: true` without this call. A signed-in dashboard user has to open the link first, otherwise the response is rejected with `400`.

<CodeGroup>
  ```bash theme={null}
  curl -X POST "https://api.sandbox.iron.xyz/api/rfis/7f3a2b18-64c9-4f8d-8c31-b0d5e9a12f77/rounds/c58e1d47-2a90-4b6f-9f03-7e2c4a8b1d55/document-link-opened" \
    -H "X-API-Key: <your-api-key>" \
    -H "Idempotency-Key: <unique-request-id>"
  ```
</CodeGroup>

Returns `200` with an empty body.

<Note>
  This `POST` requires an `Idempotency-Key` header. See [Idempotency](/idempotency) for retry behavior.
</Note>

Calling it twice for the same round returns `409`. See [Common errors](#common-errors).

## Submit a response

`POST /api/rfis/{rfi_id}/rounds/{round_id}/response`

Submits answers for one round. One call per round: there is no partial save, so send every answer you have at once.

### Body

| Field | Type | Required | Description |
| - | - | - | - |
| `answers` | array | Yes | One entry per answered question. Send `[]` for a documents-only round |
| `answers[].question_id` | string | Yes | Must match a question `id` in this round. No duplicates |
| `answers[].text_value` | string | Conditional | Required for `text` questions, and must be non-empty. Omit for `yes_no` |
| `answers[].boolean_value` | boolean | Conditional | Required for `yes_no` questions. Omit for `text` |
| `answers[].explanation` | string | No | Free-text context for the answer. Allowed on either answer type |
| `documents_uploaded` | boolean | Yes | When the round has `documents_requested: true`, upload at least one file first. `true` without an uploaded file is accepted only on a legacy round with a `document_upload_url` |

<Warning>
  Send exactly one value per answer. A `text` question with `boolean_value` set, or a `yes_no` question with `text_value` set, returns `400`. Every question marked `required: true` has to be answered in the same call.
</Warning>

<CodeGroup>
  ```bash theme={null}
  curl -X POST "https://api.sandbox.iron.xyz/api/rfis/7f3a2b18-64c9-4f8d-8c31-b0d5e9a12f77/rounds/c58e1d47-2a90-4b6f-9f03-7e2c4a8b1d55/response" \
    -H "Content-Type: application/json" \
    -H "X-API-Key: <your-api-key>" \
    -H "Idempotency-Key: <unique-request-id>" \
    -d '{
      "answers": [
        {
          "question_id": "employment_status",
          "text_value": "Employed full time as a nurse",
          "explanation": "Confirmed by the customer on a recorded call."
        },
        {
          "question_id": "trades_for_third_party",
          "boolean_value": false
        }
      ],
      "documents_uploaded": true
    }'
  ```
</CodeGroup>

Returns `200` with an empty body. The RFI moves to `needs_review` and `response_received_at` is set on both the round and the RFI.

<Note>
  This `POST` requires an `Idempotency-Key` header. See [Idempotency](/idempotency) for retry behavior.
</Note>

### Error response

A rejected response returns `400` with a plain-text body, not JSON:

```bash theme={null}
< HTTP/2 400
< content-type: text/plain; charset=utf-8
<
RFI answers are malformed
```

<Warning>
  Unlike most API errors, these `400` bodies are plain text. Don't parse them as JSON.
</Warning>

## Common errors

| Error | Cause |
| - | - |
| `400 Bad Request` | `RFI answers are malformed`: unknown or duplicated `question_id`, the wrong value field for the question's `answer_type`, an empty `text_value`, or a missing required answer |
| `400 Bad Request` | `upload the requested documents before submitting`: the round requested documents and none has been uploaded |
| `400 Bad Request` | `upload a PDF, PNG or JPEG file of at most 10 MB`: the uploaded file has the wrong type or is too large |
| `400 Bad Request` | `a liveness RFI opens only through a liveness request and takes no answers or follow-up rounds`: you submitted a response to a `liveness` RFI |
| `401 Unauthorized` | Missing or invalid `X-API-Key` |
| `403 Forbidden` | The API key lacks `View` for the read endpoints, or `UpdateCustomer` for the write endpoints |
| `404 Not Found` | Unknown customer, RFI, or round, or it belongs to another partner |
| `409 Conflict` | The RFI is not in `waiting_on_response`, the round is not the latest one, the round did not request documents or already holds 20 files, or the upload link was already recorded as opened |
| `500 Internal Error` | The request failed server-side. Safe to retry |

Among errors, `400` is the only status that carries a body. Unlike most of the API, these endpoints return no `message` or `trace_id` on `500`.

## Related guides

<CardGroup>
  <Card title="Customer in EDD" href="/enhanced-due-diligence" icon="shield" horizontal />

  <Card title="Source of Wealth" href="/source-of-wealth" icon="file-invoice" horizontal />
</CardGroup>
