@zernio/node 0.2.1169 → 0.2.1170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(index_exports);
36
36
  // package.json
37
37
  var package_default = {
38
38
  name: "@zernio/node",
39
- version: "0.2.1169",
39
+ version: "0.2.1170",
40
40
  description: "The official Node.js library for the Zernio API",
41
41
  main: "dist/index.js",
42
42
  module: "dist/index.mjs",
package/dist/index.mjs CHANGED
@@ -5,7 +5,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
5
5
  // package.json
6
6
  var package_default = {
7
7
  name: "@zernio/node",
8
- version: "0.2.1169",
8
+ version: "0.2.1170",
9
9
  description: "The official Node.js library for the Zernio API",
10
10
  main: "dist/index.js",
11
11
  module: "dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zernio/node",
3
- "version": "0.2.1169",
3
+ "version": "0.2.1170",
4
4
  "description": "The official Node.js library for the Zernio API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1474,6 +1474,13 @@ export const verifyWhatsAppNumber = <ThrowOnError extends boolean = false>(optio
1474
1474
  * inbound message webhook does not reach Zernio, `platformConnection.inboundWebhookSubscribed`
1475
1475
  * is `false`, an entry is added to `issues`, and the overall `status` is at least `warning`.
1476
1476
  *
1477
+ * For the standalone ads connections (`googleads`, `redditads`) a connection that cannot serve
1478
+ * requests until a fresh OAuth repairs it adds an entry to `issues` and a recommendation to call
1479
+ * `GET /v1/connect/{platform}/ads`, whose `authUrl` reconnects the same account. On `redditads`
1480
+ * this is a connection authorized before Zernio needed a Reddit permission it now uses (for
1481
+ * example `adsconversions`): Reddit refuses those requests until the user reconnects and allows
1482
+ * every permission on Reddit's consent screen.
1483
+ *
1477
1484
  */
1478
1485
  export const getAccountHealth = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<GetAccountHealthData, ThrowOnError>) => {
1479
1486
  return (options?.client ?? client).get<GetAccountHealthResponse, GetAccountHealthError, ThrowOnError>({
@@ -1824,6 +1831,22 @@ export const handleOAuthCallback = <ThrowOnError extends boolean = false>(option
1824
1831
  * whose token lacks that permission is flagged needsReconnection, and the health endpoints
1825
1832
  * report "Google Ads permission not granted"; this endpoint then returns an authUrl for it.
1826
1833
  *
1834
+ * **Standalone platforms (redditads).** Starts the Reddit Ads OAuth flow (Reddit permissions
1835
+ * `adsread`, `adsedit` and `adsconversions`) and creates a standalone ads SocialAccount
1836
+ * (redditads) with no parent, separate from any organic `reddit` posting account. It is
1837
+ * reached only as `redditads` in the path. If an active Reddit Ads connection already exists
1838
+ * on the profile, is not flagged needsReconnection and holds every one of those permissions,
1839
+ * returns alreadyConnected: true. Otherwise it returns an authUrl; completing it reconnects
1840
+ * the same account (same accountId). A connection authorized without `adsconversions` keeps
1841
+ * managing ads, but the health endpoints list an issue asking to reconnect Reddit Ads and
1842
+ * allow every permission, the Conversions API answers 403 `reconnect_required`, and this
1843
+ * endpoint returns an authUrl for it. Discovery lists every Reddit ad account the Reddit user
1844
+ * can reach. When the user unticks `adsread` or `adsedit` on Reddit's consent screen the
1845
+ * callback saves nothing and redirects with `error=reddit_ads_scope_not_granted`; when the
1846
+ * user reaches no Reddit ad account it redirects with `error=reddit_ads_no_ad_accounts`
1847
+ * (both with `platform=redditads`). Without `redirect_url` the browser lands on the Zernio
1848
+ * dashboard's ad account picker for the new connection.
1849
+ *
1827
1850
  * Ads accounts appear as regular SocialAccount documents with ads platform values (e.g., metaads, tiktokads) in GET /v1/accounts.
1828
1851
  *
1829
1852
  */
@@ -9801,7 +9824,7 @@ export const removeAdKeyword = <ThrowOnError extends boolean = false>(options: O
9801
9824
  * which reads the switches from the platform now, stores them, and returns `statusReadAt`
9802
9825
  * (null when the read failed and the stored values were returned).
9803
9826
  * With `live=true` (which needs `limit` of 20 or less) each returned campaign's own switch
9804
- * (`platformCampaignStatus`) is read live. Live reads cover TikTok, Meta, Google and OpenAI.
9827
+ * (`platformCampaignStatus`) is read live. Live reads cover TikTok, Meta, Google, OpenAI and Reddit.
9805
9828
  * The rolled-up `status` is not re-derived by a live read.
9806
9829
  *
9807
9830
  * **Budgets here are SYNCED**, never read live, including with `live=true`. To read the
@@ -9828,7 +9851,14 @@ export const listAdCampaigns = <ThrowOnError extends boolean = false>(options?:
9828
9851
  * accepts `portfolioBidStrategyId` instead. Google, X and OpenAI require a budget
9829
9852
  * (422 without one; OpenAI accepts daily or lifetime, Google only
9830
9853
  * `budgetType: daily`). On OpenAI `goal` sets the campaign objective, and
9831
- * `conversions` needs an active standard conversion event on the account. LinkedIn creates the
9854
+ * `conversions` needs an active standard conversion event on the account. On Reddit
9855
+ * `goal` is one of traffic, awareness, conversions, video_views or lead_generation
9856
+ * (422 otherwise) and the budget is optional: with one, the campaign uses Reddit's
9857
+ * campaign budget optimization and carries the pixel (see `promotedObject`, which then
9858
+ * needs `customEventType` for `conversions`), daily or lifetime (a lifetime budget
9859
+ * needs `endDate`, 400 otherwise). `startDate` / `endDate` set the campaign's schedule
9860
+ * and need that campaign budget (400 without one). Without a budget, each ad set
9861
+ * carries its own budget and dates. LinkedIn creates the
9832
9862
  * campaign GROUP (our campaign level) and rejects a budget, which lives on the
9833
9863
  * campaign (ad set) level there; it comes back `status: DRAFT`. Created `PAUSED`
9834
9864
  * (TikTok `DISABLE`) unless `status: ACTIVE` where the platform supports it.
@@ -9990,8 +10020,8 @@ export const getAdCampaignDetails = <ThrowOnError extends boolean = false>(optio
9990
10020
  * | `networkSettings` | 400 | Search only | 400 |
9991
10021
  * | `trackingUrlTemplate`, `finalUrlSuffix` | 400 | Yes | 400 |
9992
10022
  * | `sharedBudgetId` | 400 | Yes | 400 |
9993
- * | `budget` (CBO; ABO returns 409) | Yes | Daily only | OpenAI: daily or lifetime; others 501 |
9994
- * | `name` | Yes | 501 | 501 |
10023
+ * | `budget` (CBO; ABO returns 409) | Yes | Daily only | OpenAI and Reddit: daily or lifetime; others 501 |
10024
+ * | `name` | Yes | 501 | Reddit: Yes; others 501 |
9995
10025
  * | `platformSpecificData.spendCap` | Yes | 400 | 400 |
9996
10026
  * | `accountId` (empty campaigns) | Yes | - | - |
9997
10027
  *
@@ -10032,6 +10062,12 @@ export const getAdCampaignDetails = <ThrowOnError extends boolean = false>(optio
10032
10062
  * currency (422 below it). Lifetime can switch to daily, but OpenAI never switches a
10033
10063
  * daily cap back to lifetime (422).
10034
10064
  *
10065
+ * Reddit Ads: `budget` changes the amount of a campaign budget (Reddit campaign budget
10066
+ * optimization); a campaign whose ad groups carry the budget returns 409 or 422, use
10067
+ * PUT /v1/ads/ad-sets/{adSetId} there. Reddit cannot switch a published budget between
10068
+ * daily and lifetime, so `budget.type` must match the current one (422 otherwise).
10069
+ * `bidStrategy` is set per ad group on Reddit (PUT /v1/ads/ad-sets/{adSetId}).
10070
+ *
10035
10071
  * `accountId` forwards the update straight to Meta for a campaign with zero ads,
10036
10072
  * which would otherwise 404; the response then carries `updated: 0`.
10037
10073
  *
@@ -10064,6 +10100,10 @@ export const updateAdCampaign = <ThrowOnError extends boolean = false>(options:
10064
10100
  * account and retry. Other platforms answer 501 for an empty campaign.
10065
10101
  * `accountId` is ignored when the campaign does have ads.
10066
10102
  *
10103
+ * Reddit Ads has no delete: the campaign is archived instead (`configured_status`
10104
+ * ARCHIVED), which stops delivery for good. Its ad groups and ads keep their own
10105
+ * switches but cannot deliver under an archived campaign.
10106
+ *
10067
10107
  */
10068
10108
  export const deleteAdCampaign = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<DeleteAdCampaignData, ThrowOnError>) => {
10069
10109
  return (options?.client ?? client).delete<DeleteAdCampaignResponse, DeleteAdCampaignError, ThrowOnError>({
@@ -10297,7 +10337,7 @@ export const updateCampaignTargeting = <ThrowOnError extends boolean = false>(op
10297
10337
  * (null when the read failed and the stored values were returned).
10298
10338
  * With `live=true` (which needs a `campaignId` or `adSetId` filter) each listed ad set's own
10299
10339
  * switch (`platformAdSetStatus`) is read live, for the first 20 rows; later rows keep their
10300
- * synced value with `statusReadAt: null`. Live reads cover TikTok, Meta, Google and OpenAI.
10340
+ * synced value with `statusReadAt: null`. Live reads cover TikTok, Meta, Google, OpenAI and Reddit.
10301
10341
  * The rolled-up `status` is not re-derived by a live read. On TikTok the same live read also
10302
10342
  * returns the ad group's applied `optimizationGoal` and `billingEvent`, as TikTok's adgroup/get
10303
10343
  * reports them, so you can verify the goal TikTok applied rather than the one you requested.
@@ -10443,6 +10483,14 @@ export const getAdSetDetails = <ThrowOnError extends boolean = false>(options: O
10443
10483
  * CBO, the response is 409 with code BUDGET_LEVEL_MISMATCH. Route to
10444
10484
  * PUT /v1/ads/campaigns/{campaignId} instead.
10445
10485
  *
10486
+ * Reddit Ads: `budget` changes the ad group's amount; an ad group that spends its
10487
+ * campaign budget returns 422 (update the campaign instead), and Reddit cannot switch
10488
+ * a published budget between daily and lifetime, so `budget.type` must match the
10489
+ * current one (422 otherwise). `name` renames the ad group. `bidStrategy` maps to the
10490
+ * ad group's `bid_strategy`: LOWEST_COST_WITHOUT_CAP = automatic bidding (BIDLESS),
10491
+ * LOWEST_COST_WITH_BID_CAP = MANUAL_BIDDING with `bidAmount` as the bid, COST_CAP =
10492
+ * MAXIMIZE_VOLUME with `bidAmount` as the cap; LOWEST_COST_WITH_MIN_ROAS returns 422.
10493
+ *
10446
10494
  * `status` behaves exactly as PUT /v1/ads/ad-sets/{adSetId}/status
10447
10495
  * describes: only the ad set's own switch is written, its ads keep
10448
10496
  * theirs, and `statusUpdated` / `statusSkipped` report whether that switch
@@ -10464,7 +10512,7 @@ export const updateAdSet = <ThrowOnError extends boolean = false>(options: Optio
10464
10512
  * `status: cancelled`.
10465
10513
  *
10466
10514
  * Delete is soft on platforms that have no hard delete: LinkedIn moves the
10467
- * campaign to `PENDING_DELETION`, Pinterest archives the ad group, and X
10515
+ * campaign to `PENDING_DELETION`, Pinterest and Reddit archive the ad group, and X
10468
10516
  * soft-flags the line item. Google removes the ad group. All remain readable
10469
10517
  * for reporting.
10470
10518
  *
@@ -10488,7 +10536,7 @@ export const deleteAdSet = <ThrowOnError extends boolean = false>(options: Optio
10488
10536
  * Writes the ad set's own on/off switch and nothing else, on every platform
10489
10537
  * (Meta `configured_status`, TikTok ad group `operation_status`, Google ad
10490
10538
  * group status, LinkedIn campaign, Pinterest ad group, X line item, ChatGPT
10491
- * (OpenAI) ad group). Its ads keep their own switches: an ad you paused
10539
+ * (OpenAI) ad group, Reddit ad group `configured_status`). Its ads keep their own switches: an ad you paused
10492
10540
  * individually stays paused when the ad set resumes. The campaign above is
10493
10541
  * not touched either, so an ad set resumed under a paused campaign reads
10494
10542
  * `status: paused` until the campaign is resumed too. See the Status model
@@ -10660,6 +10708,12 @@ export const getAd = <ThrowOnError extends boolean = false>(options: OptionsLega
10660
10708
  * - **Pinterest / X / OpenAI Ads**: status + budget only. Sending
10661
10709
  * `targeting` or `creative` returns 501 with code `unsupported_platform_operation`.
10662
10710
  * OpenAI Ads budget is the campaign's spend cap, daily or lifetime (see `budget.type` below).
10711
+ * - **Reddit Ads**: status, budget, `name` (renames the ad) and targeting. `budget` goes to
10712
+ * the ad's ad group, or to its campaign when the ad group spends a campaign budget, and
10713
+ * keeps its daily or lifetime type (422 to switch). `targeting` REPLACES the ad group's
10714
+ * whole targeting (geo, communities, conversation keywords, interests, audiences,
10715
+ * languages, gender), so it applies to every ad in the ad group. `creative` returns 501:
10716
+ * a Reddit ad runs a post, so create a new ad with the new creative instead.
10663
10717
  *
10664
10718
  * **Google location and language replacement:** locations, languages and devices are
10665
10719
  * campaign-level criteria on Google, so these edits apply to every ad group and ad in
@@ -10718,7 +10772,7 @@ export const updateAd = <ThrowOnError extends boolean = false>(options: OptionsL
10718
10772
  * Cancel an ad
10719
10773
  * Deletes an ad on the platform while keeping its history, and removes empty parents only when they were created through this API.
10720
10774
  *
10721
- * Deletes the ad on the platform and marks it as cancelled in the database. The ad is preserved for history. OpenAI Ads has no delete API; the ad is archived instead (a terminal state, the closest equivalent).
10775
+ * Deletes the ad on the platform and marks it as cancelled in the database. The ad is preserved for history. OpenAI Ads has no delete API; the ad is archived instead (a terminal state, the closest equivalent). Reddit Ads has no delete either: the ad is archived (`configured_status` ARCHIVED).
10722
10776
  *
10723
10777
  * Only the ad is deleted; a campaign or ad set is never deleted while it still holds another ad. On Meta, when the ad set and campaign were created by Zernio (by `POST /v1/ads/create` or `POST /v1/ads/boost`) and Meta lists no other ad in the ad set (archived ads count), the emptied ad set is deleted too, then the campaign once it holds no other ad set. Parents created outside Zernio, and the parents of an ad imported from the platform, are always kept. On LinkedIn the ad is a creative inside a campaign inside a campaign group: the creative is always removed, the campaign only when Zernio created it and LinkedIn lists no other creative in it that is not archived, canceled or deleted, and the campaign group only when Zernio created it and LinkedIn lists no other campaign in it that is not canceled or deleted (archived campaigns count). If the creative itself cannot be removed the request fails and no parent is touched. On X the ad is a promoted post inside a line item inside a campaign: the promoted post is always removed, the line item only when Zernio created it (by `POST /v1/ads/create` or `POST /v1/ads/boost`) and X lists no other undeleted promoted post in it, and the campaign only when Zernio created it and X lists no other undeleted line item in it. If X refuses the promoted post delete the request fails and no parent is touched. To delete a whole campaign on purpose use `DELETE /v1/ads/campaigns/{campaignId}`.
10724
10778
  *
@@ -10736,7 +10790,7 @@ export const deleteAd = <ThrowOnError extends boolean = false>(options: OptionsL
10736
10790
  *
10737
10791
  * Ad-scoped pause/resume: flips ONLY this ad's own switch (Meta
10738
10792
  * `configured_status`, TikTok `operation_status`, Google ad group ad
10739
- * status, LinkedIn creative, Pinterest ad), never its parent ad set or
10793
+ * status, LinkedIn creative, Pinterest ad, Reddit ad `configured_status`), never its parent ad set or
10740
10794
  * campaign, so sibling ads keep running. X is the exception: its smallest
10741
10795
  * switch is the line item. Thin wrapper over the `status` field of
10742
10796
  * PUT /v1/ads/{adId}, for callers that want a URL symmetric to
@@ -12434,6 +12488,9 @@ export const createAdAccount = <ThrowOnError extends boolean = false>(options: O
12434
12488
  * For Google Ads: responds `429` when Google's API quota is temporarily exhausted
12435
12489
  * (instead of an empty list). Retry after a delay.
12436
12490
  *
12491
+ * For Reddit Ads (`redditads`): every Reddit ad account the connected Reddit user can reach,
12492
+ * with `businessId`, `approvalStatus` (Reddit's `admin_approval`), `timezone` and `type`.
12493
+ *
12437
12494
  */
12438
12495
  export const listAdAccounts = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<ListAdAccountsData, ThrowOnError>) => {
12439
12496
  return (options?.client ?? client).get<ListAdAccountsResponse, ListAdAccountsError, ThrowOnError>({
@@ -12550,6 +12607,22 @@ export const getDsaRecommendations = <ThrowOnError extends boolean = false>(opti
12550
12607
  * `postId` on an X Ads connection resolves through its X account, and is
12551
12608
  * a 400 when the profile has none (pass `platformPostId` instead).
12552
12609
  *
12610
+ * **Reddit.** `accountId` is the Reddit Ads connection (platform `redditads`).
12611
+ * Pass the Reddit post as `platformPostId` (`t3_abc123` or the bare id), or a
12612
+ * Zernio `postId`, which resolves through the Reddit account connected on the
12613
+ * same profile (400 when there is none). Reddit promotes only posts made by a
12614
+ * profile of the ad account: a post by any other author returns 422. Goals are
12615
+ * traffic, awareness, conversions, video_views (the post must be a video, 422
12616
+ * otherwise) and lead_generation; conversions and lead_generation need a pixel
12617
+ * that already received the event (see `promotedObject`). `leadGenFormId` returns
12618
+ * 400: Reddit retired its native lead forms, so `lead_generation` sends people to
12619
+ * `linkUrl` and optimizes toward the pixel's Lead event. Each boost creates a
12620
+ * campaign, an ad group carrying the budget, the targeting and the bid, and the ad.
12621
+ * `bidStrategy` sets the ad group's bid: LOWEST_COST_WITHOUT_CAP (or omitted) is
12622
+ * automatic bidding (BIDLESS), LOWEST_COST_WITH_BID_CAP is MANUAL_BIDDING with
12623
+ * `bidAmount` as the bid, COST_CAP is MAXIMIZE_VOLUME with `bidAmount` as the cap; any
12624
+ * other strategy returns 422 before anything is created.
12625
+ *
12553
12626
  * **Messaging boosts (Meta).** Use `goal: engagement` with
12554
12627
  * `callToAction: WHATSAPP_MESSAGE`, `MESSAGE_PAGE`, or `INSTAGRAM_MESSAGE`.
12555
12628
  * The CTA implies WHATSAPP, MESSENGER, or INSTAGRAM_DIRECT respectively;
@@ -12684,7 +12757,7 @@ export const replaceGoogleListingGroupFilters = <ThrowOnError extends boolean =
12684
12757
  * Create standalone ad
12685
12758
  * Creates a paid ad with your own creative, building the campaign and ad set it needs or adding it to existing ones.
12686
12759
  *
12687
- * Create a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X, LinkedIn, and OpenAI Ads (ChatGPT Ads).
12760
+ * Create a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X, LinkedIn, OpenAI Ads (ChatGPT Ads) and Reddit Ads.
12688
12761
  *
12689
12762
  * Google Performance Max: set `campaignType: "pmax"` and supply `assetGroup` with
12690
12763
  * text, images by role, business name and finalUrl. Creates a daily budget, PAUSED
@@ -12737,6 +12810,8 @@ export const replaceGoogleListingGroupFilters = <ThrowOnError extends boolean =
12737
12810
  *
12738
12811
  * LinkedIn creates a Single Image or Single Video Ad backed by a Direct Sponsored Content "dark post" authored by a Company Page (see `organizationId`). Supported goals are engagement, traffic, awareness, and video_views (video ads use the `video` field; video_views requires a video), and traffic ads require `linkUrl`.
12739
12812
  *
12813
+ * Reddit Ads (`accountId` of a `redditads` connection, `adAccountId` like `a2_abc123`) creates a campaign, an ad group carrying the budget, schedule, targeting and pixel, and an ad running a new promoted post authored by the ad account's first profile (422 when it has none). The creative format follows the body: `carouselCards` builds a carousel, `video` a video ad, `imageUrl` an image ad, and none of them a text ad, which cannot link out (`linkUrl` is a 400 there; `body` is required). Only the legacy single-creative shape is available: `creatives[]`, `adSetId`, `existingCampaignId` and `validateOnly` are refused. Reddit throttling surfaces as 429 `rate_limited` with `platform: reddit`; retry after a delay. To promote an existing Reddit post use POST /v1/ads/boost.
12814
+ *
12740
12815
  * **Idempotency:** this endpoint is not idempotent at the platform level (a blind retry creates a second campaign/ad set/ad). Send an `Idempotency-Key` header to make retries safe: the first request with a given key creates the ad and we store the response; a retry with the same key replays that exact response (with `Idempotent-Replayed: true`) instead of creating duplicates. Reusing a key with a different body returns 422; a key whose first request is still in flight returns 409 (retry after a short backoff). Keys are scoped to your credential and expire after 24h.
12741
12816
  *
12742
12817
  */
@@ -13043,6 +13118,11 @@ export const searchAdInterests = <ThrowOnError extends boolean = false>(options:
13043
13118
  * ids feed `TargetingSpec.workPositions`/`workEmployers`/`workIndustries`
13044
13119
  * - `industry`, `jobFunction`, `seniority`, `companySize`: the LinkedIn-only B2B facets, whose
13045
13120
  * URNs feed `TargetingSpec.industries`/`jobFunctions`/`seniorities`/`companySizes`
13121
+ * - `community`: Reddit only. Subreddits matching `q`; the id is the subreddit name for
13122
+ * `TargetingSpec.communities` / `excludedCommunities`, `platformId` Reddit's own id and
13123
+ * `audienceSize` its subscriber count when Reddit reports it
13124
+ * - `conversationKeyword`: Reddit only. Reddit's keyword suggestions for the seed `q`; the id is
13125
+ * the keyword for `TargetingSpec.conversationKeywords` / `excludedConversationKeywords`
13046
13126
  *
13047
13127
  * Availability of each dimension varies by platform. A dimension a platform cannot search
13048
13128
  * returns a 400 naming `dimension`; it never falls back to another dimension, and every
@@ -13062,7 +13142,7 @@ export const searchAdInterests = <ThrowOnError extends boolean = false>(options:
13062
13142
  * at 20, with no network call; its ids feed `TargetingSpec.languages`.
13063
13143
  *
13064
13144
  * Results are normalized across platforms into a single shape, so the same client code
13065
- * consumes Meta, TikTok, LinkedIn, X, Pinterest, and Google results.
13145
+ * consumes Meta, TikTok, LinkedIn, X, Pinterest, Google and Reddit results.
13066
13146
  *
13067
13147
  * TikTok geo searches return every matching level in one list (`type` is
13068
13148
  * `country`, `region`, `city`, `district`, or `metro` for DMA areas), and
@@ -13124,6 +13204,15 @@ export const searchAdInterests = <ThrowOnError extends boolean = false>(options:
13124
13204
  * exposes no postal-code catalog, pass postal codes directly as
13125
13205
  * `targeting.zips: [{ key }]` on `POST /v1/ads/create`
13126
13206
  *
13207
+ * Reddit (`redditads` connection) searches `geo`, `interest`, `community` and
13208
+ * `conversationKeyword`; any other dimension returns 400. Its geo search honours `geoType`
13209
+ * `city` (the default, a server-side city search), `zip` (`q` is the postal code), `country`,
13210
+ * `region` and `metro_area` (matched in Zernio against Reddit's geolocation catalog) and
13211
+ * `all`; other values return 400. `countryCode` scopes every geo search. Each result's id is
13212
+ * Reddit's geolocation id, usable as a `regions`/`metros`/`cities`/`zips` `key` (country
13213
+ * ids go in `countries`). `interest` matches Reddit's interest catalog in Zernio, with the
13214
+ * category as `path`.
13215
+ *
13127
13216
  * For geo queries, `q` should contain only the locality name (e.g. `"Amsterdam"`,
13128
13217
  * not `"Amsterdam, NL"`). Use `countryCode` to disambiguate.
13129
13218
  *
@@ -13736,6 +13825,7 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13736
13825
  * - TikTok (`tiktokads`) via the Offline Events API `/offline/batch/` (OFFLINE conversions only)
13737
13826
  * - OpenAI Ads (`openaiads`) via its Conversions API (a separate host, `bzr.openai.com`)
13738
13827
  * - Pinterest (`pinterestads`) via `POST /v5/ad_accounts/{id}/events`
13828
+ * - Reddit (`redditads`) via the Reddit Conversions API (`POST /api/v3/pixels/{pixelId}/conversion_events`)
13739
13829
  *
13740
13830
  * `destinationId` semantics differ per platform:
13741
13831
  *
@@ -13745,6 +13835,7 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13745
13835
  * - TikTok: Offline Event Set ID, e.g. `7057103914977558530`
13746
13836
  * - OpenAI Ads: pixel wire id (numeric `pixel_id`, distinct from the internal pixel id), as returned by `GET /v1/accounts/{accountId}/conversion-destinations`
13747
13837
  * - Pinterest: numeric ad account id, e.g. `549755885175` (Pinterest attributes to the ad account, not to a tag)
13838
+ * - Reddit: pixel id, e.g. `a2_abc123`, as returned by `GET /v1/accounts/{accountId}/conversion-destinations` (any other shape returns 400)
13748
13839
  *
13749
13840
  * Pinterest notes: each event needs an `email`, or `ipAddress` plus `userAgent`; others are
13750
13841
  * listed in `failures` with code `INVALID_EVENT` and the rest are still sent. `clickIds.epik`
@@ -13754,6 +13845,21 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13754
13845
  * (failures carry code `PINTEREST_EVENT_FAILED`). `consent.adUserData: DENIED` sets `opt_out`.
13755
13846
  * The connected user needs a Business Access role on the ad account (403 otherwise).
13756
13847
  *
13848
+ * Reddit notes: each event needs a match key: `clickIds.rdt_cid`, `clickIds.rdt_uuid`,
13849
+ * `email`, `phone`, `externalId`, or `ipAddress` plus `userAgent`. Reddit accepts events up to
13850
+ * 7 days old, and `eventTime` is unix SECONDS (a value more than an hour in the future is
13851
+ * refused as milliseconds). Events failing these checks are listed in `failures` with code
13852
+ * `INVALID_EVENT` and the rest are still sent. Email (lowercased, dots and `+tags` stripped
13853
+ * from the local part), phone (E.164 with the leading `+`) and `externalId` are SHA-256
13854
+ * hashed; IP and user agent go plaintext. `consent.adUserData: DENIED` sends no user fields.
13855
+ * `actionSource` maps to WEBSITE (`web`), APP (`app`) or OTHER, overridable with
13856
+ * `platformData.action_source`; `platformData.order_id` is forwarded. `testCode` is sent as
13857
+ * Reddit's `test_id`. The connection must hold the `adsconversions` permission: one
13858
+ * authorized without it returns 403 `reconnect_required` (reconnect via
13859
+ * GET /v1/connect/redditads/ads), and a Reddit user with no role on the ad account that owns
13860
+ * the pixel returns 403 `insufficient_permissions`. Reddit throttling and other rejections are
13861
+ * reported per chunk in `failures` with code `REDDIT_REJECTED`.
13862
+ *
13757
13863
  * TikTok notes: this path sends OFFLINE conversions (in-store / CRM / call-center), not web-pixel
13758
13864
  * events. Each event must carry an email or phone (TikTok requires at least one). The connected
13759
13865
  * TikTok ads account must have granted the Offline Events permission; older grants must reconnect.
@@ -13776,10 +13882,11 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13776
13882
  * entire batch if any event is malformed. Google caps at 2000. LinkedIn caps at 5000 and is
13777
13883
  * also all-or-nothing per chunk. OpenAI Ads caps at 1000 per request; larger submissions are
13778
13884
  * split into 1000-event chunks, each all-or-nothing (a malformed event fails every event in
13779
- * that chunk, not the whole request).
13885
+ * that chunk, not the whole request). Reddit works the same way, in chunks of 1000.
13780
13886
  *
13781
13887
  * Dedup: pass a stable `eventId` on every event. Meta and LinkedIn use it to dedupe against
13782
- * browser-side pixel/Insight Tag events; Google maps it to `transactionId`.
13888
+ * browser-side pixel/Insight Tag events; Google maps it to `transactionId`; Reddit receives it as
13889
+ * `conversion_id`, its dedup key against Reddit Pixel events.
13783
13890
  *
13784
13891
  * Per-platform `eventName` semantics:
13785
13892
  *
@@ -14033,15 +14140,19 @@ export const updateCampaignConversionGoals = <ThrowOnError extends boolean = fal
14033
14140
  * Lists where an ad account can receive conversion events (pixels, conversion actions or rules), so you know where to send them.
14034
14141
  *
14035
14142
  * Returns the list of pixels (Meta), conversion actions (Google),
14036
- * conversion rules (LinkedIn), pixels (OpenAI Ads) or ad accounts
14143
+ * conversion rules (LinkedIn), pixels (OpenAI Ads, Reddit) or ad accounts
14037
14144
  * (Pinterest) accessible to the connected ads account. Use the returned `id` as `destinationId` when
14038
14145
  * posting to `POST /v1/ads/conversions`.
14039
14146
  *
14040
14147
  * For Google and LinkedIn, each destination's `type` reflects the
14041
14148
  * conversion type (PURCHASE, LEAD, SIGN_UP, etc.), and the event type is
14042
- * locked to the destination. For Meta and OpenAI Ads, `type` is absent:
14149
+ * locked to the destination. For Meta, OpenAI Ads and Reddit, `type` is absent:
14043
14150
  * pixels accept any event name per request.
14044
14151
  *
14152
+ * For Reddit, the pixels of every ad account the connection reaches (only the scoped
14153
+ * ones when the connection has an ad-account scope), each with the `adAccountId` that owns
14154
+ * it. Reddit pixels are created in Reddit Ads Manager, not through Zernio.
14155
+ *
14045
14156
  * For LinkedIn, destinations are returned across every sponsored ad
14046
14157
  * account the connected token can access; the `adAccountId` field on
14047
14158
  * each destination identifies the parent ad account and is required for
@@ -14063,7 +14174,7 @@ export const listConversionDestinations = <ThrowOnError extends boolean = false>
14063
14174
  * LinkedIn (conversion rule) and Google Ads (conversion action). Meta
14064
14175
  * and OpenAI Ads pixels are created via their own tracking-tags flow
14065
14176
  * instead (`POST /v1/accounts/{accountId}/tracking-tags`); this endpoint
14066
- * returns 405 for both.
14177
+ * returns 405 for both, and for Reddit (create Reddit pixels in Reddit Ads Manager).
14067
14178
  *
14068
14179
  * **LinkedIn:** creation is NOT idempotent. A retry creates a second
14069
14180
  * destination. Deduplicate before retrying.