Skip to main content
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.
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.

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

Get your RFIs

Filter by user_id and/or status; results are sorted by due_at.
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

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

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

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

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

Status vocabulary

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

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

API reference

The full request/response schema for every RFI endpoint.

State machines

Every resource’s status vocabulary, including how the retired virtual-account rfi value relates to this resource.

Versioning

How to pin X-Api-Version and upgrade your account default.

Webhooks

The rfi.* event contract lands here once it ships.