Release Notes

Updates to the Aeropay API, including new features, changes, and fixes.

Overview

The Aeropay Developer Portal receives regular updates, incorporating new API features, additional documentation, and resolutions for known issues. The release notes provided here detail these updates, with fresh notes accompanying each release of an updated API or new API. All Aeropay API versions will be backwards compatible unless specifically stated otherwise.


Release notes

Each API and SDK product within the Aeropay ecosystem will have updates listed in the Aeropay release notes:

  • Aeropay API
  • Aeropay SDK

Aerosync API release notes can be found in the Aerosync Developer Portal.


2026-10-20 — Clearer error for unsupported characters in preauthTransactions queries

  • GET /v2/preauthTransactions Query parameters containing a single quote, double quote, backslash or control character previously returned HTTP 500 with AP-0002. They now return HTTP 400 with {"error": {"code": "AP701", "message": "Improperly Formatted Parameter"}}. Requests that already worked are unchanged. v2 only.

2026-10-15 — Queued refund webhooks and refund status fixes

Refunds that were queued behind a pending transaction now notify and report more accurately.

  • transaction_refunded webhook When several queued refunds on one transaction are released together, one webhook is now sent per released refund. Before, v2 sent one webhook listing every refund, and v1 sent one webhook with only the last refund.
    • v2 payload: refundTransaction contains only that refund, and queuedRefunds lists the refunds still queued right after it was created.
    • v1 payload: each webhook carries its own refund.
    • Webhooks from the same release may arrive out of order, so integrators should not rely on delivery order.
  • POST /transactionSearch and POST /v2/transactionSearch A declined (nsf/declined) transaction that has not been resolved now reports its declined status even when refund activity exists. Before, it reported a refunded or refund-queued status. A resolved decline (bounced and then successfully retried) and non-declined refunds are unchanged. v1 and v2 share the same search query, so both are affected.
    • On v1, refundAmount is only returned when the status is refunded or refund-queued, so a declined transaction with refund activity no longer includes refundAmount. v2 search results do not include refundAmount.
    • The merchant transaction CSV export status column is changed in the same way.

2026-09-29 — v2 credentials error, refund retry, aggregator linking and webhook typing

  • POST /v2/token Invalid API key or secret key now returns AP001 with the message invalid API key or secret key. Before, it returned AP002. This applies to merchant, user and userForMerchant scopes. v2 only: v1 POST /token still returns AP002. The user/merchant-association AP002 ("User ID doesn't exist or hasn't been associated with this merchant.") is unchanged on both versions. v2 clients that matched AP002 for bad credentials must now match AP001.
  • POST /v2/linkAccountFromAggregator v2 no longer returns AP300 (a transaction-level error) from this bank-linking endpoint. v1 is unchanged.
    • If the account links but its balance cannot be retrieved, the request now succeeds with HTTP 200 and returns userBankInfo for the linked account, instead of failing.
    • If the aggregator returns no account details at all, nothing is saved and v2 returns AP410 with HTTP 200 and a message like "We were unable to retrieve the bank account information for this connection (reference: <code>). Please reconnect your bank account and try again." (the reference part appears only when the aggregator supplies an error code).
    • A balanceUnavailable value sent in the request body is ignored.
  • POST /reverseTransaction / POST /v2/reverseTransaction A refund can now be retried when the earlier refund attempt failed (its reversal transaction ended nsf or void). Before, the original transaction still counted as fully refunded and the retry returned AP1300 ("Transaction has already been refunded"). Failed attempts no longer count toward the refunded amount. A refund against a transaction that is itself a reversal is now allowed only when every prior refund attempt against it failed. Otherwise it is still rejected (AP1304, unchanged). AP1300 is still returned when a refund actually processed.
  • user_suspended / user_active webhooks data.userid is now always sent as a JSON string. It was sometimes sent as a number when the upstream user id was an integer, which broke strictly typed consumers. Applies to both v1 and v2 payload versions. Only future events are affected.

