The order is in the Orders API. The Finances API returns nothing for it. The first explanation you'll find is Amazon's warning that recent orders might be missing. That warning is real, but it's only one of seven things that produce the same symptom, and the other six won't fix themselves by waiting.
Not all of them are Amazon's doing: some are documented API behaviour, others are sync bugs the documentation makes easy to write. Each is tied to a sentence in Amazon's published API models for Finances v2024-06-19 (listTransactions) and the legacy v0, quoted below. If you only need to know which side the problem is on, a single lookup by order narrows it down; that's after the seven. And if you'd rather be told than keep asking, there's a notification for that at the end.
Short answer: Amazon's Finances API may leave out orders from the last 48 hours, but that's only one cause. For older orders, check deferred transactions (hidden in
v0, returned asDEFERREDinv2024-06-19unless you filter them out), themarketplaceIdfilter for US MFN orders, yourpostedAfter/postedBeforewindows, the 180-day range limit, and pagination. AlistTransactionslookup withrelatedIdentifierName=ORDER_IDis the quickest first check.
Order of checks: order age →v0or atransactionStatusfilter → lookup by order ID →marketplaceId→ date windows and range → pagination.
Prefer to watch first? Here's the same diagnosis in three minutes:
1. The order is under 48 hours old (Amazon's 48-hour warning)
Financial events might not include orders from the last 48 hours.
That sentence appears on listTransactions and, in slightly different words, on all four v0 financial-event operations. Note the wording: might not. It doesn't promise the data is hidden for 48 hours, and it doesn't promise it shows up at hour 48.
What to do: treat "no transaction" for a recent order as not yet, not as an error, and make sure your sync comes back for it (see the loop at the end).
2. Deferred transactions are hidden (v0, or a RELEASED filter)
The legacy API hides deferred money entirely:
Deferred events don't appear in the
listFinancialEventsByOrderIdresponse until they are released.
The same note is on listFinancialEvents and listFinancialEventsByGroupId. So while an event is deferred, these v0 operations leave it out of the response, and that can last longer than 48 hours.
listTransactions in v2024-06-19 does return them, with a status: DEFERRED, RELEASED, or DEFERRED_RELEASED. Unless you ask it not to: transactionStatus is also a request filter. If your code sends transactionStatus=RELEASED, a natural choice when you only care about money that's been released, deferred transactions are excluded by design, and the result looks exactly like reason 1.
v0 is on its way out, too. On 21 July 2025 Amazon announced that listFinancialEvents, listFinancialEventsByGroupId and listFinancialEventsByOrderId are deprecated and will be removed on 27 August 2027, with listTransactions as the replacement. (listFinancialEventGroups isn't on that list.)
What to do: on v0, check the same order through listTransactions before assuming the money doesn't exist, and plan the migration before 2027 removes the choice. Expect it to surface transactions your data never had. On v2024-06-19, check whether a transactionStatus filter is set somewhere you forgot about.
Since this difference between versions sits behind several of the reasons below, here it is in one place:
Finances v0
|
Finances v2024-06-19
|
|
|---|---|---|
| 48-hour warning | Yes, on all four operations | Yes, on listTransactions
|
| Deferred items | Left out until released | Returned, with transactionStatus
|
| Response shape | 34 typed event lists (ShipmentEventList, RefundEventList, …) |
One list of transactions, each with a type and status |
| Look up by order | listFinancialEventsByOrderId |
listTransactions with relatedIdentifierName=ORDER_ID
|
| Page size |
MaxResultsPerPage, 1–100 |
Not configurable |
| Date range limit | 180 days between PostedAfter and PostedBefore
|
180 days between postedAfter and postedBefore
|
| Default rate / burst | 0.5 req/s, 30 | 0.5 req/s, 10 |
| Status | Three of four operations deprecated, removal on 27 Aug 2027 | Current replacement |
3. Your copy still says DEFERRED (never updated to DEFERRED_RELEASED)
The status of a deferred transaction is updated to
DEFERRED_RELEASEDwhen the transaction is released.
Put together with the status definitions, a transaction takes one of two paths:
posted ──► RELEASED
posted ──► DEFERRED ──(released)──► DEFERRED_RELEASED
The second path is the one that bites. A sync that writes each transactionId once and skips anything it has already seen will keep that transaction as DEFERRED forever. The record exists; your copy is stale. (And on v0, the second path is invisible until its last step, which is reason 2.)
What to do: upsert by transactionId and overwrite the status. Beyond that, a practice that holds up well (our recommendation, not an Amazon requirement): re-check transactions that are still DEFERRED by order rather than by date. listTransactions accepts relatedIdentifierName=ORDER_ID with the order ID as relatedIdentifierValue, and the date filter is only required when no related identifier is given. The contract doesn't say whether postedDate changes on release, so don't build on either assumption.
4. You sent marketplaceId for a US MFN order
This one is a single note in the operation description:
If you want to retrieve MFN orders in the US store, do not supply the
marketplaceIdparameter.
A "harmless" marketplace filter is natural in a multi-marketplace codebase, and exactly wrong for merchant-fulfilled US orders.
What to do: build the parameter per query rather than adding it everywhere. A request meant to cover US MFN orders shouldn't carry marketplaceId; requests for other marketplaces can.
5. Your postedAfter / postedBefore windows leave a gap
The two filters aren't symmetric:
postedAfter: The response includes financial events posted on or after this date.
postedBefore: The response includes financial events posted before (but not on) this date.
Both must be "more than two minutes before the time of the request". So the window is half-open, [postedAfter, postedBefore), and it can't reach the present. A common mistake is to treat both ends as inclusive and start the next run one second later, t1 + 1s: anything posted in that second disappears. If postedBefore is "now", the request is invalid. And a timestamp converted from local time without its offset can shift a window by hours.
What to do: make each window's postedAfter exactly the previous window's postedBefore, keep everything in UTC, and end windows a few minutes in the past.
6. A date range over 180 days returns an empty response
If
PostedAfterandPostedBeforeare more than 180 days apart, the response is empty.
Not an error: an empty, successful response, indistinguishable from "no transactions". A backfill that asks for a year in one call gets a clean nothing.
What to do: split long ranges into chunks of at most 180 days.
7. Pagination stopped early (empty pages, nextToken, 429)
To get a complete list, call this operation until
nextTokenis null. Note that this operation can return empty pages.
An empty transactions array is not the end; a null nextToken is. And each follow-up call has to "include the same arguments as the call that produced the token", so the window can't move between pages.
Throttling makes this worse. The default usage plan for listTransactions is 0.5 requests per second with a burst of 10 (0.5 and 30 for the v0 operations), and a pagination loop with no rate limiting can trigger throttling by itself. A loop that gives up on a 429 halfway through silently drops everything on the remaining pages.
What to do: space out every request, successful ones included; backoff after a 429 is not a rate limiter. On 429 and 5xx, retry the same page with a cap on attempts. If you do give up, don't record the window as done, so the next run reads it again from the start. The x-amzn-RateLimit-Limit response header reports the limit actually applied to your calls, when available.
One small oddity while you're in there: the nextToken description refers to "the specified pageSize value", but listTransactions has no pageSize parameter. Page size isn't yours to set.
Quick check: look the order up by ID
Most of the reasons above live in your sync: windows, ranges, pagination, stored state. A lookup by order skips most of that. With relatedIdentifierName=ORDER_ID the date filter isn't required, so there's no window to get wrong and no 180-day limit to hit. Leave out transactionStatus, and leave out marketplaceId if it's a US MFN order. It's a first diagnostic step, not proof either way.
Using fetch_all from the code below:
params = {"relatedIdentifierName": "ORDER_ID", "relatedIdentifierValue": order_id}
for tx in fetch_all(call_api, params):
print(tx["transactionId"], tx["transactionType"], tx["transactionStatus"], tx["postedDate"])
Then read the result:
| The lookup returns | What it tells you |
|---|---|
A transaction that is RELEASED or DEFERRED_RELEASED
|
Amazon has the data. The gap is in your sync: reasons 3 to 7 |
A transaction that is DEFERRED
|
The money is held. If your sync uses v0 or a RELEASED filter, that's reason 2; if your copy hasn't caught up with a later release, reason 3 |
| Nothing, and the order is under 48 hours old | Reason 1. Come back later |
| Nothing, and the order is well past 48 hours | Possibly a real gap on Amazon's side. Keep the request parameters, timestamps and response headers: that's what a report to Amazon needs |
It's a diagnostic, not a sync strategy: at two seconds per request, you don't want to walk every order this way.
A Python sync loop that handles all seven
The code below assumes a call_api(path, params) that signs the request and returns (status, json_body), and a store that can upsert a transaction and list the order IDs whose transactions are still DEFERRED (the order ID comes from each transaction's relatedIdentifiers). It's a sketch of the pattern for a single sequential process, not a drop-in client: with several workers you'd need a shared rate limiter, and a production version should also log failures.
import random
import time
from datetime import datetime, timedelta, timezone
PATH = "/finances/2024-06-19/transactions"
MAX_SPAN = timedelta(days=180) # wider ranges return an empty response, not an error
SAFETY_LAG = timedelta(minutes=5) # postedBefore must be > 2 minutes before the request
OVERLAP = timedelta(days=3) # re-read recent days on every run
MAX_ATTEMPTS = 6
MIN_INTERVAL = 2.0 # 0.5 req/s default; check x-amzn-RateLimit-Limit
_last_call = 0.0
def iso(dt):
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
def throttled_call(call_api, params):
"""Space out ALL requests, successful ones included."""
global _last_call
wait = _last_call + MIN_INTERVAL - time.monotonic()
if wait > 0:
time.sleep(wait)
_last_call = time.monotonic()
return call_api(PATH, params)
def call_with_retry(call_api, params):
"""Retry the SAME request on 429/5xx with capped, jittered backoff."""
for attempt in range(MAX_ATTEMPTS):
status, body = throttled_call(call_api, params)
if status == 200:
return body
if status == 429 or status >= 500:
time.sleep(min(2 ** attempt, 60) + random.uniform(0, 1))
continue
raise RuntimeError(f"listTransactions failed: HTTP {status}")
raise RuntimeError(f"listTransactions: gave up after {MAX_ATTEMPTS} attempts")
def fetch_all(call_api, params):
"""Yield every transaction for these params, following nextToken to the end."""
next_token = None
while True:
args = dict(params, nextToken=next_token) if next_token else params
payload = call_with_retry(call_api, args).get("payload", {})
yield from payload.get("transactions", []) # a page may be empty
next_token = payload.get("nextToken")
if not next_token: # stop on null token only
return
def sync(call_api, store, last_synced_until, marketplace_id=None):
"""Read [last_synced_until - OVERLAP, now - SAFETY_LAG) in <=180-day chunks."""
end = datetime.now(timezone.utc) - SAFETY_LAG # computed once, only ages
start = last_synced_until - OVERLAP
while start < end:
chunk_end = min(start + MAX_SPAN, end)
params = {"postedAfter": iso(start), "postedBefore": iso(chunk_end)}
if marketplace_id: # omit for US MFN orders
params["marketplaceId"] = marketplace_id
for tx in fetch_all(call_api, params):
store.upsert(tx["transactionId"], tx) # update, never insert-once
start = chunk_end # half-open windows: no gap
return end # save only after success; a failed run re-reads the same range
def recheck_deferred(call_api, store):
"""Look up still-DEFERRED transactions by order, independent of dates."""
for order_id in store.order_ids_with_status("DEFERRED"):
params = {"relatedIdentifierName": "ORDER_ID", "relatedIdentifierValue": order_id}
for tx in fetch_all(call_api, params):
store.upsert(tx["transactionId"], tx)
throttled_call keeps requests at least two seconds apart, the default rate; if x-amzn-RateLimit-Limit reports a different limit for your account, adjust MIN_INTERVAL. Run sync on a schedule and store the returned end as the next run's last_synced_until only after the whole run succeeds. A failed run must not move the checkpoint: save it only once every window, every page and every write has gone through. end is computed once, so it only moves further into the past while a long run continues, and the two-minute rule keeps holding.
The three-day overlap re-reads recent days, which picks up late arrivals (reason 1) and status changes posted inside that period (reason 3); the upsert makes the re-reads harmless. Three days is a working default, not a number from Amazon, so tune it against what you observe. recheck_deferred covers the rest of reason 3: it looks up still-deferred transactions by order, whatever their dates. It costs at least one request per order (more if an order's transactions span several pages), at two seconds each, so run it less often than sync, or let notifications do most of that work (next section).
Poll less: subscribe to TRANSACTION_UPDATE
Everything so far assumes polling. Amazon offers a push alternative: when it announced the v0 deprecation, it pointed out that the TRANSACTION_UPDATE notification "notifies you when there is a new transaction posted to a seller's account" and that "you can use this notification to reduce your calls to the Finances API". You subscribe to it through the Notifications API.
Two details in the notification's published schema decide how you use it:
- It doesn't carry the transaction. The payload has related identifiers, a transaction type, a posted date and a total amount, but no transactionId and no transactionStatus. Treat it as a pointer: it tells you which order to re-read, and listTransactions gives you the record to store.
- The identifier is spelled differently. In Amazon's example payload the order identifier is named OrderId, while the listTransactions filter expects ORDER_ID. Map the name rather than passing it through.
A handler built on fetch_all from above:
ORDER_ID_NAMES = {"ORDER_ID", "OrderId"} # the notification example spells it OrderId
def on_transaction_update(call_api, store, notification):
"""Re-read the order a TRANSACTION_UPDATE notification points at."""
payload = notification["Payload"]["TransactionUpdateNotification"]
for rid in payload.get("RelatedIdentifiers", []):
if rid.get("RelatedIdentifierName") in ORDER_ID_NAMES:
params = {"relatedIdentifierName": "ORDER_ID",
"relatedIdentifierValue": rid["RelatedIdentifierValue"]}
for tx in fetch_all(call_api, params):
store.upsert(tx["transactionId"], tx)
With this in place, new transactions (reason 1) reach you without waiting for the next scheduled run, and sync can run far less often. Don't drop it entirely, though: the announcement describes the notification as covering a new transaction posted to the account, and it doesn't say whether a deferred transaction being released also triggers one. Until you've seen that in your own data, keep recheck_deferred and a periodic sync as the safety net for reason 3 and for any notification that never arrives.
API2Cart provides a unified API for 80+ eCommerce platforms, including Amazon; see the Amazon integration page for what's covered.
Which of the seven has bitten you? And if you've found an eighth, I'd like to hear it.
Top comments (0)