> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kirafin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# RFIs

> Read and answer requests for information with GET and PATCH /v1/rfis on API version 2026-06-01 — the item is the addressable unit, a batch answer is all-or-nothing, and a document item is answered with multipart uploads.

A **request for information** (RFI) is a single need Kira has of one of your subclients, with its **items nested** — the specific things being asked for. It's the only resource you deal with: there are no groups, no rounds, and no separate endpoint for the items themselves.

This guide covers:

* Reading your RFIs and their nested items (`GET`).
* Answering 1 to N items in one call, all-or-nothing (`PATCH`).
* Uploading, removing, and downloading the files a `document` item asks for.
* Mixing this API with the Kira b2b dashboard to answer the same RFI.
* The status vocabulary and the error shapes.

Every example uses the sandbox base URL `https://api.balampay.com/sandbox`; in production, drop the `/sandbox` prefix. Every call needs the standard credentials — see [Authentication](/guides/authentication).

<Note>
  **You never create an RFI.** Kira raises them from your subclient's verification activity; you only read and answer. There is no `POST /v1/rfis` in this reference.
</Note>

## The item is the unit, even in a batch

Every write here names an `item_id` — never the RFI itself. Reading the RFI as a whole is for display (it's the group your dashboard renders as one screen: a deadline, an anchor, N things to answer); answering is always per item, because each item carries its own review and its own event. Batching 1 to N items into one `PATCH` call avoids charging you a round-trip per item for what was, for you, one action — filling a form and hitting submit — but it doesn't change what's being answered.

## Handling RFIs on your side

`PATCH` here is built for automation — your systems answering the moment your subclient submits a response, no person in the loop. If your team sometimes handles an RFI by hand instead, that same read-and-answer surface is coming to the Kira b2b dashboard next — same items, same status vocabulary, a form instead of a request. Until then, this API is the only way to answer.

<Note>
  **Answering through the API doesn't lock you into it.** Kira doesn't restrict an RFI — or even a single item — to one channel once you've used it: answer item 1 through this API and item 2 through the dashboard, and both are normal answers. Once an item is answered, it reads as answered regardless of which channel wrote it — the dashboard shows whatever came in through the API, and a later `GET` here shows whatever came in through the dashboard, with no notion of "channel" anywhere in the model.
</Note>

## Get your RFIs

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/rfis \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY"
```

Filter by `user_id` and/or `status`; results are sorted by `due_at`.

```json theme={null}
{
  "data": [
    {
      "rfi_id": "0198f2c1-0000-0000-0000-000000000000",
      "user_id": "5a1f0000-0000-0000-0000-000000000000",
      "status": "pending",
      "due_at": "2026-08-28T23:59:59Z",
      "blocking": { "type": "transfer", "transfer_uuid": "7c3e0000-0000-0000-0000-000000000000" },
      "resolution_reason": null,
      "created_at": "2026-08-14T15:02:11Z",
      "first_answered_at": null,
      "updated_at": null,
      "closed_at": null,
      "items": [
        {
          "item_id": "0198f2c2-0000-0000-0000-000000000000",
          "ordinal": 1,
          "prompt": "What is the counterparty's EIN?",
          "answer_type": "identifier",
          "answer_spec": { "format": "ein" },
          "target_key": "ein",
          "status": "pending",
          "answer_value": "12-345678",
          "documents": [],
          "review_note": "An EIN has 9 digits; this one has 8.",
          "updated_at": "2026-08-15T10:14:00Z",
          "returned_at": "2026-08-15T16:40:00Z"
        }
      ]
    }
  ],
  "pagination": { "total": 1, "limit": 10, "offset": 0, "has_more": false }
}
```

`GET /v1/rfis/{rfi_id}` returns the same shape for a single RFI (no `data`/`pagination` wrapper — the object directly).

### Reading the shape

* **Items are nested, not a separate endpoint.** You read "what we need to unblock you" in one call, and the deadline (`due_at`) and the anchor (`blocking`) are columns on the parent row — splitting them out would force two calls to render one screen.
* **`blocking` is the anchor, read-only.** `{ "type": "transfer" | "virtual_account_deposit", … }`, or `null` when the RFI is about the subclient directly. There is no virtual-account or capability anchor, and there won't be one — if an RFI is about a specific account, that lives in the `prompt` text, not a field.
* **`answer_spec` is closed per `answer_type`** — it's the complete set of keys that item will validate an answer against, not a hint. This is also why a brand-new question shape never touches this contract: it's data on the item, not a version bump.
* **`client_uuid` never appears.** You're the one calling, so a field that always reads back "you" tells you nothing. Same reasoning excludes `created_by` and `returned_by` — which Kira analyst acted is internal.

## Answer 1 to N items

```bash theme={null}
curl -X PATCH https://api.balampay.com/sandbox/v1/rfis/0198f2c1-0000-0000-0000-000000000000/items \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "item_id": "0198f2c2-0000-0000-0000-000000000000", "answer_value": "12-3456789" },
      { "item_id": "0198f2c3-0000-0000-0000-000000000000", "answer_value": "Export sales to three LATAM customers" }
    ]
  }'
```

Four things this endpoint promises:

1. **Marked `answered` on receipt** — not when anything downstream accepts it. Nothing outside this API can leave your response hanging.
2. **All-or-nothing.** Every named item is validated against its own `answer_spec` **before anything is written**. If one fails, the call returns `422` with one entry per failing item and **the server is left exactly as it was** — never a mix of saved and refused answers.
3. **Answering a returned item is answering it again.** A returned item is still `status: "pending"`, with a `review_note` saying what to fix — same `item_id`, same `due_at`, no successor item or RFI to discover.
4. **Partial answers are the normal case.** Answering item 1 and 3 and leaving item 2 is expected, not an edge case; the RFI stays `pending` while anything remains `pending`.

<Warning>
  A `document` item is never answered by this endpoint — sending `answer_value` for one returns `422`. Its answer is the set of files, so it's answered by the three endpoints below.
</Warning>

## Answer a `document` item with files

A `document` item's answer **is** its set of files. You send them as `multipart/form-data` against the item's own endpoint — never base64 inside the `PATCH` body, which inflates a file by a third and doubles the memory it takes to handle.

```bash theme={null}
curl -X POST https://api.balampay.com/sandbox/v1/rfis/0198f2c1-0000-0000-0000-000000000000/items/0198f2c4-0000-0000-0000-000000000000/documents \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY" \
  -F "files=@january.pdf" \
  -F "files=@february.pdf"
```

Every file goes under the `files` field; one call can carry several. The response is the item, in the same shape a `GET` returns it:

```json theme={null}
{
  "item": {
    "item_id": "0198f2c4-0000-0000-0000-000000000000",
    "ordinal": 1,
    "prompt": "Your last 3 months of bank statements",
    "answer_type": "document",
    "answer_spec": { "mime_types": ["application/pdf"], "max_files": 5 },
    "target_key": null,
    "status": "answered",
    "answer_value": null,
    "documents": [
      {
        "document_id": "0198f2d0-0000-0000-0000-000000000000",
        "file_name": "january.pdf",
        "mime_type": "application/pdf",
        "size_bytes": 91204,
        "checksum": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "uploaded_at": "2026-08-20T10:00:00Z"
      },
      {
        "document_id": "0198f2d1-0000-0000-0000-000000000000",
        "file_name": "february.pdf",
        "mime_type": "application/pdf",
        "size_bytes": 88410,
        "checksum": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
        "uploaded_at": "2026-08-20T10:00:00Z"
      }
    ],
    "review_note": null,
    "updated_at": "2026-08-20T10:00:00Z",
    "returned_at": null
  }
}
```

Four things this endpoint promises:

1. **Uploading adds to the set — it never replaces it.** Sending the second statement doesn't delete the first, which is what would happen if every upload were "the answer". The first upload leaves the item `answered`; a later one moves `updated_at` and leaves `first_answered_at` alone.
2. **The limits are the item's own `answer_spec`.** `mime_types` and `max_files` are what that item accepts, and a file outside them is a `422` naming the limit. An item with no spec still has ceilings: the accepted types are `application/pdf`, `image/jpeg`, `image/png`, `image/heic` and `image/webp`, at most 20 files, each at most 30 MB. A spec can only narrow those, never widen them — so what you read in `answer_spec` is the complete rule.
3. **A file has to be what it says it is.** The bytes are checked against the declared `Content-Type`; a payload renamed to `.pdf` is refused rather than stored.
4. **`checksum` is the SHA-256 of what we received**, so you can prove the file that landed is the file you sent.

### Remove one file

```bash theme={null}
curl -X DELETE https://api.balampay.com/sandbox/v1/rfis/{rfi_id}/items/{item_id}/documents/{document_id} \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY"
```

Removing is explicit, and it returns the item with the shorter set. **The last file can't be removed** — an item can't read `answered` with nothing to review, so it's a `422`. To swap the only file, upload the replacement first, then remove the old one.

### Download a file

```bash theme={null}
curl https://api.balampay.com/sandbox/v1/rfis/{rfi_id}/items/{item_id}/documents/{document_id} \
  -H "X-Api-Version: 2026-06-01" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "x-api-key: $KIRA_API_KEY"