2026-09-22 — Location id validation, bank-link duplicate message and blocked-user 403

  • merchantLocationId (v2 requests) The documented "location UUID" form is now accepted. Before, validation required a standard RFC 4122 UUID, so a real location UUID (a 10-character lowercase hex value) was always rejected with AP701 "invalid UUID format". A standard 36-character UUID is now rejected. The integer location id form is unchanged. This also applies to POST /v2/paymentLink.
  • Bank account linking AP410 now uses a specific default message: "This account is already connected to another user. Please share the last four digits with [email protected] to investigate." It is used when a bank account is already linked to another user, replacing the previous message "Bank account already exists". The default AP410 message used by other link failures changed to the same text. If you match on AP410 message text, switch to matching the code.
  • GET /user The blocked-user 403 option (AP120 with HTTP 403), available on v2 since 2026-08-18, now also applies to v1 GET /user for merchants where it is enabled.
  • transaction_declined (RTP declines) and transaction_rfp_delivered v2 webhooks data.userId now contains the user's aeroPassUserUuid instead of the raw numeric id. If no matching uuid exists, it falls back to the numeric userId as before.

2026-09-17 — AP307 reason codes on model-rejected transactions

  • POST /transaction / POST /v2/transaction An AP307 (model reject) error can now include a reasonCodes array when reason codes are available. On v2 the field is error.reasonCodes. On v1 it is a top-level reasonCodes next to success, code and error. This includes v1 requests sent with X-AP-Version: 2023-06-05. The field is omitted when no reason codes are available. Other transaction endpoints do not return it.

2026-09-03 — v1 transaction_refunded webhook for queued refunds

  • transaction_refunded (v1 payload) Queued refunds previously leaked v2-shaped fields. The payload now matches the flat legacy contract: status is "queued", id is null (no refund transaction exists yet), amount is a dollar string and apFee is "0.00". A new reversalId field (the refund record id) is added to the payload for a refund that is queued and to the payload for a refund that is processed immediately. A queued refund that is released later does not include reversalId. The v2 payload is unchanged and does not include reversalId.

2026-09-01 — v2 paymentLink contract, serialization fixes and transaction_resolved for resolved NSF

  • POST /v2/paymentLink v2 now has its own validated request and response contract. v1 POST /paymentLink is unchanged. v2 callers that relied on the old unvalidated behavior must update.
    • uuid, transaction_uuid, locationId and subscriptionId are rejected with AP701 ("Field(s) not supported: ..."). paymentType of subscription is rejected with AP701.
    • merchantLocationId replaces uuid and locationId. It accepts an integer location id (a numeric string also works) or a location UUID. A location UUID that belongs to a different merchant returns AP204. An integer location id that does not belong to the merchant returns AP203 ("Unable to get location for merchant id."). A location UUID that does not exist returns AP208. (The location-UUID format check was corrected on 2026-09-22.)
    • amount accepts the v2 money shape {"amount": ..., "currency": ...}.
    • referenceId is accepted and validated, echoed in the response, and added to the generated link as an r query parameter.
    • Missing both phone and email now returns AP700 immediately. Before, it only failed silently after the request was queued.
  • v2 userId values Some older migrated accounts whose stored username was a prefixed string now return the bare UUID in userId. This covers GET /v2/user and all v2 transaction objects (reverseTransaction, GET /v2/transaction, transactionSearch and v2 webhooks).
  • v2 missing values v2 bank account objects now return JSON null, instead of the literal strings "None" or "undefined", for a missing bankName or status. v2 transaction objects and v2 webhooks return null for a missing attribute value, name or description. v1 responses and v1.1 webhook payloads keep the legacy string values.
  • transaction_resolved webhook: it is now also delivered when a declined (nsf) transaction is marked resolved by Aeropay support. Before, it was delivered only when a retried return was processed. The payload is the same as for the existing topic: the original (declined) transaction, in the standard v1 or v2 shape for the subscriber, with userId as the user UUID when available. Marking a transaction as no longer resolved does not send the webhook.

This applies to v1 and v2 webhook subscriptions. If you subscribe to transaction_resolved, expect events that no retried return preceded, and make your handler idempotent per transaction. No other action is required.


2026-08-27 — Merchant tip configuration endpoint

