Ads

Reddit Ads

Run image, video, carousel and text ads on Reddit, promote existing posts, target subreddits and keywords, and send conversions to a Reddit pixel.

Reddit Ads works through its own connection, redditads, with its own authorization and its own accountId. It does not depend on an organic Reddit account, which matters because organic publishing to Reddit is paused (see Reddit). Campaigns, targeting, reporting and the Conversions API all work.

Connection platform redditads
platform on ad endpoints reddit
Structure Campaign, ad group, ad, all created by one call
Goals traffic, awareness, conversions, video_views, lead_generation
Formats Image, video, text, a carousel of 2 to 10 cards, or a post that already exists
Headline Required, up to 300 characters
Changes after launch Status, name, budget, bid and targeting
Audiences Target audiences built in Reddit Ads Manager, by id
Measurement Reddit pixel (created in Reddit) and the Conversions API

#Before you start

Connect with a Reddit login that has a role on one or more ad accounts in Reddit Ads Manager. Set up two things there first. Reddit insists on both, and its API offers no way to make them:

  • A pixel. Each ad group carries one, even when the goal has nothing to do with conversions. If the ad account has none, creating an ad fails with 422 feature_not_available. Add one in Events Manager inside Reddit Ads Manager.
  • A profile. On Reddit, every ad is really a post by a profile that belongs to the ad account. Pozy uses the account's first profile for new ads.

#Connect

Call the connect endpoint with the profile. There is no posting account involved, so there is no accountId on this call.

curl "https://api.pozy.ai/v1/connect/redditads/ads?profileId=66b2e19d8c3f5a7e9d0b1c2a&redirect_url=https://app.example.com/ads/connected" \
  -H "Authorization: Bearer $POZY_API_KEY"

The response carries an authUrl. Reddit's consent screen asks for three permissions:

Permission Used for Required
adsread Reading ad accounts, campaigns, ads, pixels and metrics Yes
adsedit Creating and changing campaigns, ad groups, ads and their posts Yes
adsconversions Sending events through the Conversions API No, but conversions fail without it

After consent the browser lands on your redirect_url. Call the connect endpoint again to read the connection:

Response
{
  "alreadyConnected": true,
  "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
  "platform": "redditads",
  "username": "acme_marketing",
  "displayName": "Reddit Ads (acme_marketing)"
}

On failure the redirect carries platform=redditads and an error: reddit_ads_auth_failed (declined), reddit_ads_scope_not_granted, reddit_ads_no_ad_accounts or reddit_ads_callback_error.

#Choose ad accounts

By default Pozy syncs all the ad accounts the Reddit login has access to. To limit that, send adAccountId on the connect call, or adAccountIds as a repeated or comma-separated list. Ids have the form a2_gu5tcd9ah5se; anything else is rejected with 400.

#Reconnect without losing history

When the token stops working, or the connection lacks one of the three permissions, the connect call returns an authUrl again instead of alreadyConnected. Sending the person through it reconnects the same accountId, so synced ads and stored ids stay as they are. Pass force=true to get a fresh authUrl even when the connection is healthy.

Reconnect, do not disconnect

Disconnecting and connecting again creates a new connection with a new accountId. To fix a permission, run the connect call on the existing one.

#Create an ad

POST /v1/ads/create writes the post and builds the campaign, ad group and ad. The creative fields you send pick the format.

curl -X POST https://api.pozy.ai/v1/ads/create \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: planner-launch-reddit" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "adAccountId": "a2_gu5tcd9ah5se",
    "name": "Planner launch",
    "goal": "traffic",
    "budgetAmount": 50,
    "budgetType": "daily",
    "headline": "The planner your team will actually open",
    "body": "Tasks, docs and meetings in one place. Free for teams of 5.",
    "imageUrl": "https://cdn.example.com/planner.png",
    "linkUrl": "https://example.com/planner",
    "callToAction": "SIGN_UP",
    "countries": ["US", "CA"],
    "targeting": {
      "communities": ["productivity", "r/projectmanagement"],
      "conversationKeywords": ["task manager"]
    }
  }'
