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 | 25 |
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/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/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. |
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. |
POST /api/v1/changes | write | Propose a batch of bid, state and negative 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/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; filter on state.
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. |
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. 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: 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. |
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 | |
biddingStrategy | stringnullable | |
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/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. |
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. 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: 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. |
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 not entities a caller can read in v1 — they are created through POST /api/v1/changes and shown in the review queue.
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. |
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. 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: 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. |
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. |
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 not readable in v1.
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. |
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. 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: 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. |
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. |
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, read it from the ad group it lives in.
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. |
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. 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/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. |
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. 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: 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 the first backfill has finished — proposals are refused with sync_incomplete until it has.
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. |
initialSyncComplete | boolean | False while the first backfill is still running. Proposals are refused until it is true. |
syncing | boolean | True while a sync is running or queued right now. |
firstSyncedDate | stringnullable | Oldest report day held, YYYY-MM-DD. |
lastSyncedDate | stringnullable | Newest report day held, YYYY-MM-DD. |
syncedDays | integer | How many report days are held. |
campaigns | integer | Campaigns in the snapshot. |
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. 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 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 | review waits for a human; auto applies by itself. |
label | stringnullable | The batch label for an API proposal, or the rule name for a rule run. |
ruleId | stringnullable | Null for an API proposal — it has no 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 | Still awaiting approval. |
counts.applied | integer | |
counts.failed | integer | |
counts.reverted | integer | |
counts.discarded | integer |
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. 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. |
entityType | stringnullable | campaign / adGroup / keyword / target / productAd / negativeKeyword / negativeTarget. |
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, … |
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 |
POST /api/v1/changes
Propose a batch of bid, state and negative 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: a signed-in human applies or turns down the proposal in MerchDash's review queue. On an auto-apply key, or with "applyMode": "auto", MerchDash applies the batch itself as soon as it is accepted. Either way an API key can watch the outcome through GET /api/v1/runs/:runId and can never approve, discard or revert a run itself.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json. |
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. A mixed batch 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.
| Field | Type | Meaning |
|---|---|---|
entityType | enumkeyword · targetrequired | |
entityId | stringrequired | Must exist in this marketplace's snapshot. |
bid | numberrequired | In the marketplace currency. $0.02–$1,000 ($0.25 minimum on Sponsored Brands). 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. |
reason | stringoptional | Up to 200 characters, shown to the reviewer. Display only. |
Operation: set_state
Enable or pause an entity.
| Field | Type | Meaning |
|---|---|---|
entityType | enumcampaign · adGroup · keyword · target · productAdrequired | |
entityId | stringrequired | |
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: add_negative_keyword
Add a negative keyword at campaign or ad-group scope.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
adGroupId | stringoptional | Omit for a campaign-wide negative. Present means ad-group scope. |
keywordText | stringrequired | 1–80 characters. |
matchType | enumNEGATIVE_EXACT · NEGATIVE_PHRASErequired | |
reason | stringoptional |
Operation: add_negative_target
Add a negative product target (an ASIN) to an ad group.
| Field | Type | Meaning |
|---|---|---|
campaignId | stringrequired | |
adGroupId | stringrequired | Required — Amazon has no campaign-scoped negative product target. |
asin | stringrequired | A 10-character ASIN. |
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: a signed-in human applies the proposal in MerchDash, 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. A retry, a second proposal, or a nightly automation rule asking for the same campaign in the same marketplace lands on that same reservation instead of building a second one.
Headers
| Header | Required | Meaning |
|---|---|---|
Content-Type | required | Must be application/json. |
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
- Sponsored Products only. An SB ad group needs a creative and a landing page and an SD one a tactic, neither of which this API can supply, so a node naming a Sponsored Brands or Sponsored Display campaign is refused.
- 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 the matched container and its id; it does not echo settings that were ignored. - At most 2000 campaigns and ad groups, and 20000 ASINs, keywords and targeting clauses, per request.
Request body
| Field | Type | Meaning |
|---|---|---|
profileId | stringrequired | Every node in the graph belongs to this marketplace. |
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. |
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. 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. |
budget | numberrequired | Daily budget in the marketplace currency. 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. Created if the account does not have one, and the campaign waits for it. |
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 and be Sponsored Products. |
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. 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. $0.02–$1,000, and the account's own maximum-bid setting applies. 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. 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. |
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 | |
state | enumENABLED · PAUSEDoptional | |
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 change rows the run holds. Not the number of entities: ASINs and targeting clauses are batched one row per ad group. |
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 }. |
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. | running, discarded, partial |
running | A human approved it and the writes are in flight. | applied, partial, failed |
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. | reverted, running |
failed | The run ended without writing anything it meant to write. | nothing — terminal |
reverted | At least one applied change has since been rolled back. | 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 browser session on an endpoint that needs a key, or "applyMode": "auto" on a key whose owner has not turned auto-apply on for it. |
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. 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. |
idempotency_conflict | 409 | fix | This Idempotency-Key was already used for a different request body. |
creation_conflict | 409 | later | A campaign or ad group this request wants to create is already reserved by another proposal that nobody has answered yet. Apply or turn that one down first — the reservation is what stops the same container being built twice. |
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 25 un-answered proposals. Retry once a human has worked through some. |
sync_incomplete | 409 | later | The marketplace's first data sync has not finished, so proposals cannot be validated against its snapshot yet. |
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. |