Merchants can now read their tipping configuration through the v2 API.

  • GET /v2/merchant/tipConfiguration is a new endpoint. It requires a merchant-scoped bearer token and returns the authenticated merchant's tip settings. Tipping is configured per merchant, not per location.
    • When tipping is enabled, the response is {"tipConfiguration": {"enabled": true, "options": [{"type", "value"}], "defaultValue": null, "customTipEnabled": <bool>}}.
    • type is percentage or flat. For percentage, value is a number. For flat, value is a money object {"amount": <integer pennies>, "currency": "USD"}.
    • customTipEnabled is true when the merchant offers a custom-tip option. The custom option is not repeated in options.
    • defaultValue is always present but is currently null. No default tip selection is stored yet.
    • When tipping is disabled, the response is {"tipConfiguration": {"enabled": false}}, and the other keys are omitted.
    • A token without merchant scope returns AP101 with HTTP 401. Unhandled failures return AP-0002 with HTTP 500.

This is a v2-only endpoint. No integrator action is required.


2026-08-18 — v2 apFee standardization

Aeropay API v2 monetary representation is now consistent between amount and apFee:

  • POST /v2/transaction, GET /v2/transaction, and POST /v2/reverseTransaction no longer return an apFee field. Fee amounts are no longer surfaced on these endpoints.
  • POST /v2/transactionSearch now returns apFee as an object matching the existing amount shape — { "amount": <integer, cents>, "currency": <string> } — instead of a dollar-denominated string (e.g. "0.25").

This is a v2-only change; v1 endpoints, including CSV and totals reports, are unaffected. No action is required for integrations that don't read apFee from these endpoints today.

This change is controlled by a feature flag. The new behavior applies by default, but the previous apFee behavior can be temporarily retained for individual merchants during migration. If your integration reads apFee and needs more time to update, contact Aeropay support.


2026-08-18 — DELETE webhook response, blocked-user 403, and payout location validation

  • DELETE /v2/webhook: a successful delete now returns {"webhooks": [ ... ]}, listing every webhook that was deleted. Previously the response body was an empty object {}.
  • DELETE /v2/webhook: deleting a webhook that does not exist (no match for topic / webhookId) now returns HTTP 404 with {"error": {"code": "AP1109", "message": "No webhook found for topic=... webhookId=..."}}. Previously this case returned HTTP 200 with an empty object.
  • DELETE /webhook (v1): the same no-match case now returns HTTP 200 with {"error": "<message>", "success": false, "code": "AP1109"}.
  • GET /v2/user: for a blocked (suspended) user, the call returns AP120 with HTTP 403. The message is "Your Aeropay account requires review before additional transactions can be processed. Please contact [email protected] for assistance." This applies for merchants where this is enabled. Otherwise behavior is unchanged.
  • POST /v2/payoutTransaction: merchantLocationId is now validated like it is for POST /v2/transaction. Previously payouts skipped the check. A location that does not exist, or that belongs to another merchant, returns AP303 with HTTP 400. If merchantLocationId is omitted, the merchant's online location is used, as before; if the merchant has none, AP203 is returned. This also applies to v1 POST /payoutTransaction, which returns AP303 with HTTP 200 and the error in the body.
  • POST /v2/transaction: AP303 now returns HTTP 400. The AP303 message changed from "Transaction missing location." to "Transaction location is missing or invalid." The new message also applies to v1.
  • POST /v2/paymentLink: errors now return HTTP status codes instead of 200. AP009 and AP101 return 401. AP700 and AP701 return 400. All other errors still return 200.

The DELETE /v2/webhook, GET /v2/user, POST /v2/transaction (HTTP 400 for AP303) and POST /v2/paymentLink changes are v2-only. The v1 POST /payoutTransaction and POST /transaction endpoints use the same location validation and the new AP303 message, and still return HTTP 200 with the error in the body. Integrators that call DELETE /v2/webhook should read the webhooks array and handle the 404 AP1109 response. Payout callers must either omit merchantLocationId or send one that belongs to their merchant.


2026-08-13 — Webhook payload field

  • v2 transaction webhooks: payloads now include merchantUserReputation, with the value standard, vip, or blocked. It appears in the data of transaction_completed, transaction_declined, transaction_resolved, transaction_voided, transaction_rfp_delivered, transaction_refunded (on data.transaction), and preauthorized_transaction_created. The field is omitted when the reputation status cannot be resolved. v1 payloads never include it.

This is a v2-only change. The new webhook field is additive. No integrator action is required.


2026-08-11 — HTTP 409 for idempotency errors on reverseTransaction

  • POST /v2/reverseTransaction: AP1400 (idempotency key reused with a different body) and AP1401 (same key still being processed) now return HTTP 409. All other error codes on this route still return 200. GET on this route is unchanged.

