Amazon Ads API 8 min read

Setting Up Amazon Marketing Stream V2: By Hand, or By AI

Two routes to the same pipeline. Paste one prompt into Claude or ChatGPT and have it do the whole thing with your values already in it, or walk the six steps yourself and understand each one. Either way the same four traps decide whether you end up with data or with a subscription that says ACTIVE and delivers nothing.

•
Updated Oct 2026
Setting Up Amazon Marketing Stream V2: By Hand, or By AI

The Short Answer #

In one line

A Marketing Stream V2 pipeline is one CloudFormation stack and one API call — and then four checks, because the API will tell you it worked in at least three ways that do not mean data is arriving.

There are two ways to do this and they lead to the same place. The command generator fills your region, dataset and account IDs into every command, and it also writes a prompt carrying those same values that you can hand to Claude or ChatGPT. If you would rather drive, the six steps below are what the generator emits, explained.

Pick by what you want afterwards. If this pipeline is going to be yours to operate, read the traps section even if you let an AI do the typing — they are the part that costs a day, and they are not in the error messages.

Before You Start #

Three things have to be true, and none of them are on the tool page:

  1. Amazon Ads API credentials that already work

    An LWA client ID and an access token for the advertiser account. Prove they work before you create a single AWS resource — a credential problem discovered at the subscription step looks exactly like a destination problem.

  2. Your advertiser account ID

    The amzn1.ads-account.g.… form. In V2 this goes in the request body, under accessRequestedAccounts. There is no scope header, which is the first thing that trips anyone who has used the Ads API before.

  3. An AWS account, in a region your realm allows

    Stream delivers into your own AWS account; there is no hosted option. NA is us-east-1 only. EU allows eu-west-1, eu-west-2 and eu-south-2. FE allows us-west-2 and ap-southeast-1. The bucket and the queue must sit in the same one.

eu-south-2 needs enabling in your AWS account before it will take a stack. That is an AWS account setting, not a Stream one, and the failure arrives as a generic CloudFormation error.

Route One: Hand It to an AI #

Open the command generator, fill in the seven fields on the left, and press Copy the prompt. You get a prompt with your realm, region, dataset, destination name and account IDs already in it, plus the operating rules that matter when a model is running AWS commands on your behalf.

Three of those rules are doing real work, and they are worth knowing even if you never read the prompt:

  • Never invent an ARN. Every bucket ARN, queue ARN and queue URL is read out of the CloudFormation stack outputs. A model asked to subscribe before the stack exists will happily construct an ARN that looks right, and Amazon will accept the subscription against a bucket you do not own.
  • Ignore everything you remember about Stream. Almost all the Marketing Stream material in a model's training data is V1: Firehose, IAM roles, CAPABILITY_NAMED_IAM, delta records. V2 shares the name and almost nothing else. Without this line you get a confident V1 setup.
  • One step at a time, show the real output. The checks only work if somebody looks at the responses, and a model running six commands in a row will summarise them as "done".

Give it AWS CLI access, not screenshots. The verification steps read stack outputs and list S3 objects. A model that cannot run commands can write them for you, but it cannot tell you whether your pipeline works — which is the entire difficult part.

Route Two: The Six Steps #

  1. Get Amazon's template

    StreamV2_S3_CF_Template.yaml from the ads-advanced-tools-docs repo. It creates the bucket, the notification queue, a dead-letter queue, two customer-managed KMS keys and access logging, and attaches the policies that let Amazon write. Doing it by hand is possible and is mostly a way to get a policy subtly wrong.

  2. Deploy it

    Two parameters: DestinationName (3–24 characters, lowercase letters, numbers and hyphens) and StreamRealm (NA, EU or FE). One stack per client application, not per dataset. No --capabilities flag — the template creates no IAM roles at all, which is the cleanest single difference from V1.

  3. Read the stack outputs

    StreamDataBucketArn and StreamDestinationQueueArn are what the subscription needs. StreamDataBucketName and StreamDestinationQueueUrl are what the verification needs. Take all four now rather than reconstructing them later.

  4. Create the subscription

    POST /adsApi/v1/create/streamSubscriptions, one subscription per request, advertiser account in the body. The two ARNs go in destination.s3Destination.

  5. Check the response arrays

    Not the status code. See below — this is the step that quietly loses subscriptions.

  6. Prove data is landing, then alarm on it stopping

    List the bucket. Read a message off the queue. Then put a CloudWatch alarm on the queue's NumberOfMessagesSent before you walk away.

The Four Traps #

Everything above is typing. This is the part that decides whether you have a pipeline.

1. The endpoint answers 207 whether it worked or not

Subscription creation returns Multi-Status 207. A rejected subscription — BAD_REQUEST for an invalid destination, FORBIDDEN if you are not authorised for that advertiser account — arrives inside errors[] under that same 207. Any client written as if status == 200 treats a rejection as a success, and you find out weeks later when somebody asks where the data is.

Read success[] and errors[]. Never the status line.

2. ACTIVE does not mean data is flowing

A new subscription comes back ACTIVE straight away. Amazon validates that your bucket and queue are reachable at the moment you subscribe, and then nothing after. A bucket policy missing s3:PutObject leaves you ACTIVE with an empty bucket and no error anywhere — Amazon's own V2 documentation now says as much.

The only proof is objects in S3.

3. Records are restated totals, not deltas

V1 sent deltas you added up. V2 sends the total for a record's time window, and re-sends that record with a higher version when the number changes. A loader that appends, or that sums the versions, inflates every figure it reports. Upsert on the highest version per record key.

This is the one that does not announce itself. The pipeline looks perfect; the numbers are just wrong.

4. Silence is the failure mode

A stalled stream does not publish a zero. It publishes nothing, so the metric has no datapoints, so a default CloudWatch alarm sits in INSUFFICIENT_DATA for ever and you are exactly as blind as you were before you built it. --treat-missing-data breaching is the flag that turns the alarm into an actual alarm.

Build it on day one. The whole reason you need it is that nothing else will tell you.

Which Dataset #

Two at the moment, both available in all three realms:

DatasetGrainWhat it is
ads-performance-v1HourlyPerformance consolidated across Sponsored Products, Brands, Display, TV and DSP
ads-converted-product-v1DailyConverted-product data across the same ad products

Start with ads-performance-v1. It is hourly, so it tells you fastest whether delivery actually works — and a setup you cannot verify quickly is a setup you will debug slowly.

One subscription per request, so two datasets means two calls against the same destination.

Running PPC across several accounts?

I built a dayparting control room that runs hourly across a PPC manager's client accounts: rules, dry runs, approvals and an audit log. Tell me what your tooling can't do and I'll quote the build at a fixed price.

Found this helpful? Share it: