Skip to main content

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 called
  • change-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 modified
  • entities links in _links pointing 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 AUTOMATIC and MANUAL campaign 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 MANUAL campaign'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.

  1. Create the campaign:
POST /v1/sponsored-product-ads/campaigns

with campaignType: AUTOMATIC. Provide name, budget, budgetType, pacing, startDate, and brandId.

  1. Poll the change-request link from the response until status is ACCEPTED, then follow an entities link to retrieve the campaignId.

  2. 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.

  1. Create the campaign:
POST /v1/sponsored-product-ads/campaigns

with campaignType: MANUAL.

  1. Poll the change-request link until ACCEPTED, then follow an entities link to get the campaignId.

  2. 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.

  1. 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 status is ACCEPTEDentities links in _links point to the created or updated resources.
  • When status is REJECTED — a rejectionReason field explains why the change failed. The entity was not created or modified.

ChangeRequest Rejection Reasons

Rejection reasonApplies toDescription
"The specified SKU does not exist in the product catalog."Create targetSKU not found in OTTO's product catalog — can only be verified asynchronously.
"The partner account is blocked and cannot create or modify campaigns."AllPartner has been blocked; no write operations will be processed until the block is lifted.
"An internal error occurred while processing the request. Please retry."AllTransient internal failure. The entity was not modified — safe to retry.

Campaign Status

StatusDescription
PENDINGThe campaign is being validated and prepared for creation.
WAITINGThe campaign is valid and waiting for its startDate to be reached.
ACTIVEThe campaign is live and delivering ads.
PAUSEDThe campaign has been manually paused. Ad delivery is stopped but configuration is preserved.
ENDEDThe campaign has passed its endDate and is no longer delivering ads.
CANCELEDThe campaign was canceled and cannot be reactivated.

ChangeRequest Status

Every write operation returns a changeRequest in the response body:

StatusDescription
PENDINGThe request has been accepted and is being processed asynchronously.
ACCEPTEDThe change was applied successfully and is now reflected in GET responses.
REJECTEDThe 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, or LIFETIME.
  • pacing - Controls how the budget is spent: ASAP (spend as fast as possible) or EVEN (distribute evenly over the period).
  • matchType - Determines how closely a search query must match a keyword: EXACT, PHRASE, or BROAD.
  • 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 _links object 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 href property (full URI).