This is a v2-only change. Integrators that checked only for HTTP 200 with an error body should handle 409.


2026-08-07 — Idempotency error code and merchant-not-enabled message

  • POST /v2/reverseTransaction: reusing an Idempotency-Key with a different request body now returns error code AP1400. The message is "This operation is idempotent and it requires correct usage of Idempotency Key. Idempotency Key cannot be reused across different payloads of this operation". It previously surfaced as a generic error.
  • AP210 message (returned when the merchant is not verified) changed from "This merchant is not activated, please contact [email protected] for more information." to "This merchant is not currently enabled for Aeropay transactions. Please have them contact [email protected] for assistance."

Integrators that match on message text for AP210 should match on the code instead.


2026-07-30 — Duplicate webhook subscriptions rejected

  • POST /v2/webhook (and POST /webhook): creating a subscription with the same merchant, topic, url, and payload version as an existing one now returns AP1108 with the message Duplicate webhook subscription for topic {topic} and url {url}. Previously duplicates were accepted.

Integrators should treat AP1108 as "already subscribed". No action is needed for existing subscriptions.


2026-07-28 — AP118 passthrough and AeroSync account display

  • POST /v2/user (and POST /user): AP118 ("User cannot be created with numbers or special characters") is now returned to the caller. Previously it was caught and replaced by a generic error.
  • User bank account objects (v1 and v2): for accounts linked through AeroSync, accountLast4 returns the AeroSync-provided account number display value when one is stored, instead of the last 4 digits of the account number. The display value is saved when the account is linked, or when a balance check finds none stored. Other aggregators are unchanged.

Integrators that assume accountLast4 is exactly 4 characters should not rely on that for AeroSync accounts.


2026-07-23 — New transaction_resolved webhook, merchantReputation timeout, AP317 message

  • New webhook topic transaction_resolved: it is delivered when a retried return is processed and the original declined transaction is marked resolved. The payload is the original (declined) transaction, not the retry. v2 subscribers receive the standard v2 transaction object in data, with userId as the user UUID when available. The topic is accepted by POST /v2/webhook and POST /webhook.
  • POST /v2/merchantReputation (and POST /merchantReputation): fixed 504 timeouts for merchants with very large user lists. The check that the submitted userIds belong to the merchant now looks up only the submitted users. There is no change to request or response shape.
  • AP317 message changed from "This user or transaction has been throttled for security and AML risks" to "Too many failed attempts. Please wait a few minutes and try again. If the problem continues, please contact support."

Subscribe to transaction_resolved to learn when a previously declined transaction has been cleared. No action is required for existing integrations.


2026-07-21 — Error messages, refund webhook payloads, and token error

  • AP314 message changed from "Transaction already exists" / "Transaction with uuid of {uuid} already exists" to "This transaction request has already been submitted. Please refresh the page or session before trying again." The message no longer includes the uuid.
  • transaction_refunded webhook (v2): fixes to refund payloads. data.transaction.userId is the user UUID. data.refundTransaction and data.queuedRefunds are now always taken from the full refund lists. When a transaction is voided because of an NSF return, its queued refunds are reported with status: "abandoned", and any processed reversals are included in refundTransaction. Abandoned and queued refund items in queuedRefunds no longer include paymentType; each carries id, referenceId, amount and status.
  • POST /v2/token and POST /token (user scope): when the userId does not exist or is not associated with the merchant, the AP002 message is now "User ID doesn't exist or hasn't been associated with this merchant." It was "invalid API key or secret key". The code is still AP002.

Integrators that match on message text should match on error codes instead.


2026-07-16 — Pagination fix

  • POST /v2/transactionSearch (and POST /transactionSearch): paging.nextPage is now false when the returned page has no items. Previously it could report true for an empty page.

No action is required.


2026-07-14 — Reversal uuid validation

  • POST /v2/reverseTransaction: the optional uuid field is no longer required to be a version 4 UUID. Previously a non-v4 value returned AP701 for uuid. A uuid that already exists on a reversal still returns AP314.

No integrator action is required.


2026-07-09 — transaction_refunded webhook payload

  • transaction_refunded webhook (v2): data.refundTransaction entries are now full v2 transaction objects for processed reversals (id, amount, status, paymentType, userId as UUID, referenceId, and so on). Previously they carried only id, referenceId, amount, status: "pending", and paymentType: "reversal". data.queuedRefunds keeps id, referenceId, amount, and status: "queued". referenceId is reported at the top level of each object.