Response (201)
{
  "ad": {
    "_id": "66f0a1b2c3d4e5f6a7b8c9d0",
    "name": "Planner launch",
    "platform": "reddit",
    "status": "pending_review",
    "adType": "standalone",
    "goal": "traffic",
    "budget": { "amount": 50, "type": "daily" },
    "platformCampaignId": "2160485120262584829",
    "platformAdSetId": "2160485120597863293",
    "platformAdId": "2160485120873925373",
    "platformAdAccountId": "a2_gu5tcd9ah5se"
  },
  "message": "Ad created and submitted for review"
}

The campaign and ad group are named <name> - Campaign and <name> - Ad Group unless you send campaignName and adSetName. If Reddit rejects a later step, Pozy archives what it already created so a failed request does not leave an empty campaign behind.

Send status: "PAUSED" to build the whole campaign switched off and check it before any money goes out. Set campaignStatus when the campaign should differ from its ad group and ad.

#Formats

Format Send
Image headline, imageUrl, linkUrl, optional body and callToAction
Video headline, video.url, a thumbnail in video.thumbnailUrl or imageUrl, linkUrl, and optionally body and callToAction. The video must be a URL: video.id is refused.
Carousel headline, between 2 and 10 carouselCards, and optionally body. A card has imageUrl, callToAction, a caption (its headline or description) and a linkUrl, which defaults to the ad's own.
Text Just headline and body. Text ads have no destination, so sending linkUrl gets 400.

Behind the scenes Reddit turns the creative into a post by the ad account's profile, open to comments, and the ad promotes it.

Button labels for callToAction: LEARN_MORE, SHOP_NOW, SIGN_UP, CONTACT_US, DOWNLOAD, GET_QUOTE, SUBSCRIBE, APPLY_NOW, BOOK_NOW, BUY_TICKETS, DONATE_NOW, GET_DIRECTIONS, GET_SHOWTIMES, LISTEN_NOW, ORDER_NOW, SEE_MENU or INSTALL_MOBILE_APP. Omit the field to show no button.

#Goals and the pixel

goal Optimizes for Needs
traffic Clicks
awareness Impressions
video_views 6-second views (optimizationGoal: "VIDEO_VIEW_6S", the default) or 15-second views (VIDEO_VIEW_15S) A video creative
conversions One pixel event, set in promotedObject.customEventType A pixel that has already received that event
lead_generation The pixel's LEAD event, sending people to linkUrl A pixel that has already received LEAD

Every ad group carries a pixel. Pozy uses promotedObject.pixelId (or tracking.pixelId) when you send one, and the ad account's first pixel otherwise. List pixels with GET /v1/accounts/{accountId}/conversion-destinations.

On conversions, customEventType is one of PURCHASE, LEAD, SIGN_UP (or COMPLETE_REGISTRATION), ADD_TO_CART, or PAGE_VISIT (or VIEW_CONTENT, CONTENT_VIEW):

JSON
{
  "goal": "conversions",
  "promotedObject": { "pixelId": "a2_gu5tcd9ah5se", "customEventType": "PURCHASE" }
}
The event has to exist first

Reddit only optimizes toward an event the pixel has already seen. Before a conversions or lead_generation create, Pozy checks when the pixel last received it. If it never did, the call returns 422 invalid_resource_state. Fire the event from your site, or send one through the Conversions API, then retry.

Both goals use Reddit's 7-day click, 1-day view attribution window. Reddit retired its native lead forms on September 30, 2026, so lead_generation drives traffic to your own page and leadGenFormId returns 400.

#Budget, schedule and bidding

  • The budget sits on the ad group. daily spends up to budgetAmount per day; lifetime spends it over the flight and needs endDate.
  • startDate defaults to now. A date or time without an offset is read in the ad account's time zone, and a date-only endDate runs to 23:59:59 that day.
  • New ad groups bid automatically. Set a manual bid or a cost cap after creation (see below).

#Promote an existing post

POST /v1/ads/boost promotes a Reddit post that already exists, with its votes and comments. Pass the Reddit post id as platformPostId, with or without the t3_ prefix. Targeting goes inside the targeting object.

