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
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.
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
GET /oauth/authorize— authorization-code flow with PKCE. Tenants sign in and consent.POST /oauth/token— exchange codes, refresh tokens, or mint machine tokens viaclient_credentials.POST /oauth/revoke— revoke a refresh token when a user disconnects (RFC 7009).- Discovery:
/.well-known/oauth-authorization-server.
Send the access token as Authorization: Bearer <token> on every call.
Scopes
| Scope | Grants |
|---|---|
account:read | Account and merchant metadata. |
retail:read | The Amazon SP-API surfaces: on-demand reports, pricing, and the operation catalog (orders, inventory, catalog, finances, Data Kiosk). |
ads:read | The Amazon Ads API surfaces: profiles, reporting v3, exports, entity lists, budget usage, recommendations. |
brand_analytics:read | Brand Analytics data (rides the retail surface today). |
sql:query | Caller-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:read | Brand context docs: the manifest and individual doc content per brand. |
timeline:read | The brand timeline: events, stakes, and the read-time Amazon Ads change projection. |
intelligence:read | MixShift 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:write | Append 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:write | Audited 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:write | Audited 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
| Endpoint | Scope | What it does |
|---|---|---|
GET /api/amazon/merchants | retail:read | Merchants you can act for (one row per account + marketplace). |
POST /api/amazon/reports + poll/document | retail:read | On-demand SP-API reports; documents return as short-lived presigned URLs. |
/api/amazon/pricing/* | retail:read | Featured Offer Expected Price + Competitive Summary batches. |
GET /api/amazon/spapi/operationsPOST /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/profilesGET /api/amazon/ads/operationsPOST /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.
| Parameter | Type | What it is |
|---|---|---|
sellerId | string | Amazon'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. |
legacySellerId | positive integer | The 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. |
marketplace | string | Optional 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.
| Operation | Ad type | Creative body | Landing page |
|---|---|---|---|
sb.create_ad_groups | n/a | { campaignId, name, state }. No bid: bids live on the keywords and targets created inside the ad group. | n/a |
sb.create_product_collection_ads | PRODUCT_COLLECTION | { asins, brandName, brandLogoAssetID, brandLogoCrop, headline, customImages } | Store |
sb.create_brand_video_ads | BRAND_VIDEO | { asins, videoAssetIds, brandName, brandLogoAssetID, brandLogoCrop, headline } | Store |
sb.create_video_ads | VIDEO | { asins, videoAssetIds } only. No brand fields: sending them is the most common validation failure on this type. | Product detail page |
sb.create_store_spotlight_ads | STORE_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)
| Endpoint | Transport | Use with |
|---|---|---|
POST /mcp | Streamable HTTP | Cursor, Codex CLI and IDE, claude.ai, and any current MCP client. |
GET /mcp/sse | HTTP + 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.
| Service | Prefix | What it answers |
|---|---|---|
| Attribution | INS- | 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. |
| Forecasting | FCT- | Reserved. Expected baselines join the same catalog, gate, and metering when they ship; nothing to call yet. |
| Request | What it answers |
|---|---|
INS-OPS-BRIDGE-01 | Why ordered sales moved between two periods, item by item: traffic, conversion, price, and mix, bridged to the total. |
INS-ADS-BRIDGE-01 | Why advertising results moved, at campaign, ad group, keyword or target, search term, or ASIN grain. |
INS-DUO-01 | Both bridges on the same period, joined: TACOS, attributed share, paid pressure, and ad AOV against ASP. |
INS-LOSTSALES-01 | Sales lost to stock-outs, sellable inventory, and weeks of cover. |
INS-MONTHLY-01 | The monthly read: month over month and year over year, retail and advertising, in one bundle. The substrate of the Monthly Report Max plugin skill. |
| Endpoint | What it does |
|---|---|
GET /api/intelligence/ids | The 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, ...).
| Endpoint | Scope | What it does |
|---|---|---|
GET /v1/me | account:read | Who this token is: tenant id, person label, granted scopes. |
GET /v1/accounts | account:read | Every 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-summaryGET /v1/ads/account-pacingGET /v1/ads/campaign-performanceGET /v1/ads/search-termsGET /v1/ads/asin-performance | ads:read | Windowed ads performance: account rollups, MTD pacing, and per-campaign / search-term / ASIN aggregates with cursor pagination. |
GET /v1/retail/salesGET /v1/retail/traffic | retail:read | Daily retail series (sales, orders, units, sessions, page views) with window totals. |
GET /v1/brand-analytics/search-query-performance | brand_analytics:read | Search 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
| Endpoint | Scope | What it does |
|---|---|---|
GET /v1/timeline | timeline:read | The 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/event | timeline:read + timeline:write | Append 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>/corroborate | timeline:read + timeline:write | Corroborate a stake: append a corroboration and refresh the stake status and close date in one step. |
GET /v1/context/docs | context:read | The brand context manifest: every doc per brand with its type, revision, and content hash. |
GET /v1/context/doc | context:read | One 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.
| Endpoint | Scope | What it does |
|---|---|---|
POST /api/query | full read set + sql:query | Run 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/tables | full read set + sql:query | List the tables in your database. |
GET /api/table/<name> | full read set + sql:query | Columns 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.
| Endpoint | Scope | What it does |
|---|---|---|
POST /api/named-query | full read set | Run 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/ids | full read set | The 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)
- Write operations default to a dry run: the call validates, snapshots current state, logs an audit row, and returns a preview without touching Amazon.
- Only an explicit
"dryRun": falsecommits. Show your user the preview first. - Commits are capped at 200 items per call and audit-logged with the pre-write snapshot.
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.
- Preview first. Send
"mode": "preview"with achangeSetof up to 10 SKUs. The service snapshots the current values of exactly the attributes you are changing, runs Amazon's real validation without mutating anything (mutatedAmazon: false), and returns Amazon's issues, achangeSetId, and achangeSetHashthat expires in 24 hours. - Commit against the approval, not against content.
"mode": "commit"takes only thechangeSetIdandapprovedChangeSetHash. The service replays the stored, previewed payload, so a commit cannot apply anything your user did not see. A mismatched, expired, or already-used approval is refused. - Drift fails closed. If a value you are replacing changed on Amazon between the
preview and the commit, that SKU is refused as
STALErather than overwritten. Re-preview to pick up the new baseline. - Only content attributes, whole-attribute only. Three classes are writable: text (item name, Item Highlights, bullets, description, keywords), product data (ingredients, item form), and images (main plus the eight other image slots). Identifiers, brand, compliance and safety attributes, variations, price, quantity, fulfillment, and deletes are all refused.
- Per-SKU results. A multi-SKU change set is reviewed as one set and reported per SKU, so a partial outcome is visible instead of averaged away.
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.
- Rate limit: 240 requests/minute per credential (HTTP 429 +
Retry-Afterwhen exceeded). - Access tokens are short-lived: 12 hours for a service credential's
client_credentialsmint and for a partner API session (the authorization-code token a partner app holds on a tenant's behalf), 24 hours for interactive sign-in. Refresh viaPOST /oauth/tokenbefore expiry. - Amazon-side throttles surface as
kind: "throttled"; back off and retry.
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 get | What it means | Do |
|---|---|---|
400 bad_request | Amazon 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_report | Amazon 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_required | The merchant's Amazon authorization was lost. | Re-connect the account, then retry. |
429 throttled | Rate limited, ours or Amazon's. | Back off and retry. Honour Retry-After. |
502 upstream_unavailable /
host_unreachable | Amazon'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 unknown | Genuinely 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:
- the operation id (e.g.
fulfillment_inbound.get_shipments), - the
amazon_error_codeandmessagefrom the envelope, - the parameters you sent, minus anything you consider sensitive.
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