Added

15 September 2026

🚀 New APIs

  1. Compliance API — RFI webhooks: rfi_created and rfi_status_changed. Stop polling RFIs. Both payloads carry organizationId, rfiId, and origin; rfi_status_changed adds previousStatus / currentStatus in statusChangeDetails, drawn from outstanding, validatingSubmission, submissionValidationsFailed, submitted, and cancelled. Events fire only on a real transition. Fetch submission failures and cancellation reasons from GET /compliance/rfis/{rfiId}.

⭐ Enhancements

  1. Platform — API keys are environment-prefixed, and you can hold more than one. New keys start with mural_test_ (sandbox) or mural_live_ (production). A key sent to the wrong environment now returns 401 with a message naming the correct endpoint, rejected before any decrypt or database lookup. Keys issued before this change have no prefix and continue to work unchanged. An organization can also hold several active keys at once and rotate by minting a new key and explicitly revoking the old one, so a rotation no longer has to break a running integration. Rate limits remain the same in aggregate, not per-key.
  2. Platform — rate-limited requests return a typed RateLimitExceededError. Exceeding the API key rate limit now returns a documented 429 carrying params.retryAfterSeconds, alongside a standard Retry-After header. The API has always enforced this limit; it was never documented. Successful responses now also carry X-RateLimit-Limit / -Remaining / -Reset under the standard names, so you can stay under the limit without provoking a rejection. Previously those shipped only suffixed (X-RateLimit-Limit-api) and only on allowed requests; a rejection carried Retry-After-api and a body naming the third-party ThrottlerException. The suffixed headers still ship unchanged. The error is published on every secured operation in the OpenAPI spec, and the new Rate Limits guide explains how the limit is counted — notably that requests made on behalf of a child organization count against the parent, so all children share one budget per endpoint. The limit and its window stay out of the error body; read them from the headers, which a 429 also carries.
  3. Compliance API — government ID failures now name the specific document problem. The attestation failureInfo union adds ten government-ID members, plus documentSide (front / back) where one side is at fault. Failures are ordered most-actionable first. See uploadAttestations for more information.
  4. Compliance API — stricter postal-code and address validation. A postal code containing . (for example the Brazilian CEP 06.465-134) now returns 400 "Postal code must not contain a period" for every country, instead of being forwarded to the KYC provider and rejected downstream.
  5. Payins API — pay-ins expire, and late deposits are refunded. Each pay-in carries an expiresAt, and funds arriving after it are returned to the sender rather than credited. The payin status union adds refunded with refundInfo giving the cause (payinAfterExpiry, or a provider-initiated refund). Refunds arrive as a separate event after the pay-in closes: the pay-in transitions to canceled first, then a second payin_status_changed delivers canceledrefunded once the refund settles.
  6. Sandbox — simulated deposit caps are lower, and an over-cap request is rejected. POST /api/sandbox/simulate/payin now caps each rail well below the previous limits: 50 USD for ACH and wire, 1,000 MXN for SPEI, 65,000 ARS for CVU, and 275 BRL for PIX (previously 100 USD and 100,000 MXN). A request above the cap returns 400. The caps exist to conserve the shared sandbox testnet balance and are not product limits. The per-rail cap is published in each amount field's description in the API reference.

🐛 Bug Fixes

  1. Compliance API — RFI submissions are no longer blocked by unrelated attestations. Submitting an RFI response previously returned a retryable 412 when any attestation on the review was rejected or still validating, including types the RFI never requested and that cannot be re-uploaded through it. Both checks are now scoped to the attestation types the RFI names.
  2. Payouts API — TransactionInProgressError is documented as 409, not 429. This error returned 429 from February 2026 until a change on 2026-05-04 set it to 409 Conflict, so two errors would stop sharing one status code in the OpenAPI spec. The error reference was never updated and still said 429. If you branch on 429 to detect a concurrent transaction on the same account, that branch stopped firing in May — switch it to 409. A 429 from the API now means rate limiting and nothing else. The Errors reference is corrected and live.