```

```json theme={null}
{
  "download_url": "https://kira-user-files.s3.us-west-2.amazonaws.com/...",
  "expires_at": "2026-08-20T10:05:00Z"
}
```

You get a **link, not the bytes** — and a JSON body rather than a `302`, so your own code decides what to do next and the expiry is something you can read.

<Warning>
  **The link is a bearer credential.** Anyone holding it can fetch the file until `expires_at`, which is **minutes** away — it is deliberately short-lived because a URL survives in browser history, proxy logs, and `Referer` headers. Don't store it, don't log it, and don't put it in a page you keep open: ask for a new one when you need the file again. An expired link is refused by S3, not by us, so re-request rather than retrying it.
</Warning>

The file downloads under the name it was uploaded with — the `Content-Disposition` is inside the signature, so you get `january.pdf` rather than an internal storage path.

<Note>
  **Uploading and removing follow the same rules as answering.** Both are the item being answered again, so both are refused on an already-closed RFI (`409`) — and both are recorded in the RFI's own history, where the set of files each attempt carried stays readable.
</Note>

## Status vocabulary

**The RFI:**

| `status`       | What it means for you                                                                 |
| -------------- | ------------------------------------------------------------------------------------- |
| `pending`      | It's your turn: something's unanswered, or an answer was returned and needs replacing |
| `answered`     | You answered everything; Kira is reviewing                                            |
| `resolved`     | Kira confirmed the need satisfied — you're unblocked (terminal)                       |
| `not_resolved` | Closed without resolving; `resolution_reason` is `expired` or `rejected` (terminal)   |

`pending` always means **it's your turn** — an `answered` RFI that gets one item returned goes back to `pending`, so you can automate purely on `status` without walking every item to know whether you have work. `resolved` and `not_resolved` are always Kira's own act on the whole RFI — never something inferred from the items, so don't expect one the moment every item happens to read `answered`, and don't assume `answered` comes first: an RFI closes as `resolved` straight from `pending` when Kira no longer needs what's still outstanding. `withdrawn` is an internal-only outcome: a withdrawn RFI never appears here at all (it 404s), not as a visible row.

**Each item:**

| `status`   | Who has the ball                                                     |
| ---------- | -------------------------------------------------------------------- |
| `pending`  | You: haven't answered, or an answer was returned — see `review_note` |
| `answered` | Kira: under review                                                   |

An item carries no terminal state of its own — it cycles between `pending` and `answered` for as long as Kira keeps returning it. The next round after a return is another `PATCH` against the same `item_id`, and the RFI's `due_at` never moves.

## Errors

| Case                                                                                                                 | Status |
| -------------------------------------------------------------------------------------------------------------------- | ------ |
| RFI or item belongs to another client, or the RFI is `withdrawn`                                                     | `404`  |
| An answer in the batch fails its item's `answer_spec` — one entry per item, and nothing in the batch is written      | `422`  |
| An item in the batch with no answer — there is no empty answer                                                       | `422`  |
| A `document` item sent `answer_value` — it has no answer to give here                                                | `422`  |
| Answering an item on an already-closed RFI (`resolved`, `not_resolved`, `withdrawn`)                                 | `409`  |
| Answering an item that's currently `answered` — it hasn't been returned, so there's nothing to replace yet           | `409`  |
| Uploading or removing a file on an item of an already-closed RFI                                                     | `409`  |
| A file outside the item's accepted `mime_types`, or one that would take the set past `max_files` — nothing is stored | `422`  |
| A file larger than 30 MB, or more than 20 files in one call                                                          | `422`  |
| A file whose bytes are not the type it was sent as                                                                   | `422`  |
| Uploading files to an item that asked for something else, or removing the last file of an answered item              | `422`  |
| A `document_id` the item doesn't carry                                                                               | `404`  |

Error messages are one line naming what failed, because they're what you show your subclient and what an analyst would otherwise have to write by hand.

## Who's attributed, and what that means for what you read

**You call this API, always** — your subclient answers in whatever interface you provision for them, and you relay the response to Kira. Two consequences that don't show up in any field but matter for what you read:

* **Audit attributes to you.** The actor behind an item's answer — and the uploader of every file — is the identity of the API key that called Kira, never your subclient's. That's the whitelabel model stated plainly, not a limitation.
* **`first_answered_at` measures the hand-off, not the answer.** It's when you passed the response to Kira, not when your subclient gave it to you — the gap between those two moments is invisible here. It's still useful for an SLA on **your** side of this contract; a "how long does my subclient take" dashboard built on this column would be measuring something else.

## Next steps

<CardGroup cols={2}>
  <Card title="API reference" icon="code" href="/api-reference">
    The full request/response schema for every RFI endpoint.
  </Card>

  <Card title="State machines" icon="diagram-project" href="/guides/state-machines">
    Every resource's status vocabulary, including how the retired virtual-account `rfi` value relates to this resource.
  </Card>

  <Card title="Versioning" icon="code-branch" href="/guides/versioning">
    How to pin `X-Api-Version` and upgrade your account default.
  </Card>

  <Card title="Webhooks" icon="bell" href="/guides/webhooks">
    The `rfi.*` event contract lands here once it ships.
  </Card>
</CardGroup>
