
# When a call fails

Every failure comes back in the same envelope:

```json
{ "ok": false, "kind": "bad_request", "source": "amazon_rejected", "retryable": false,
  "friendly": "...", "message": "...",
  "amazon": { "status": 400, "code": "InvalidInput", "message": "..." },
  "amazon_error_code": "InvalidInput", "status": 400 }
```

Branch on `kind`, not on the HTTP status. `friendly` is safe to show a person as-is. `message` is the specific reason, and it is the field to read when you are trying to fix something.

Two fields answer the questions below in one word each. `source` says whose problem it is: `request` (fix your request), `account` (the account owner acts: scopes, re-authorization, activation), `amazon_rejected` (Amazon said no to this specific request, and its own words are under `amazon`), `amazon_unavailable` (temporary; wait and retry) or `mixshift` (ours; tell us). `retryable` says whether sending the same call again can ever succeed; when it is `false`, a retry loop cannot end. Some failures also carry `fix`, a concrete next step. On the MCP tools, a failed call carries the same envelope as its result text and also sets the result's `isError: true`.

## The only question that matters first: whose problem is it?

Retrying the wrong kind of failure is the most common way to lose an afternoon here, so the answer is meant to be obvious from the response alone.

| You get | Whose problem | Retry? |
| --- | --- | --- |
| `400` `invalid_request` | **Yours, caught before Amazon.** An operation id not in the catalog, a missing path parameter, a body where none is taken, or a malformed body. Nothing was sent to Amazon. | **No.** Fix the request; `friendly` and `fix` say what to change. These used to arrive as `500` `unknown`. |
| `400` `bad_request` | **Yours.** Amazon rejected the request itself. | **No.** It is deterministic and will fail identically forever. Change the request. |
| `403` `restricted_report` | An access or role gap. | No. Re-authorize the merchant, or use a different operation. |
| `409` `reauth_required` | The merchant's Amazon connection lapsed. | Re-connect the account first, then yes. |
| `422` `merchant_inactive` | The merchant is real and its authorization is fine, but it is not activated for Amazon Ads in MixShift, so Amazon will not serve its campaign data. | **No, and do not re-authorize.** Needs a person to activate the merchant in MixShift. |
| `403` `profile_not_authorized` | The advertising login this merchant is connected through does not have access to it at Amazon. The MixShift connection itself is working. | No, and re-authorizing will not change it. Check the advertising login's access at Amazon, or ask us to re-map it. On `reporting_v1.create_report`, where you name the accounts yourself, check the ids first: a DSP seat id in place of the `dspAdvertiserId` fails exactly this way. |
| `404` `merchant_not_found` | The merchant selector matched nothing. | No. See [Identify a merchant](/knowledge-base/builder-platform/reference/merchant-identifiers). |
| `429` `throttled` | Rate limiting, by us or by Amazon. | Yes, after backing off. Honour `Retry-After`. One exception: a 429 from `reporting_v1.create_report` means reports are already in flight, a concurrency cap, and backing off will not clear it. Delete a report you no longer need, or wait for one to finish. |
| `502` `upstream_unavailable` or `host_unreachable` | Amazon's, or the network. | Yes, later. **For writes, check whether it applied first.** |
| `500` `unknown` | Ours, and now rare. | Once. If it persists, tell us. |

The write caveat on `502` is worth reading twice: a server error does not prove Amazon rejected your change before acting on it. Re-read the entity before you re-send, or you can apply the same change twice.

`merchant_inactive` and `profile_not_authorized` are newer, and they are a split rather than an addition: until 2026-09-15 an inactive merchant and an unmapped advertiser both arrived as `409` `reauth_required`, which sent callers into a re-authorize loop that could never succeed. If your integration branches on the HTTP status rather than on `kind`, these two now land somewhere you may not handle.

## The rest of the set

The table above is triage, not the full list. The service serves 27 kinds in
total, and the ones it does not cover fall into two families you will meet as
soon as you go past reads.

**Guarded listings writes.** A change set is proposed, approved, then committed,
and each step has its own way of going wrong. None of these are worth a blind
retry; each one tells you which step to go back to.

