- 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
documentitem asks for. - Mixing this API with the Kira b2b dashboard to answer the same RFI.
- The status vocabulary and the error shapes.
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 anitem_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
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. blockingis the anchor, read-only.{ "type": "transfer" | "virtual_account_deposit", … }, ornullwhen 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 theprompttext, not a field.answer_specis closed peranswer_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_uuidnever appears. You’re the one calling, so a field that always reads back “you” tells you nothing. Same reasoning excludescreated_byandreturned_by— which Kira analyst acted is internal.
Answer 1 to N items
- Marked
answeredon receipt — not when anything downstream accepts it. Nothing outside this API can leave your response hanging. - All-or-nothing. Every named item is validated against its own
answer_specbefore anything is written. If one fails, the call returns422with one entry per failing item and the server is left exactly as it was — never a mix of saved and refused answers. - Answering a returned item is answering it again. A returned item is still
status: "pending", with areview_notesaying what to fix — sameitem_id, samedue_at, no successor item or RFI to discover. - Partial answers are the normal case. Answering item 1 and 3 and leaving item 2 is expected, not an edge case; the RFI stays
pendingwhile anything remainspending.
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.
files field; one call can carry several. The response is the item, in the same shape a GET returns it:
- 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 movesupdated_atand leavesfirst_answered_atalone. - The limits are the item’s own
answer_spec.mime_typesandmax_filesare what that item accepts, and a file outside them is a422naming the limit. An item with no spec still has ceilings: the accepted types areapplication/pdf,image/jpeg,image/png,image/heicandimage/webp, at most 20 files, each at most 30 MB. A spec can only narrow those, never widen them — so what you read inanswer_specis the complete rule. - A file has to be what it says it is. The bytes are checked against the declared
Content-Type; a payload renamed to.pdfis refused rather than stored. checksumis the SHA-256 of what we received, so you can prove the file that landed is the file you sent.
Remove one file
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
302, so your own code decides what to do next and the expiry is something you can read.
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_atmeasures 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.