This is a v2 webhook payload change. Integrators should expect the larger object shape in refundTransaction.


2026-07-07 — Webhook payload fixes and bank account limit enforcement

  • user_suspended and user_active webhooks (v2): data.userid is now the user UUID (aeroPassUserUuid) when one exists, falling back to the integer id. v1 payloads are unchanged.
  • transaction_declined webhook (v2): for declines caused by ACH returns, referenceId is now reported at the top level of data, not inside data.attributes.
  • Bank account linking: the configured per-user limits are now enforced. AP414 (unique-account limit) counts a user's active linked accounts, so removing an account frees a slot. AP415 (per-account re-link limit) counts every link of the same routing and account number, including removed ones. Previously both limits could be skipped, including when the two configured limit values were equal. Users at or over the limits now receive AP414 or AP415.

Integrators using the v2 webhook payloads should read userid as a UUID where available.


2026-06-25 — v2 transaction webhook field consistency

The v2 payloads for transaction_completed, transaction_voided, transaction_declined and transaction_refunded now return three fields in the same format across these topics.

  • userId is now the consumer UUID (the aeroPassUserUuid value) instead of the numeric user id, on all four topics. If the UUID cannot be resolved, the numeric id is still sent.
  • referenceId is now a top-level field on the transaction object. When a transaction carried it only as an entry in attributes (name: "referenceId"), the value is promoted to the top level and that entry is removed from attributes.
  • userAccountId is now present (as an integer) on transaction_completed. It was previously dropped before the webhook was sent.

This affects v2 webhook payloads only. Integrators that matched on a numeric userId in v2 webhooks must expect UUIDs.


2026-06-18 — Rejected request-for-payment (RFP) events now void the transaction

  • transaction_voided webhook (RFP): a payer rejection reported by the RTP provider (Rtp.Payment.Rejected) is now handled the same as a cancellation (Rtp.Payment.Canceled), including when the request had already been delivered to the payer. The RFP is marked rejected, the transaction is voided, and the transaction_voided webhook is sent.

Applies to merchants using RTP request-for-payment.


2026-06-16 — v2 transaction_refunded webhook payload reshaped

  • transaction_refunded webhook, v2 payload (breaking): data.refundTransaction changes from a single object to an array of refund objects, and a new data.queuedRefunds array is added. The shape is now { "transaction": {...}, "refundTransaction": [ ... ], "queuedRefunds": [ ... ] }. It has the same contents as the GET /v2/transaction/{id}/refunds response, except that the refunds list is under the key refunds there and refundTransaction here.
    • When a refund is queued, refundTransaction is [] and queuedRefunds lists every queued refund on the transaction (status: "queued").
    • When a queued refund is processed, refundTransaction lists the refund(s) and queuedRefunds holds anything still queued.
    • When queued refunds are abandoned because the parent transaction went NSF or void, one webhook is sent per transaction. It carries all abandoned refunds in queuedRefunds with status: "abandoned".

This is a v2-only change. v1 payloads keep the single flat refund object. Integrators on v2 must update their parsing.


2026-06-11 — RFP void reason attribute

  • RFP voids: when a request for payment is voided because the provider reported a rejection reason, a voidReason attribute is saved on the transaction and returned in its attributes array (name: "voidReason", value is the reason code, description is the end-user message).

Applies to RTP request-for-payment voids that carry a rejection reason.


2026-06-09 — Refund identifiers (uuid, referenceId) and queued refund detail

  • POST /v2/reverseTransaction: the request body accepts two optional fields. referenceId is stored as a referenceId attribute on the refund. uuid sets the id of the refund (queued or immediate); if omitted, one is generated.
    • uuid must be a valid UUID v4, otherwise the request fails with AP701 (parameter uuid). A uuid that is already used by another refund fails with AP314.
  • POST /v2/reverseTransaction (queued refunds): when the refund is queued (action: "queued"), refundTransaction is now an object describing the queued refund, with id (the refund uuid), referenceId when provided, amount ({ "amount", "currency" }) and status: "queued".
  • GET /v2/transaction/{id}/refunds (breaking): the response field queuedRefund (a single object with amount and currency, summing all queued refunds) is replaced by queuedRefunds, an array. Each item has id, referenceId, amount ({ "amount", "currency" }) and status: "queued". When nothing is queued the value is [] (previously null).
  • Queuing behavior: each queued refund request is now stored separately with its own uuid. Previously a second queued refund on the same transaction was merged into the existing queued amount.
  • transaction_refunded webhook: a webhook is now also sent when a refund is queued (in addition to when it is created). The webhook payload shape changed again on 2026-06-16, see above.