cURL
curl -X POST https://api.pozy.ai/v1/ads/boost \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: boost-t3-1o4x9qz" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "adAccountId": "a2_gu5tcd9ah5se",
    "platformPostId": "t3_1o4x9qz",
    "name": "Boost launch thread",
    "goal": "traffic",
    "budget": { "amount": 30, "type": "daily" },
    "linkUrl": "https://example.com/planner",
    "targeting": { "countries": ["US"], "communities": ["productivity"] }
  }'

The post you boost must come from a profile that belongs to the ad account. Boosting someone else's post fails with 422 and param: "platformPostId"; add that author as a profile of the ad account in Reddit Ads Manager, then try again. The video_views goal only works on a video post.

#Targeting

Field Values
countries ISO codes. With no location at all, the ad targets the US.
regions, metros, cities, zips { "key": "..." } from targeting search, dimension=geo
excludedLocations countries, regions, cities and zips only
gender all, male or female
languages Codes such as en or de
targeting.interests { "id": "..." } from targeting search, dimension=interest
targeting.communities, targeting.excludedCommunities Subreddit names, with or without r/
targeting.conversationKeywords, targeting.excludedConversationKeywords Free text
audienceInclude, audienceExclude Ids of custom audiences built in Reddit Ads Manager

Age, income, behaviors and radius targeting return 400 naming the field. Custom audiences cannot be created or uploaded through the API (POST /v1/ads/audiences returns 422 on a redditads connection), so build them in Reddit and target them by id.

Search Reddit's catalog with GET /v1/ads/targeting/search and dimension set to geo, interest, community or conversationKeyword:

cURL
curl "https://api.pozy.ai/v1/ads/targeting/search?accountId=66b2e19d8c3f5a7e9d0b1c2d&q=gaming&dimension=community" \
  -H "Authorization: Bearer $POZY_API_KEY"

A community result's id is the subreddit name to send, with its subscriber count in audienceSize. On geo, geoType is city (default), zip, region, metro_area, country or all.

#Manage a running campaign

Campaigns, ad groups and ads each have their own status on Reddit. Pozy changes only the one you address.

Change Call Notes
Pause or resume PUT /v1/ads/{adId}/status, /v1/ads/ad-sets/{adSetId}/status or /v1/ads/campaigns/{campaignId}/status Only that level's switch changes.
Rename name on PUT /v1/ads/{adId}, /v1/ads/ad-sets/{adSetId} or /v1/ads/campaigns/{campaignId}
Budget budget: { amount, type } on PUT /v1/ads/{adId} or PUT /v1/ads/ad-sets/{adSetId} A daily budget stays daily and a lifetime one stays lifetime; trying to switch gets 422.
Bid bidStrategy and bidAmount on PUT /v1/ads/ad-sets/{adSetId} Strategies: COST_CAP, LOWEST_COST_WITH_BID_CAP, LOWEST_COST_WITHOUT_CAP
Targeting targeting on PUT /v1/ads/{adId} Replaces the whole set. A field you leave out is cleared (locations fall back to the US).
Stop DELETE on the ad, ad group or campaign Archives on Reddit and marks Pozy's copy cancelled.

GET /v1/ads/ad-sets/{adSetId}/targeting reads an ad group's current targeting and schedule before you replace it. Creative edits are not available (501 feature_not_available); create a new ad instead.

POST /v1/ads/campaigns creates a campaign on its own. With a budget it becomes a campaign-budget campaign, where the budget changes through PUT /v1/ads/campaigns/{campaignId} rather than the ad group. Adding an ad to an existing campaign or ad group (existingCampaignId, adSetId) is not available on Reddit.

Pozy also imports the ads you build directly in Reddit Ads Manager: GET /v1/ads?platform=reddit shows all ads of the synced ad accounts, wherever they were made. Once Reddit drops an archived object from its lists, Pozy marks it cancelled.

#Measure results