| You get | What happened | Retry? |
| --- | --- | --- |
| `404` `change_set_not_found` | The id does not exist, or was never yours. | No. |
| `409` `change_set_expired` | It aged out before you committed it. | No. Propose it again. |
| `409` `change_set_already_committed` | It already applied. | **No, and do not re-propose without re-reading**: the change is live. |
| `409` `approval_mismatch` / `stale_approval` | The approval does not match what you are committing, or the listing moved underneath it. | No. Re-read, re-propose, re-approve. |
| `409` `schema_drift` | Amazon changed the product type's schema since the change set was built. | No. Rebuild against the current schema. |
| `422` `patch_not_allowed` | The attribute cannot be patched this way. | No. |
| `403` `listings_write_not_enabled` | Writes are not enabled for this account. | No. Talk to us. |
| `503` `listings_write_disabled` / `listings_media_write_disabled` | MixShift has the surface switched off globally. | Later, yes. |

**Reports.**

| You get | What happened | Retry? |
| --- | --- | --- |
| `400` `report_retired` | Amazon retired that report type. Nothing was sent. | No. Use a current type. |
| `502` `report_fatal` | Amazon accepted the report then failed to produce it. | **Not as the same request** (`retryable` is `false`). Check the parameters, such as the date range, before you create it again. |

**Configuration.** `503` `spapi_not_configured` and `503` `ads_not_configured`
mean the relevant Amazon API is not switched on for the MixShift service; `403`
`insufficient_scope` means your credential is missing a scope the surface needs,
which an admin grants at `/admin`.

## `bad_request`: what Amazon actually said

This is the one to know, because it used to be the confusing one. A request Amazon refused because of its own parameters used to come back as a `500`, which reads as an outage and invites a retry that could never work. It now comes back as a `400` and carries the words Amazon used:

- `amazon_error_code` is the code Amazon returned, such as `InvalidInput` or `InvalidParameterValue`.
- `status` is the HTTP status Amazon returned, separate from ours.
- `responsePayload` is the full response from Amazon, when we captured it.

In the plugin, the same information prints to the terminal, and `--json` carries `amazon_error_code`, `amazon_status` and `amazon_response`. Scripts get exit code `12`, which is distinct from the generic failure code precisely so a script can stop instead of retrying.

Most of the time this means a parameter is missing, misspelled, or in the wrong case. The operation catalog (`GET /api/amazon/spapi/operations`) documents the required parameters and the casing rules for each call, and it is the first thing to check.

## Reporting a failure, and why it is worth your thirty seconds

Sometimes the request was wrong **because our documentation said to make it that way.**

The operation catalog is maintained by hand. Amazon occasionally enforces a parameter it does not document, or requires two parameters only in combination, and until someone hits it our notes look complete. When our notes are wrong, every caller walks into the same wall.

Here is the asymmetry that makes your report valuable: **we can see that a call failed and which operation it was. We cannot see your parameters.** You are holding the half we are missing.

So please send three things:

1. the **operation id**, for example `fulfillment_inbound.get_shipments`
2. the **`amazon_error_code`** and the `message` from the envelope
3. the **parameters you sent**, minus anything you consider sensitive

Where to send it:

- **Plugin users:** run `mixshift feedback`. It attaches the context for you.
- **Everyone else:** [support@mixshift.io](mailto:support@mixshift.io).

We record the Amazon error code against the operation, so the same code repeating on the same call surfaces as a pattern on our side. A confirmed gap normally turns into a catalog note that the next caller never trips over. That is the whole loop, and it starts with someone bothering to tell us.

## Related

- [Identify a merchant: sellerId and legacySellerId](/knowledge-base/builder-platform/reference/merchant-identifiers)
- [Limits and guardrails](/knowledge-base/builder-platform/reference/limits-and-guardrails)
- [Work with live Amazon operations](/knowledge-base/builder-platform/how-to/work-with-live-amazon-operations)
- Operation-level reference: [mcp.mixshift.io/developers](https://mcp.mixshift.io/developers)