Builder Platform · Partner API

Build against your MixShift data and your connected Amazon accounts, behind one OAuth 2.0 gateway. early access

What is in the Builder Platform

Five surfaces, one credential. Everything below is included with your MixShift subscription.

Warehouse

Read-only SQL against your own tenant database, plus the contract-stable /v1 data endpoints for accounts, ads performance, retail, and Brand Analytics.

Live Amazon operations

Cataloged SP-API and Ads API calls on the authorization your accounts already granted MixShift, including audited writes. No developer application with Amazon.

Brand memory

The brand context your team maintains and the change log of what happened and why: launches, promotions, stockouts, and price tests, with corroboration.

Intelligence

MixShift Intelligence: finished reads served as data. Attribution today (why sales and ad results moved, lost sales, the monthly read); forecasting joins the same catalog when it ships.

MCP server

The same gateway as an MCP server, so Claude, ChatGPT, Cursor, Codex, and any other MCP client can work against your data in plain language.

New here, or want the plain-language version first? Builder Platform in the knowledge base walks through each surface without the reference detail.

Getting access

New to MixShift? Everything below assumes a MixShift account with activated data behind it. If you do not have one yet, create your account, then connect your Amazon accounts and activate ads + retail data (getting started guide). Most accounts are fully populated within 24-48 hours of activation; large catalogs can take longer. Then come back here to set up credentials.

Then pick your path, depending on whose data your code touches.

Guide: Get access

most customers

Building on your own MixShift account

You do not need to register an application. A tenant admin creates a service credential at /admin ("Create with raw secret"): you get a client_id and client_secret on the spot (the secret is shown once, at creation, so store it in your secret manager as you mint it). Exchange it for a bearer token with the client_credentials grant at POST /oauth/token and start calling.

Credentials are read-only by default (ads:write is grantable at mint time), revocable and rotatable at any time, and built for unattended use: schedulers, sync pipelines, CI, and multi-user apps that serve your own team.

Setting up the MixShift Claude plugin for unattended runs rather than your own code? Use the admin console's primary setup code flow instead: a one-time code the plugin exchanges itself, so no human ever handles the secret.

partner apps

Building a product that other MixShift tenants sign into

Then you need a registered partner application so each tenant can authorize you on a consent screen. Email support@mixshift.io with your app name, redirect URIs, and the scopes you need; you receive a client_id (and a secret for confidential clients) and use the authorization-code flow with PKCE below.

Coming from legacy direct MySQL access (IP allowlist + database credentials)? The service credential replaces that setup. The same SQL runs through POST /api/query, so nothing about your workflow changes except the connection. You drop the IP dependency (any host works, and no allowlist updates when your network changes), your server never holds a database password, and compromise recovery is fast: revoke the credential and any stolen access token expires within 12 hours, the current service-credential token TTL. Partner API sessions run the same 12-hour TTL.

OAuth 2.0

Send the access token as Authorization: Bearer <token> on every call.

Scopes