v2 only. Integrators reading queuedRefund from GET /v2/transaction/{id}/refunds must switch to queuedRefunds.


2026-05-21 — Abandoned queued refunds now notify, plus preauth not-found message

  • transaction_refunded webhook: when a queued refund is discarded because its parent transaction ended as NSF or void, a transaction_refunded webhook is now sent with the refund status: "abandoned" and paymentType: "reversal". Previously the queued refund was deleted with no notification. The v2 payload shape for this event changed on 2026-06-16, see above.
  • GET /v2/preauthTransaction/{id} (not found): looking up a preauthorized transaction that does not exist still raises AP313, but the message changes from "Preauth ineligible for capture" to "Preauthorized Transaction with status of not_found not eligible for capture". The same message applies to the v1 lookup by id.

2026-05-07 — v2 webhook payload fixes

  • v2 webhooks (transaction_completed, transaction_declined, transaction_voided, transaction_rfp_delivered, transaction_refunded): when the pre-built v2 transaction object is missing from the event, the v2 payload is now generated from the raw transaction. It has nested amount ({ "amount", "currency" }), ISO 8601 createdDate/modifiedDate, referenceId and isRtp. Previously the v1-style shape was sent to v2 subscribers in these cases.
  • transaction_voided webhook: status is now always void in both the v1 and v2 payloads (v2: data.status).

The first applies to v2 subscribers only; the second applies to all subscribers.


2026-05-05 — v1 webhook payloads: field filtering restored, wider preauthorized field list

  • v1 transaction webhooks: payloads for transaction_completed, transaction_declined, transaction_voided, transaction_refunded and transaction_rfp_delivered are limited to the documented set of fields. A release on 2026-04-30 changed the fields sent in v1 payloads; this restores the documented field list, which now also includes returnCode.
  • preauthorized_transaction_created webhook (v1): the payload now includes these additional fields: transactionId, merchantAccountId, merchantLocationId, userAccountId, userName, userEmail, dollarAmount, originalAmount, description, merchantReputationStatus, userReputationStatus, expiryDate, cancellationDate, creditPartnerId.

v1 webhooks only. v2 payloads are unchanged.


2026-04-23 — Idempotency key lookup: encoding and whitespace

  • GET /v2/transaction/idempotency/{idempotencyKey}: the path value is now URL-decoded, so keys containing # can be sent as %23.
  • GET /v2/transaction/idempotency/{idempotencyKey}: the key is trimmed of leading and trailing whitespace before lookup.

v2 only.


2026-04-21 — AP115 and AP119 show the upstream message

  • POST /v2/user (and v1 user creation): when the phone number (AP115) or country code (AP119) is rejected and a message is supplied, that message is returned in the error. Otherwise the previous fixed text is returned. The error codes are unchanged.

Applies to v1 and v2.


2026-04-14 — New endpoint: look up a transaction by idempotency key

  • GET /v2/transaction/idempotency/{idempotencyKey}: returns the response originally stored for a POST /v2/transaction call made with that Idempotency-Key header value. Requires a merchant-scoped token.
    • Returns an empty result (no transaction data) if the key does not exist or belongs to another merchant; the two cases are not distinguishable.
    • Keys are retained for one day, after which the transaction cannot be found by key.
    • An empty key fails with AP700 (HTTP 400).

v2 only. Follow-up changes shipped on 2026-04-23, see above.


2026-04-02 — Webhook payloadVersion format

  • Webhooks (v2): payloadVersion is now always a decimal string, for example "2.0" instead of "2". Values that already contain a decimal (for example "1.1") are unchanged. This applies to the payloadVersion field on webhooks delivered to merchants (including preauthorized_transaction_created) and to the webhook object returned for v2 callers.

Integrators that compared payloadVersion == "2" must update to "2.0".


