Sponsored Product Ads
The Sponsored Product Ads API allows partners to manage advertising campaigns that promote their products
in OTTO search results and product listings.
All write operations are asynchronous and return 202 Accepted.
PATCH operations use JSON Merge Patch (RFC 7386)
with Content-Type: application/merge-patch+json. Only include the fields you want to change;
omitted fields remain unchanged. Setting a nullable field to null clears its value.
Asynchronous Creation Flow
Entity IDs (e.g. campaignId, targetId, keywordId) are not returned synchronously on creation.
Instead, every write response contains only a changeRequest object and a links array.
The _links object always includes:
self— the collection endpoint that was calledchange-request— direct link to poll the status of this specific change
{
"changeRequest": {
"requestId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"requestType": "CREATE_CAMPAIGN",
"status": "PENDING",
"lastModifiedAt": "2026-04-23T08:00:00Z"
},
"_links": {
"self": { "href": "https://api.otto.market/v1/sponsored-product-ads/campaigns" },
"change-request": { "href": "https://api.otto.market/v1/sponsored-product-ads/change-requests/3fa85f64-5717-4562-b3fc-2c963f66afa6" }
}
}
Follow the change-request link to poll for the outcome. Use exponential backoff between retries to avoid 429 Too Many Requests errors. Once the status transitions to ACCEPTED,
the response contains:
entityIds— the list of IDs of all entities that were created or modifiedentitieslinks in_linkspointing to the created or modified resources
{
"requestId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"requestType": "CREATE_CAMPAIGN",
"status": "ACCEPTED",
"lastModifiedAt": "2026-04-23T08:00:01Z",
"entityIds": ["a1b2c3d4-0001-4000-8000-000000000001"],
"_links": {
"self": { "href": "https://api.otto.market/v1/sponsored-product-ads/change-requests/3fa85f64-5717-4562-b3fc-2c963f66afa6" },
"entities": [
{ "href": "https://api.otto.market/v1/sponsored-product-ads/campaigns/a1b2c3d4-0001-4000-8000-000000000001" }
]
}
}
If the status is REJECTED, a rejectionReason field explains why the request failed.
The entity was not created and no entities links are present.
One pending change request at a time
Only one change request per campaign may be PENDING at a time. Submitting a new write operation
while a change request for that campaign is still pending returns 409 Conflict synchronously:
{
"errors": [
{
"message": "A change request for this campaign is already in progress. Please wait for it to complete."
}
],
"timestamp": "2026-04-15T10:00:00Z"
}
Poll the change-request link until the status transitions to ACCEPTED or REJECTED before
retrying the operation.
Endpoint groups
- Campaigns — Create and manage ad campaigns. Supports
AUTOMATICandMANUALcampaign types, updating campaign settings such as budget, pacing, and dates, and controlling the campaign lifecycle (pause, re-activate). - Targets — Manage the products (SKUs) advertised within a campaign. Each target links a SKU to a campaign and optionally defines a bid.
- Keywords — Manage the keywords that trigger a
MANUALcampaign's ads. Keywords belong to a target and define the search terms, match type, and keyword type (positive or negative). - Change Requests — Retrieve the processing status of any write operation by its
requestId, including the rejection reason if the change was not accepted.
Common use cases
Creating an Automatic Campaign
Automatic campaigns let OTTO determine targeting on the partner's behalf. No keyword configuration is required.
- Create the campaign:
POST /v1/sponsored-product-ads/campaigns
with campaignType: AUTOMATIC. Provide name, budget, budgetType, pacing, startDate, and brandId.
-
Poll the
change-requestlink from the response until status isACCEPTED, then follow anentitieslink to retrieve thecampaignId. -
Add targets:
POST /v1/sponsored-product-ads/campaigns/{campaignId}/targets
Add the SKUs to advertise. For AUTOMATIC campaigns, a per-target bid is required.
Creating a Manual Campaign with Keywords
Manual campaigns give partners full control over which search terms trigger their ads.
- Create the campaign:
POST /v1/sponsored-product-ads/campaigns
with campaignType: MANUAL.
-
Poll the
change-requestlink untilACCEPTED, then follow anentitieslink to get thecampaignId. -
Add targets:
POST /v1/sponsored-product-ads/campaigns/{campaignId}/targets
Add the SKUs to advertise. Poll the change-request link to get each targetId via the entities links.
- Add keywords:
POST /v1/sponsored-product-ads/campaigns/{campaignId}/targets/{targetId}/keywords
Provide keyword, matchType (EXACT, PHRASE, or BROAD), and keywordType (POSITIVE or NEGATIVE).
A keyword-level bid can be set to override the campaign default.
Pausing and Re-activating a Campaign
Use the update campaign endpoint to control the campaign lifecycle. Targets and keywords are preserved in both directions.
PATCH /v1/sponsored-product-ads/campaigns/{campaignId}
Content-Type: application/merge-patch+json
Pause:
{
"status": "PAUSED"
}
Re-activate:
{
"status": "ACTIVE"
}
Only campaigns with status ACTIVE or PAUSED can be toggled. Campaigns in terminal states
(ENDED, CANCELED) cannot be reactivated.
Adding Negative Keywords to Exclude Search Terms
Negative keywords prevent ads from being shown when specific search terms are used, helping to avoid
irrelevant traffic and improve campaign efficiency. They can be added to any target in an AUTOMATIC campaign.
POST /v1/sponsored-product-ads/campaigns/{campaignId}/targets/{targetId}/keywords
Set keywordType: NEGATIVE and choose a matchType to control how broadly the exclusion applies:
{
"keywords": [
{
"keyword": "cheap",
"matchType": "BROAD",
"keywordType": "NEGATIVE"
},
{
"keyword": "used laptop",
"matchType": "EXACT",
"keywordType": "NEGATIVE"
}
]
}
Negative keywords do not require a bid. Existing negative keywords can be managed via the
PATCH and DELETE keyword endpoints.
Checking the Status of a Change Request
Every write response contains a change-request link. Follow it to poll the outcome:
GET /v1/sponsored-product-ads/change-requests/{requestId}
Use exponential backoff between poll attempts to avoid 429 Too Many Requests errors (e.g. start at 1 s, double on each retry: 1 s → 2 s → 4 s → 8 s …).
- When
statusisACCEPTED—entitieslinks in_linkspoint to the created or updated resources. - When
statusisREJECTED— arejectionReasonfield explains why the change failed. The entity was not created or modified.
ChangeRequest Rejection Reasons
| Rejection reason | Applies to | Description |
|---|---|---|
"The specified SKU does not exist in the product catalog." | Create target | SKU not found in OTTO's product catalog — can only be verified asynchronously. |
"The partner account is blocked and cannot create or modify campaigns." | All | Partner has been blocked; no write operations will be processed until the block is lifted. |
"An internal error occurred while processing the request. Please retry." | All | Transient internal failure. The entity was not modified — safe to retry. |
Campaign Status
| Status | Description |
|---|---|
PENDING | The campaign is being validated and prepared for creation. |
WAITING | The campaign is valid and waiting for its startDate to be reached. |
ACTIVE | The campaign is live and delivering ads. |
PAUSED | The campaign has been manually paused. Ad delivery is stopped but configuration is preserved. |
ENDED | The campaign has passed its endDate and is no longer delivering ads. |
CANCELED | The campaign was canceled and cannot be reactivated. |
ChangeRequest Status
Every write operation returns a changeRequest in the response body:
| Status | Description |
|---|---|
PENDING | The request has been accepted and is being processed asynchronously. |
ACCEPTED | The change was applied successfully and is now reflected in GET responses. |
REJECTED | The change was rejected. The entity remains in its previous state. |
Glossary
- campaignId - Unique identifier of an ad campaign.
- targetId - Unique identifier of a target (SKU-to-campaign association).
- keywordId - Unique identifier of a keyword within a target.
- campaignType - Defines whether OTTO selects targeting automatically (
AUTOMATIC) or the partner configures it manually (MANUAL). - budgetType - The period over which the campaign budget is applied:
DAILY,WEEKLY,MONTHLY, orLIFETIME. - pacing - Controls how the budget is spent:
ASAP(spend as fast as possible) orEVEN(distribute evenly over the period). - matchType - Determines how closely a search query must match a keyword:
EXACT,PHRASE, orBROAD. - keywordType - Defines whether a keyword should trigger (
POSITIVE) or suppress (NEGATIVE) the ad. - bid - The maximum amount the partner is willing to pay per click, expressed in minor units (cents).
- changeRequest - Tracks the asynchronous processing state of a write operation.
- SKU - Stock Keeping Unit; the partner's unique product identifier.
- Links - HAL-style
_linksobject included in every response, providing navigation URLs. Keys are relation types:self(current resource),next/prev(pagination),change-request(status of the write operation),entities(array of links to created or modified resources, present once a change request is accepted). - Link - A HAL link object with an
hrefproperty (full URI).