@zernio/node 0.2.1168 → 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.1168",
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.1168",
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.1168",
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
  *
@@ -10054,9 +10090,19 @@ export const updateAdCampaign = <ThrowOnError extends boolean = false>(options:
10054
10090
  * to resolve, so it is invisible to `/v1/ads/tree` and this endpoint would
10055
10091
  * 404. That state is produced by the two-step create flow (campaign, then
10056
10092
  * ads via `existingCampaignId`) whenever Meta rejects the ad step. To
10057
- * delete such a shell, send `accountId` in the body: we skip the local
10058
- * lookup entirely and forward the delete to Meta. `accountId` is ignored
10059
- * when the campaign does have ads.
10093
+ * delete such a shell, send `accountId` in the body or the query string:
10094
+ * we skip the local lookup entirely and forward the delete to Meta, or to
10095
+ * X when `accountId` is an X or X Ads connection. On X the ad account comes
10096
+ * from the synced campaign, which wins over `adAccountId`; when the campaign
10097
+ * was never synced, send `adAccountId` too. Before deleting, Zernio asks X
10098
+ * whether the campaign still holds undeleted line items (ads not synced
10099
+ * yet); if it does the answer is 409 and nothing is deleted, so sync the ad
10100
+ * account and retry. Other platforms answer 501 for an empty campaign.
10101
+ * `accountId` is ignored when the campaign does have ads.
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.
10060
10106
  *
10061
10107
  */
10062
10108
  export const deleteAdCampaign = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<DeleteAdCampaignData, ThrowOnError>) => {
@@ -10291,7 +10337,7 @@ export const updateCampaignTargeting = <ThrowOnError extends boolean = false>(op
10291
10337
  * (null when the read failed and the stored values were returned).
10292
10338
  * With `live=true` (which needs a `campaignId` or `adSetId` filter) each listed ad set's own
10293
10339
  * switch (`platformAdSetStatus`) is read live, for the first 20 rows; later rows keep their
10294
- * 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.
10295
10341
  * The rolled-up `status` is not re-derived by a live read. On TikTok the same live read also
10296
10342
  * returns the ad group's applied `optimizationGoal` and `billingEvent`, as TikTok's adgroup/get
10297
10343
  * reports them, so you can verify the goal TikTok applied rather than the one you requested.
@@ -10437,6 +10483,14 @@ export const getAdSetDetails = <ThrowOnError extends boolean = false>(options: O
10437
10483
  * CBO, the response is 409 with code BUDGET_LEVEL_MISMATCH. Route to
10438
10484
  * PUT /v1/ads/campaigns/{campaignId} instead.
10439
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
+ *
10440
10494
  * `status` behaves exactly as PUT /v1/ads/ad-sets/{adSetId}/status
10441
10495
  * describes: only the ad set's own switch is written, its ads keep
10442
10496
  * theirs, and `statusUpdated` / `statusSkipped` report whether that switch
@@ -10458,7 +10512,7 @@ export const updateAdSet = <ThrowOnError extends boolean = false>(options: Optio
10458
10512
  * `status: cancelled`.
10459
10513
  *
10460
10514
  * Delete is soft on platforms that have no hard delete: LinkedIn moves the
10461
- * 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
10462
10516
  * soft-flags the line item. Google removes the ad group. All remain readable
10463
10517
  * for reporting.
10464
10518
  *
@@ -10482,7 +10536,7 @@ export const deleteAdSet = <ThrowOnError extends boolean = false>(options: Optio
10482
10536
  * Writes the ad set's own on/off switch and nothing else, on every platform
10483
10537
  * (Meta `configured_status`, TikTok ad group `operation_status`, Google ad
10484
10538
  * group status, LinkedIn campaign, Pinterest ad group, X line item, ChatGPT
10485
- * (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
10486
10540
  * individually stays paused when the ad set resumes. The campaign above is
10487
10541
  * not touched either, so an ad set resumed under a paused campaign reads
10488
10542
  * `status: paused` until the campaign is resumed too. See the Status model
@@ -10654,6 +10708,12 @@ export const getAd = <ThrowOnError extends boolean = false>(options: OptionsLega
10654
10708
  * - **Pinterest / X / OpenAI Ads**: status + budget only. Sending
10655
10709
  * `targeting` or `creative` returns 501 with code `unsupported_platform_operation`.
10656
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.
10657
10717
  *
10658
10718
  * **Google location and language replacement:** locations, languages and devices are
10659
10719
  * campaign-level criteria on Google, so these edits apply to every ad group and ad in
@@ -10712,9 +10772,9 @@ export const updateAd = <ThrowOnError extends boolean = false>(options: OptionsL
10712
10772
  * Cancel an ad
10713
10773
  * Deletes an ad on the platform while keeping its history, and removes empty parents only when they were created through this API.
10714
10774
  *
10715
- * 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).
10716
10776
  *
10717
- * 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. To delete a whole campaign on purpose use `DELETE /v1/ads/campaigns/{campaignId}`.
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}`.
10718
10778
  *
10719
10779
  */
10720
10780
  export const deleteAd = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<DeleteAdData, ThrowOnError>) => {
@@ -10730,7 +10790,7 @@ export const deleteAd = <ThrowOnError extends boolean = false>(options: OptionsL
10730
10790
  *
10731
10791
  * Ad-scoped pause/resume: flips ONLY this ad's own switch (Meta
10732
10792
  * `configured_status`, TikTok `operation_status`, Google ad group ad
10733
- * 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
10734
10794
  * campaign, so sibling ads keep running. X is the exception: its smallest
10735
10795
  * switch is the line item. Thin wrapper over the `status` field of
10736
10796
  * PUT /v1/ads/{adId}, for callers that want a URL symmetric to
@@ -12428,6 +12488,9 @@ export const createAdAccount = <ThrowOnError extends boolean = false>(options: O
12428
12488
  * For Google Ads: responds `429` when Google's API quota is temporarily exhausted
12429
12489
  * (instead of an empty list). Retry after a delay.
12430
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
+ *
12431
12494
  */
12432
12495
  export const listAdAccounts = <ThrowOnError extends boolean = false>(options: OptionsLegacyParser<ListAdAccountsData, ThrowOnError>) => {
12433
12496
  return (options?.client ?? client).get<ListAdAccountsResponse, ListAdAccountsError, ThrowOnError>({
@@ -12544,6 +12607,22 @@ export const getDsaRecommendations = <ThrowOnError extends boolean = false>(opti
12544
12607
  * `postId` on an X Ads connection resolves through its X account, and is
12545
12608
  * a 400 when the profile has none (pass `platformPostId` instead).
12546
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
+ *
12547
12626
  * **Messaging boosts (Meta).** Use `goal: engagement` with
12548
12627
  * `callToAction: WHATSAPP_MESSAGE`, `MESSAGE_PAGE`, or `INSTAGRAM_MESSAGE`.
12549
12628
  * The CTA implies WHATSAPP, MESSENGER, or INSTAGRAM_DIRECT respectively;
@@ -12678,7 +12757,7 @@ export const replaceGoogleListingGroupFilters = <ThrowOnError extends boolean =
12678
12757
  * Create standalone ad
12679
12758
  * Creates a paid ad with your own creative, building the campaign and ad set it needs or adding it to existing ones.
12680
12759
  *
12681
- * 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.
12682
12761
  *
12683
12762
  * Google Performance Max: set `campaignType: "pmax"` and supply `assetGroup` with
12684
12763
  * text, images by role, business name and finalUrl. Creates a daily budget, PAUSED
@@ -12731,6 +12810,8 @@ export const replaceGoogleListingGroupFilters = <ThrowOnError extends boolean =
12731
12810
  *
12732
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`.
12733
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
+ *
12734
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.
12735
12816
  *
12736
12817
  */
@@ -13037,6 +13118,11 @@ export const searchAdInterests = <ThrowOnError extends boolean = false>(options:
13037
13118
  * ids feed `TargetingSpec.workPositions`/`workEmployers`/`workIndustries`
13038
13119
  * - `industry`, `jobFunction`, `seniority`, `companySize`: the LinkedIn-only B2B facets, whose
13039
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`
13040
13126
  *
13041
13127
  * Availability of each dimension varies by platform. A dimension a platform cannot search
13042
13128
  * returns a 400 naming `dimension`; it never falls back to another dimension, and every
@@ -13056,7 +13142,7 @@ export const searchAdInterests = <ThrowOnError extends boolean = false>(options:
13056
13142
  * at 20, with no network call; its ids feed `TargetingSpec.languages`.
13057
13143
  *
13058
13144
  * Results are normalized across platforms into a single shape, so the same client code
13059
- * consumes Meta, TikTok, LinkedIn, X, Pinterest, and Google results.
13145
+ * consumes Meta, TikTok, LinkedIn, X, Pinterest, Google and Reddit results.
13060
13146
  *
13061
13147
  * TikTok geo searches return every matching level in one list (`type` is
13062
13148
  * `country`, `region`, `city`, `district`, or `metro` for DMA areas), and
@@ -13118,6 +13204,15 @@ export const searchAdInterests = <ThrowOnError extends boolean = false>(options:
13118
13204
  * exposes no postal-code catalog, pass postal codes directly as
13119
13205
  * `targeting.zips: [{ key }]` on `POST /v1/ads/create`
13120
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
+ *
13121
13216
  * For geo queries, `q` should contain only the locality name (e.g. `"Amsterdam"`,
13122
13217
  * not `"Amsterdam, NL"`). Use `countryCode` to disambiguate.
13123
13218
  *
@@ -13730,6 +13825,7 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13730
13825
  * - TikTok (`tiktokads`) via the Offline Events API `/offline/batch/` (OFFLINE conversions only)
13731
13826
  * - OpenAI Ads (`openaiads`) via its Conversions API (a separate host, `bzr.openai.com`)
13732
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`)
13733
13829
  *
13734
13830
  * `destinationId` semantics differ per platform:
13735
13831
  *
@@ -13739,6 +13835,7 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13739
13835
  * - TikTok: Offline Event Set ID, e.g. `7057103914977558530`
13740
13836
  * - OpenAI Ads: pixel wire id (numeric `pixel_id`, distinct from the internal pixel id), as returned by `GET /v1/accounts/{accountId}/conversion-destinations`
13741
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)
13742
13839
  *
13743
13840
  * Pinterest notes: each event needs an `email`, or `ipAddress` plus `userAgent`; others are
13744
13841
  * listed in `failures` with code `INVALID_EVENT` and the rest are still sent. `clickIds.epik`
@@ -13748,6 +13845,21 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13748
13845
  * (failures carry code `PINTEREST_EVENT_FAILED`). `consent.adUserData: DENIED` sets `opt_out`.
13749
13846
  * The connected user needs a Business Access role on the ad account (403 otherwise).
13750
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
+ *
13751
13863
  * TikTok notes: this path sends OFFLINE conversions (in-store / CRM / call-center), not web-pixel
13752
13864
  * events. Each event must carry an email or phone (TikTok requires at least one). The connected
13753
13865
  * TikTok ads account must have granted the Offline Events permission; older grants must reconnect.
@@ -13770,10 +13882,11 @@ export const getConversionsQuality = <ThrowOnError extends boolean = false>(opti
13770
13882
  * entire batch if any event is malformed. Google caps at 2000. LinkedIn caps at 5000 and is
13771
13883
  * also all-or-nothing per chunk. OpenAI Ads caps at 1000 per request; larger submissions are
13772
13884
  * split into 1000-event chunks, each all-or-nothing (a malformed event fails every event in
13773
- * that chunk, not the whole request).
13885
+ * that chunk, not the whole request). Reddit works the same way, in chunks of 1000.
13774
13886
  *
13775
13887
  * Dedup: pass a stable `eventId` on every event. Meta and LinkedIn use it to dedupe against
13776
- * 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.
13777
13890
  *
13778
13891
  * Per-platform `eventName` semantics:
13779
13892
  *
@@ -14027,15 +14140,19 @@ export const updateCampaignConversionGoals = <ThrowOnError extends boolean = fal
14027
14140
  * Lists where an ad account can receive conversion events (pixels, conversion actions or rules), so you know where to send them.
14028
14141
  *
14029
14142
  * Returns the list of pixels (Meta), conversion actions (Google),
14030
- * conversion rules (LinkedIn), pixels (OpenAI Ads) or ad accounts
14143
+ * conversion rules (LinkedIn), pixels (OpenAI Ads, Reddit) or ad accounts
14031
14144
  * (Pinterest) accessible to the connected ads account. Use the returned `id` as `destinationId` when
14032
14145
  * posting to `POST /v1/ads/conversions`.
14033
14146
  *
14034
14147
  * For Google and LinkedIn, each destination's `type` reflects the
14035
14148
  * conversion type (PURCHASE, LEAD, SIGN_UP, etc.), and the event type is
14036
- * 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:
14037
14150
  * pixels accept any event name per request.
14038
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
+ *
14039
14156
  * For LinkedIn, destinations are returned across every sponsored ad
14040
14157
  * account the connected token can access; the `adAccountId` field on
14041
14158
  * each destination identifies the parent ad account and is required for
@@ -14057,7 +14174,7 @@ export const listConversionDestinations = <ThrowOnError extends boolean = false>
14057
14174
  * LinkedIn (conversion rule) and Google Ads (conversion action). Meta
14058
14175
  * and OpenAI Ads pixels are created via their own tracking-tags flow
14059
14176
  * instead (`POST /v1/accounts/{accountId}/tracking-tags`); this endpoint
14060
- * returns 405 for both.
14177
+ * returns 405 for both, and for Reddit (create Reddit pixels in Reddit Ads Manager).
14061
14178
  *
14062
14179
  * **LinkedIn:** creation is NOT idempotent. A retry creates a second
14063
14180
  * destination. Deduplicate before retrying.