2026-03-31 — Clearer errors from POST /v2/reverseTransaction and GET /v2/transaction/{id}

Failures that used to come back with a generic error code now return specific codes.

  • Transaction not found returns error code AP316 with the message Transaction {id} not found, where {id} is the transaction UUID on v2. The message text is unchanged; the error.code was previously the generic ApError. This applies to POST /v2/reverseTransaction and GET /v2/transaction/{id}.
  • Refunding a transaction in a status that cannot be refunded (for example void or NSF) returns error code AP1306. The message is now Cannot reverse transaction with status of {status}. It was previously Can not reverse with status of {status} under the generic ApError code.
  • Missing-parameter errors (AP700) now name the v2 parameter. GET /v2/transaction/{id} reports id instead of transactionId. POST /v2/reverseTransaction also reports id.
  • Refund success message (v2): message now cites transaction UUIDs, as Refund transaction {refundUuid} created for transaction {originalUuid}. It previously cited numeric ids. This applies to v2 refunds that are processed immediately.
  • Unauthorized requests to the v2 pay API (HTTP 401 from the gateway authorizer) now return a fixed body: {"error": {"code": "AP008", "message": "Invalid token."}}.

The AP316 and AP1306 codes are not version-gated in the code. The AP700 parameter name and the refund success message change apply to v2 only. Integrators that match on the generic ApError code or on the old message text for these cases should switch to AP316 and AP1306.


2026-03-26 — Request-for-payment expiry shortened to 15 minutes

  • Request-for-payment (RFP) expiry: RFPs created for instant pay-in now expire 15 minutes after creation. Before this change the expiry was set to the date after creation (YYYY-MM-DD, no time). An expired RFP voids the transaction (see the 2026-03-24 entry).

Applies only to merchants and transactions that take the RFP path.


2026-03-24 — Instant pay-in (request for payment)

  • POST /transaction and POST /v2/transaction can now pay in instantly through a request for payment (RFP) sent to the user's bank. This applies to payment transactions that set rtp to true (on POST /transaction it applies to requests that send X-AP-Version: 2023-06-05). The transaction is eligible only if the request's userIdentification includes a full address or a full birth date, the merchant is set up for instant pay-in, and the user's bank supports request for payment. If it is not eligible, the transaction is processed as ACH as before. An RFP transaction stays pending until the user approves the request in their banking app. When the user approves, it becomes completed and the transaction_completed webhook is sent. If the user rejects it, or it expires or fails, the transaction is voided and transaction_voided is sent.
  • New webhook topic transaction_rfp_delivered is sent when the request for payment reaches the user's bank. It can be subscribed to with POST /webhook or POST /v2/webhook. The payload is always the v2 transaction object.

Applies to merchants set up for instant pay-in.


2026-03-19 — Idempotency-Key format relaxed on POST /v2/reverseTransaction

  • POST /v2/reverseTransaction: the Idempotency-Key header no longer has to be a UUID v4. Any non-empty value without whitespace is accepted. A key that is empty or contains whitespace is still rejected.

This applies to v2 only. No action is required; keys you already send keep working.


2026-03-05 — connectionId on v2 bank account objects

  • v2 bank account objects (including each account returned by GET /v2/bankAccounts) now include connectionId. It holds the AeroSync connection (user) id for AeroSync-linked accounts. It is null for other accounts, such as Plaid, or when there is no stored link token.

This applies to v2 only. The change is additive.


2026-03-03 — Safe retry on POST /v2/reverseTransaction

  • POST /v2/reverseTransaction supports safe retries through the optional Idempotency-Key request header. The header value must be a UUID v4. A retry with the same key and merchant returns the stored response instead of refunding again. The same key with a different body is rejected, and the error message contains "This operation is idempotent and it requires correct usage of Idempotency Key. Idempotency Key cannot be reused across different payloads of this operation", prefixed with "Error getting or creating the Idempotency: ". The same key used by a different merchant is also rejected. A request that arrives while the first is still in flight returns a conflict with the message Another refund in progress and error code 409. Stored records are kept without expiry. Requests without the header behave as before.

This is v2 behavior. To make a refund request safe to retry, send a unique UUID v4 Idempotency-Key and reuse it with an identical body on each retry.


2026-02-12 — Corrected POST /v2/confirmUser output

  • POST /v2/confirmUser now returns the user in v2 form: user with id (the user UUID), firstName, lastName, type, email, phoneNumber and createdDate as an ISO 8601 UTC timestamp. Before, the id value in the v2 output was not the UUID, and the user lookup ignored the API version.

The confirmUser change is v2 only.


2026-01-29 — POST /v2/user existing-user response key

  • POST /v2/user when the user already exists now returns the user under the key user (together with displayMessage). It previously used the key existingUser. This is v2 only. v1 still returns existingUser. Integrators reading existingUser from v2 responses must switch to user.

2026-01-27 — v2 response cleanups (dates, voids, confirmUser)

  • v2 transaction objects now return createdDate and modifiedDate as ISO 8601 UTC timestamps. Epoch-second strings are converted. This applies to the transaction objects built for v2 responses, including refundTransaction from POST /v2/reverseTransaction.
  • POST /v2/reverseTransaction: when the reversal is a void of an unbatched pending transaction, refundTransaction in v2 now holds the voided transaction. Before it was built from the reversal record, which does not exist in the void case.
  • POST /v2/confirmUser: the v2 user object is now built from the verified user. createdDate is formatted as an ISO 8601 UTC timestamp. The previous formatter silently dropped numeric-string dates and returned null.

All items are v2 only.


2026-01-22 — User UUIDs and consistent v2 shapes

  • GET /v2/user and POST /v2/user now return user in the v2 shape: id (user UUID), firstName, lastName, type, email, phoneNumber, createdDate. GET /v2/user also includes userStatus (Active or Suspended). The phone field is named phoneNumber rather than phone.
  • v2 bank account objects (for example the response of PATCH /v2/userBankAccount/{bankAccountId}) no longer include connectionToken or externalBankAccountId.
  • POST /v2/capturePreauthTransaction now returns the pre-authorization with status set to completed. The v2 preauth object is rebuilt after the transaction id is saved to the preauth. Before it was not refreshed.
  • Webhooks, v2: transaction_refunded now delivers {"transaction": <original v2 transaction>, "refundTransaction": <v2 refund transaction>} instead of a single transaction object. preauthorized_transaction_created uses the v2 preauth object for v2 calls. Transaction objects in v2 webhook and reverse responses carry the user UUID in userId.
  • POST /v2/reverseTransaction: refunding a transaction whose refunds already total the full transaction amount returns AP1300 (transaction already refunded).

Items are v2 only unless noted. The AP1300 check is in the shared refund logic and is not version-gated, so it also applies to v1 refund requests.


2026-01-13 — v2 webhook, transaction search and preauth shapes

  • Webhooks, v2: for events that originate from v2 API calls, the transaction_completed, transaction_refunded, transaction_voided and transaction_declined payloads now use the v2 transaction object. Before, they were the truncated v1 shape. The returnCode is still added for transaction_declined. For v1 subscribers (events not originating from v2 calls), the transaction_refunded, transaction_voided and transaction_declined payloads now also include deliveryMethod, as transaction_completed already did.
  • POST /v2/transactionSearch: results are now built as v2 transaction objects, enriched with customer, location and merchant names, with userId set to the user UUID. The response contains transactions and paging.
  • GET /v2/preauthTransactions: userId on each returned preauthorization is now the user UUID instead of the numeric user id.
  • GET /v2/preauthTransactions: the response now has the same v2 shape whether or not the single-preauth id (UUID) query parameter is sent. Before, the v2 formatting was skipped when it was sent.
  • GET /v2/preauthTransactions: the date filters dateStart and dateEnd accept MM-DD-YYYY HH:MM:SS or MM-DD-YYYY. A date-only end date is treated as 23:59:59. An unparseable date now returns the generic ApError code with the message Incorrect date format, can be either %m-%d-%Y %H:%M:%S or %m-%d-%Y: {value}. Before, a bad date was silently replaced by 1970-01-01 (start) or now (end). The timezone query parameter is no longer applied to dateStart and dateEnd; the dates are used as sent.

The v2 webhook payload change and the userId change are v2 only. The date-filter change is not version-gated and also applies to v1 GET /preauthTransactions.


2026-01-08 — userId in reverse transaction response

  • POST /v2/reverseTransaction: refundTransaction in the v2 response now carries the user UUID in userId, along with merchantId, userAccountId and amount. This is v2 only.