Developers
API endpoint reference
Generated from the API contract itself: every endpoint, every field, every code.
Every endpoint MerchDash’s public API answers, with its parameters, its response fields, and what it refuses. This page is generated from the API’s own contract, so it cannot drift from what the server actually does. For how to get a key and how the review model works, start at Public API.
All endpoints sit under https://app.merchdash.net/api/v1/ and take Authorization: Bearer md_live_…. Collections answer { data, nextCursor }; single resources answer { data }. Reads are served from MerchDash’s synced archive rather than live from Amazon.
Limits
| Limit | Value |
|---|---|
| Rows per page, default / maximum | 100 / 500 |
| Metric window, default / maximum | 30 days ending 2 days ago / 365 days |
Changes in one POST /api/v1/changes | 500 |
Campaigns + ad groups in one POST /api/v1/structures | 2000 |
ASINs + keywords + targets in one POST /api/v1/structures | 20000 |
Request body, POST /api/v1/changes and /structures | 24 MB, every other endpoint accepts 1 MB |
| Un-answered proposals held per marketplace | 75 |
Rate limits are per key and per account, and are described on the Public API page.
Every endpoint
| Endpoint | Scope | What it answers |
|---|---|---|
GET /api/v1/profiles | read | Every marketplace this account owns and has synced. |
GET /api/v1/campaigns | read | Campaigns with their metrics for a window. |
GET /api/v1/portfolios | read | Portfolios with their budget caps. |
GET /api/v1/videos | read | The Sponsored Brands videos in the account's Creative Asset Library, with their names. |
POST /api/v1/videos/uploads | write | Start uploading a Sponsored Brands video into the account's Creative Asset Library: returns the URL to PUT the file to. |
POST /api/v1/videos/uploads/:uploadId/complete | write | Register an uploaded video in the Creative Asset Library, step 3 after PUTting the file. |
GET /api/v1/images | read | The logos and custom images in an account's Creative Asset Library, by name. |
POST /api/v1/images/uploads | write | Start uploading a Sponsored Brands logo or custom image: returns the URL to PUT the file to. |
POST /api/v1/images/uploads/:uploadId/complete | write | Register an uploaded logo or image in the Creative Asset Library, step 3 after PUTting the file. |
GET /api/v1/stores | read | The account's Amazon Stores and their pages, the landing pages a Sponsored Brands ad may point to. |
GET /api/v1/ad-groups | read | Ad groups with their metrics for a window. |
GET /api/v1/keywords | read | Keywords with their metrics for a window. |
GET /api/v1/targets | read | Targeting clauses with their metrics for a window. |
GET /api/v1/asins | read | Advertising performance aggregated per ASIN. |
GET /api/v1/product-ads | read | Product ads (an ASIN/SKU in one ad group) with their metrics for a window. |
GET /api/v1/sb-ads | read | Sponsored Brands ads (creatives) with their metrics for a window. |
GET /api/v1/sb-purchased-products | read | What shoppers bought after Sponsored Brands ads, per purchased ASIN, for a window. |
GET /api/v1/negative-keywords | read | Negative keywords at campaign and ad-group scope. |
GET /api/v1/negative-targets | read | Negative product targets (excluded ASINs and brands) at campaign and ad-group scope. |
GET /api/v1/audiences | read | The audiences a marketplace's Sponsored Products campaigns can carry a bid adjustment for. |
GET /api/v1/placements | read | Metrics per campaign and placement for a window, Sponsored Products and Sponsored Brands. |
POST /api/v1/recommendations/bids | read | Amazon's suggested bids for keywords and auto-targeting clauses in an existing Sponsored Products ad group. |
POST /api/v1/recommendations/sb-bids | read | Amazon's suggested bids for keywords and product targets in a Sponsored Brands campaign. |
POST /api/v1/recommendations/keywords | read | Amazon's ranked keyword suggestions, with suggested bids, for an ad group or a set of ASINs. |
POST /api/v1/recommendations/categories | read | The product categories Amazon recommends targeting for a set of ASINs. |
POST /api/v1/recommendations/products | read | The products Amazon suggests targeting for a set of your advertised ASINs (Sponsored Products product targeting). |
POST /api/v1/recommendations/budgets | read | Amazon's suggested daily budget for campaigns, with what running out of budget cost them (Sponsored Products and Sponsored Brands). |
POST /api/v1/negative-brands/search | read | Find brands by keyword, Amazon's ids for the brands a Sponsored Products negative can exclude. |
POST /api/v1/eligibility | read | Whether ASINs can be advertised, and Amazon's reasons when they cannot. |
GET /api/v1/search-terms | read | Shopper search terms with their metrics for a window. |
GET /api/v1/metrics/daily | read | Account totals for each day in a window. |
GET /api/v1/sync/status | read | How current the synced archive is for a marketplace. |
POST /api/v1/sync/start | read | Sync a marketplace now, the dashboard's Sync button. |
GET /api/v1/runs | read | Automation runs for a marketplace, newest activity first. |
GET /api/v1/runs/:runId | read | One run, the polling target for the runId a proposal returned. |
GET /api/v1/runs/:runId/changes | read | The individual changes a run proposed, and what became of each. |
GET /api/v1/changes | read | Change history: what MerchDash wrote to Amazon, for one entity or the whole marketplace. |
POST /api/v1/changes | write | Propose a batch of bid, state, negative, campaign-setting and portfolio changes, for a human to approve, or to apply at once. |
POST /api/v1/structures | write | Propose new campaigns, ad groups, product ads, keywords and targeting clauses for a human to approve. |
GET /api/v1/rules | read | The marketplace's automation rules. |
GET /api/v1/rules/:ruleId | read | One automation rule. |
POST /api/v1/rules | automation | Create an automation rule. |
POST /api/v1/rules/copy | automation | Copy saved rules into other marketplaces of the same account. |
PATCH /api/v1/rules/:ruleId | automation | Edit an automation rule: its script, name, schedule, mode, category, season, or whether it is on. |
DELETE /api/v1/rules/:ruleId | automation | Delete an automation rule. |
POST /api/v1/rules/:ruleId/run | automation | Run an automation rule now, the same as pressing Run on the Automation page. |
POST /api/v1/rules/:ruleId/run/cancel | automation | Take a rule's run out of the queue before it starts. |
GET /api/v1/rules/:ruleId/run | read | The progress and outcome of the rule's latest manual run. |
POST /api/v1/rules/validate | automation | Check a rule script without saving or running it. |
POST /api/v1/rules/preview | automation | Dry-run a script: what it would change right now, without saving it and without writing anything. |
GET /api/v1/rules/preview/:jobId | read | The progress and result of a dry run. |
POST /api/v1/rules/preview/:jobId/cancel | automation | Stop a dry run that is still running. |
GET /api/v1/categories | read | The marketplace's rule categories. |
POST /api/v1/categories | automation | Create a rule category. |
PATCH /api/v1/categories/:categoryId | automation | Rename a rule category, move it into another one, or switch it on or off. |
DELETE /api/v1/categories/:categoryId | automation | Delete a rule category. |
GET /api/v1/runs/:runId/log | read | A run's log: what its rule wrote with log(), and MerchDash's own notes. |
POST /api/v1/runs/:runId/apply | automation | Approve a pending review and write its changes to Amazon, the same as pressing Apply in the review queue. |
POST /api/v1/runs/:runId/retry-failed | automation | Send a finished run's failed changes to Amazon again, the Retry button in History. |
POST /api/v1/runs/:runId/discard | automation | Turn down a pending review, the same as pressing Discard in the review queue. Nothing is written to Amazon. |
PATCH /api/v1/runs/:runId/changes/:changeId | automation | Change the bid or budget a pending change proposes, before approving it, the review table's own edit. |
GET /api/v1/profiles
Every marketplace this account owns and has synced.
Not "the marketplaces currently ticked in the switcher", the switcher is a UI preference. This is every advertising profile the account owns and has synced at least once, which is exactly the set of profileIds the other endpoints accept.
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The Amazon advertising profile id. |
accountName | stringnullable | Advertiser account name from the last Amazon discovery. Null if MerchDash has not discovered it. |
countryCode | stringnullable | ISO country of the marketplace, when known. |
marketplaceId | stringnullable | Amazon marketplace id, when known. |
region | stringnullable | Amazon Ads API region (NA / EU / FE) this profile is read through. |
lastSyncAt | timestampnullable | When a sync last completed for this marketplace. Null until the first one finishes. |
GET /api/v1/campaigns
Campaigns with their metrics for a window.
One row per campaign in the marketplace's snapshot, including campaigns that delivered nothing in the window, their metrics are zero rather than absent. Archived campaigns are included; every row carries state, so filter on it after reading.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only this campaign. |
portfolioId | stringoptional | Only the campaigns in this portfolio (ids from GET /api/v1/portfolios). |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then campaignId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
adFormat | stringnullable | Sponsored Brands ad format of the row's ads: PRODUCT_COLLECTION, STORE_SPOTLIGHT, VIDEO or BRAND_VIDEO as Amazon names it, or MIXED when they differ (a campaign answers for all of its ads, a keyword or target for its ad group's). Null for Sponsored Products and Sponsored Display, and for a Sponsored Brands row whose ads have not been synced. Open set: Amazon may add formats. |
campaignId | string | Amazon campaign id. Always a string, never a number, ids exceed the safe integer range. |
name | stringnullable | |
targetingType | stringnullable | MANUAL or AUTO for Sponsored Products. |
budget | numbernullable | Daily budget in the marketplace currency. |
budgetType | stringnullable | |
suggestedBudget | numbernullable | Amazon's suggested daily budget as of the last sync, for an enabled Sponsored Products or Sponsored Brands campaign. Null when Amazon gave none, and for a campaign that is not enabled (the sync does not refresh it). POST /api/v1/recommendations/budgets asks Amazon now, with the missed-sales estimate. |
percentTimeInBudget | numbernullable | Amazon's "Avg Time In Budget" over the campaign's last 7 delivery days, 0–1, as of the last sync. Null when Amazon's window is older than 14 days or it has no data, and for a campaign that is not enabled. |
biddingStrategy | stringnullable | |
placementTopPct | numbernullable | Sponsored Products placement bid adjustment for top of search, in percent. Null means none (0%), and always null for SB/SD. Set with the set_placement_bid operation of POST /api/v1/changes. |
placementProductPagePct | numbernullable | The same for product pages. |
placementRestOfSearchPct | numbernullable | The same for the rest of search. |
audienceBids | arraynullable | Sponsored Products audience bid adjustments, as last synced: [{ audienceId, name, percent }] (name is null until the audience's name has been read). [] means none; null means not known, and is always null for SB/SD. Set with the set_audience_bid operation of POST /api/v1/changes; list the audiences a marketplace offers with GET /api/v1/audiences. |
portfolioId | stringnullable | |
portfolioName | stringnullable | |
startDate | stringnullable | |
endDate | stringnullable | |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/portfolios
Portfolios with their budget caps.
One row per portfolio Amazon lists for the marketplace, including portfolios that hold no campaigns. No metric window: a portfolio has no report of its own, sum its campaigns from GET /api/v1/campaigns, which carry portfolioId. The budget cap is read as of the last sync; change it with the set_portfolio_budget operation of POST /api/v1/changes.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then portfolioId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
portfolioId | string | Amazon portfolio id, as a string. |
name | stringnullable | |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. |
budgetPolicy | stringnullable | NO_CAP, MONTHLY_RECURRING (the cap resets every calendar month) or DATE_RANGE (one total between the start and end date). Null when the portfolio has not been synced yet. |
budgetAmount | numbernullable | The cap, in budgetCurrency. Null with NO_CAP. |
budgetCurrency | stringnullable | |
budgetStartDate | stringnullable | YYYY-MM-DD. The day the cap starts to apply. |
budgetEndDate | stringnullable | YYYY-MM-DD. After it, a DATE_RANGE cap stops the portfolio's campaigns. Null means no end date. |
inBudget | booleannullable | Amazon's own flag: false once the portfolio has spent its cap. Null when Amazon did not say. |
campaignCount | integer | Campaigns in this portfolio in MerchDash's snapshot, archived ones included. |
GET /api/v1/videos
The Sponsored Brands videos in the account's Creative Asset Library, with their names.
One row per video in Amazon's Creative Asset Library for the marketplace. Archived and deleted videos are left out. The library is copied during a sync, at most once a day, and only for a marketplace that runs Sponsored Brands ads; a video uploaded through MerchDash is listed straight away. name is what a rule's addVideoAd(video: "…") looks a video up by. A freshly uploaded video reads PROCESSING until refresh=true asks Amazon again (a rule run checks for itself).
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
refresh | enumtrue · falseoptional | Ask Amazon for the current status of up to 5 videos still processing, per Amazon account, before answering; spends one request of the key's write budget, like a live read. Defaults to false. Use it to wait for ready after an upload. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then assetId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
assetId | string | Amazon's asset id, without the :version_… suffix. Every version of one video is one row. |
version | stringnullable | The newest version MerchDash has seen, e.g. version_v1. |
name | stringnullable | The video's name in the asset library. |
status | stringnullable | Amazon's status for the video, upper-cased, e.g. ACTIVE or PROCESSING. |
ready | boolean | True when status is ACTIVE: Amazon has finished processing the video and an ad can use it. |
durationSeconds | numbernullable | Length of the video in seconds, when Amazon reported it. |
POST /api/v1/videos/uploads
Start uploading a Sponsored Brands video into the account's Creative Asset Library: returns the URL to PUT the file to.
Step 1 of 3. MerchDash asks Amazon for a pre-signed upload URL; step 2 is yours: PUT the file's bytes to uploadUrl (with the contentType given, no Authorization header, the URL carries its own signature) before expiresAt; step 3 is POST /api/v1/videos/uploads/:uploadId/complete. The file goes straight to Amazon's storage and never through MerchDash. Nothing is in the library until step 3. Once registered, a rule finds the video by name: ad_group.addVideoAd(video: "<name>", asin: "B0…") builds a Sponsored Brands video ad from it. An assistant without a way to send a file (no shell) cannot do step 2; the owner can upload in Amazon Ads instead, which MerchDash copies with its daily sync.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon account whose library gets the video, as returned by GET /api/v1/profiles. Not merged with a linked partner: a video belongs to one account, the one whose ad groups will use it. |
filename | stringrequired | The file's name, ending in .mp4 or .mov. |
size | integerrequired | The file's length in bytes. At most 500 MB, Amazon's limit for Sponsored Brands videos. |
name | stringrequired | Up to 255 characters. The name rules look the video up by; one the library already has is refused, so two videos are never ambiguous. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
uploadId | string | Pass it to the complete call. Valid for 55 minutes, on this server only: after a MerchDash restart, start again. |
uploadUrl | string | Amazon's pre-signed URL. PUT the file's bytes to it, nothing else. |
method | string | Always PUT. |
contentType | string | The Content-Type to send with the PUT, from the file's extension. |
expiresAt | timestamp | When the upload session ends. |
name | string | The name the video will be registered under. |
POST /api/v1/videos/uploads/:uploadId/complete
Register an uploaded video in the Creative Asset Library, step 3 after PUTting the file.
Amazon processes the video for about a minute: ready turns true (status ACTIVE) in GET /api/v1/videos?refresh=true, and only then can an ad use it. specFailures lists what Amazon's Sponsored Brands video checks flagged (aspect ratio, length, resolution); a video with any is unlikely to pass Amazon's ad review. Repeating the call for the same uploadId answers the first result again rather than registering twice. Amazon has no way to delete a library video. An unknown or expired uploadId (55 minutes, or a MerchDash restart) is 404: start again.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | The body is optional. A body that is just {} is accepted whatever its Content-Type; any other: Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
uploadId (path) | stringrequired | From POST /api/v1/videos/uploads. |
Request body
| Field | Type | Meaning |
|---|---|---|
name | stringoptional | Overrides the name given at the start. Defaults to it. |
asin | stringoptional | The product the video shows, an ASIN starting B0. Optional; addVideoAd(asin:) can name it instead. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
assetId | string | |
version | stringnullable | |
name | string | |
status | stringnullable | Usually PROCESSING at first. |
ready | boolean | True once Amazon has finished processing it. |
specFailures | array | Amazon's words for each Sponsored Brands video check the file failed. Empty when it passed. |
GET /api/v1/images
The logos and custom images in an account's Creative Asset Library, by name.
What MerchDash holds of the image library. It is copied only when something asks for it, a rule's addSbAd(logo: "…"), the app, or refresh=true here, so it can be empty for an account that has images. refresh=true walks the library at Amazon first (spending one request of the key's write budget, like a live read). name is what addSbAd(logo: "<name>") looks a logo up by. Most Merch accounts have no logo: they have no Brand Registry.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon account, as returned by GET /api/v1/profiles. Not merged with a linked partner. |
refresh | enumtrue · falseoptional | Read the library from Amazon first. Defaults to false. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: name ascending, ignoring case, then assetId
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
assetId | string | |
version | stringnullable | |
name | stringnullable | |
kind | enumlogo · image | logo for a brand logo, image for a custom image. |
status | stringnullable | Amazon's status, upper-cased, e.g. ACTIVE or PROCESSING. |
ready | boolean | True when status is ACTIVE. |
width | integernullable | |
height | integernullable |
POST /api/v1/images/uploads
Start uploading a Sponsored Brands logo or custom image: returns the URL to PUT the file to.
The image twin of POST /api/v1/videos/uploads, the same three steps: start here, PUT the bytes to uploadUrl, then POST /api/v1/images/uploads/:uploadId/complete. A rule then uses a logo by name: ad_group.addSbAd(from: FIND_SB_AD("…"), logo: "<name>"). Amazon wants a logo of at least 400×400 pixels and a custom image of at least 1200×628; MerchDash cannot see the pixels here, so a smaller file is refused by Amazon later.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon account whose library gets the image. Not merged with a linked partner. |
filename | stringrequired | The file's name, ending in .png, .jpg or .jpeg. |
size | integerrequired | The file's length in bytes. At most 5 MB. |
name | stringrequired | Up to 255 characters. The name rules look the image up by; one the library already has is refused. |
kind | enumlogo · imageoptional | logo (default) or image, a custom image for a product collection ad. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
uploadId | string | Pass it to the complete call. Valid for 55 minutes, on this server only. |
uploadUrl | string | Amazon's pre-signed URL. PUT the file's bytes to it. |
method | string | Always PUT. |
contentType | string | The Content-Type to send with the PUT. |
expiresAt | timestamp | |
name | string | |
kind | enumlogo · image |
POST /api/v1/images/uploads/:uploadId/complete
Register an uploaded logo or image in the Creative Asset Library, step 3 after PUTting the file.
Listed in GET /api/v1/images at once; ready turns true when Amazon has processed it. Repeating the call for the same uploadId answers the first result again. An unknown or expired uploadId is 404: start again.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | The body is optional. A body that is just {} is accepted whatever its Content-Type; any other: Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
uploadId (path) | stringrequired | From POST /api/v1/images/uploads. |
Request body
| Field | Type | Meaning |
|---|---|---|
name | stringoptional | Overrides the name given at the start. Defaults to it. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
assetId | string | |
version | stringnullable | |
name | string | |
kind | enumlogo · image | |
status | stringnullable | |
ready | boolean | |
width | integernullable | |
height | integernullable |
GET /api/v1/stores
The account's Amazon Stores and their pages, the landing pages a Sponsored Brands ad may point to.
Asked of Amazon at request time (remembered for 30 minutes), so it spends one request of the key's write budget, like a live read. A page's name or url is what addSbAd(landingPage: "…") takes. Empty for an account without a store, which is most Merch accounts.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon account, as returned by GET /api/v1/profiles. Not merged with a linked partner. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: as Amazon lists them
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
storeName | stringnullable | |
pages | array | The store's pages: { id, name, url }. |
GET /api/v1/ad-groups
Ad groups with their metrics for a window.
One row per ad group in the snapshot. campaignId narrows the read to one campaign.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only ad groups in this campaign. |
portfolioId | stringoptional | Only ad groups in the campaigns of this portfolio (ids from GET /api/v1/portfolios). |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then adGroupId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
adFormat | stringnullable | Sponsored Brands ad format of the row's ads: PRODUCT_COLLECTION, STORE_SPOTLIGHT, VIDEO or BRAND_VIDEO as Amazon names it, or MIXED when they differ (a campaign answers for all of its ads, a keyword or target for its ad group's). Null for Sponsored Products and Sponsored Display, and for a Sponsored Brands row whose ads have not been synced. Open set: Amazon may add formats. |
adGroupId | string | |
campaignId | stringnullable | |
name | stringnullable | |
defaultBid | numbernullable | The ad group's default bid, what a keyword or target with no bid of its own bids. |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/keywords
Keywords with their metrics for a window.
Positive keywords only. Negative keywords are read from GET /api/v1/negative-keywords. A Sponsored Brands keyword is listed once MerchDash has proven it positive (it delivered, or Amazon confirmed it): Amazon's old SB export mixed unmarked negatives in, and those are left out.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only keywords in this campaign. |
portfolioId | stringoptional | Only keywords in the campaigns of this portfolio (ids from GET /api/v1/portfolios). |
adGroupId | stringoptional | Only keywords in this ad group. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then keywordId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
adFormat | stringnullable | Sponsored Brands ad format of the row's ads: PRODUCT_COLLECTION, STORE_SPOTLIGHT, VIDEO or BRAND_VIDEO as Amazon names it, or MIXED when they differ (a campaign answers for all of its ads, a keyword or target for its ad group's). Null for Sponsored Products and Sponsored Display, and for a Sponsored Brands row whose ads have not been synced. Open set: Amazon may add formats. |
keywordId | string | |
campaignId | stringnullable | |
adGroupId | stringnullable | |
keywordText | stringnullable | |
matchType | stringnullable | EXACT / PHRASE / BROAD. |
bid | numbernullable | The keyword's own bid. Null means it has none and bids its ad group's defaultBid. Also null while MerchDash has not read the clause's bid yet (one seen only in reports); set_bid on such a clause is refused until the next sync reads it. |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/targets
Targeting clauses with their metrics for a window.
Positive targeting clauses, product targets, categories, and an auto campaign's close-match / loose-match / substitutes / complements clauses. Negative targets are read from GET /api/v1/negative-targets. A Sponsored Brands clause is listed once MerchDash has proven it positive, as for keywords.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only targets in this campaign. |
portfolioId | stringoptional | Only targets in the campaigns of this portfolio (ids from GET /api/v1/portfolios). |
adGroupId | stringoptional | Only targets in this ad group. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then targetId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
adFormat | stringnullable | Sponsored Brands ad format of the row's ads: PRODUCT_COLLECTION, STORE_SPOTLIGHT, VIDEO or BRAND_VIDEO as Amazon names it, or MIXED when they differ (a campaign answers for all of its ads, a keyword or target for its ad group's). Null for Sponsored Products and Sponsored Display, and for a Sponsored Brands row whose ads have not been synced. Open set: Amazon may add formats. |
targetId | string | |
campaignId | stringnullable | |
adGroupId | stringnullable | |
expression | stringnullable | The targeting clause as Amazon stores it. |
expressionType | stringnullable | |
bid | numbernullable | Null means the clause bids its ad group's defaultBid. Also null while MerchDash has not read the clause's bid yet (one seen only in reports); set_bid on such a clause is refused until the next sync reads it. |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/asins
Advertising performance aggregated per ASIN.
An ASIN-LEVEL AGGREGATION, not a list of product ads. One row per advertised ASIN, summing every ad of it across every campaign and ad group in the marketplace. ads is how many distinct product ads contributed. To act on one ad, find its adId with GET /api/v1/product-ads?asin=… (or ?adGroupId=…).
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only the delivery of this campaign: each ASIN's metrics, ads and campaigns then describe that campaign alone. |
portfolioId | stringoptional | Only the delivery of the campaigns in this portfolio (ids from GET /api/v1/portfolios), summed the same way. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: asin ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace you asked for. An ASIN row can span both halves of a linked pair of Amazon accounts, the ASIN is the identity here, not the profile. |
asin | string | |
ads | integer | Distinct product ads of this ASIN that delivered in the window. |
campaigns | integer | Distinct campaigns this ASIN delivered in during the window. |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/product-ads
Product ads (an ASIN/SKU in one ad group) with their metrics for a window.
One row per product ad in the snapshot, the adId that set_state with entityType: "productAd" needs. Sponsored Products and Sponsored Display only: Sponsored Brands ads are a different kind of entity and are not listed here. Archived ads are left out unless includeArchived=true. state is the ad's OWN state; whether it can deliver also depends on its ad group and campaign, which live answers. Amazon's Sponsored Display report does not name the ad, so an SD ad's metrics are its ad group's for its ASIN.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only ads in this campaign. |
portfolioId | stringoptional | Only ads in the campaigns of this portfolio (ids from GET /api/v1/portfolios). |
adGroupId | stringoptional | Only ads in this ad group. |
asin | stringoptional | Only ads of this ASIN, every ad group it is advertised in, so "pause this ASIN everywhere" is one read. Several ASINs, comma-separated (up to 50), are one read too: use that instead of one request per ASIN, which spends the read budget (120 a minute per key) fast. |
includeArchived | enumtrue · falseoptional | Also list archived ads. Defaults to false. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then adId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
adId | string | |
campaignId | stringnullable | |
adGroupId | stringnullable | |
asin | stringnullable | |
sku | stringnullable | Seller SKU. Null for vendors and when Amazon did not export one. |
live | boolean | True when the ad, its ad group and its campaign are all ENABLED (a level never synced counts as enabled), i.e. the ad can deliver. |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/sb-ads
Sponsored Brands ads (creatives) with their metrics for a window.
One row per Sponsored Brands ad in the snapshot. An SB ad is a creative, a headline, a brand, a landing page and up to five ASINs, or a video, and Amazon reports its traffic per ad, not per ASIN, so metrics belong to the ad as a whole. Metrics come from Amazon's per-ad report, which MerchDash keeps for the last 60 days; an ad that did not deliver in the window has zeros. Archived ads are left out unless includeArchived=true.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only ads in this campaign. |
adGroupId | stringoptional | Only ads in this ad group. |
adFormat | enumVIDEO · PRODUCT_COLLECTION · BRAND_VIDEO · STORE_SPOTLIGHToptional | Only ads of this format. |
includeArchived | enumtrue · falseoptional | Also list archived ads. Defaults to false. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then adId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adId | string | |
campaignId | stringnullable | |
adGroupId | stringnullable | |
name | stringnullable | |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. |
adFormat | stringnullable | VIDEO, PRODUCT_COLLECTION, BRAND_VIDEO or STORE_SPOTLIGHT. Open: Amazon may add formats. |
headline | stringnullable | |
brandName | stringnullable | |
asins | array | The ASINs the creative shows, sorted. |
servingStatus | stringnullable | Amazon's serving status, e.g. AD_POLICING_PENDING_REVIEW. |
moderationStatus | stringnullable | PENDING, APPROVED or REJECTED, Amazon's ad review. Null when Amazon answered a status MerchDash has not seen. |
landingPageType | stringnullable | e.g. STORE, PRODUCT_LIST, DETAIL_PAGE. |
landingPageUrl | stringnullable | |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/sb-purchased-products
What shoppers bought after Sponsored Brands ads, per purchased ASIN, for a window.
One row per (campaign, ad group, purchased ASIN, attribution type) with purchases in the window, from Amazon's Sponsored Brands purchased-product report (kept for the last 60 days). The purchased ASIN is not always one the ad showed: attributionType says Promoted for an advertised product and Brand Halo for another product of the same brand. Sales-side only, Amazon reports no impressions, clicks or cost here.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only this campaign. |
asin | stringoptional | Only purchases of this ASIN. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then campaignId, adGroupId, purchasedAsin and attributionType ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
campaignId | string | |
adGroupId | stringnullable | Null when Amazon's row named no ad group. |
purchasedAsin | string | |
attributionType | stringnullable | Amazon's attribution, e.g. Promoted or Brand Halo. Null when Amazon left it out. |
orders | integer | Orders within 14 days of an ad click. |
units | integer | |
sales | number | In the marketplace currency. |
newToBrandOrders | integer | Of orders, those from shoppers who had not bought from the brand in the last 12 months. |
newToBrandUnits | integer | |
newToBrandSales | number |
GET /api/v1/negative-keywords
Negative keywords at campaign and ad-group scope.
One row per negative keyword in the snapshot. Sponsored Products negatives are swept from Amazon at both scopes on every state sweep. Amazon offers no Sponsored Brands list, so SB negatives are read from the SB targets export instead, which only the full sweep (about every ten days) fetches: one added in the Amazon console shows up after the next full sweep, and one removed there is archived by it. Archived negatives are left out unless includeArchived=true. Remove one with the remove_negative_keyword operation of POST /api/v1/changes.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only negatives in this campaign (both scopes). |
portfolioId | stringoptional | Only negatives in the campaigns of this portfolio (ids from GET /api/v1/portfolios). |
adGroupId | stringoptional | Only negatives in this ad group. |
keywordText | stringoptional | Only negatives with exactly this text, compared case-insensitively, check here before adding one, or find the id to remove. |
matchType | enumNEGATIVE_EXACT · NEGATIVE_PHRASEoptional | Only negatives of this match type. |
state | enumENABLED · PAUSED · ARCHIVEDoptional | Only negatives in this state. When given, includeArchived is ignored. |
includeArchived | enumtrue · falseoptional | Also list archived (removed) negatives. Defaults to false. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then negativeKeywordId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
negativeKeywordId | string | |
campaignId | stringnullable | |
adGroupId | stringnullable | Null means campaign scope. |
scope | enumcampaign · adGroup | Campaign scope is a different Amazon entity from an ad-group negative. |
keywordText | stringnullable | |
matchType | stringnullable | NEGATIVE_EXACT / NEGATIVE_PHRASE. |
GET /api/v1/negative-targets
Negative product targets (excluded ASINs and brands) at campaign and ad-group scope.
One row per negative targeting clause in the snapshot, with the same sweep coverage and Sponsored Brands timing as GET /api/v1/negative-keywords. asin is set for an excluded ASIN, brandId for an excluded brand, and both are null for any other clause. Archived clauses are left out unless includeArchived=true. Remove one with the remove_negative_target operation of POST /api/v1/changes.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only negatives in this campaign (both scopes). |
portfolioId | stringoptional | Only negatives in the campaigns of this portfolio (ids from GET /api/v1/portfolios). |
adGroupId | stringoptional | Only negatives in this ad group. |
asin | stringoptional | Only clauses excluding this ASIN (brand clauses never match). |
state | enumENABLED · PAUSED · ARCHIVEDoptional | Only negatives in this state. When given, includeArchived is ignored. |
includeArchived | enumtrue · falseoptional | Also list archived (removed) negatives. Defaults to false. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then negativeTargetId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | Amazon ad product. Open enum: Amazon may add products. |
state | stringnullable | ENABLED / PAUSED / ARCHIVED as of the last sync. Null if never observed. |
negativeTargetId | string | |
campaignId | stringnullable | |
adGroupId | stringnullable | Null means campaign scope (Sponsored Products auto campaigns only). |
scope | enumcampaign · adGroup | |
asin | stringnullable | |
brandId | stringnullable | The brand an excluded-brand clause names (as POST /api/v1/negative-brands/search returns it). Null for an ASIN clause. |
expression | stringnullable | The clause as Amazon stores it (JSON). |
GET /api/v1/audiences
The audiences a marketplace's Sponsored Products campaigns can carry a bid adjustment for.
One row per audience Amazon offers for bid adjustments (its own, and the advertiser's Amazon Marketing Cloud ones), as last read. Served from MerchDash's stored copy and never calls Amazon, so a marketplace nothing has read audiences for yet lists none: a sync fills it. Use audienceId or name as the audience of the set_audience_bid operation.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then audienceId ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
audienceId | string | |
name | stringnullable | Amazon's display name for the audience. |
path | stringnullable | Where Amazon files it, e.g. "Audience Category › Custom-built › Product". |
GET /api/v1/placements
Metrics per campaign and placement for a window, Sponsored Products and Sponsored Brands.
One row per (campaign, placement) that delivered in the window. For Sponsored Products, the placements the bid adjustments on GET /api/v1/campaigns act on. Sponsored Brands placements are listed too, with adProduct saying which is which; they have no placementCode, because set_placement_bid changes Sponsored Products campaigns only. A campaign with no placement traffic in the window has no rows.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only this campaign. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId ascending, then campaignId ascending, then placement ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | The marketplace this row belongs to. |
campaignId | string | |
placement | string | Amazon's label, e.g. "Top of Search on-Amazon". |
adProduct | string | SPONSORED_PRODUCTS or SPONSORED_BRANDS. |
placementCode | stringnullable | TOP / PRODUCT_PAGE / REST_OF_SEARCH, the placement value set_placement_bid takes. Null for a placement that has no adjustment, and for every Sponsored Brands row. |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
POST /api/v1/recommendations/bids
Amazon's suggested bids for keywords and auto-targeting clauses in an existing Sponsored Products ad group.
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). Amazon answers per theme (CONVERSION_OPPORTUNITIES always, plus an event such as PRIME_DAY around one), each with a low, a medium and a high suggested bid per expression and, when it has them, the weekly clicks and orders similar products got at each of those bids.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
adGroupId | stringrequired | A Sponsored Products ad group in the snapshot. |
expressions | arrayrequired | Up to 100 { type, value? }. type is CLOSE_MATCH / LOOSE_MATCH / SUBSTITUTES / COMPLEMENTS (no value) or KEYWORD_EXACT_MATCH / KEYWORD_PHRASE_MATCH / KEYWORD_BROAD_MATCH with the keyword as value. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
campaignId | string | |
adGroupId | string | |
themes | array | [{ theme, expressions: [{ type, value, suggestedBids: [low, medium, high] }], impact: { clicks: [{lower, upper}], orders: [{lower, upper}] } | null }]. Bids are in the marketplace currency. |
POST /api/v1/recommendations/sb-bids
Amazon's suggested bids for keywords and product targets in a Sponsored Brands campaign.
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). One answer per keyword and per target sent, in the order sent: Amazon's suggested bid with the low and high end of its range, or error when Amazon had no suggestion for it. Bids depend on the ad format, so say adFormat when the campaign's ads are video.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
campaignId | stringrequired | A Sponsored Brands campaign in the snapshot. |
adFormat | enumPRODUCT_COLLECTION · VIDEOoptional | The ad format the bids are for. Amazon's default when left out. |
keywords | arrayoptional | [{ keywordText, matchType }], matchType EXACT / PHRASE / BROAD. |
targets | arrayoptional | [{ type, value }]: ASIN_SAME_AS with an ASIN, or ASIN_CATEGORY_SAME_AS with a category id. Up to 100 keywords and targets together, at least one. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
campaignId | string | |
keywords | array | [{ keywordText, matchType, suggested, low, high, error }]. Bids are in the marketplace currency; error is null when Amazon suggested one. |
targets | array | [{ type, value, suggested, low, high, error }]. |
POST /api/v1/recommendations/keywords
Amazon's ranked keyword suggestions, with suggested bids, for an ad group or a set of ASINs.
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). Send adGroupId for keywords that fit an existing Sponsored Products ad group, or asins for keywords that fit products you have not advertised yet, exactly one of the two.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
adGroupId | stringoptional | An existing Sponsored Products ad group. |
asins | arrayoptional | Up to 50 ASINs. |
maxRecommendations | integeroptional | 1–200. Defaults to 100. |
sortBy | enumDEFAULT · CLICKS · CONVERSIONSoptional | Amazon's ranking. Defaults to DEFAULT. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
adGroupId | stringnullable | |
keywords | array | [{ keyword, recId, searchTermImpressionRank, searchTermImpressionShare, bids: [{ matchType, rank, suggested, low, high }] }] in Amazon's order. The impression rank and share are the account's own over 30 days, null when it has not targeted the term. |
POST /api/v1/recommendations/categories
The product categories Amazon recommends targeting for a set of ASINs.
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash).
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
asins | arrayrequired | Up to 1,000 ASINs. |
includeAncestor | booleanoptional | Also return the parent categories of each recommendation, more categories, less relevant ones. Defaults to false. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
categories | array | [{ categoryId, name, path, parentId, canBeTargeted }]. |
POST /api/v1/recommendations/products
The products Amazon suggests targeting for a set of your advertised ASINs (Sponsored Products product targeting).
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). Send the ASINs you advertise; Amazon answers with other products worth targeting with a product (ASIN) target, ranked in its own order, each with the themes that led it there. Only the first page of Amazon's answer is returned unless you page on with cursor. Send adGroupId to have each suggestion say whether that ad group already targets the product (read from MerchDash's snapshot of its targets, as a rule's alreadyTargeted is); without it alreadyTargeted is null. Page on until nextCursor is null; rank restarts at 1 on every page.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
asins | arrayrequired | Up to 50 ASINs you advertise. |
maxRecommendations | integeroptional | 1–100. Defaults to 100. |
adGroupId | stringoptional | A Sponsored Products ad group of this marketplace. Fills alreadyTargeted; an unknown one is 404, another ad product 400. |
cursor | stringoptional | The nextCursor of the previous answer, unchanged, to read the next page. Up to 2000 characters. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
adGroupId | stringnullable | The ad group alreadyTargeted was read against; null when none was sent. |
products | array | [{ asin, rank, themes, alreadyTargeted }] in Amazon's order, one row per suggested ASIN. themes are Amazon's own labels, possibly empty. alreadyTargeted is true or false when adGroupId was sent, otherwise null. rank counts within this page. |
nextCursor | stringnullable | Pass as cursor for the next page; null when Amazon has no more. |
POST /api/v1/recommendations/budgets
Amazon's suggested daily budget for campaigns, with what running out of budget cost them (Sponsored Products and Sponsored Brands).
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). Pairs with set_campaign_budget. Amazon reads the last 7 days the campaign delivered in, about 3 days behind, a long-dormant campaign answers for an old week, so check windowEnd. GET /api/v1/campaigns has the suggestion as of the last sync.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
campaignIds | arrayrequired | Up to 100 campaign ids of this marketplace, Sponsored Products or Sponsored Brands, as strings. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
campaigns | array | One entry per campaign sent, in that order: { campaignId, adProduct, budget, suggestedBudget, percentTimeInBudget, windowStart, windowEnd, missedImpressions, missedClicks, missedSales, error }. budget is the current daily budget from the snapshot; percentTimeInBudget is 0–1 (the console's "Avg Time In Budget"), null when Amazon has no data; each missed* is { lower, upper } or null; error says why Amazon gave nothing for that campaign, else null. |
POST /api/v1/negative-brands/search
Find brands by keyword, Amazon's ids for the brands a Sponsored Products negative can exclude.
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). Returns Amazon's brand ids, what an excluded-brand negative targets. Pass one as brandId of the add_negative_target operation of POST /api/v1/changes to exclude the brand, and match it against the brandId of GET /api/v1/negative-targets to see whether it already is.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
keyword | stringrequired | 1–100 characters. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
brands | array | [{ brandId, name }]. |
POST /api/v1/eligibility
Whether ASINs can be advertised, and Amazon's reasons when they cannot.
A LIVE Amazon read: Amazon is asked at request time, through the marketplace's own connection and its own Amazon rate limit, so it is slower than a snapshot read and Amazon's answer may change from one call to the next. Nothing is written. Besides the read budget, each call spends one request of the key's write budget. Amazon's refusals come back in this API's own codes: a request Amazon rejects is validation, an id Amazon does not know (gone since the last sync) is not_found, Amazon throttling is rate_limited, Amazon being down or not answering within 30 seconds is temporarily_unavailable, and a connection Amazon no longer accepts is conflict (reconnect the account in MerchDash). Ask before creating product ads: an ineligible ASIN is refused by Amazon when the ad is created.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | |
asins | arrayrequired | Up to 50 ASINs, Amazon's limit for this check; send more as further requests. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAYoptional | Which ad product to check. Defaults to SPONSORED_PRODUCTS. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAY | |
products | array | [{ asin, sku, overallStatus, eligible, reasons: [{ name, message, severity, helpUrl }] }]. overallStatus is ELIGIBLE / ELIGIBLE_WITH_WARNING / INELIGIBLE; eligible is true for ELIGIBLE and ELIGIBLE_WITH_WARNING and false otherwise, INELIGIBLE, or a status Amazon did not send or MerchDash does not know. An ASIN Amazon has no record of for this account (not sold by this marketplace's seller, or no longer in the catalog) does not fail the request: it comes back with overallStatus: null, eligible: false and a single reason named NOT_FOUND. |
GET /api/v1/search-terms
Shopper search terms with their metrics for a window.
One row per (campaign, ad group, keyword, search term) as Amazon reported it. For a product-targeting campaign the "search term" may be an ASIN rather than a shopper query, that is Amazon's own shape.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Only search terms of this campaign. |
portfolioId | stringoptional | Only search terms of the campaigns in this portfolio (ids from GET /api/v1/portfolios). Combined with campaignId, both must hold. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: profileId, campaignId, adGroupId, keywordId, searchTerm, all ascending
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
adProduct | string | |
campaignId | string | |
adGroupId | string | |
keywordId | string | The keyword or target the term matched through. |
searchTerm | string | |
keyword | stringnullable | The keyword text or targeting clause it matched. |
matchType | stringnullable | |
metrics | object | The six report metrics summed over the window. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/metrics/daily
Account totals for each day in a window.
One row per report day, summing every campaign in the marketplace, the same daily totals the MerchDash dashboard charts. This is the endpoint to ask "what did we spend on this day"; the entity reads above answer "what did this campaign do over this window", and summing a page of them is neither cheaper nor more correct. A day the archive holds nothing for is absent rather than zero, before the first sync covered it, no spend and no data look identical, and only GET /api/v1/sync/status can tell them apart. When the marketplace is one half of a linked pair the row is the sum of both, exactly as the dashboard shows it, so a row carries no profileId of its own. There is no paging: the whole window comes back in one response, which the 365-day window cap bounds at 365 rows.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
campaignId | stringoptional | Sum only this campaign. It narrows what each day totals; it never splits a day into several rows. |
adProduct | enumSPONSORED_PRODUCTS · SPONSORED_BRANDS · SPONSORED_DISPLAYoptional | Sum only this Amazon ad product. |
start | dateoptional | First day of the metric window, inclusive, as YYYY-MM-DD (UTC, the marketplace's own report days). Defaults to 29 days before end. |
end | dateoptional | Last day of the metric window, inclusive, as YYYY-MM-DD. Defaults to 2 days ago, because Amazon is still restating attribution for the days after that. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: date ascending
| Field | Type | Meaning |
|---|---|---|
date | string | The report day, YYYY-MM-DD, in the marketplace's own report calendar. |
metrics | object | The six report metrics summed over that one day. |
metrics.impressions | integer | Impressions in the window. |
metrics.clicks | integer | Clicks in the window. |
metrics.cost | number | Ad spend in the window, in the marketplace currency. |
metrics.orders | integer | Attributed orders (14-day) in the window. |
metrics.units | integer | Attributed units (14-day) in the window. |
metrics.sales | number | Attributed sales (14-day) in the window, in the marketplace currency. |
GET /api/v1/sync/status
How current the synced archive is for a marketplace.
The freshness of the data every read above answers from, and whether proposals are unlocked, they are refused with sync_incomplete until its first full backfill has completed, and again while any full sync (a full re-sync included) is queued or running.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
lastSyncAt | timestampnullable | When a sync last completed. For a linked pair, the older of the two marketplaces' times (null while either has never synced), GET /api/v1/profiles gives each one's own. |
initialSyncComplete | boolean | True once the first full (95-day) backfill has completed and no full sync, a first backfill, a resumed one or a full re-sync, is queued or running. A sync that completes without being that backfill does not count, however much data the marketplace holds; after a first backfill that failed or was paused, its next sync runs the backfill instead. Computed over the marketplace and its linked partner. Each request checks its own set instead, a proposal its scope, an apply the run's own marketplace and those its changes touch, and a rule run or dry run the rule's marketplace and its linked partner, both of which it evaluates (a rule run writes to both; a dry run writes nothing), so this can read false once such a request would be accepted. |
syncing | boolean | True while a sync is running or queued right now. |
firstSyncedDate | stringnullable | Oldest report day held, YYYY-MM-DD. For a linked pair, the value common to both marketplaces. |
lastSyncedDate | stringnullable | Newest report day held, YYYY-MM-DD. For a linked pair, the value common to both marketplaces. |
syncedDays | integer | How many report days are held. For a linked pair, the value common to both marketplaces. |
campaigns | integer | Campaigns in the snapshot. |
POST /api/v1/sync/start
Sync a marketplace now, the dashboard's Sync button.
Refreshes the last 7 days of reports and the current entities (a marketplace whose first backfill never completed runs that instead), on both accounts of a linked pair. Returns at once; poll GET /api/v1/sync/status until syncing is false. An account already syncing, or synced in the last 15 minutes (unless its first backfill is still owed, which has no cooldown), is not started, results says which; 429 rate_limited when all were recent. MerchDash's own writes reach the snapshot at once. Writes nothing to Amazon; starting one spends one request of the key's write budget.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace, as returned by GET /api/v1/profiles. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
profileId | string | |
started | boolean | True when at least one sync was queued by this request. |
results | array | One entry per Amazon account of the marketplace: { profileId, result }, result one of started, already_syncing, recently_synced. |
GET /api/v1/runs
Automation runs for a marketplace, newest activity first.
Every run MerchDash recorded for the marketplace, proposals made through this API and runs made by the account's own automation rules alike. trigger says which.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
status | enumpreview · running · applied · partial · failed · reverted · discardedoptional | Only runs in this public status. Omit for all. Internal bookkeeping states are never accepted or returned. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: most recent activity first, then runId, a run whose mode is review enters the list when it is approved, not when it was proposed
| Field | Type | Meaning |
|---|---|---|
runId | string | |
profileId | string | |
status | enumpreview · running · applied · partial · failed · reverted · discarded | Closed enum. |
trigger | string | api, manual, schedule, …, what started the run. |
mode | stringnullable | How the rule or proposal that made the run is set up: review sends its changes to the review queue, auto writes them by itself; manual is a bulk edit made in the dashboard. It is that setting, not what this run did, a rule run started with an explicit apply or an auto-apply proposal whose hand-off failed can differ. Read status for what happened. |
label | stringnullable | The batch label for an API proposal, or the rule name for a rule run. |
ruleId | stringnullable | The automation rule that made the run (GET /api/v1/rules/{ruleId}); a deleted rule keeps its id. Null for an API proposal and for a dashboard bulk edit, neither has a rule. |
apiKeyId | stringnullable | Which key proposed it. Survives that key being revoked. |
startedAt | timestampnullable | |
finishedAt | timestampnullable | |
counts | object | How the run's own changes stand. Revert records are not counted. |
counts.proposed | integer | Not written yet. While the run is preview or partial they await approval. While it is running some may be being written and others left for approval (an apply of selected changes, or creates kept back for review), read the run again once it settles; an auto-applied run asks nobody. |
counts.applied | integer | |
counts.failed | integer | |
counts.reverted | integer | |
counts.discarded | integer | Never written and never going to be: turned down, or left unwritten by a run that was stopped or failed. |
GET /api/v1/runs/:runId
One run, the polling target for the runId a proposal returned.
The same object the list returns. An unknown id and a run belonging to another account answer identically with 404 not_found: telling them apart would describe somebody else's account.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired | The run id, as returned by POST /api/v1/changes. |
Response
Single resource, { data: { … } }.
Fields: same as runs.list
GET /api/v1/runs/:runId/changes
The individual changes a run proposed, and what became of each.
One row per change the run itself proposed. The revert records a later revert writes are excluded: they belong to the revert, not to this run, and status: reverted on the original already says it happened.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired | |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: changeId ascending
| Field | Type | Meaning |
|---|---|---|
changeId | string | |
runId | string | |
status | enumproposed · applied · failed · reverted · discarded | Closed enum. proposed is not written yet, awaiting approval, or, while the run is running, possibly being written by it (read it again once the run settles); discarded was never written and never will be, turned down, or left unwritten by a run that was stopped or failed. |
entityType | stringnullable | campaign / adGroup / keyword / target / productAd / negativeKeyword / negativeTarget / portfolio. |
entityId | stringnullable | Null for a change that creates something, there is no id until it is applied. |
entityName | stringnullable | |
campaignId | stringnullable | |
adGroupId | stringnullable | |
field | stringnullable | What the change alters: bid, state, negative, budget, biddingStrategy, placement:<label>, name, portfolio (a campaign's, shown by name), portfolioBudget, … |
beforeVal | stringnullable | The value MerchDash read before proposing. Null for a create. |
afterVal | stringnullable | The value the change writes. |
reason | stringnullable | The caller's own note, shown to the human reviewing it. |
error | stringnullable | Why this change failed, when it did. |
appliedAt | timestampnullable | |
createdAt | timestampnullable |
GET /api/v1/changes
Change history: what MerchDash wrote to Amazon, for one entity or the whole marketplace.
Changes MerchDash wrote to Amazon, by rules, API proposals, bulk edits and reverts, newest first. Read it before changing a bid, so you do not undo a recent change or fight a rule. Pending and failed changes are in their run (GET /api/v1/runs); changes made in the Amazon console are not recorded.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The Amazon advertising profile (marketplace) to read, as returned by GET /api/v1/profiles. When this marketplace is one half of a linked pair of Amazon accounts, the read covers both, exactly as the MerchDash dashboard does; every row carries the profileId it actually came from. |
entityId | stringoptional | Only changes to this entity (any id). Creates carry no entity id. |
entityType | enumcampaign · adGroup · keyword · target · productAd · negativeKeyword · negativeTarget · portfolio · sbAd · asinoptional | Only changes to this kind of entity. |
campaignId | stringoptional | Only changes to this campaign and what is inside it. |
field | stringoptional | Only this field (bid, state, budget, …). |
start | dateoptional | First day of appliedAt, YYYY-MM-DD (UTC). Defaults to 29 days before end. |
end | dateoptional | Last day, inclusive. Defaults to today. At most 365 days. |
limit | integeroptional | Rows per page, 1–500. Defaults to 100. |
cursor | stringoptional | An opaque cursor from a previous response's nextCursor. It encodes the endpoint and the filters it was issued for: reusing it with different filters is invalid_cursor rather than a silently wrong page. When start/end were left to their defaults, the cursor keeps the window the first page used, so paging across midnight UTC continues the same window. Paging is not a snapshot, rows synced between two pages may appear. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: appliedAt descending, then changeId descending
| Field | Type | Meaning |
|---|---|---|
changeId | string | |
runId | string | The run that wrote it (GET /api/v1/runs/:runId). |
profileId | string | |
kind | enumchange · revert | revert is the write that undid an earlier change, named by revertOf. |
status | enumapplied · reverted | reverted: written, then undone by the change revertedBy names. |
entityType | stringnullable | |
entityId | stringnullable | |
entityName | stringnullable | |
campaignId | stringnullable | |
adGroupId | stringnullable | |
field | stringnullable | As in GET /api/v1/runs/:runId/changes. |
beforeVal | stringnullable | |
afterVal | stringnullable | |
reason | stringnullable | |
appliedAt | timestamp | When Amazon accepted it. |
createdAt | timestampnullable | When it was proposed. |
revertOf | stringnullable | For a revert, the changeId it undid. |
revertedBy | stringnullable | For a reverted change, the revert's changeId. |
trigger | stringnullable | What started the run: api, manual, schedule, … |
ruleId | stringnullable | The rule behind it; null for an API proposal or a bulk edit. |
label | stringnullable | The rule name or the proposal's label. |
POST /api/v1/changes
Propose a batch of bid, state, negative, campaign-setting and portfolio changes, for a human to approve, or to apply at once.
Writes a run and returns immediately; the writing to Amazon, when there is any, happens in the background. On a review key, the default, and what every key is until its owner says otherwise, nothing reaches Amazon here at all: the proposal waits in MerchDash's review queue until a human applies or turns it down there, or a key with the automation scope does so through POST /api/v1/runs/:runId/apply or /discard. On an auto-apply key, or with "applyMode": "auto", MerchDash applies the batch itself as soon as it is accepted. Either way GET /api/v1/runs/:runId reports the outcome. No key can revert a run.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | required | 1–128 visible ASCII characters, unique per logical request. A retry with the same key and the same body returns the first call's response and creates nothing; the same key with a different body is 409 idempotency_conflict. Keys are remembered for 24 hours. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | Every change in the batch must belong to this marketplace, counting its linked partner account, whose entities are found and written exactly like its own (each change goes to the account that owns the entity). A batch naming an entity of any other marketplace is refused whole. |
label | stringrequired | 1–80 characters. What the review card is titled, the human deciding sees this and nothing else about who sent it. |
changes | arrayrequired | 1–500 change objects, discriminated on operation. set_auto_clause_bids expands into several changes each, and a request whose expansion exceeds 500 is refused whole rather than truncated. |
applyMode | enumreview · autooptional | What happens to the proposal. review queues it for a human, which is what a key does by default. auto writes it to Amazon as soon as it is accepted, with nobody in the loop, and is only allowed on a key whose owner has turned auto-apply on for it, on any other key it is 403 scope. Omit it to use whatever the key is set to. A key that CAN auto-apply may still send review for a batch that wants a second pair of eyes; the reverse is never possible. |
Operation: set_bid
Set a keyword's or target's bid. Refused when the keyword or target, its ad group or its campaign is archived, Amazon takes no changes there.
| Field | Type | Meaning |
|---|---|---|
entityType | enumkeyword · targetrequired | |
entityId | stringrequired | Must exist in this marketplace's snapshot as a positive keyword or targeting clause. A negative's id is not one: bid and state changes to negatives go through their own operations. |
bid | numberrequired | In the marketplace currency, within Amazon's range for that marketplace and ad product (US: $0.02–$1,000; Sponsored Brands $0.10–$49, $0.25 minimum for video ads). A bid outside it is written at the limit and flagged as adjusted. A bid above the account's own maximum-bid setting is written at that maximum and flagged as adjusted, exactly as an automation rule's would be, except for a keyword or target that already bids above the maximum: it may keep any bid up to its current one, so it can come down gradually, but not rise. |
reason | stringoptional | Up to 200 characters, shown to the reviewer. Display only. |
Operation: set_state
Enable or pause an entity, a portfolio included, and a Sponsored Products negative keyword or negative target. Refused when the entity, its ad group or its campaign is archived, Amazon takes no changes there. A portfolio may have one state change waiting for approval at a time, a pending rename or budget change does not block it, and a negative one change (409 conflict).
| Field | Type | Meaning |
|---|---|---|
entityType | enumcampaign · adGroup · keyword · target · productAd · portfolio · negativeKeyword · negativeTargetrequired | |
entityId | stringrequired | For a negative, its negativeKeywordId / negativeTargetId from GET /api/v1/negative-keywords or /negative-targets. A paused negative stops excluding its term but is not removed; to remove one use remove_negative_keyword / remove_negative_target. |
state | enumENABLED · PAUSEDrequired | ARCHIVED is not offered: archiving is irreversible on Amazon and cannot be undone by a revert. |
reason | stringoptional |
Operation: set_auto_clause_bids
Set the bids of an auto campaign's four predefined targeting clauses, the same action as Actions → Adjust Auto Targeting Bid in the dashboard. This is the only operation that reads from Amazon while it compiles: an auto campaign's clauses appear in MerchDash's own snapshot only once they have delivered, so the clauses are enumerated live and the request expands into one target-bid change per clause it actually alters. Expect it to be slower than the other operations, and to answer 503 temporarily_unavailable when Amazon cannot be reached, no proposal is created in that case.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | An auto-targeting Sponsored Products campaign. Any other campaign is refused: nothing else has these clauses. |
bids | objectrequired | One bid per clause, keyed close-match, loose-match, substitutes, complements. A clause you omit, or set to null, is left alone. Same range and the same maximum-bid clamp as set_bid. |
adGroupIds | arrayoptional | Narrows the fill to these ad groups of the campaign, at most 125 of them. Omit it to fill every ad group in the campaign. An empty array is refused rather than read as "all". |
emptyOnly | booleanoptional | Default true: only clauses with no bid of their own, the ones inheriting the ad group default, are filled. false overwrites existing clause bids too. |
reason | stringoptional | Shown to the reviewer on every change this expands into. Omit it and each change explains itself (what the clause was bidding, or which default it was inheriting). |
Operation: set_portfolio_budget
Set a portfolio's budget cap, the most all of its campaigns may spend together, including its type and dates. The cap is one object on Amazon, so it is recorded and reverted whole: History shows the cap before and after, and a revert restores the previous cap exactly. Because the whole cap is written, a portfolio may have one budget change waiting for approval at a time: while one is proposed (or being applied), another, this operation or remove_portfolio_budget_cap, is 409 conflict. If MerchDash has not yet read the portfolio's current cap (a portfolio synced before caps were, or created since the last sync), the change still applies but cannot be reverted, there is no previous cap to put back.
| Field | Type | Meaning |
|---|---|---|
portfolioId | stringrequired | Must exist in this marketplace's snapshot (GET /api/v1/portfolios). An archived portfolio is refused: Amazon cannot change one. |
amount | numberrequired | The cap, above zero, in the portfolio's currency. Rounded to cents. |
policy | enumMONTHLY_RECURRING · DATE_RANGEoptional | MONTHLY_RECURRING resets every calendar month; DATE_RANGE is one total from startDate to endDate, after which the portfolio's campaigns stop. Omit it to keep the portfolio's current policy, or, for a portfolio with no cap, to get MONTHLY_RECURRING. To remove a cap use remove_portfolio_budget_cap. |
startDate | dateoptional | YYYY-MM-DD. Required with DATE_RANGE unless the portfolio already has a date-range cap. Omitted, the current start date is kept while the policy stays the same, and dropped when it changes. |
endDate | dateoptional | YYYY-MM-DD, not before startDate. null removes the end date. Omitted, it is kept on the same terms as startDate. |
reason | stringoptional |
Operation: remove_portfolio_budget_cap
Take a portfolio's budget cap off, so its campaigns spend against their own daily budgets only. A revert puts the previous cap back. If MerchDash has not yet read the portfolio's current cap (a portfolio synced before caps were, or created since the last sync), the change still applies but cannot be reverted, there is no previous cap to put back.
| Field | Type | Meaning |
|---|---|---|
portfolioId | stringrequired | |
reason | stringoptional |
Operation: rename_portfolio
Rename a portfolio.
| Field | Type | Meaning |
|---|---|---|
portfolioId | stringrequired | |
name | stringrequired | 1–255 characters. Leading and trailing whitespace is trimmed; a line break, tab or other control character inside it is refused. A name another portfolio of the same Amazon account already has, an archived one does not count, (each half of a linked pair is checked on its own, one portfolio in each may carry the same name) (compared trimmed, case-folded and with inner whitespace collapsed, so two spaces between words count as one) is refused: portfolios are found by name, and two with one name are ambiguous to every rule that looks one up. So is a name another portfolio is being renamed to in a proposal still waiting for approval (409 conflict), and a second rename of a portfolio whose first is still waiting. |
reason | stringoptional |
Operation: add_negative_keyword
Add a negative keyword at campaign or ad-group scope. Sponsored Products and Sponsored Brands campaigns only, a Sponsored Display campaign is refused, as is an archived campaign or ad group.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
adGroupId | stringoptional | Omit for a campaign-wide negative. Present means ad-group scope. |
keywordText | stringrequired | 1–80 characters. Leading and trailing whitespace is trimmed; a line break, tab or other control character inside it is refused. An ASIN is refused on a Sponsored Products auto campaign or a Sponsored Brands campaign, where only a negative product target (add_negative_target) can block a product. |
matchType | enumNEGATIVE_EXACT · NEGATIVE_PHRASErequired | |
reason | stringoptional |
Operation: add_negative_target
Add a negative product target, one ASIN, or a whole brand, at campaign or ad-group scope. Send exactly one of asin and brandId. Sponsored Products and Sponsored Brands campaigns only, a Sponsored Display campaign is refused, as is an archived campaign or ad group. An excluded brand is written as an ASIN_BRAND_SAME_AS clause; that write has not yet run against Amazon, so check the applied change in History the first time you use it.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
adGroupId | stringoptional | Omit for a campaign-wide negative, allowed on auto-targeting Sponsored Products campaigns, and on Sponsored Brands campaigns, where it is written into every ad group of the campaign. Present means ad-group scope. |
asin | stringoptional | A 10-character ASIN. Required unless brandId is sent; the two are exclusive. |
brandId | stringoptional | A brand id from POST /api/v1/negative-brands/search, up to 40 characters. Required unless asin is sent. |
reason | stringoptional |
Operation: remove_negative_keyword
Remove (archive) a negative keyword at either scope. Reverting it from History creates it again with the same text, match type and scope; a negative that was paused when removed is not revertible, since the re-created one would be enabled. Removing a negative and adding the same one back in one batch is refused. A negative may have one change (pause, enable or removal) waiting for approval at a time: while one is pending in another proposal, this is 409 conflict.
| Field | Type | Meaning |
|---|---|---|
negativeKeywordId | stringrequired | As returned by GET /api/v1/negative-keywords. An already-archived one is refused. |
reason | stringoptional |
Operation: remove_negative_target
Remove (archive) a negative product target at either scope. Reverting re-creates an excluded ASIN or brand; a negative that was paused when removed can be removed but not reverted. Removing a negative and adding the same one back in one batch is refused. A negative may have one change (pause, enable or removal) waiting for approval at a time: while one is pending in another proposal, this is 409 conflict.
| Field | Type | Meaning |
|---|---|---|
negativeTargetId | stringrequired | As returned by GET /api/v1/negative-targets. An already-archived one is refused. |
reason | stringoptional |
Operation: set_placement_bid
Set a Sponsored Products campaign's bid adjustment for one placement (placement + percent) or for several at once (placements), what a rule's setPlacementBid() does. Placements you do not name and the bidding strategy are kept. One such change per campaign per batch: a second set_placement_bid or set_bidding_strategy for the same campaign is refused, so name every placement you want in one change. placements becomes one change per placement in the review, none depending on another (leave one out, or revert one, and the rest stand); a placement already at its value is skipped. And one waiting for approval at a time: while a placement or bidding-strategy change for the campaign (from another batch or a rule run) is still proposed, a new one is 409 conflict naming that run, retry once that run has been applied or turned down (one being applied now only needs to finish).
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
placement | enumTOP · PRODUCT_PAGE · REST_OF_SEARCHoptional | With percent, for one placement. Send this or placements, one of the two is required. |
percent | integeroptional | Whole percent, 0–900. 0 removes the adjustment. |
placements | objectoptional | Several placements: {"TOP": 60, "PRODUCT_PAGE": 20, "REST_OF_SEARCH": 20}, any of the three, each a whole percent 0–900. |
reason | stringoptional |
Operation: set_audience_bid
Set a Sponsored Products campaign's bid adjustment for one audience, what a rule's setAudienceBid() does. Only that audience changes: the campaign's other audiences, placements and bidding strategy are kept as Amazon holds them when the change is written, so several audiences on one campaign can go in one batch (the same audience twice is refused). A percent of 0 removes the adjustment. Refused when the campaign is not Sponsored Products or is archived, when the adjustment already has that value, when the audience matches nothing the marketplace offers (or a name matches several, use the id), and when it would be the campaign's 11th (Amazon allows 10). A campaign may have one change to a given audience waiting for approval at a time: while one is proposed (from another batch or a rule run), a new one for the same audience is 409 conflict naming that run. Reverting puts the previous value back, or removes the adjustment if there was none. The Amazon request shape is taken from its OpenAPI spec and has not yet been confirmed against a live account.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
audience | stringrequired | An audienceId or (part of) a name from GET /api/v1/audiences, case-insensitive. |
percent | integerrequired | Whole percent, 0–900. 0 removes the adjustment. |
reason | stringoptional |
Operation: set_campaign_budget
Set a campaign's daily budget, what a rule's setBudget() does on a campaign. Sponsored Products, Sponsored Brands and Sponsored Display. Refused when the campaign is archived, when its budget is not a daily one (a Sponsored Brands lifetime budget, Amazon cannot change a budget's type), and when the budget is already that amount. A campaign may have one budget change waiting for approval at a time: while one is proposed (from another batch or a rule run), a new one is 409 conflict naming that run, retry once that run has been applied or turned down (one being applied now only needs to finish). The proposed amount can be changed before it is applied with PATCH /api/v1/runs/:runId/changes/:changeId. Reverting puts the previous budget back.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
budget | numberrequired | In the marketplace currency, greater than 0 (0 or less is validation), rounded to the cent. Below Amazon's floor of 1 it is written at the floor and flagged as adjusted. Amazon's per-marketplace minimum and maximum apply when the change is written. |
reason | stringoptional |
Operation: set_bidding_strategy
Set a Sponsored Products campaign's bidding strategy (dynamic bids), what a rule's setBiddingStrategy() does. The placement bid adjustments are kept. A Sponsored Brands or Display campaign is refused (they have no such setting), as is an archived one and a strategy the campaign already has. It shares the campaign's one placement slot with set_placement_bid, because both write the whole placement map: one of the two per campaign per batch, and while either is waiting for approval for the campaign (from another batch or a rule run), a new one is 409 conflict naming that run. Reverting puts the previous strategy back, unless it was one MerchDash cannot set, such as Amazon's rule-based bidding.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
strategy | stringrequired | "DYNAMIC BIDS - UP AND DOWN", "DYNAMIC BIDS - DOWN ONLY", "FIXED BIDS", or Amazon's own value (autoForSales, legacyForSales, manual, or AUTO_FOR_SALES, LEGACY_FOR_SALES, MANUAL). Case and spacing do not matter. Amazon's RULE_BASED is not offered: it needs settings this operation does not take. |
reason | stringoptional |
Operation: rename_campaign
Rename a campaign, what a rule's rename() does on a campaign. Sponsored Products, Sponsored Brands and Sponsored Display. Refused when the campaign is archived or already has that exact name. A campaign may have one rename waiting for approval at a time (409 conflict). Reverting puts the previous name back.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
name | stringrequired | 1–128 characters, the limit POST /api/v1/structures applies to a campaign it creates. Leading and trailing whitespace is trimmed; a line break, tab or other control character inside it is refused. A name another campaign of the same Amazon account already has, an archived one does not count; each half of a linked pair is checked on its own, is refused, compared trimmed, case-folded and with inner whitespace collapsed, the way /structures and a rule's FIND_CAMPAIGN find a campaign by name. So are two renames to one name in a batch, and, 409 conflict, a name another campaign is being renamed to in a proposal still waiting for approval, or one a proposal (POST /api/v1/structures, or a rule) has reserved for a campaign it has not created yet. |
reason | stringoptional |
Operation: set_campaign_portfolio
Move a campaign into a portfolio, or take it out of the one it is in, what a rule's moveToPortfolio() does. Refused when the campaign is archived or already where it is asked to go, and when the portfolio is archived, unknown, or belongs to the other Amazon account of a linked marketplace (a campaign can only be in a portfolio of its own account). A campaign may have one move waiting for approval at a time (409 conflict), and none is accepted into a portfolio that a waiting proposal archives. Reverting moves the campaign back to the portfolio it was in; a campaign that was in none cannot be reverted out again.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
portfolioId | stringrequired nullable | From GET /api/v1/portfolios. null takes the campaign out of its portfolio, it must be sent: an absent or blank portfolioId is validation, never read as "out". |
reason | stringoptional |
Operation: archive_portfolio
Archive a portfolio, what a rule's portfolio.archive() does. PERMANENT on Amazon: an archived portfolio cannot be enabled, renamed, given a cap or have campaigns moved into it, by anyone, ever again, and this change has no revert. It is its own operation, never a set_state value, so it cannot be sent by accident. Refused when the portfolio is already archived; when the same batch also changes it (its state, cap or name) or moves a campaign into it; and, 409 conflict, while another proposal waiting for approval does either. Equally, while an archive waits for approval nothing else for that portfolio is accepted.
| Field | Type | Meaning |
|---|---|---|
portfolioId | stringrequired | |
reason | stringoptional |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
runId | string | Poll GET /api/v1/runs/:runId for what a human decides. |
count | integer | How many changes the run holds. |
applyMode | enumreview · auto | What this run will actually do, what the key allows, narrowed by whatever the request asked for. review means a human decides; auto means MerchDash is already writing it. |
status | enumpreview · running · applied · partial · failed · reverted · discarded | The run's status at the instant it was created, which is preview either way, an auto-applied run leaves it within seconds, on its own. Branch on applyMode, not on this: with applyMode: "auto" never tell a user their changes are waiting for approval. Poll GET /api/v1/runs/:runId for what happened. |
POST /api/v1/structures
Propose new campaigns, ad groups, product ads, keywords and targeting clauses for a human to approve.
Takes a nested graph and turns it into durable creation intents plus one run. On a review key, the default, nothing reaches Amazon here: the proposal is applied from the review queue (or by a key with the automation scope, through POST /api/v1/runs/:runId/apply), and only then is the structure built, parents before children. On an auto-apply key, or with "applyMode": "auto", MerchDash starts building it immediately, in that same dependency order. Because a create cannot be made idempotent by value, the id does not exist yet, each container is RESERVED under a stable identity at proposal time, so it is never built twice. A retry with the same Idempotency-Key replays the first answer. A second proposal naming a campaign, ad group or new portfolio that another run has already reserved is refused with 409 creation_conflict naming that run: retry once that run has been applied or turned down, or, for one Amazon is still creating, a little later, and an automation rule asking for it skips it as already pending.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | required | 1–128 visible ASCII characters, unique per logical request. A retry with the same key and the same body returns the first call's response and creates nothing; the same key with a different body is 409 idempotency_conflict. Keys are remembered for 24 hours. |
What this endpoint will not do
- Builds Sponsored Products structures. Sponsored Brands only as far as an ad group that ALREADY EXISTS (by
adGroupId, or by name inside an existing SB campaign): keywords or targets, never both in one SB ad group, and not the other kind than it already holds, because Amazon keeps the two apart, and no ASINs (an SB ad advertises through its creative). A new SB ad group needs an ad first, which this API cannot supply, so it is refused, and Amazon refuses a keyword in an existing SB ad group that has no ad yet. Sponsored Display is refused (an SD ad group needs a tactic). - Auto-targeting campaigns take no keywords and no positive targeting clauses. Amazon creates their close match, loose match, substitutes and complements clauses with the ad group and lets nothing be added to them; only negatives are accepted there, through POST /api/v1/changes.
- Created entities start ENABLED unless a node says
"state": "PAUSED", and campaigns are created with no start date, so a proposal begins spending when the human applies it. - A campaign or ad group that already exists, named by id, or matched by the name you gave, is added to, never modified: its budget, bid, targeting and state are not changed here.
alreadyPresentreports a container matched by name, with its id (one you named by id is not echoed back); it does not echo settings that were ignored. A request in which everything already exists proposes nothing and is refused with 400validation("Everything in this request already exists"), its boundeddetailslisting the first of the matched entries in thealreadyPresentshape ({ kind, value, …Id }, never a{ message, path }problem, which is how a client tells this refusal from any othervalidation), with no count, so a re-send of a structure that was already built is that refusal, not a second run. - At most 2000 campaigns and ad groups, and 20000 ASINs, keywords and targeting clauses, per request.
- Character limits (label, names, keyword text, reason) count UTF-16 code units, as JavaScript's
lengthdoes: an emoji or another character outside the Basic Multilingual Plane counts as two.
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | Every node in the graph belongs to this marketplace, counting its linked partner account: a campaign or ad group named by id, or matched by name, in the partner is added to there, and what is created under it is created in the partner. A new campaign (and a new portfolio) is created in the pair's primary account, whichever half profileId names. |
label | stringrequired | 1–80 characters. What the review card is titled. |
campaigns | arrayoptional | Campaigns to create, or existing campaigns to add ad groups to. Each entry names EITHER name OR campaignId, never both. A name the marketplace already has is ADDED TO, not created again, the same get-or-create an automation rule performs, so sending the same request twice builds one campaign. Shape of a new campaign: { name, budget, targeting: "manual" | "auto", state?, biddingStrategy?, portfolio? (a name) or portfolioId?, adGroups: [{ name, defaultBid?, asins?: ["B0…"], keywords?: [{ text, matchType: "EXACT" | "PHRASE" | "BROAD", bid? }], targets?: [{ asin | category, bid? }] }] }; of an existing one { campaignId, adGroups: [...] }. Any other field is refused by name, so a typo is a 400, never ignored. |
adGroups | arrayoptional | The single-shot form: ad groups that ALREADY exist, named by adGroupId, to add ASINs, keywords or targeting clauses to. This is how a script adds product ads, there is no stage-then-commit step, unlike the dashboard's paste box, and unlike that path nothing is written until a human approves it. |
applyMode | enumreview · autooptional | What happens to the proposal. review queues it for a human, which is what a key does by default. auto builds the structure on Amazon as soon as the request is accepted, and is only allowed on a key whose owner has turned auto-apply on for it, on any other key it is 403 scope. Omit it to use whatever the key is set to. Worth a thought here in particular: a campaign or an ad group Amazon has created cannot be un-created by a revert. |
Node: campaign (new)
| Field | Type | Meaning |
|---|---|---|
name | stringrequired | 1–128 characters. Leading and trailing whitespace is trimmed; a line break, tab or other control character inside it is refused. Compared case-insensitively and whitespace-collapsed, exactly as Amazon collides names. A campaign of this name that already exists is used as-is; its targeting type must match the one asked for, since Amazon cannot change that after creation. A name an existing campaign is being renamed to (rename_campaign, or a rule) in a proposal still waiting for approval is 409 creation_conflict: retry once that run is decided, and once the rename is applied the request adds to that campaign. |
budget | numberrequired | Daily budget in the marketplace currency, above zero (0 or less is validation). Below Amazon's floor of 1 it is raised to it; above the account's creation ceiling it is lowered to that and reported in adjustments. |
targeting | enummanual · autorequired | Amazon cannot change a campaign's targeting type after it is created. |
state | enumENABLED · PAUSEDoptional | Defaults to ENABLED. |
biddingStrategy | stringoptional | Amazon's value or the Campaign Manager label, "DYNAMIC BIDS - UP AND DOWN", "DYNAMIC BIDS - DOWN ONLY" (the default), or "FIXED BIDS". |
portfolio | stringoptional | A portfolio name, 1–255 characters (the limit rename_portfolio allows). Leading and trailing whitespace is trimmed; a line break, tab or other control character inside it is refused. Created if the account does not have one, and the campaign waits for it. |
portfolioId | stringoptional | An existing portfolio, by the id GET /api/v1/portfolios lists, instead of portfolio (send one of the two). It must exist in this marketplace and not be archived, an id the account does not have is validation; to create a portfolio, name it with portfolio. |
adGroups | arrayoptional | Ad groups to get-or-create inside it by name. An existing adGroupId may also be used when the campaign name resolves to an existing campaign; if the campaign itself has to be created, its children must use name. |
Node: campaign (existing)
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | Must exist in this marketplace. Sponsored Products, or Sponsored Brands when every ad group under it already exists. |
adGroups | arrayrequired | At least one ad group to create in it, or to add leaves to. |
Node: adGroup (new)
| Field | Type | Meaning |
|---|---|---|
name | stringrequired | 1–128 characters. Leading and trailing whitespace is trimmed; a line break, tab or other control character inside it is refused. Unique within its campaign, "Core terms" in two campaigns is two ad groups. One the campaign already holds is added to rather than created again, and then defaultBid is not needed. |
defaultBid | numberoptional | The bid a keyword or clause with no bid of its own uses. In the marketplace currency, greater than 0 (0 or less is validation); Amazon's range for the marketplace and the account's own maximum-bid setting apply exactly as for set_bid (a bid outside them is written at the limit and flagged as adjusted). Required only when the ad group has to be created, an ad group of this name that the campaign already holds keeps its own default bid. |
state | enumENABLED · PAUSEDoptional | Defaults to ENABLED. |
asins | arrayoptional | ASINs to advertise, as 10-character strings. |
keywords | arrayoptional | |
targets | arrayoptional |
Node: adGroup (existing)
| Field | Type | Meaning |
|---|---|---|
adGroupId | stringrequired | Must exist in this marketplace. At least one of asins/keywords/targets is required, an existing ad group's own settings are not changed here. |
asins | arrayoptional | |
keywords | arrayoptional | |
targets | arrayoptional |
Node: keyword
| Field | Type | Meaning |
|---|---|---|
text | stringrequired | 1–80 characters. Whitespace, tabs and line breaks included, collapses to single spaces; any other control character is refused. An ASIN is refused: bought as keyword text it is a literal string no shopper types, so it costs a write and never serves. Send it under targets. |
matchType | enumEXACT · PHRASE · BROADrequired | |
bid | numberoptional | Omit to follow the ad group's default bid. Greater than 0 (0 or less is validation); Amazon's range for the marketplace and the account's own maximum-bid setting apply exactly as for set_bid (a bid outside them is written at the limit and flagged in adjustments). |
state | enumENABLED · PAUSEDoptional | |
reason | stringoptional | Up to 200 characters, shown to the reviewer. Display only. |
Node: target
| Field | Type | Meaning |
|---|---|---|
asin | stringoptional | A product target. Exactly one of asin or category. |
category | stringoptional | An Amazon category id. Exactly one of asin or category. |
bid | numberoptional | Omit to follow the ad group's default bid. Greater than 0 (0 or less is validation); Amazon's range for the marketplace and the account's own maximum-bid setting apply exactly as for set_bid (a bid outside them is written at the limit and flagged in adjustments). |
state | enumENABLED · PAUSEDoptional | |
reason | stringoptional | Up to 200 characters, shown to the reviewer. Display only. Targeting clauses are reviewed in batches; when a batch's clauses carry different reasons, its row shows the first as "<reason> (and N more)", N being the other clauses in that batch. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
runId | string | Poll GET /api/v1/runs/:runId for what a human decides. |
count | integer | How many change rows the run holds. Not the number of entities: an ad group's ASINs are batched into rows of at most 1,000, and its targeting clauses into rows per state (ENABLED / PAUSED) of at most 1,000. |
intents | integer | How many containers were reserved, portfolios, campaigns and ad groups. |
applyMode | enumreview · auto | What this run will actually do, what the key allows, narrowed by whatever the request asked for. review means a human decides; auto means MerchDash is already building it. |
status | enumpreview · running · applied · partial · failed · reverted · discarded | The run's status at the instant it was created, which is preview either way, an auto-applied run leaves it within seconds, on its own. Branch on applyMode, not on this. Poll GET /api/v1/runs/:runId for what happened. |
alreadyPresent | arraynullable | Up to the first 50 things the marketplace snapshot already held, so they were added to rather than proposed again. Absent when there were none. Each entry is { kind, value } plus the id it resolved to, adGroupId for asin, keyword, target and adGroup, campaignId for campaign, or portfolioId for portfolio. |
alreadyPresentCount | integernullable | Total number of already-present containers and leaves, including entries omitted from the bounded alreadyPresent sample. Absent when there were none. |
alreadyPresentTruncated | booleannullable | True when alreadyPresentCount is larger than the returned alreadyPresent sample. Absent when there were no already-present items. |
adjustments | arraynullable | Values an account or Amazon ceiling moved, with the number that will actually be used. Absent when there were none. Each entry is { path, field, note }. |
GET /api/v1/rules
The marketplace's automation rules.
Every automation rule saved for the marketplace, with its script. The same rules the Automation page lists. Rules belong to one marketplace: the same rule in two marketplaces is two rules.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace, as returned by GET /api/v1/profiles. Rules and categories belong to one marketplace each. Unlike the data reads, a linked pair of Amazon accounts is NOT merged here. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: the order the Automation page shows them in. Not paginated: nextCursor is always null.
| Field | Type | Meaning |
|---|---|---|
ruleId | string | |
profileId | string | |
name | string | |
code | string | The rule's script. |
schedule | string | See the schedule field of POST /api/v1/rules. A rule saved long ago may carry an older value the scheduler still understands. |
mode | enumreview · auto | |
enabled | boolean | Whether the schedule is on. |
categoryId | stringnullable | |
seasonEnabled | boolean | |
startDate | datenullable | |
endDate | datenullable | |
createdAt | timestampnullable | |
updatedAt | timestampnullable | |
lastRunAt | timestampnullable | When the rule last ran, by hand or on schedule. |
lastRunId | stringnullable | That run, for GET /api/v1/runs/:runId. Null when there is no run to follow: it never ran, or its last run left no readable record, a scheduled run that changed nothing and logged nothing (manual runs record even that) or failed before recording one, a run stopped while it was still evaluating, a review since discarded before any of it was applied (one applied in part first stays readable and named here), or a run hidden from you (lastRunAt still says when it ran). A manual run that fails before recording one changes neither field: they keep naming the run before it. |
GET /api/v1/rules/:ruleId
One automation rule.
The same object the list returns. An unknown id and a rule belonging to another account both answer 404 not_found.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
ruleId (path) | stringrequired | As returned by GET /api/v1/rules. |
Response
Single resource, { data: { … } }.
Fields: same as rules.list
POST /api/v1/rules
Create an automation rule.
Saves a rule the same way the Automation page's editor does. The script is checked first, and a script that would not run is refused with validation and the line it failed on. A rule name the marketplace already uses is refused with 409 conflict, and details carries the existing rule's id. Send an Idempotency-Key when retrying after a timeout so the exact first response is replayed.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | optional | Recommended for API-key callers. The same key and request replay the first response; reusing it for different rule contents is 409 idempotency_conflict. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace the rule belongs to. |
name | stringrequired | 1–120 characters, unique within the marketplace (compared case-insensitively, ignoring surrounding spaces and with inner whitespace collapsed, the way RULE("…") finds a rule). |
code | stringrequired | The rule's script in the MerchDash automation language (see /docs/language), at most 200,000 characters. Checked before it is saved: a script that would not run is refused with validation, and details names the line. |
schedule | stringoptional | When the rule runs by itself: manual, every12h, every24h, weekly, biweekly, monthly, or weekday:<sunday…saturday> or date:YYYY-MM-DD. manual means only when somebody runs it. Defaults to manual on create. |
mode | enumreview · autooptional | review puts the changes a run proposes in the review queue. auto writes them to Amazon as soon as the rule runs, with nobody in the loop. Defaults to review on create. |
enabled | booleanoptional | Whether the schedule is on. A manual rule cannot be enabled, because it has no schedule to turn on, and changing a rule's schedule to manual switches it off. Defaults to false on create, so a new rule never runs by itself until you say so. |
categoryId | stringoptional nullable | A category of the same marketplace, or null for none. |
seasonEnabled | booleanoptional | Limits scheduled runs to the dates between startDate and endDate. |
startDate | dateoptional nullable | YYYY-MM-DD. First day of the season. |
endDate | dateoptional nullable | YYYY-MM-DD, not before startDate. Last day of the season. |
lastRun* and timestamps are written by MerchDash, never by a caller.Response
Single resource, { data: { … } }.
Fields: the created rule, same as rules.list
POST /api/v1/rules/copy
Copy saved rules into other marketplaces of the same account.
The dashboard's copy dialog. Only the rule travels, its script, schedule, mode and season, never its runs or History. A name a target marketplace already uses (compared trimmed, whitespace-collapsed and case-insensitively) is skipped and listed in skipped, never duplicated. A copy keeps the source rule's mode, so an auto rule is copied as auto. Copies start switched off unless activate is true, and even then a rule with no schedule or a script that would not run stays off. A schedule only an operator may set is copied as manual (so that copy stays off too). An unknown rule id is 404; a marketplace this account does not own is 404.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | optional | Recommended for API-key callers. The same key and request replay the first response instead of copying again. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace the rules are copied FROM. |
ruleIds | arrayrequired | Rules of that marketplace to copy, at most 100. |
targetProfileIds | arrayrequired | Marketplaces to copy into, at most 50. The source marketplace itself is ignored. |
activate | booleanoptional | Switch the copies on. Defaults to false. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
results | array | One entry per target: { profileId, copied, skipped: [rule name], created: [{ sourceRuleId, ruleId }] }. |
copied | integer | Copies made, across all targets. |
skipped | integer | Copies skipped because the name was taken. |
PATCH /api/v1/rules/:ruleId
Edit an automation rule: its script, name, schedule, mode, category, season, or whether it is on.
Only the fields you send change. A new script is checked the same way a create checks it. Turning a rule on also re-checks its current script, so an old rule that no longer runs cannot be switched on. The edit applies from the rule's next run; a run already in flight keeps the script it started with. A run still queued for a slot picks up a new script when it starts, but whether it writes to Amazon (mode, or the apply it was asked with) was settled when it was accepted.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
ruleId (path) | stringrequired | As returned by GET /api/v1/rules. |
Request body
| Field | Type | Meaning |
|---|---|---|
name | stringoptional | 1–120 characters, unique within the marketplace (compared case-insensitively, ignoring surrounding spaces and with inner whitespace collapsed, the way RULE("…") finds a rule). |
code | stringoptional | The rule's script in the MerchDash automation language (see /docs/language), at most 200,000 characters. Checked before it is saved: a script that would not run is refused with validation, and details names the line. |
schedule | stringoptional | When the rule runs by itself: manual, every12h, every24h, weekly, biweekly, monthly, or weekday:<sunday…saturday> or date:YYYY-MM-DD. manual means only when somebody runs it. Defaults to manual on create. |
mode | enumreview · autooptional | review puts the changes a run proposes in the review queue. auto writes them to Amazon as soon as the rule runs, with nobody in the loop. Defaults to review on create. |
enabled | booleanoptional | Whether the schedule is on. A manual rule cannot be enabled, because it has no schedule to turn on, and changing a rule's schedule to manual switches it off. Defaults to false on create, so a new rule never runs by itself until you say so. |
categoryId | stringoptional nullable | A category of the same marketplace, or null for none. |
seasonEnabled | booleanoptional | Limits scheduled runs to the dates between startDate and endDate. |
startDate | dateoptional nullable | YYYY-MM-DD. First day of the season. |
endDate | dateoptional nullable | YYYY-MM-DD, not before startDate. Last day of the season. |
profileId included: a rule cannot move to another marketplace. An empty body is refused.Response
Single resource, { data: { … } }.
Fields: the updated rule, same as rules.list
DELETE /api/v1/rules/:ruleId
Delete an automation rule.
Stops a run of the rule that is waiting in the queue or in flight, its own evaluation and the writes it makes by itself; an apply of one of its reviews that a person approved is that person's and carries on, and removes the rule. Its runs and change history are kept, so every change it ever made stays readable and revertible in History. Pending reviews from the deleted rule leave the review queue; their run and proposed rows remain retained as audit data, but there is no longer a rule card from which to approve them, and POST /api/v1/runs/:runId/apply refuses them with 409 conflict (their creation reservations were released), discard them instead.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
ruleId (path) | stringrequired | As returned by GET /api/v1/rules. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
ruleId | string | |
deleted | boolean | Always true. |
POST /api/v1/rules/:ruleId/run
Run an automation rule now, the same as pressing Run on the Automation page.
Starts the run in the background and returns immediately. Poll GET /api/v1/rules/:ruleId/run until status is no longer queued or running, then follow its runId. A rule in review mode proposes its changes, and they wait in the review queue; approve them with POST /api/v1/runs/:runId/apply. A rule in auto mode writes its changes to Amazon itself. Asking again while the rule is already queued or running attaches to that run and does not start a second one, except a run in the other mode, or one MerchDash is still finishing after an update or an interruption, which is 409 run_in_progress. Refused with sync_incomplete until the rule's marketplace, and its linked partner, which the run also evaluates and writes, has completed its first full backfill, and while a full sync (a full re-sync included) is queued or running in either; that gate is checked first, so during a full sync asking again under a new Idempotency-Key, or from a dashboard session, which sends none, is sync_incomplete even while the rule is queued or running (a retry with the same key still replays; or follow the run through GET /api/v1/rules/:ruleId/run). During a MerchDash deploy it is refused with 503 restarting before anything starts, so it is safe to send again.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | The body is optional. A body that is just {} is accepted whatever its Content-Type; any other: Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | required | Required for API-key callers; ignored for dashboard sessions. A retry with the same key reattaches to the original start or returns its retained outcome instead of running the rule again. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
ruleId (path) | stringrequired | As returned by GET /api/v1/rules. |
Request body
| Field | Type | Meaning |
|---|---|---|
apply | booleanoptional | Overrides the rule's own mode for this one run. false only proposes, even on an auto rule. true writes to Amazon straight away, even on a review rule. Omit it to follow the rule's mode. |
Response
Single resource, { data: { … } }.
Fields: same as rules.runStatus
POST /api/v1/rules/:ruleId/run/cancel
Take a rule's run out of the queue before it starts.
Only a run still WAITING for a run slot is removed, including one a MerchDash update set aside to start again (GET /api/v1/rules/:ruleId/run reads it queued). A run that has already started is NOT stopped, it is writing to the live Amazon account, and abandoning it halfway is an operator decision, and running says so. After a cancel, retrying POST /api/v1/rules/:ruleId/run with the same Idempotency-Key reports the cancelled run (status: "failed" with the reason in error) instead of starting it; a new key starts a new run. A cancelled apply run gives back the auto-apply token its start spent.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | Not checked. A body, if sent, must be an empty JSON object, read as UTF-8, one that is not valid UTF-8, or holds a NUL or an unpaired surrogate, is validation. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
ruleId (path) | stringrequired | As returned by GET /api/v1/rules. |
Request body
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
ruleId | string | |
removed | boolean | True when a queued run was taken out of the queue. |
running | boolean | True when nothing was removed because the run has already started. |
GET /api/v1/rules/:ruleId/run
The progress and outcome of the rule's latest manual run.
The polling target after POST /api/v1/rules/:ruleId/run. MerchDash keeps a finished run's outcome here for about 15 minutes. A live run remains visible across replicas and restarts through its durable execution marker. After the retained outcome expires, status is idle and runId is the rule's last recorded run, which GET /api/v1/runs/:runId still answers. Polling never uses up the result, so two callers polling the same rule both see it.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
ruleId (path) | stringrequired | As returned by GET /api/v1/rules. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
ruleId | string | |
status | enumidle · queued · running · done · failed | queued: waiting for one of the account's run slots. running: evaluating or applying. done: finished, and runId says what it did. failed: the run did not finish, and error says why. idle: nothing is being tracked for this rule right now. Closed enum. |
runId | stringnullable | The run this produced, once it is done (or failed with a run to show), null there too when that run is hidden from you: it finished, with no id to follow. When idle, the rule's last recorded run, null when it has never run, or its last run left no readable record (see lastRunId on the rule). Read it with GET /api/v1/runs/:runId. |
message | stringnullable | Progress while queued or running ("Applying change 12 of 240…"). Display only: never parse it. |
error | stringnullable | Why a failed run did not finish. |
POST /api/v1/rules/validate
Check a rule script without saving or running it.
The same check the rule editor runs as you type, and the one POST /api/v1/rules and PATCH run before saving. A script that fails is still answered 200, with valid: false, because asking whether a script is valid is not a bad request. The language is described in /docs/language.md.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Request body
| Field | Type | Meaning |
|---|---|---|
code | stringrequired | The script, at most 200,000 characters. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
valid | boolean | |
error | stringnullable | Why the script would not run. Null when it is valid. |
line | integernullable | The line the problem is on, when the check can name one. |
POST /api/v1/rules/preview
Dry-run a script: what it would change right now, without saving it and without writing anything.
The rule editor's Dry run. The script is evaluated against the marketplace's synced data, and each change it proposes is checked against Amazon's current state, so a change that would do nothing is left out. Nothing is saved and nothing is written to Amazon. It runs in the background: poll GET /api/v1/rules/preview/:jobId for the result, usually within seconds and on a large account within minutes. A script that would not run is refused with validation before anything starts. Refused with sync_incomplete until the marketplace and its linked partner (both evaluated) have completed their first full backfill, and while a full sync (a full re-sync included) is queued or running in either, and with 503 restarting during a MerchDash deploy. Send an Idempotency-Key when retrying after a timeout so the first job id is replayed instead of starting the same dry-run twice.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | optional | Recommended for API-key callers. The same key and request replay the first job id; reusing it for different preview contents is 409 idempotency_conflict. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace to evaluate against. |
code | stringrequired | The script to try. |
ruleId | stringoptional | A saved rule of the same marketplace this script belongs to, usually an edited version of it. Cool-downs that count "this rule"'s own changes (DAYS_SINCE_CHANGE("bid", "this rule")) then read that rule's history, as the real run would. Without it, the script is tried as a new rule that has never changed anything. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
jobId | string | Poll GET /api/v1/rules/preview/:jobId. |
status | enumrunning |
GET /api/v1/rules/preview/:jobId
The progress and result of a dry run.
A result is kept for at least 24 hours after it finishes, and until then every poll returns it from any application replica. A worker restart cancels its running dry-runs, while their durable status remains available. After the retention window the job is 404 not_found and has to be started again with a new Idempotency-Key.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
jobId (path) | stringrequired | As returned by POST /api/v1/rules/preview. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
jobId | string | |
status | enumrunning · done · failed · cancelled | running until the dry run ends. done: result holds the answer. failed: the script failed while it ran (a division by zero, a time limit), and error says why; result still carries its log. A dry run that could not run at all is also failed: result is null and error says why. cancelled: MerchDash stopped it, usually for a deploy; start it again. Closed enum. |
message | stringnullable | Progress while running. Display only: never parse it. |
error | stringnullable | |
result | objectnullable | Null while running. |
result.proposed | integer | How many changes the rule would make. |
result.impact | object | How much of the account those changes touch. |
result.impact.campaigns | integer | |
result.impact.adGroups | integer | |
result.impact.targetings | integer | Keywords, targets and negatives. |
result.impact.approximate | boolean | True when a count stopped at its ceiling and is a lower bound. |
result.changes | array | The first 200 (by default) changes, each { entityType, entityId, entityName, campaignId, campaignName, adGroupId, adGroupName, field, beforeVal, afterVal, reason }, the same fields GET /api/v1/runs/:runId/changes uses, plus the two names. |
result.sampled | boolean | True when changes is only the first part of proposed. |
result.truncated | boolean | True when the rule hit its own change cap (# MAX CHANGES) and stopped early, so later blocks never ran. |
result.log | string | What the rule wrote with log(), plus MerchDash's notes on what it skipped and why. The first place to look when a rule proposes nothing. |
POST /api/v1/rules/preview/:jobId/cancel
Stop a dry run that is still running.
Cancellation is cooperative: the job stops at its next safe checkpoint. Poll the ordinary preview status endpoint until it reports cancelled. A job that has expired or belongs to another account is 404.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | Not checked. A body, if sent, must be an empty JSON object, read as UTF-8, one that is not valid UTF-8, or holds a NUL or an unpaired surrogate, is validation. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
jobId (path) | stringrequired | As returned by POST /api/v1/rules/preview. |
Request body
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
jobId | string | |
status | enumcancelling · done · failed · cancelled |
GET /api/v1/categories
The marketplace's rule categories.
The named groups rules are sorted into on the Automation page. A category that is switched off stops the scheduled runs of every rule inside it, and inside every category nested in it, without changing each rule's own enabled. parentId gives the nesting.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace, as returned by GET /api/v1/profiles. Rules and categories belong to one marketplace each. Unlike the data reads, a linked pair of Amazon accounts is NOT merged here. |
Response
Collection, { data: [ … ], nextCursor }, and nextCursor is null on the last page.
Order: the order the Automation page shows them in. Not paginated: nextCursor is always null.
| Field | Type | Meaning |
|---|---|---|
categoryId | string | |
profileId | string | |
name | string | |
enabled | boolean | The category's own switch. A category nested inside an off one is paused too while this stays true. |
parentId | stringnullable | The category this one sits inside, or null at the top level. |
createdAt | timestampnullable | |
updatedAt | timestampnullable |
POST /api/v1/categories
Create a rule category.
The new category goes to the top of the Automation page. A name the marketplace already uses is refused with 409 conflict, and details carries the existing category's id. Put rules in it with categoryId on POST or PATCH /api/v1/rules. Send an Idempotency-Key when retrying after a timeout.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Idempotency-Key | optional | Recommended for API-key callers. The same key and request replay the first response; reusing it for different category contents is 409 idempotency_conflict. |
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | The marketplace the category belongs to. |
name | stringrequired | 1–60 characters, unique within the marketplace. |
enabled | booleanoptional | Off stops the scheduled runs of every rule in the category and in every category nested inside it. Defaults to true on create. |
parentId | stringoptional nullable | The category this one sits inside, of the same marketplace, or null for the top level (the default on create). A category can be nested up to 4 levels deep and never inside itself or one of its own sub-categories; a parent that breaks either rule is refused with 400 validation. |
Response
Single resource, { data: { … } }.
Fields: the created category, same as categories.list
PATCH /api/v1/categories/:categoryId
Rename a rule category, move it into another one, or switch it on or off.
Only the fields you send change. Switching a category off stops its rules' scheduled runs, and those of the categories nested in it, but a rule can still be run by hand. Send parentId: null to take a category out of its parent.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
categoryId (path) | stringrequired | As returned by GET /api/v1/categories. |
Request body
| Field | Type | Meaning |
|---|---|---|
name | stringoptional | 1–60 characters, unique within the marketplace. |
enabled | booleanoptional | Off stops the scheduled runs of every rule in the category and in every category nested inside it. Defaults to true on create. |
parentId | stringoptional nullable | The category this one sits inside, of the same marketplace, or null for the top level (the default on create). A category can be nested up to 4 levels deep and never inside itself or one of its own sub-categories; a parent that breaks either rule is refused with 400 validation. |
Response
Single resource, { data: { … } }.
Fields: the updated category, same as categories.list
DELETE /api/v1/categories/:categoryId
Delete a rule category.
Only the category is deleted. Its rules are kept, become uncategorized, and keep their own schedule and enabled. So a rule that was held back only because its category was off starts running on schedule again. Categories nested in it move up to its own parent.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
categoryId (path) | stringrequired | As returned by GET /api/v1/categories. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
categoryId | string | |
deleted | boolean | Always true. |
GET /api/v1/runs/:runId/log
A run's log: what its rule wrote with log(), and MerchDash's own notes.
The same text as the Run log in the dashboard. It holds the lines the rule wrote with log() (at most 200) and MerchDash's notes about the run: entities it skipped and why, a change cap it hit, a time limit. When a rule proposes nothing, this usually says why. Runs the API proposed have no log of their own: theirs is an empty string, or only MerchDash's notes on finishing one after a restart (for example "[recovered automatically after restart]"). Do not use an empty log to tell API runs apart, use trigger.
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
runId | string | |
log | string | Plain text, one line per entry. Empty when there is nothing to show. |
POST /api/v1/runs/:runId/apply
Approve a pending review and write its changes to Amazon, the same as pressing Apply in the review queue.
Works on any run awaiting review: status preview, or partial while some of its changes are still proposed (a review applied in part), whether a rule or an API proposal created it. It returns as soon as the apply has started, and the writes happen in the background. Poll GET /api/v1/runs/:runId: it reports running until the writes settle, then applied, partial, failed (every change was refused), reverted (a review applied in part one of whose changes was since rolled back) or the status it had before, preview, or partial for a review already applied in part, for an apply stopped before it wrote anything, or a changeIds apply none of whose changes could be written while the rest of the run still awaits review. Poll until it is no longer running. A second apply of the same run with the same change selection attaches to it, so it does not write anything twice. A different selection while one is in progress is 409 conflict. A run that is no longer awaiting review is 409 conflict. An apply about to start is refused with sync_incomplete until the marketplace has completed its first full backfill, and while a full sync (a full re-sync included) is queued or running in its scope; attaching to one already running is not.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | The body is optional. A body that is just {} is accepted whatever its Content-Type; any other: Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired | A run awaiting review: status preview, or partial while some of its changes are still proposed (a review applied in part), see GET /api/v1/runs?status=preview and ?status=partial. |
Request body
| Field | Type | Meaning |
|---|---|---|
changeIds | arrayoptional | Apply only these changes of the run (ids from GET /api/v1/runs/:runId/changes), at most 10000. The rest stay pending, and the run reads partial until they are applied or discarded, or, if none of the selected changes could be written, what it read before (preview, or partial for a review already applied in part). Omit it to apply every pending change. |
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
runId | string | |
status | enumrunning | The apply has been accepted. Poll GET /api/v1/runs/:runId for the outcome. When the marketplace already has several applies writing, this one waits for a slot and starts by itself; it reads running meanwhile and writes nothing until its turn. |
total | integer | How many changes this apply will write. |
POST /api/v1/runs/:runId/retry-failed
Send a finished run's failed changes to Amazon again, the Retry button in History.
For a run that reads partial or failed, or a review some of whose approved changes failed. Only changes that are safe to send twice go back: a bid, state, budget or other setting, an archive, and a create Amazon refused outright (the apply looks the entity up by name first, so nothing is built twice). A create whose answer was lost may already exist on Amazon and is left alone; it is counted in skipped. The writes happen in the background exactly as an apply's do: poll GET /api/v1/runs/:runId until it is no longer running. A run still writing, or one with nothing to resend, is 409 conflict; refused with sync_incomplete while the marketplace is backfilling. Each call spends one request of the key's auto-apply budget, as an apply does.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | Not checked. A body, if sent, must be an empty JSON object, read as UTF-8, one that is not valid UTF-8, or holds a NUL or an unpaired surrogate, is validation. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired | A run with failed changes: status partial or failed (GET /api/v1/runs?status=partial). |
Request body
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
runId | string | |
status | enumrunning | The resend has started (or is waiting for one of the account's apply slots). Poll GET /api/v1/runs/:runId. |
retried | integer | How many failed changes go back to Amazon. |
skipped | integer | Failed changes left as they are, because sending them again could create something twice. Run the rule again to propose them afresh. |
POST /api/v1/runs/:runId/discard
Turn down a pending review, the same as pressing Discard in the review queue. Nothing is written to Amazon.
Works on any run awaiting review: status preview, or partial while some of its changes are still proposed (a review applied in part). What happens to the run afterwards depends on where it came from. An API proposal is kept and reads discarded. A rule's review is deleted, as it is in the dashboard, so GET /api/v1/runs/:runId then answers 404. If part of the review was already applied, the applied changes stay in History, only the rest is discarded, and the run settles on what it wrote (status in the answer). A run that is no longer awaiting review is 409 conflict: an applied run is undone by reverting it in MerchDash, not by discarding it.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | optional | Not checked. A body, if sent, must be an empty JSON object, read as UTF-8, one that is not valid UTF-8, or holds a NUL or an unpaired surrogate, is validation. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired | A run awaiting review: status preview, or partial while some of its changes are still proposed (a review applied in part), see GET /api/v1/runs?status=preview and ?status=partial. |
Request body
Response
Single resource, { data: { … } }.
| Field | Type | Meaning |
|---|---|---|
runId | string | |
status | enumdiscarded · applied · partial · reverted | The status the run now reads. discarded when nothing of it was written. A review part of which was already applied keeps what it wrote and reads applied, partial when some of it failed, or reverted when some of it has since been rolled back. |
deleted | boolean | True when the run was removed entirely (a rule's review with nothing applied). False when it stays readable at its id. |
PATCH /api/v1/runs/:runId/changes/:changeId
Change the bid or budget a pending change proposes, before approving it, the review table's own edit.
Only a change still proposed, and only one that writes a number: a keyword or target bid (field: "bid") or a campaign budget (field: "budget"). The new bid passes the same checks a rule's does, Amazon's range for the marketplace and ad format, and the owner's maximum bids, so afterVal in the answer may differ from what was asked: lower where a ceiling caps it, higher where Amazon's floor raises it. Sending the original value again returns the change to exactly what was proposed. A change that is no longer proposed, whose run is being applied right now, or whose review's rule has since been deleted (that review can only be discarded) is 409 conflict; a change id from a different run is 404.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json, with the body in UTF-8, a body that is not valid UTF-8 is validation, as is a NUL or an unpaired surrogate (\ud800) in any string. Character limits count UTF-16 code units, as JavaScript's length does (an emoji counts as two). Fields that refuse control characters say so. |
Parameters
| Parameter | Type | Meaning |
|---|---|---|
runId (path) | stringrequired | A run awaiting review: status preview, or partial while some of its changes are still proposed. |
changeId (path) | stringrequired | As returned by GET /api/v1/runs/:runId/changes. |
Request body
| Field | Type | Meaning |
|---|---|---|
value | numberrequired | The new bid (greater than 0) or daily budget (at least 1, Amazon's floor, a smaller one is validation), rounded to cents. |
Response
Single resource, { data: { … } }.
Fields: same as one row of runs.changes
Run statuses
The lifecycle a proposal moves through, as reported by GET /api/v1/runs/{runId}. This is a closed enum: a client may switch on it exhaustively, and MerchDash’s internal bookkeeping states are mapped into these rather than added to them.
| Status | Meaning | Can become |
|---|---|---|
preview | Proposed and waiting for a human to approve it in MerchDash. Nothing has been written to Amazon. A rule's review whose rule has since been deleted stays here but can no longer be approved, only discarded (POST /api/v1/runs/:runId/apply answers 409 conflict). | running, discarded, partial |
running | The writes are in flight: a human approved it, or it applies by itself (an auto rule or a proposal sent with applyMode: "auto"). | applied, partial, failed, preview, reverted |
applied | Every change was written to Amazon. | reverted |
partial | Some changes were written and others were not, because they failed, or because they are still awaiting approval. As with preview, a review whose rule has since been deleted can no longer be approved, only discarded. | reverted, running, applied |
failed | The run ended without writing anything it meant to write, as far as MerchDash could tell. One exception: a create Amazon did not answer in time is looked up afterwards, and if Amazon did make it, the run moves to partial or applied. | partial, applied |
reverted | At least one applied change has since been rolled back. A review still awaiting approval reads partial instead, even with a rolled-back change, until the rest of it is applied or discarded. | nothing, terminal |
discarded | A human turned the proposal down. Nothing was written to Amazon, and the run stays readable at this id. | nothing, terminal |
Error codes
Every error response carries one of these as code, alongside a human-readable error. Switch on the code; the message wording may be improved at any time.
| Code | HTTP | Retry? | What happened |
|---|---|---|---|
invalid_key | 401 | stop | The bearer token is not a usable MerchDash API key. |
key_revoked | 401 | stop | The key was revoked by its owner. |
key_expired | 401 | stop | The key is past its expiry date. |
plan | 403 | stop | The account's plan does not include API access. The key is not revoked, an upgrade re-enables it. |
scope | 403 | fix | The credential does not carry the capability this request requires, a read-only key on a write route, a key without the automation scope on an automation route, a browser session on an endpoint that needs a key, "applyMode": "auto" on a key whose owner has not turned auto-apply on for it, or a marketplace-limited key running, cancelling, dry-running, approving, discarding or reading the log of a rule or run that also covers a linked Amazon account the key does not. |
account_unavailable | 403 | stop | The account is not currently available. Contact the account administrator. |
not_found | 404 | fix | No such route, or no such resource in this account. An id belonging to somebody else answers exactly this. |
validation | 400 | fix | The request could not be read or accepted. A query parameter or body field this endpoint does not document is refused here rather than ignored, so a misspelled filter cannot come back as an unfiltered answer, and so is a query parameter sent more than once. May carry a bounded details array naming each problem. |
invalid_cursor | 400 | fix | The cursor is not one of ours, or was issued for a different endpoint or different filters. |
payload_too_large | 413 | fix | The JSON request body exceeds this endpoint's size limit. |
conflict | 409 | fix | The request conflicts with the resource's current state. Some of these can clear without the request changing, once another proposal or apply on the same entity (or, for a rename, wanting the same name) is decided, or once the Amazon account is reconnected in MerchDash, but whether a given one will cannot be told from the response, so an automatic retry should treat every conflict as fix and leave the rest to a human reading the message. |
idempotency_conflict | 409 | fix | This Idempotency-Key was already used for a different request body. |
run_in_progress | 409 | later | The rule is already running in the other mode (preview vs apply), or its last run is still being written (MerchDash finishing it after an update or an interruption). A request cannot attach to that run, which would answer it with the wrong mode or has no job to attach to; retry once the run has finished, GET /api/v1/rules/{ruleId}/run says when. |
creation_conflict | 409 | later | A campaign, ad group or portfolio this request wants to create is already reserved by another run, one not yet applied or turned down, or one still creating it on Amazon. Retry once that run is decided, or later for one being created; each details entry names the run and says which. The reservation is what stops the same container being built twice. A new campaign whose name an existing campaign is being renamed to, in a proposal still waiting for approval, is refused the same way. |
rate_limited | 429 | wait | A rate limit was exhausted. Retry-After says how long to wait. |
review_queue_full | 429 | later | This marketplace already holds 75 un-answered proposals. Retry once a human has worked through some. |
sync_incomplete | 409 | later | A marketplace in scope (the message names it, for a linked pair, possibly the partner) has not completed its first full (95-day) backfill yet, after one that failed or was paused, its next sync runs that backfill, or has a full sync, a first backfill or a full re-sync, which rebuilds the archive, queued or running, so proposals cannot be validated against its snapshot yet. Retry later; GET /api/v1/sync/status shows progress, but its initialSyncComplete covers a linked pair whole and can stay false after this request would pass. |
restarting | 503 | wait | MerchDash is mid-deploy. The request was refused before it did anything and is safe to re-send. |
temporarily_unavailable | 503 | wait | A dependency the request needs, shared rate enforcement, for one, could not be reached. |
internal | 500 | wait | MerchDash could not complete the request because of an unexpected server error. Retry with backoff and report the requestId if it persists. |