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 a changeRequest object and a _links object.

For campaign and target write responses, _links always includes:

  • self — the collection endpoint that was called
  • change-request — direct link to poll the status of this specific change

Keyword write responses also include target and campaign links.

{
"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 on changeRequest. For accepted change-request lookups, the response currently includes a campaign link in _links.

{
"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, and startDate.

  1. Poll the change-request link from the response until status is ACCEPTED, then read changeRequest.entityIds to get 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.

  1. Add keywords:
POST /v1/sponsored-product-ads/campaigns/{campaignId}/targets/{targetId}/keywords

Provide keyword, matchType (EXACT, PHRASE, or BROAD), and keywordType (NEGATIVE).


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. Poll the change-request link until ACCEPTED, then read changeRequest.entityIds to get the campaignId.

  3. Add targets:

POST /v1/sponsored-product-ads/campaigns/{campaignId}/targets

Add the SKUs to advertise. Read changeRequest.entityIds to get each targetId.

  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 ACCEPTED — accepted change-request lookups include a campaign link in _links.
  • When status is REJECTED — a rejectionReason field explains why the change failed. The entity was not created.

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 responses, providing navigation URLs. Keys vary by operation: self, next/prev, change-request, campaign, target, keywords.
  • Link - A HAL link object with an href property (full URI).