For each ad and each day (in the ad account's time zone), Pozy stores impressions, clicks and spend, and works out CTR, CPC and CPM from them. GET /v1/ads/{adId}/analytics returns one ad; GET /v1/ads/tree sums them up the campaign tree.

  • Those three metrics are the whole sync. reach, video and conversion metrics read 0 on Reddit.
  • On connect, Pozy imports the last 90 days.
  • Demographic breakdowns are not available.

#Conversions API

Pixels are created in Reddit Ads Manager; the tracking tags endpoints do not manage Reddit pixels. Server-side events go to POST /v1/ads/conversions with the pixel id as destinationId:

cURL
curl -X POST https://api.pozy.ai/v1/ads/conversions \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
    "destinationId": "a2_gu5tcd9ah5se",
    "events": [{
      "eventName": "Purchase",
      "eventTime": 1798804800,
      "eventId": "order_12345",
      "value": 25.99,
      "currency": "USD",
      "user": { "email": "customer@example.com", "clickIds": { "rdt_cid": "1234567890abcdef" } }
    }]
  }'
  • Each event needs a match key: clickIds.rdt_cid, clickIds.rdt_uuid, an email, a phone, an externalId, or the IP address with the user agent.
  • eventTime is Unix seconds. Reddit accepts events up to 7 days old.
  • eventId is Reddit's dedupe key against the same event fired by the pixel.
  • The connection needs adsconversions. Without it the call returns 403 reconnect_required.

Conversions covers event names, hashing and batching across networks.

#Rate limits

Reddit limits requests per connected Reddit user. When Reddit answers 429 with a short wait, Pozy waits and retries up to twice for you. A longer wait comes back as 429 rate_limited with a Retry-After header. Your workspace's own rate limit applies on top.

#Errors you will see

Status and code Cause Fix
422 feature_not_available ("has no pixel" or "has no profile") The ad account is missing a pixel or a profile Create it in Reddit Ads Manager, then retry.
422 invalid_resource_state The campaign optimizes for an event the pixel has not seen yet Trigger that event on your site, or report it with the Conversions API.
400 with param: "promotedObject.customEventType" A conversions goal with no event, or one Reddit does not support Pick an event from the list above.
400 with param: "promotedObject.pixelId" The pixel belongs to a different ad account Send a pixel owned by adAccountId.
400 with param: "linkUrl" linkUrl on a text ad Drop the link, or switch to an image, video or carousel ad.
400 with param: "headline" No headline, or one longer than 300 characters Add or trim the headline.
400 with param: "endDate" Lifetime budget, no end date Add endDate.
422 with param: "budget" You tried to change the budget type, or set an ad group budget inside a campaign-budget campaign Keep the current type, or edit the campaign's budget instead.
403 reconnect_required The connection lacks adsconversions Run the connect call again.
429 rate_limited Reddit's per-user limit Wait Retry-After seconds.
502 platform_api_error Reddit refused the call The message carries Reddit's own explanation.

#Endpoints

Endpoint What it does
GET /v1/connect/{platform}/ads Connect ads for a platform
GET /v1/ads/accounts List ad accounts
POST /v1/ads/create Create standalone ad
POST /v1/ads/boost Boost post as ad
POST /v1/ads/campaigns Create a standalone campaign
GET /v1/ads/tree Get campaign tree
GET /v1/ads List ads
GET /v1/ads/{adId} Get ad details
PUT /v1/ads/{adId} Update ad
PUT /v1/ads/{adId}/status Pause or resume a single ad
PUT /v1/ads/ad-sets/{adSetId} Update an ad set
PUT /v1/ads/ad-sets/{adSetId}/status Pause or resume a single ad set
GET /v1/ads/ad-sets/{adSetId}/targeting Read a Reddit ad group's targeting and schedule
PUT /v1/ads/campaigns/{campaignId} Update a campaign
PUT /v1/ads/campaigns/{campaignId}/status Pause or resume a campaign
DELETE /v1/ads/{adId} Cancel an ad
DELETE /v1/ads/ad-sets/{adSetId} Delete an ad set
DELETE /v1/ads/campaigns/{campaignId} Delete a campaign
GET /v1/ads/{adId}/analytics Get ad analytics
GET /v1/ads/targeting/search Search targeting options
GET /v1/accounts/{accountId}/conversion-destinations List conversion destinations
POST /v1/ads/conversions Send conversion events

#Webhook events

Event When it fires
account.ads.initial_sync_completed Ads initial sync completed.
account.ads.sync_failed Ads sync failed.
account.ads.sync_recovered Ads sync recovered.
ad.review.approved Ad review approved.
ad.review.rejected Ad review rejected.