Skip to main content
These are known sandbox and API quirks worth knowing before you build against them. Each one is a hard-won integration detail — field renames, inconsistent casing, contradictory status fields, and missing endpoints — that can cost you time if you hit it blind. They are listed upfront so you don’t lose an hour debugging. All of these are documented gotchas that the v2026-XX-XX revamp resolves.

Identity / user lifecycle

Sandbox does NOT auto-verify — but it CAN auto-reject. Identity verification runs automatically on create (verification_triggered: true) and can auto-REJECT within seconds if required KYC data is missing — most commonly an omitted source_of_funds (the KYC provider’s questionnaire requires it). A rejected user fires the user.verification.failed webhook and flips to terminal REJECTED (note: verification_status may still read unverified — gate on status). Include source_of_funds in the create payload. The rejection reason is delivered ONLY in the user.verification.failed webhook (data.reasons[]) — GET /v1/users/{id} never exposes it.To verify a user, create a COMPLETE user (scalar values may be fake, images must look real), then ask your Kira contact to flip it to VERIFIED. If REJECTED, ask your Kira contact to re-trigger verification. (The verify+approved@kira.test magic-email behavior was a proposal that never shipped.)
status and verification_status can contradict each other on the same user. Observed in production data: status: VERIFIED paired with verification_status: unverified / in_review / started. The relationship between the two fields is undefined today. Gate your state machine on status, and treat verification_status as advisory only.
Undocumented user status values exist in real tenants: ACTIVE and REVIEW are common but not enumerated anywhere. Handle them as “non-terminal, do not gate flow on them.”
  • Field renames between request and response. For example, address_street becomes residential_address.street_line_1, address_zip_code becomes postal_code, and address_state becomes subdivision. Always parse the response shape — never assume request fields round-trip.
  • Country codes are inconsistent across resources. ISO-3 (MEX, USA) on users, ISO-2 (MX, US) on recipients. Make sure your client maps correctly.
For the full status vocabulary and webhook event mapping, see State machines & webhook catalog.

Virtual accounts

  • GET /v1/virtual-accounts/{id}/balance returns 200 with available_balance on an active ACT VA, and payout/preview + payout return 200/201 with a fee breakdown. While the VA is still activating, balance returns 400 — confirm readiness via account_number being a real account number — non-null AND not equal to "PENDING-ACT-ACCOUNT" (the sentinel is ACT-only; SWIFT-capable / crypto VAs show account_number: null until provisioned) — and the virtual_account.deposit_* webhooks. approved does not mean funds-ready.
  • provider and currency come back null in the list view. Use GET /v1/virtual-accounts/{id} if you need them.
Casing is inconsistent across surfaces — ALWAYS compare statuses case-insensitively. GET resource status is UPPERCASE for payouts and deposits (CREATED/PENDING/PROCESSING/COMPLETED; PENDING/COMPLETED) and follows the version vocabulary for VAs (lowercase approved/rfi/deactivated on this pin). BUT the payout 201 create response returns status: "created" lowercase while GET /v1/payouts/{id} returns CREATED for the same payout. Flat payout/VA webhook events carry lowercase data.status (created, pending, processing, activating); payout.status_changed carries UPPERCASE at data.data.status; user.* events carry UPPERCASE data.status (CREATED). See State machines & webhook catalog.
One ACT virtual account per user. Re-creating returns 409 Conflict. Use the existing VA via List/Get.

Pre-verified test users (current ACT-product fields gap)

The seed user we ship was verified via AiPrise but is missing the field set required for ACT VA creation (immigration_status, additional_info:has_us_bank_account, additional_info:has_denied_bank_account, etc.). You can still read/list/operate the existing VAs on that user. To create a fresh ACT VA on a user, the user must be re-verified with the full ACT field set — coordinate with your Kira contact.

Recipients

  • Recipient response uses recipient_id, not id, and created_ts (non-ISO 8601), not created_at. The User and VA resources use the standard id/created_at — recipients are the outlier.
  • account.bank_address.state and .postal_code are silently dropped to empty strings in the response, even when sent on the request. Read-after-write does NOT preserve them today.

Payouts

  • No fee schedule endpoint and no dry_run mode. Preview Payout will return 400 "Total fees exceed or equal the payout amount" for amounts below the minimum (~$3 for SWIFT) — there’s no way to know the minimum without iterating.
  • Duplicate fields in fee response. fees.total and fees.total_fees are identical. Use either.
  • Unit ambiguity. bank_account_fee_percentage: "0.0004" is a ratio; bank_account_fee: "0.01" is dollars. Different units, sibling fields.

Deposits

simulate-deposit returns 201 with status: "completed" immediately — but the simulated deposit does NOT appear in GET /v1/virtual-accounts/{id}/deposits (the list stays empty) and does NOT change GET /…/balance (the sandbox balance is a fixed provider value; the endpoint itself still works — 200 with available_balance on an active ACT VA). Confirmation of a simulated deposit is the 201 response itself plus the (hand-delivered) virtual_account.deposit_funds_received webhook — NOT the deposits list, NOT a balance delta. Note settlement_triggered: false on a fiat deposit is normal (fiat is already completed), not an error.
  • Snake_case + camelCase mixed in the same response objectdeposit_id next to internalPaymentId. Tolerate both casings in your parser.

Webhooks

  • Webhook router is on a different stack. The path is /webhooks/register (NO /v1/ prefix). Naming uses client_uuid here instead of client_id everywhere else.
  • No webhook CRUD endpoints today. You cannot list, update, or delete a registered webhook via the API. The register endpoint doesn’t return an id either, so you have no handle to manage what’s been registered. Coming in v2026-XX-XX.
  • No retry policy on failed deliveries. Your endpoint must be highly available or you lose events.
For webhook setup and event payloads, see Webhooks. For the complete event catalog and per-resource state machines, see State machines & webhook catalog.