ScopeGrants
account:readAccount and merchant metadata.
retail:readThe Amazon SP-API surfaces: on-demand reports, pricing, and the operation catalog (orders, inventory, catalog, finances, Data Kiosk).
ads:readThe Amazon Ads API surfaces: profiles, reporting v3, exports, entity lists, budget usage, recommendations.
brand_analytics:readBrand Analytics data (rides the retail surface today).
sql:queryCaller-authored SQL reach over your tenant database: POST /api/query, GET /api/tables, GET /api/table/<name>, and the equivalent MCP tools. Bundled by default when a service credential is minted with no explicit scope list; request it explicitly alongside the domain reads if you pick a specific scope set instead. See Warehouse SQL.
context:readBrand context docs: the manifest and individual doc content per brand.
timeline:readThe brand timeline: events, stakes, and the read-time Amazon Ads change projection.
intelligence:readMixShift Intelligence: the request catalog, running a request, and fetching an async run (/api/intelligence/* and the intelligence_* MCP tools). Granted to interactive sign-ins by default; tenant admins grant it to a service credential at /admin. See MixShift Intelligence.
timeline:writeAppend timeline events (stakes, comments, action outcomes) and corroborate stakes. Opt-in per credential, granted at mint time at /admin. Appends are attributed to the signed-in credential, never to a value in the request.
ads:writeAudited Ads mutations: bids, budgets, states, negatives, campaign creation. Opt-in per credential: tenant admins can grant it when creating a service credential at /admin; partner applications get it case by case from MixShift. Every write defaults to a dry-run preview and is audit-logged.
listings:writeAudited listing-content updates on existing SKUs: title, Item Highlights, bullets, description, keywords, the catalog-compliance fields Amazon asks for in listing warnings (ingredients, item form), and the nine product image slots. Every change previews against Amazon first and commits only against the exact approved change set. Tenant admins can grant it when creating a service credential at /admin. See Listing content writes.

Raw-SQL warehouse access (POST /api/query) requires the full read set plus sql:query; see Warehouse SQL below.

Amazon operations

Live calls against the two Amazon APIs, on the authorization your accounts already granted MixShift. The operation catalogs are self-describing: list them to see every callable operation and its parameters.

Guide: Work with live Amazon operations

EndpointScopeWhat it does
GET /api/amazon/merchantsretail:readMerchants you can act for (one row per account + marketplace).
POST /api/amazon/reports + poll/documentretail:readOn-demand SP-API reports; documents return as short-lived presigned URLs.
/api/amazon/pricing/*retail:readFeatured Offer Expected Price + Competitive Summary batches.
GET /api/amazon/spapi/operations
POST /api/amazon/spapi/call
retail:read
(+ listings:write for listing writes)
Catalog-driven SP-API operations: orders, FBA inventory, FBA inbound shipments, catalog items, fees, finances, product type definitions, Data Kiosk, and the guarded listing-content write. The operations listing documents every call.
GET /api/amazon/ads/profiles
GET /api/amazon/ads/operations
POST /api/amazon/ads/call
ads:read (+ ads:write for writes)Catalog-driven Amazon Ads operations: reporting v3, exports, entity lists, budget usage, recommendations, and audited writes.

Inbound shipments to FBA. The SP-API catalog covers the inbound side of FBA and not only the sellable side, so you can see stock on its way to Amazon rather than just stock that already arrived: shipment status by pipeline stage (working, shipped, in transit, receiving, closed), the line items on a date range of shipments, and expected versus received quantities per SKU on one shipment. That is what lets your own tooling attribute an out-of-stock to receiving latency instead of to demand. These operations are read-only and they run on the authorization your accounts already granted MixShift, so there is nothing extra to request from Amazon.

Two details save you a failed first call. Both of the search-style inbound calls take an explicit query type (the per-shipment item call is keyed by shipment id instead and takes none), and the shipment search additionally needs a status list or a shipment id list, including when you are querying by date range. And treat paging as load-bearing here: the per-shipment item response can return a continuation token next to a short item list, so the first page is not necessarily the shipment's whole manifest. The operations listing carries the required parameters and the paging rules for each call.

Which merchant: sellerId and legacySellerId

Every call above acts for one merchant, and there are two ways to name one. Getting this wrong is a common first-call failure, so it is worth thirty seconds up front.

ParameterTypeWhat it is
sellerIdstringAmazon's own merchant token, the A... / ENTITY... string. Selects a merchant on its own only when the token maps to a single row. A seller trading in several marketplaces needs marketplace alongside it.
legacySellerIdpositive integerThe MixShift seller id. The durable unique key for exactly one merchant row (one seller in one marketplace). Takes precedence over sellerId and marketplace. Prefer it whenever you have it.
marketplacestringOptional hint (US, CA, MX, ...) used only to narrow a sellerId that matches more than one row.

Both values come back from GET /api/amazon/merchants, one row per account and marketplace. GET /api/amazon/spapi/operations also returns this contract as a merchantIdentifiers object next to the operation list, so a program never has to hardcode it.

There is no merchantId parameter. If you passed one it was ignored, and the call behaved as if you had named no merchant at all, which succeeds only when your credential can act for exactly one.

The same distinction reaches the warehouse. Seller-scoped tables generally carry both an integer SellerID and a varchar AmazonSellerID, and the indexes are usually built on the integer. On spapi_settlement, for instance, the integer SellerID is indexed together with the posted date, while the Amazon token is indexed with the settlement id instead. Filtering by the token plus a date range leaves the date with no usable index and turns a sub-second query into a table walk. Filter on the integer SellerID unless a table says otherwise.

Creating a complete Sponsored Brands ad

Deployed 2026-08-26. Building a full SB v4 ad from nothing takes an ad group plus one ad-type-specific create, because Amazon exposes a separate v4 endpoint per creative type instead of one shared ad shape. Build in order: sb.create_campaigns, then sb.create_ad_groups, then the create below for the ad type you want.

OperationAd typeCreative bodyLanding page
sb.create_ad_groupsn/a{ campaignId, name, state }. No bid: bids live on the keywords and targets created inside the ad group.n/a
sb.create_product_collection_adsPRODUCT_COLLECTION{ asins, brandName, brandLogoAssetID, brandLogoCrop, headline, customImages }Store
sb.create_brand_video_adsBRAND_VIDEO{ asins, videoAssetIds, brandName, brandLogoAssetID, brandLogoCrop, headline }Store
sb.create_video_adsVIDEO{ asins, videoAssetIds } only. No brand fields: sending them is the most common validation failure on this type.Product detail page
sb.create_store_spotlight_adsSTORE_SPOTLIGHT{ brandName, brandLogoAssetID, brandLogoCrop, headline, subpages }. No top-level asins: the advertised ASINs ride inside subpages[], one entry per Store subpage.Store subpages

Two acceptance rules Amazon enforces that this catalog cannot pre-flight for you. First, every asset id (videoAssetIds, brandLogoAssetID, customImages[].assetId) must already exist in the Creative Asset Library: there is no upload API on this surface, so a new video or logo goes in through the Amazon console first, then assets.list_recent or an existing ad's creative gives you the id. Second, on the three Store-landing types, every advertised ASIN must already appear on that Store page or Amazon rejects the ad; reading an ad already serving in the destination ad group (sb.list_ads) is the best available way to confirm this before a fresh build, since there is no cataloged pre-flight read for it yet.

Dry run does not test any of this. A dry run checks request shape only (the array is present, the 200-item cap) and never reaches Amazon, so none of the creative or landing-page rules above are validated until you commit. Amazon validates per item, so commit one ad first and read its error result before sending the rest of a batch. And unless a user explicitly asked for the ad to go live immediately, create with state: "PAUSED": an ENABLED ad inside an enabled ad group and campaign starts serving, and billing, the moment the commit lands.

Creative Asset Library: assets.list_recent

Deployed 2026-08-28. POST /assets/search, profile-scoped, reads the newest page of a merchant's Creative Asset Library: asset ids, type, status, per-program eligibility, and file metadata.

Read it for what it actually is. It returns only the newest page (a few dozen rows) rather than the whole library, and despite its name, a filter in the body or query string is silently ignored: you get the identical page back with HTTP 200 whether or not you sent one, so never treat a result as filtered. Pagination is not currently usable: the response carries a token, but no way to spend it has been found. And the library is per-profile, not account-wide, so one merchant returning zero assets does not mean the account has none.

File metadata differs by asset type: a video asset populates resolutionWidth / resolutionHeight and durationInSeconds; an image asset populates width / height and aspectRatio. Use this operation to find the asset id of something just uploaded in the console, and to check which ad programs an asset already qualifies for (specCheckApprovedPrograms). It reads the library; it does not add to it.

DSP v1 reads

Deployed 2026-08-28. Six operations cover a DSP advertiser's campaigns, ad groups, and creatives, including where a creative is actually live: dsp.list_campaigns, dsp.list_ad_groups, dsp.list_ad_creatives, dsp.list_creative_associations, dsp.list_association_moderations, and dsp.list_eligible_creatives.

Discovery: a brand's DSP advertiser id comes from the advertising account surfaces (accounts.list_manager_accounts, linked accounts of type DSP_ADVERTISING_ACCOUNT), not from a Sponsored Ads profile id. These operations carry detailed pagination and taxonomy notes in the operation catalog itself; list GET /api/amazon/ads/operations for the full text before building against them.

MCP server

The same tool surface is exposed as an MCP server, so AI clients can use the warehouse and the Amazon surfaces directly. Two transports:

Guide: Connect AI tools over MCP (per-client setup for Claude, ChatGPT, Cursor, and Codex)

EndpointTransportUse with
POST /mcpStreamable HTTPCursor, Codex CLI and IDE, claude.ai, and any current MCP client.
GET /mcp/sseHTTP + SSE (legacy)Older MCP clients. Kept for compatibility.

Authenticate with Authorization: Bearer <access_token> on every request. Unattended setups mint from a service credential via client_credentials (see Getting access), while interactive users can manually mint a token at /login?mode=direct. Client setup examples for Cursor and Codex live in the auth setup guide. ChatGPT and claude.ai connectors register automatically: add a connector with this server URL and sign in with your MixShift account when the OAuth window opens.

Tools cover the warehouse (legacy_query, table listing, describe), the Amazon SP-API and Ads surfaces (amazon_*), and the org-brain services: timeline_list, timeline_add_event, timeline_corroborate, context_list_docs, and context_get_doc; and MixShift Intelligence: intelligence_catalog, intelligence_run, intelligence_poll, and intelligence_get. A credential may connect when it holds the scope for at least one tool, and each tool checks its own scope on every call: the SQL tools need the full read set plus sql:query, the timeline tools need timeline:read (writes also need timeline:write), the context tools need context:read, and the intelligence tools need intelligence:read. A tool you lack the scope for returns a clear insufficient-scope result rather than data.

MixShift Intelligence

Finished reads served as data, not rows to interpret. MixShift Intelligence is a catalog of named requests; each returns a JSON result your report, agent, or app can quote directly, with the arithmetic checked server-side. Open to every tenant by default and included in the subscription; every run is metered by request id so usage reports roll up per service.

The catalog is organized by service. Each request id carries its service prefix, and ids are append-only: a request never changes meaning, and a new capability is a new id.

ServicePrefixWhat it answers
AttributionINS-Why a number moved, decomposed into contributions that sum exactly to the change, with the caveats the engine raises on its own (dark periods, promotions, surge and matched windows, restatements, end-of-life exclusions). Five requests today, listed below.
ForecastingFCT-Reserved. Expected baselines join the same catalog, gate, and metering when they ship; nothing to call yet.
RequestWhat it answers
INS-OPS-BRIDGE-01Why ordered sales moved between two periods, item by item: traffic, conversion, price, and mix, bridged to the total.
INS-ADS-BRIDGE-01Why advertising results moved, at campaign, ad group, keyword or target, search term, or ASIN grain.
INS-DUO-01Both bridges on the same period, joined: TACOS, attributed share, paid pressure, and ad AOV against ASP.
INS-LOSTSALES-01Sales lost to stock-outs, sellable inventory, and weeks of cover.
INS-MONTHLY-01The monthly read: month over month and year over year, retail and advertising, in one bundle. The substrate of the Monthly Report Max plugin skill.
EndpointWhat it does
GET /api/intelligence/idsThe deployed catalog: id, version, content revision, service, and a one-line purpose.
POST /api/intelligence/run{ "id", "params" }. Params name the merchant (sellerId plus marketplaceId, or brand) and the period. Add "evidence": true for the What We Know statements. On the retail-backed requests a large account answers account_too_large_use_async; retry with "async": true and poll. Each account may hold two computing requests at a time; a third answers busy.
GET /api/intelligence/runs/<runId>Poll or fetch an async run you started.

Every result stamps the engine and evidence versions it was computed with, and the public GET /health reports the versions currently serving. Results carry a limitations list; read it before quoting figures. The same requests are available as the intelligence_* MCP tools and through the plugin command line (mixshift intelligence). Intelligence is not part of the /v1 partner contract: the result shape is versioned by the engine, not by the /v1 conventions.

Errors follow the { ok:false, kind, friendly } shape: not_enrolled (403) means MixShift switched Intelligence off for this account; bad_params (400); merchant_not_resolved (422); no_data_for_period (404); busy (429) when the service or this account is at its concurrency cap, retry shortly.

Partner data plane: /v1

Guide: Use the /v1 data endpoints

The /v1 endpoints are the contract-stable partner surface: snake_case params, cursor pagination, explicit currency on money fields, and date windows anchored to the latest fully complete Amazon data day. Domain errors use { "error": { "code", "message" } } with a small stable code vocabulary (account_required, account_not_found, validation_error, rate_limited, ...).

EndpointScopeWhat it does
GET /v1/meaccount:readWho this token is: tenant id, person label, granted scopes.
GET /v1/accountsaccount:readEvery account/brand the tenant exposes, with per-domain auth status (retail, ads) and currency. Pass an account's id as account_id on data endpoints.
GET /v1/ads/account-summary
GET /v1/ads/account-pacing
GET /v1/ads/campaign-performance
GET /v1/ads/search-terms
GET /v1/ads/asin-performance
ads:readWindowed ads performance: account rollups, MTD pacing, and per-campaign / search-term / ASIN aggregates with cursor pagination.
GET /v1/retail/sales
GET /v1/retail/traffic
retail:readDaily retail series (sales, orders, units, sessions, page views) with window totals.
GET /v1/brand-analytics/search-query-performancebrand_analytics:readSearch Query Performance aggregated by query (weekly grain). Domains without data return 200 with an availability block.

The brand memory endpoints are /v1 too and follow the same conventions; they get their own section below.

Brand memory

The data endpoints above tell you what the numbers did. Brand memory is the part of the platform that knows why, and it is reachable with the same credential. Two halves: the brand context your team maintains (what a brand sells, how the account is structured, what normal looks like) and the change log of what actually happened to it, including the advertising changes committed through MixShift.

A stake is the change-log entry that says "this is a thing that changes how the numbers should be read": it carries a category (launch, promotional window, stockout, price test, media spike, brand migration, and more), a time window, and a required plain-language interpretation. Stakes can then be corroborated by someone else, so agreement is earned rather than asserted. This is what lets your own dashboard explain a spend spike instead of just drawing it.

Guide: Work with brand memory

EndpointScopeWhat it does
GET /v1/timelinetimeline:readThe brand timeline: native events plus the read-time Amazon Ads change projection, filterable by brand, family, kind, date window, and the stake facets (category, source, status, affects, tag, interval overlap, future). Cursor pagination via next_cursor.
POST /v1/timeline/eventtimeline:read + timeline:writeAppend one event: a structural stake (category plus interpretation), a comment, or an action outcome. Attributed to your credential. knowledge.* and server-reserved kinds are rejected.
POST /v1/timeline/event/<id>/corroboratetimeline:read + timeline:writeCorroborate a stake: append a corroboration and refresh the stake status and close date in one step.
GET /v1/context/docscontext:readThe brand context manifest: every doc per brand with its type, revision, and content hash.
GET /v1/context/doccontext:readOne context doc by brand and type (corpus docs also take a name), with content and revision.

These return the partner envelope like the rest of /v1: a bare success body, or { "error": { "code", "message" } } on failure. timeline:read and context:read come with a read-only credential; timeline:write is opt-in at mint time. Brand context is read-only on /v1: docs are authored through the MixShift plugin, so your integration consumes the brand profile rather than maintaining it. The same capabilities are available as MCP tools (see MCP server).

Warehouse SQL: /api/query

Guide: Query the warehouse (includes migrating off direct MySQL access)

Read your warehouse directly with SQL when the /v1 endpoints do not cover what you need. Your credential resolves server-side to your own tenant database (the standard MixShift schema) over a read-only connection, so a query only ever sees your own data.

EndpointScopeWhat it does
POST /api/queryfull read set + sql:queryRun read-only SQL. Body { sql, params?, queryTimeoutMs? }; params is a positional array (? placeholders) or a named object. Returns { ok: true, rows, rowCount, durationMs } or { ok: false, kind, friendly }.
GET /api/tablesfull read set + sql:queryList the tables in your database.
GET /api/table/<name>full read set + sql:queryColumns for one table (a SHOW COLUMNS result).

Raw SQL spans every data domain, so this surface needs the full read set (account:read, ads:read, retail:read, brand_analytics:read) plus sql:query, the separate grant for caller-authored reach. Both are included by default when a service credential is minted with no explicit scope list; if you request a specific scope set instead, add sql:query explicitly, or the four domain reads alone will not reach this endpoint. Per-call limits: queryTimeoutMs defaults to 60s and caps at 120s; a response caps at 50,000 rows (too_many_rows) and 10 MB (response_too_large), so paginate large extracts with LIMIT / OFFSET or chunk by date. Raw SQL couples you to the warehouse schema; prefer /v1 when you want a versioned contract.

Named queries: /api/named-query

MixShift keeps a catalog of vetted warehouse queries on the server, so a client can ask for a result by id instead of carrying the SQL itself. The query text can be corrected or tuned without you shipping anything, and every result tells you which version answered it. The catalog ids are also published unauthenticated at /.well-known/mixshift-query-pack, so a client can check which ids exist before it holds a token.

EndpointScopeWhat it does
POST /api/named-queryfull read setRun one catalog entry. Body { id, params?, sellerIds?, queryTimeoutMs? }. Returns the same envelope as /api/query plus revision (a content hash of the SQL that ran, so a result stays attributable to exact query text) and applied_params. An id this deployment does not carry returns 404 with kind: "unknown_query".
GET /api/named-query/idsfull read setThe deployed catalog: every entry id with its current revision. Diff this against the ids you depend on before you ship, rather than discovering skew through a failed call.

Check applied_params before you treat a result as filtered. Entry parameter schemas are deliberately tolerant: a parameter the deployed entry does not declare is dropped and the query still runs, which is what lets your integration and this service deploy on separate schedules. The cost of that tolerance is that a filter we do not recognise is ignored silently and you still get ok: true. So every successful response carries applied_params, the sorted names of the parameters this execution actually bound. If a parameter you sent is missing from that list, it was dropped and your rows are unfiltered on that dimension. Names only, never values. Failed responses do not carry it: nothing ran, and the error kind already tells you why.

Sub-brand labels. Some sellers run several distinct brands under a single Amazon account, separated only by a brand label on the retail and ads records. The catalog carries discovery entries (sbd-01 retail, sbd-02 ads, sbd-03 vendor, sbd-04 cross-side match rate) that report the distinct labels per side, how much of the catalog or ad spend carries no label yet, and how well the retail and ads labels agree with each other. The entries behind brand context accept optional retail_brand_label and ads_brand_label filters. Both are optional and both default to no filter, which is exactly why applied_params matters here: it is how you confirm a label filter was honored rather than dropped, and that is the difference between one brand's numbers and the whole account's.

Sub-brand economics. Three more catalog entries rank labels by dollars instead of by count: sbd-05 retail (mws_items.Brand), sbd-06 ads (campaign.Brand), and sbd-07 vendor (vendor_items.CustomBrand). Each returns trailing 365-day revenue or ad spend per label, a trailing 90-day slice of it, and the last date the label sold or spent, one row per label per seller. Every label is included, including ones you may go on to treat as dormant, so totals built from these entries still reconcile against Seller Central, Amazon Ads, or Vendor Central. None take entry-specific params beyond the sellerIds every named-query call already sends.

Write safety model

Two write surfaces, two shapes. Both audit every change and both put a preview in front of it.

Amazon Ads writes (ads:write)

Listing content writes (listings:write)

Listing writes use a stronger two-call contract on listings.patch_listings_item, because the content is customer facing and Amazon applies parts of it asynchronously.

Images apply asynchronously, and that changes how you verify. An image value is a list of { marketplace_id, media_location } (no language_tag) where media_location is an https:// or s3:// URL Amazon can reach. Amazon accepts the patch, then fetches your URL and re-hosts the image itself, so a later read returns an m.media-amazon.com URL and never the URL you sent, and a fetch failure appears as a listing issue minutes later rather than in the patch response. Do not verify an image by comparing your URL to what a read returns, because it will never match. Verify that the slot's URL changed and that the listing's issues stayed clean.

ACCEPTED means Amazon accepted the submission for processing. It does not mean the change is published. Confirm publication by reading the listing (listings.get_listings_item with includedData=summaries,attributes,issues) and the shared catalog record (catalog.get_item). This surface deliberately does not poll Amazon on your behalf: your workflow decides how long to wait and what to do next.

A failed write does not always mean nothing happened. This is the part of the contract most worth building around, because getting it wrong duplicates a customer-facing change. Three failure kinds are ambiguous by nature, since none of them proves Amazon rejected your change before applying it: upstream_unavailable (Amazon's own server failed while processing the request), throttled (shed, but a throttle response can arrive after acceptance), and host_unreachable (no response at all, so the request may still have landed). An unclassified transport failure counts as ambiguous too, because the safe reading of "we do not know" is that it might have applied. Every other kind is a definitive Amazon-side rejection, and there "nothing was applied" is safe to assume. On the ambiguous three, verify before you act and never blindly re-send the mutation, or you can apply the same change twice.

You are not left to work that out alone. MixShift auto-retries a read through an Amazon server error and deliberately does not do the same for a write, because that retry is exactly the move that could double-apply. Instead the affected SKU comes back UNCERTAIN rather than failed, and the service performs one read-back against Amazon, comparing what is live now against both the value you asked for and the value it captured before the write, then reports per attribute what it could confirm. Everything confirmed reads back as ACCEPTED; anything it cannot confirm stays UNCERTAIN with a detail that says so; and a change set where some content did land never terminates as though nothing changed, so a partial outcome stays visible.

For text attributes, an inconclusive read-back leaves the change set resumable, and the safe next step is to re-run the commit with the same changeSetId: it re-reads and reconciles the same attempt instead of sending a second one. Image slots settle in that single pass rather than staying open, because Amazon fetches and re-hosts those asynchronously and a later read of the listing is the only real confirmation available. Either way, the rule for your integration is the same: on an ambiguous outcome, read before you write again.

Not yet available from the bundled mixshift CLI: use the REST endpoint or the amazon_spapi_call MCP tool. CLI support is coming.

Errors and limits

Guide: Limits and guardrails

Failures share one envelope: { "ok": false, "kind": "...", "friendly": "...", "message": "..." }. Branch on kind (insufficient_scope, reauth_required, throttled, merchant_not_found, ...), not HTTP status. friendly is a short summary safe to show a user; message carries the specific reason, so read it when you need to fix something. A rejected listing patch, for example, names the SKU, the attribute, the item index, and what was wrong with it.

Whose problem is it, and should you retry?

The single most useful thing the envelope tells you is whether the failure is yours, ours, or Amazon's. Retrying the wrong class of failure is the most common way to waste an afternoon, so the status code and the kind are chosen to answer that directly.

You getWhat it meansDo
400 bad_requestAmazon rejected your request. Your parameters. Carries Amazon's own status and amazon_error_code (InvalidInput, InvalidParameterValue, ...) plus its full response under responsePayload.Fix the request and send it again. Re-sending it unchanged will fail identically every time, so a blind retry loop never terminates. If our own docs led you here, tell us (below).
403 restricted_reportAmazon refused access. Usually a role or restricted-data grant, sometimes an authorization that predates a newer role.Do not retry. Re-authorize the merchant, or use a different operation.
409 reauth_requiredThe merchant's Amazon authorization was lost.Re-connect the account, then retry.
429 throttledRate limited, ours or Amazon's.Back off and retry. Honour Retry-After.
502 upstream_unavailable / host_unreachableAmazon's side failed, or we could not reach it. We already retried reads.Retry later. For writes, check whether it applied first: a 5xx does not prove Amazon rejected it before acting.
500 unknownGenuinely unclassified, and now rare. Treat it as ours.Retry once; if it persists, report it.

Before this, an Amazon 400 came back as a 500. If you built retry logic against that, you can simplify it: a 4xx from us now means stop, and a 5xx means it may be worth trying again.

Reporting an error (and why it is worth your 30 seconds)

If a call fails and our own documentation told you to make it that way, that is a bug on our side, not yours. The operation catalog is hand-maintained: a required parameter Amazon enforces but does not document, a casing rule, a combination that is only valid together. When our notes are wrong, every caller hits the same wall.

We see that a call failed and which operation. We do not see your parameters. So the fastest fix starts with you sending three things:

Send it to support@mixshift.io. Plugin users can run mixshift feedback instead, which attaches the context automatically. A recurring amazon_error_code on one operation is how a documentation gap gets found and fixed, and it usually turns into a catalog note the next caller never trips over.

Questions: support@mixshift.io · mcp.mixshift.io