Tactical recommendations

Recommendations the service publishes for an account, and the routes to apply or edit one.

class ad_api.api.Recommendations(account: str = 'default', marketplace: Marketplaces = Marketplaces.EU, credentials: Dict[str, str] | None = None, proxies: Dict[str, str] | None = None, verify: bool = True, timeout: int | None = None, debug: bool = False, access_token: str | None = None, verify_additional_credentials: bool = True)

Note

Amazon lists these under Recommendations & insights in its API index, alongside Overlapping audiences, without a beta marker. Seven other pages there do carry (beta); this one does not, so the label is gone from this page and from the readme.

### Endpoint map

:header-rows: 1

Path

Method

Kind

Answer

POST /recommendations/list

list_recommendations

read

200

POST /recommendations/apply

apply_recommendations

write

200

PUT /recommendations/{recommendationId}

update_recommendation

write

200

All three answer at version 1, which is the default. Unlike the Insights routes, where version 1 was retired and answered 415, these work without passing a version.

### The STATUS filter

The filter field STATUS does not take campaign states. It takes the stages a recommendation passes through, and the service lists them when you get it wrong:

Filter for recommendation status must only contain values
[PUBLISHED, APPLY_IN_PROGRESS, APPLY_SUCCESS, APPLY_FAILED, REJECTED]

ENABLED and ACTIVE are refused. That enum is not in the contract and only comes from the rejection message.

### About filters

filters behaves oddly, and the contract does not explain it:

:header-rows: 1

Body

Result

{"maxResults": 10}

200, the account’s recommendations

{"filters": []}

400 Member must have length greater than or equal to 1

{"filters": [{...}]}

200, the filtered set

Required when present, optional in practice. Each filter takes field, values, include and operator:

:header-rows: 1

Member

Type

Meaning

field

string

RECOMMENDATION_ID, AD_PRODUCT, RECOMMENDATION_TYPE, STATUS, GROUPING_TYPE or CAMPAIGN_ID.

values

array

Between 1 and 500 strings.

include

boolean

Include the match or exclude it. Defaults to true.

operator

string

EXACT is the only value.

### A body is required

Every method takes one. Calling without it fails before the request is sent:

KeyError: 'body'

That comes from the package, not the service, so it costs nothing but says nothing helpful either.

### The writes were not exercised

apply_recommendations edits real bidding and budgets on the account, and update_recommendation edits the recommendation before it is applied. Neither was called: the account held no recommendation to apply.

Read one first with list_recommendations. The fields update_recommendation expects change with recommendationType, and the contract describes a bidding rule and a budget rule but not every type.

### Example: read the recommendations

from ad_api.api import Recommendations

client = Recommendations(account='my_account', debug=False)

res = client.list_recommendations(body={"maxResults": 50})
payload = res.payload

print(payload["totalResults"])
for rec in payload["recommendations"]:
    print(rec.get("recommendationId"), rec.get("recommendationType"))

An empty list with totalResults at 0 means the account has none. That is a fact about the account, not a failed call, and a small account with a few campaigns often has none.

### Example: filter by status

from ad_api.api import Recommendations

client = Recommendations(account='my_account', debug=False)

res = client.list_recommendations(body={
    "maxResults": 50,
    "filters": [{
        "field": "STATUS",
        "values": ["PUBLISHED"],
        "include": True,
        "operator": "EXACT",
    }],
})

### Example: apply one

This changes the account. Read the list first.

from ad_api.api import Recommendations

client = Recommendations(account='my_account', debug=False)

res = client.apply_recommendations(
    body={"recommendationIds": ["your-recommendation-id"]})
print(res.payload)
list_recommendations(version: int = 1, **kwargs) → ApiResponse

Retrieves a list of recommendations.

Request Body (required)
ListRecommendationsRequest {
maxResults (integer) default: 500 minimum: 1 maximum: 500
nextToken (string) Token to retrieve the next page of results.
filters (array) minItems: 1 maxItems: 10 [
ListRecommendationsFilter {
include (boolean): Flag to specify if the filter should be included or excluded. default: true
field* FilterField (string): Field to filter by. Enum: [‘RECOMMENDATION_ID’, ‘AD_PRODUCT’, ‘RECOMMENDATION_TYPE’, ‘STATUS’, ‘GROUPING_TYPE’, ‘CAMPAIGN_ID’]
values* (array) minItems: 1 maxItems: 500 [
string
]
operator FilterOperator (string): Operator to filter field by. Enum: [‘EXACT’]
}
]
}

The values STATUS accepts are not the states of a campaign. They are the stages a recommendation passes through, and the service names them in its rejection message: PUBLISHED, APPLY_IN_PROGRESS, APPLY_SUCCESS, APPLY_FAILED and REJECTED. ENABLED and ACTIVE are refused.

filters behaves oddly. An empty list answers 400 Member must have length greater than or equal to 1, yet leaving the member out altogether answers 200 with the account’s recommendations. So it is required when present, and optional in practice.

A body is required: without one the call fails before reaching the service, with KeyError: 'body'.

Returns

ApiResponse with recommendations, totalResults and nextToken. The list is empty for an account that has none, which is a fact about the account rather than a failed call.

apply_recommendations(version: int = 1, **kwargs) → ApiResponse

Applies one or more recommendations.

Request Body (required)
ApplyRecommendationsRequest {
recommendationIds* (array) minItems: 1 maxItems: 100 [
(string) Recommendation identifier.
]
}

Warning

This changes campaigns. Applying a recommendation edits real bidding and budgets on the account, and there is no undo richer than changing them back by hand.

Not exercised: no recommendation existed on the account to apply.

Returns

ApiResponse

update_recommendation(recommendationId, version: int = 1, **kwargs) → ApiResponse

Updates a recommendation.

path recommendationId:string | Required. The identifier of the recommendation.

Request Body (required)
UpdateRecommendationRequest {
ruleBasedBidding UpdateRuleBasedBidding {
Can only be updated for recommendations with recommendationType NEW_CAMPAIGN_BIDDING_RULE or CAMPAIGN_BIDDING_RULE.
recommendedRuleRoas* (number)
}
recommendedValue (string) Recommended value of the recommendation. Type of data expected for each recommendation type:
budgetRule UpdateBudgetRule {
ruleDetails* UpdateBudgetRuleDetails {
duration UpdateBudgetRuleDuration {
dateRangeTypeDuration UpdateBudgetRuleDurationDateRange {
endDate (string): End date of the budget rule in YYYY-MM-DD format. The end date is inclusive.
}
budgetIncreaseBy BudgetRuleIncreaseBy {
value*(number): Budget of the rule.
}
ruleName* (string): Name of the budget rule. Required to be unique within a campaign. minLength: 1 maxLength: 355
performanceMeasureCondition BudgetRulePerformanceMeasureCondition {
threshold*(number): Threshold of the performance metric.
}
}
}
}

Warning

This edits a recommendation before it is applied. Read one first with list_recommendations for the shape it expects, since the fields change with recommendationType.

Not exercised, for the same reason as apply_recommendations.

Returns

ApiResponse