late-sdk 0.0.855 → 0.0.857
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.
- checksums.yaml +4 -4
- data/README.md +19 -2
- data/docs/AdCampaignsApi.md +364 -6
- data/docs/{ListAdKeywords200ResponseKeywordsInner.md → AdKeyword.md} +7 -3
- data/docs/AdKeywordMetrics.md +30 -0
- data/docs/AddAdKeywords201Response.md +18 -0
- data/docs/AddAdKeywordsRequest.md +24 -0
- data/docs/AddAdKeywordsRequestKeywordsInner.md +20 -0
- data/docs/AddAdKeywordsRequestKeywordsInnerAnyOf.md +20 -0
- data/docs/ConnectApi.md +2 -2
- data/docs/CreatePostRequest.md +6 -6
- data/docs/CreateStandaloneAdRequest.md +4 -2
- data/docs/CreateTrackingTagRequest.md +3 -1
- data/docs/KeywordEntry.md +49 -0
- data/docs/ListAdKeywords200Response.md +1 -1
- data/docs/ListCampaignNegativeKeywords200Response.md +18 -0
- data/docs/ListCampaignNegativeKeywords200ResponseKeywordsInner.md +22 -0
- data/docs/PostsApi.md +1 -1
- data/docs/RemoveAdKeyword200Response.md +20 -0
- data/docs/ReplaceCampaignNegativeKeywords200Response.md +22 -0
- data/docs/ReplaceCampaignNegativeKeywordsRequest.md +20 -0
- data/docs/TrackingTagsApi.md +1 -1
- data/docs/UpdateAdKeyword200Response.md +18 -0
- data/docs/{UpdateAdStatusRequest.md → UpdateAdKeywordRequest.md} +2 -2
- data/docs/WebhooksApi.md +2 -2
- data/lib/zernio-sdk/api/ad_campaigns_api.rb +358 -9
- data/lib/zernio-sdk/api/connect_api.rb +2 -2
- data/lib/zernio-sdk/api/posts_api.rb +2 -2
- data/lib/zernio-sdk/api/tracking_tags_api.rb +2 -2
- data/lib/zernio-sdk/api/webhooks_api.rb +4 -4
- data/lib/zernio-sdk/models/{list_ad_keywords200_response_keywords_inner.rb → ad_keyword.rb} +28 -8
- data/lib/zernio-sdk/models/ad_keyword_metrics.rb +207 -0
- data/lib/zernio-sdk/models/add_ad_keywords201_response.rb +149 -0
- data/lib/zernio-sdk/models/add_ad_keywords_request.rb +250 -0
- data/lib/zernio-sdk/models/add_ad_keywords_request_keywords_inner.rb +103 -0
- data/lib/zernio-sdk/models/add_ad_keywords_request_keywords_inner_any_of.rb +225 -0
- data/lib/zernio-sdk/models/create_post_request.rb +6 -0
- data/lib/zernio-sdk/models/create_standalone_ad_request.rb +55 -5
- data/lib/zernio-sdk/models/create_tracking_tag_request.rb +48 -4
- data/lib/zernio-sdk/models/keyword_entry.rb +105 -0
- data/lib/zernio-sdk/models/list_ad_keywords200_response.rb +1 -1
- data/lib/zernio-sdk/models/list_campaign_negative_keywords200_response.rb +149 -0
- data/lib/zernio-sdk/models/list_campaign_negative_keywords200_response_keywords_inner.rb +199 -0
- data/lib/zernio-sdk/models/remove_ad_keyword200_response.rb +157 -0
- data/lib/zernio-sdk/models/replace_campaign_negative_keywords200_response.rb +170 -0
- data/lib/zernio-sdk/models/replace_campaign_negative_keywords_request.rb +219 -0
- data/lib/zernio-sdk/models/update_ad_keyword200_response.rb +147 -0
- data/lib/zernio-sdk/models/{update_ad_status_request.rb → update_ad_keyword_request.rb} +3 -3
- data/lib/zernio-sdk/version.rb +1 -1
- data/lib/zernio-sdk.rb +14 -2
- data/openapi.yaml +299 -32
- data/spec/api/ad_campaigns_api_spec.rb +64 -1
- data/spec/api/connect_api_spec.rb +1 -1
- data/spec/api/posts_api_spec.rb +1 -1
- data/spec/api/tracking_tags_api_spec.rb +1 -1
- data/spec/api/webhooks_api_spec.rb +2 -2
- data/spec/models/ad_keyword_metrics_spec.rb +72 -0
- data/spec/models/{list_ad_keywords200_response_keywords_inner_spec.rb → ad_keyword_spec.rb} +18 -6
- data/spec/models/add_ad_keywords201_response_spec.rb +36 -0
- data/spec/models/add_ad_keywords_request_keywords_inner_any_of_spec.rb +46 -0
- data/spec/models/add_ad_keywords_request_keywords_inner_spec.rb +21 -0
- data/spec/models/add_ad_keywords_request_spec.rb +54 -0
- data/spec/models/create_standalone_ad_request_spec.rb +6 -0
- data/spec/models/create_tracking_tag_request_spec.rb +10 -0
- data/spec/models/keyword_entry_spec.rb +32 -0
- data/spec/models/list_campaign_negative_keywords200_response_keywords_inner_spec.rb +52 -0
- data/spec/models/list_campaign_negative_keywords200_response_spec.rb +36 -0
- data/spec/models/remove_ad_keyword200_response_spec.rb +42 -0
- data/spec/models/replace_campaign_negative_keywords200_response_spec.rb +48 -0
- data/spec/models/replace_campaign_negative_keywords_request_spec.rb +46 -0
- data/spec/models/update_ad_keyword200_response_spec.rb +36 -0
- data/spec/models/{update_ad_status_request_spec.rb → update_ad_keyword_request_spec.rb} +6 -6
- data/zernio-sdk-0.0.857.gem +0 -0
- metadata +58 -10
- data/zernio-sdk-0.0.855.gem +0 -0
data/openapi.yaml
CHANGED
|
@@ -31,7 +31,7 @@ info:
|
|
|
31
31
|
|
|
32
32
|
Key features: Unified posting to 16 platforms, ads management on 7 ad networks (via /v1/ads), aggregated analytics, unified inbox (DMs, comments, reviews), webhooks, OAuth connect, queue scheduling, and white-label support for agencies managing unlimited accounts.
|
|
33
33
|
|
|
34
|
-
Supported posting platforms: Twitter/X, Instagram, WhatsApp, Facebook, LinkedIn, TikTok, YouTube, Pinterest, Reddit, Bluesky, Threads, Google Business, Telegram, Snapchat, Discord, Slack. Supported ad platforms: Meta Ads, Google Ads, TikTok Ads, LinkedIn Ads, Pinterest Ads, X Ads, OpenAI Ads.
|
|
34
|
+
Supported posting platforms: Twitter/X, Instagram, WhatsApp, Facebook, LinkedIn, TikTok, YouTube, Pinterest, Reddit, Bluesky, Threads, Google Business, Telegram, Snapchat, Discord, Slack. Supported ad platforms: Meta Ads, Google Ads, TikTok Ads, LinkedIn Ads, Pinterest Ads, X Ads, OpenAI Ads. Snapchat is a closed beta with no public release date: connections are gated behind approval and return 403 `PLATFORM_BETA_RESTRICTED` until then.
|
|
35
35
|
x-category: Social
|
|
36
36
|
x-website: https://zernio.com
|
|
37
37
|
x-thumbnail: https://rapidapi-prod-apis.s3.amazonaws.com/b24d3df5-563c-4a50-9e1e-1ad3eb1fce69.png
|
|
@@ -121,6 +121,8 @@ x-documentation:
|
|
|
121
121
|
| Telegram | Yes | - | - | - |
|
|
122
122
|
| Snapchat | Yes | - | - | - |
|
|
123
123
|
|
|
124
|
+
> **Snapchat Note:** Snapchat is a closed beta with no public release date. Connecting a Snapchat account is gated behind approval and returns 403 `PLATFORM_BETA_RESTRICTED` until then.
|
|
125
|
+
|
|
124
126
|
> **LinkedIn Analytics Note:** For personal LinkedIn accounts, analytics are only available for posts published through Zernio. This is a LinkedIn API limitation: the `memberCreatorPostAnalytics` endpoint only returns metrics for posts authored by the authenticated user. Company/organization page analytics are not affected and work for all posts.
|
|
125
127
|
|
|
126
128
|
> **Google Business Analytics Note:** Per-post analytics for Google Business Profile are deprecated by Google with no replacement, so Google Business posts always report `syncStatus: "unavailable"` with an explanatory `errorMessage`. Location-level metrics (impressions, clicks, calls, directions, bookings) are available via the dedicated `/v1/analytics/googlebusiness/performance` endpoint.
|
|
@@ -9180,6 +9182,49 @@ components:
|
|
|
9180
9182
|
page_id: { type: string }
|
|
9181
9183
|
earliestAd: { type: string, format: date-time }
|
|
9182
9184
|
latestAd: { type: string, format: date-time }
|
|
9185
|
+
AdKeyword:
|
|
9186
|
+
type: object
|
|
9187
|
+
properties:
|
|
9188
|
+
id: { type: string }
|
|
9189
|
+
accountId: { type: string, description: Social account ID owning the sync }
|
|
9190
|
+
profileId: { type: string }
|
|
9191
|
+
platform: { type: string, enum: [google] }
|
|
9192
|
+
adAccountId: { type: string, description: Google customer ID }
|
|
9193
|
+
campaignId: { type: string }
|
|
9194
|
+
campaignName: { type: [string, "null"] }
|
|
9195
|
+
campaignStatus: { type: [string, "null"] }
|
|
9196
|
+
adSetId: { type: string, description: Google ad group ID }
|
|
9197
|
+
adSetName: { type: [string, "null"] }
|
|
9198
|
+
adSetStatus: { type: [string, "null"] }
|
|
9199
|
+
keyword: { type: string }
|
|
9200
|
+
matchType: { type: string, enum: [exact, phrase, broad, unknown] }
|
|
9201
|
+
status: { type: string, enum: [active, paused] }
|
|
9202
|
+
negative: { type: boolean }
|
|
9203
|
+
qualityScore: { type: [integer, "null"], description: 'Google Quality Score, 1-10. Null when unrated.' }
|
|
9204
|
+
syncedAt: { type: [string, "null"], format: date-time }
|
|
9205
|
+
metrics:
|
|
9206
|
+
type: [object, "null"]
|
|
9207
|
+
description: 'Trailing 30-day window. Null on rows synced before the metrics columns existed (re-synced on the keyword''s next weekly sweep).'
|
|
9208
|
+
properties:
|
|
9209
|
+
windowDays: { type: integer }
|
|
9210
|
+
clicks: { type: integer }
|
|
9211
|
+
impressions: { type: integer }
|
|
9212
|
+
cost: { type: number, description: 'Account currency, not USD-normalized' }
|
|
9213
|
+
conversions: { type: number }
|
|
9214
|
+
firstPageCpc: { type: [number, "null"], description: Account currency }
|
|
9215
|
+
firstPositionCpc: { type: [number, "null"], description: Account currency }
|
|
9216
|
+
KeywordEntry:
|
|
9217
|
+
description: 'A Google Search keyword: a bare string (BROAD match), or an object naming the match type.'
|
|
9218
|
+
oneOf:
|
|
9219
|
+
- type: string
|
|
9220
|
+
minLength: 1
|
|
9221
|
+
maxLength: 80
|
|
9222
|
+
description: 'Keyword text; defaults to BROAD match'
|
|
9223
|
+
- type: object
|
|
9224
|
+
required: [text]
|
|
9225
|
+
properties:
|
|
9226
|
+
text: { type: string, minLength: 1, maxLength: 80 }
|
|
9227
|
+
matchType: { type: string, enum: [exact, phrase, broad] }
|
|
9183
9228
|
ConversionEvent:
|
|
9184
9229
|
type: object
|
|
9185
9230
|
description: |
|
|
@@ -15018,6 +15063,10 @@ paths:
|
|
|
15018
15063
|
Create and optionally publish a post. Immediate posts (`publishNow: true`) include `platformPostUrl` in the response.
|
|
15019
15064
|
Content is optional when media is attached, all platforms have `customContent`, every platform entry is an X Article (`platformSpecificData.article`), or every platform entry is a LinkedIn text-free reshare (`platformSpecificData.reshareUrl` with no text). See each platform's schema for media constraints.
|
|
15020
15065
|
|
|
15066
|
+
## Scheduling
|
|
15067
|
+
|
|
15068
|
+
Pick one of `scheduledFor` (schedule), `publishNow: true` (publish synchronously) or `queuedFromProfile` (next queue slot). With none of them and `isDraft` unset, the post is saved as a draft. `platforms` is required unless the post is a draft. `isDraft: true` wins over `publishNow` and `scheduledFor` (the post is saved, never published); `publishNow: true` wins over `scheduledFor`. A `scheduledFor` already in the past is not rejected: the post is published synchronously in the same request, exactly like `publishNow`.
|
|
15069
|
+
|
|
15021
15070
|
## Idempotency
|
|
15022
15071
|
|
|
15023
15072
|
Two layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post.
|
|
@@ -15055,6 +15104,7 @@ paths:
|
|
|
15055
15104
|
description: 'Post caption/text. Optional when media is attached, all platforms have customContent, every platform entry is an X Article (platformSpecificData.article), or every platform entry is a LinkedIn text-free reshare (platformSpecificData.reshareUrl with no text). Required for other text-only posts.'
|
|
15056
15105
|
mediaItems:
|
|
15057
15106
|
type: array
|
|
15107
|
+
description: 'Media attached to every platform in the request (a platform entry can override it with `customMedia`). Each entry needs a publicly reachable HTTPS `url`; `type` (image, video, gif, document) is inferred from the URL extension when omitted and a `type` that contradicts the extension is rejected with 400. Upload files with `POST /v1/media/presign` first; per-platform size, duration and format limits are listed on each platform schema.'
|
|
15058
15108
|
items: { $ref: '#/components/schemas/MediaItem' }
|
|
15059
15109
|
platforms:
|
|
15060
15110
|
type: array
|
|
@@ -15092,13 +15142,22 @@ paths:
|
|
|
15092
15142
|
- $ref: '#/components/schemas/BlueskyPlatformData'
|
|
15093
15143
|
- $ref: '#/components/schemas/DiscordPlatformData'
|
|
15094
15144
|
- $ref: '#/components/schemas/SlackPlatformData'
|
|
15095
|
-
scheduledFor:
|
|
15096
|
-
|
|
15145
|
+
scheduledFor:
|
|
15146
|
+
type: string
|
|
15147
|
+
format: date-time
|
|
15148
|
+
description: 'When to publish. Required unless `publishNow` is true, `queuedFromProfile` is set, or the post is a draft. An ISO 8601 value with a `Z` or offset (`2026-01-15T10:00:00Z`, `2026-01-15T11:00:00+01:00`) is taken as-is; a value without one (`2026-01-15T10:00:00` or `2026-01-15 10:00`) is read as local time in `timezone`. A value already in the past is published synchronously in the same request. Ignored when `publishNow` is true.'
|
|
15149
|
+
publishNow:
|
|
15150
|
+
type: boolean
|
|
15151
|
+
default: false
|
|
15152
|
+
description: 'Publish to every platform synchronously in this request instead of scheduling; the response then carries each platform result and `platformPostUrl`, with HTTP 207 when some platforms failed. Takes precedence over `scheduledFor`; ignored when `isDraft` is true.'
|
|
15097
15153
|
isDraft:
|
|
15098
15154
|
type: boolean
|
|
15099
15155
|
default: false
|
|
15100
15156
|
description: When true, saves the post as a draft. When none of scheduledFor, publishNow, or queuedFromProfile are provided, the post defaults to draft automatically.
|
|
15101
|
-
timezone:
|
|
15157
|
+
timezone:
|
|
15158
|
+
type: string
|
|
15159
|
+
default: UTC
|
|
15160
|
+
description: 'IANA timezone (`Europe/Madrid`, `America/New_York`) used to interpret a `scheduledFor` (root or per-platform) that carries no `Z` or offset. Has no effect on values that already carry one. An unknown name returns 400 when `scheduledFor` is set.'
|
|
15102
15161
|
tags:
|
|
15103
15162
|
type: array
|
|
15104
15163
|
description: "Tags/keywords. YouTube constraints: each tag max 100 chars, combined max 500 chars, duplicates auto-removed."
|
|
@@ -15111,8 +15170,14 @@ paths:
|
|
|
15111
15170
|
type: array
|
|
15112
15171
|
description: "Stored for reference only. This field does NOT automatically create @mentions when publishing. For LinkedIn @mentions, use the /v1/accounts/{accountId}/linkedin-mentions endpoint to resolve profile URLs to URNs, then embed the returned mentionFormat directly in the post content field."
|
|
15113
15172
|
items: { type: string }
|
|
15114
|
-
crosspostingEnabled:
|
|
15115
|
-
|
|
15173
|
+
crosspostingEnabled:
|
|
15174
|
+
type: boolean
|
|
15175
|
+
default: true
|
|
15176
|
+
description: 'Stored on the post and echoed back on reads. Publishing does not branch on it: every entry in `platforms` is published regardless, so treat it as a label for your own tooling.'
|
|
15177
|
+
metadata:
|
|
15178
|
+
type: object
|
|
15179
|
+
additionalProperties: true
|
|
15180
|
+
description: 'Free-form key/value pairs of your own, stored on the post and returned on reads and in webhook payloads. Zernio also writes the bookkeeping keys `usageCounted`, `usageRefunded` and `hidden` into this object; do not set them, and they are stripped from webhook payloads.'
|
|
15116
15181
|
tiktokSettings:
|
|
15117
15182
|
$ref: '#/components/schemas/TikTokPlatformData'
|
|
15118
15183
|
description: 'Root-level TikTok settings applied to the TikTok platforms sent in the same request. Merged into each platform''s platformSpecificData, with platform-specific settings taking precedence.'
|
|
@@ -18011,7 +18076,7 @@ paths:
|
|
|
18011
18076
|
schema:
|
|
18012
18077
|
type: string
|
|
18013
18078
|
enum: [facebook, instagram, linkedin, twitter, tiktok, youtube, threads, reddit, pinterest, bluesky, googlebusiness, telegram, snapchat, discord, slack, whatsapp]
|
|
18014
|
-
description: Social media platform to connect
|
|
18079
|
+
description: 'Social media platform to connect. `snapchat` is a closed beta with no public release date: it returns 403 `PLATFORM_BETA_RESTRICTED` until the account is approved.'
|
|
18015
18080
|
- name: profileId
|
|
18016
18081
|
in: query
|
|
18017
18082
|
required: true
|
|
@@ -18126,7 +18191,7 @@ paths:
|
|
|
18126
18191
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
18127
18192
|
'402': { $ref: '#/components/responses/PaymentRequired' }
|
|
18128
18193
|
'403':
|
|
18129
|
-
description: "No access to profile,
|
|
18194
|
+
description: "No access to profile, BYOK required for AppSumo Twitter, or Snapchat closed beta (code PLATFORM_BETA_RESTRICTED)"
|
|
18130
18195
|
'404':
|
|
18131
18196
|
description: Profile not found
|
|
18132
18197
|
post:
|
|
@@ -21734,6 +21799,10 @@ paths:
|
|
|
21734
21799
|
value:
|
|
21735
21800
|
error: "Cannot connect to this profile. It exceeds your Pro plan limit of 5 profiles."
|
|
21736
21801
|
code: "PROFILE_LIMIT_EXCEEDED"
|
|
21802
|
+
betaRestricted:
|
|
21803
|
+
value:
|
|
21804
|
+
error: "Snapchat integration is currently in beta. Please wait until it is publicly released."
|
|
21805
|
+
code: "PLATFORM_BETA_RESTRICTED"
|
|
21737
21806
|
'500':
|
|
21738
21807
|
description: Failed to connect Snapchat account
|
|
21739
21808
|
|
|
@@ -25959,7 +26028,7 @@ paths:
|
|
|
25959
26028
|
|
|
25960
26029
|
`name`, `url` and `events` are required. `url` must be a valid URL and `events` must contain at least one event. Whitespace is trimmed from `url` before validation.
|
|
25961
26030
|
|
|
25962
|
-
Webhooks are
|
|
26031
|
+
Webhooks are auto-disabled only once the endpoint has had no successful delivery for 3 days AND has either reached 20 consecutive terminal failures (each one an event that exhausted the full retry ladder) or been failing continuously for 3 days. The owner is emailed; re-enable it with `isActive: true`.
|
|
25963
26032
|
|
|
25964
26033
|
A restricted (zrk_) API key can only subscribe to events whose resource group
|
|
25965
26034
|
the key holds; an event outside the key's groups is rejected with 403, so a
|
|
@@ -26071,7 +26140,7 @@ paths:
|
|
|
26071
26140
|
|
|
26072
26141
|
When provided, `name` must be 1-50 characters, `url` must be a valid URL, and `events` must contain at least one event. Whitespace is trimmed from `url` before validation.
|
|
26073
26142
|
|
|
26074
|
-
Webhooks are
|
|
26143
|
+
Webhooks are auto-disabled only once the endpoint has had no successful delivery for 3 days AND has either reached 20 consecutive terminal failures (each one an event that exhausted the full retry ladder) or been failing continuously for 3 days. The owner is emailed; re-enable it with `isActive: true`.
|
|
26075
26144
|
|
|
26076
26145
|
A restricted (zrk_) API key can only set `events` to events whose resource
|
|
26077
26146
|
group the key holds; an event outside the key's groups is rejected with 403.
|
|
@@ -41282,30 +41351,126 @@ paths:
|
|
|
41282
41351
|
properties:
|
|
41283
41352
|
keywords:
|
|
41284
41353
|
type: array
|
|
41285
|
-
items:
|
|
41286
|
-
type: object
|
|
41287
|
-
properties:
|
|
41288
|
-
id: { type: string }
|
|
41289
|
-
accountId: { type: string, description: Social account ID owning the sync }
|
|
41290
|
-
profileId: { type: string }
|
|
41291
|
-
platform: { type: string, enum: [google] }
|
|
41292
|
-
adAccountId: { type: string, description: Google customer ID }
|
|
41293
|
-
campaignId: { type: string }
|
|
41294
|
-
campaignName: { type: [string, "null"] }
|
|
41295
|
-
campaignStatus: { type: [string, "null"] }
|
|
41296
|
-
adSetId: { type: string, description: Google ad group ID }
|
|
41297
|
-
adSetName: { type: [string, "null"] }
|
|
41298
|
-
adSetStatus: { type: [string, "null"] }
|
|
41299
|
-
keyword: { type: string }
|
|
41300
|
-
matchType: { type: string, enum: [exact, phrase, broad, unknown] }
|
|
41301
|
-
status: { type: string, enum: [active, paused] }
|
|
41302
|
-
negative: { type: boolean }
|
|
41303
|
-
syncedAt: { type: [string, "null"], format: date-time }
|
|
41354
|
+
items: { $ref: '#/components/schemas/AdKeyword' }
|
|
41304
41355
|
pagination: { $ref: '#/components/schemas/Pagination' }
|
|
41305
41356
|
'400': { $ref: '#/components/responses/BadRequest' }
|
|
41306
41357
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
41307
41358
|
'403':
|
|
41308
41359
|
description: Ads access required. Legacy plans need the Ads add-on; included by default on usage-based plans.
|
|
41360
|
+
post:
|
|
41361
|
+
x-resource-group: "ads"
|
|
41362
|
+
operationId: addAdKeywords
|
|
41363
|
+
tags: ["Ad Campaigns"]
|
|
41364
|
+
x-platforms: ["google"]
|
|
41365
|
+
summary: Add Search keywords to an ad group
|
|
41366
|
+
description: |
|
|
41367
|
+
Adds one or more keyword criteria to an existing Google Search ad group,
|
|
41368
|
+
without touching the keywords already there (unlike the whole-set diff on
|
|
41369
|
+
`PUT /v1/ads/{adId}`, `keywords`/`negativeKeywords` in `platformSpecificData`,
|
|
41370
|
+
which replaces the set). Set `negative: true` to add ad-group-level negatives
|
|
41371
|
+
instead of positive keywords.
|
|
41372
|
+
security:
|
|
41373
|
+
- bearerAuth: []
|
|
41374
|
+
requestBody:
|
|
41375
|
+
required: true
|
|
41376
|
+
content:
|
|
41377
|
+
application/json:
|
|
41378
|
+
schema:
|
|
41379
|
+
type: object
|
|
41380
|
+
required: [accountId, adSetId, keywords]
|
|
41381
|
+
properties:
|
|
41382
|
+
accountId: { type: string, description: Social account ID (Google Ads) }
|
|
41383
|
+
adSetId: { type: string, description: Google ad group ID to add the keywords to }
|
|
41384
|
+
keywords:
|
|
41385
|
+
type: array
|
|
41386
|
+
minItems: 1
|
|
41387
|
+
maxItems: 1000
|
|
41388
|
+
items:
|
|
41389
|
+
anyOf:
|
|
41390
|
+
- type: string
|
|
41391
|
+
description: 'Keyword text; defaults to BROAD match'
|
|
41392
|
+
- type: object
|
|
41393
|
+
required: [text]
|
|
41394
|
+
properties:
|
|
41395
|
+
text: { type: string, minLength: 1, maxLength: 80 }
|
|
41396
|
+
matchType: { type: string, enum: [exact, phrase, broad] }
|
|
41397
|
+
negative: { type: boolean, default: false, description: 'Add as ad-group-level negatives instead of positive keywords' }
|
|
41398
|
+
responses:
|
|
41399
|
+
'201':
|
|
41400
|
+
description: Keywords added
|
|
41401
|
+
content:
|
|
41402
|
+
application/json:
|
|
41403
|
+
schema:
|
|
41404
|
+
type: object
|
|
41405
|
+
properties:
|
|
41406
|
+
keywords:
|
|
41407
|
+
type: array
|
|
41408
|
+
items: { $ref: '#/components/schemas/AdKeyword' }
|
|
41409
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
41410
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
41411
|
+
'404': { description: 'The ad group ("adSetId") was not found for this account.' }
|
|
41412
|
+
'501': { description: Only available on Google Ads accounts }
|
|
41413
|
+
|
|
41414
|
+
/v1/ads/keywords/{keywordId}:
|
|
41415
|
+
patch:
|
|
41416
|
+
x-resource-group: "ads"
|
|
41417
|
+
operationId: updateAdKeyword
|
|
41418
|
+
tags: ["Ad Campaigns"]
|
|
41419
|
+
x-platforms: ["google"]
|
|
41420
|
+
summary: Pause or enable a Search keyword
|
|
41421
|
+
description: |
|
|
41422
|
+
Changes `ad_group_criterion.status` for one keyword criterion (M.140).
|
|
41423
|
+
Negative keywords have no status on Google and cannot be paused or enabled.
|
|
41424
|
+
security:
|
|
41425
|
+
- bearerAuth: []
|
|
41426
|
+
parameters:
|
|
41427
|
+
- { name: keywordId, in: path, required: true, schema: { type: string }, description: Zernio keyword ID (not the Google criterion ID) }
|
|
41428
|
+
requestBody:
|
|
41429
|
+
required: true
|
|
41430
|
+
content:
|
|
41431
|
+
application/json:
|
|
41432
|
+
schema:
|
|
41433
|
+
type: object
|
|
41434
|
+
required: [status]
|
|
41435
|
+
properties:
|
|
41436
|
+
status: { type: string, enum: [active, paused] }
|
|
41437
|
+
responses:
|
|
41438
|
+
'200':
|
|
41439
|
+
description: Keyword updated
|
|
41440
|
+
content:
|
|
41441
|
+
application/json:
|
|
41442
|
+
schema:
|
|
41443
|
+
type: object
|
|
41444
|
+
properties:
|
|
41445
|
+
keyword: { $ref: '#/components/schemas/AdKeyword' }
|
|
41446
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
41447
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
41448
|
+
'404': { description: Keyword not found }
|
|
41449
|
+
'422': { description: 'Negative keywords have no status on Google; they cannot be paused or enabled.' }
|
|
41450
|
+
delete:
|
|
41451
|
+
x-resource-group: "ads"
|
|
41452
|
+
operationId: removeAdKeyword
|
|
41453
|
+
tags: ["Ad Campaigns"]
|
|
41454
|
+
x-platforms: ["google"]
|
|
41455
|
+
summary: Remove a Search keyword
|
|
41456
|
+
description: Removes one keyword criterion (positive or negative) from its ad group (M.140).
|
|
41457
|
+
security:
|
|
41458
|
+
- bearerAuth: []
|
|
41459
|
+
parameters:
|
|
41460
|
+
- { name: keywordId, in: path, required: true, schema: { type: string }, description: Zernio keyword ID (not the Google criterion ID) }
|
|
41461
|
+
responses:
|
|
41462
|
+
'200':
|
|
41463
|
+
description: Keyword removed
|
|
41464
|
+
content:
|
|
41465
|
+
application/json:
|
|
41466
|
+
schema:
|
|
41467
|
+
type: object
|
|
41468
|
+
properties:
|
|
41469
|
+
removed: { type: boolean, description: Always true on success }
|
|
41470
|
+
keywordId: { type: string }
|
|
41471
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
41472
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
41473
|
+
'404': { description: Keyword not found }
|
|
41309
41474
|
|
|
41310
41475
|
/v1/ads/campaigns:
|
|
41311
41476
|
get:
|
|
@@ -41627,6 +41792,102 @@ paths:
|
|
|
41627
41792
|
'404': { description: Campaign not found }
|
|
41628
41793
|
'501': { description: Operation not supported on this platform }
|
|
41629
41794
|
|
|
41795
|
+
/v1/ads/campaigns/{campaignId}/negative-keywords:
|
|
41796
|
+
get:
|
|
41797
|
+
x-resource-group: "ads"
|
|
41798
|
+
operationId: listCampaignNegativeKeywords
|
|
41799
|
+
tags: ["Ad Campaigns"]
|
|
41800
|
+
x-platforms: ["google"]
|
|
41801
|
+
summary: List campaign-level negative keywords
|
|
41802
|
+
description: |
|
|
41803
|
+
Returns the campaign-level negative keywords (`campaign_criterion.negative`),
|
|
41804
|
+
distinct from the ad-group-level negatives under `GET /v1/ads/keywords`. Read
|
|
41805
|
+
live from Google on every call (not synced to Postgres), and gated by the
|
|
41806
|
+
shared Google Ads operations budget like every other on-demand Google surface.
|
|
41807
|
+
|
|
41808
|
+
The platform is always discovered from the campaign itself; a non-Google
|
|
41809
|
+
campaign returns 501 rather than 404, whether or not `platform` was passed.
|
|
41810
|
+
security:
|
|
41811
|
+
- bearerAuth: []
|
|
41812
|
+
parameters:
|
|
41813
|
+
- { name: campaignId, in: path, required: true, schema: { type: string }, description: Platform campaign ID }
|
|
41814
|
+
- { name: platform, in: query, schema: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter, openai] }, description: 'Optional and NOT authoritative: the resolved campaign''s own platform decides 200 vs 501, never this hint.' }
|
|
41815
|
+
responses:
|
|
41816
|
+
'200':
|
|
41817
|
+
description: Campaign-level negative keywords
|
|
41818
|
+
content:
|
|
41819
|
+
application/json:
|
|
41820
|
+
schema:
|
|
41821
|
+
type: object
|
|
41822
|
+
properties:
|
|
41823
|
+
keywords:
|
|
41824
|
+
type: array
|
|
41825
|
+
items:
|
|
41826
|
+
type: object
|
|
41827
|
+
properties:
|
|
41828
|
+
criterionId: { type: string }
|
|
41829
|
+
text: { type: string }
|
|
41830
|
+
matchType: { type: string, enum: [exact, phrase, broad] }
|
|
41831
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
41832
|
+
'404': { description: Campaign not found }
|
|
41833
|
+
'429': { description: Google Ads operations budget exhausted; retry later }
|
|
41834
|
+
'501': { description: Only available on Google Ads campaigns }
|
|
41835
|
+
put:
|
|
41836
|
+
x-resource-group: "ads"
|
|
41837
|
+
operationId: replaceCampaignNegativeKeywords
|
|
41838
|
+
tags: ["Ad Campaigns"]
|
|
41839
|
+
x-platforms: ["google"]
|
|
41840
|
+
summary: Replace campaign-level negative keywords
|
|
41841
|
+
description: |
|
|
41842
|
+
Replaces the FULL set of campaign-level negative keywords (C.270): the desired
|
|
41843
|
+
list is diffed against what Google already has, and the difference is applied
|
|
41844
|
+
as one `create`/`remove` mutate. Send an empty array to clear every campaign
|
|
41845
|
+
negative.
|
|
41846
|
+
|
|
41847
|
+
The platform is always discovered from the campaign itself; a non-Google
|
|
41848
|
+
campaign returns 501 rather than 404, whether or not `platform` was sent.
|
|
41849
|
+
security:
|
|
41850
|
+
- bearerAuth: []
|
|
41851
|
+
parameters:
|
|
41852
|
+
- { name: campaignId, in: path, required: true, schema: { type: string }, description: Platform campaign ID }
|
|
41853
|
+
requestBody:
|
|
41854
|
+
required: true
|
|
41855
|
+
content:
|
|
41856
|
+
application/json:
|
|
41857
|
+
schema:
|
|
41858
|
+
type: object
|
|
41859
|
+
required: [keywords]
|
|
41860
|
+
properties:
|
|
41861
|
+
platform: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter, openai], description: 'Optional and NOT authoritative: the resolved campaign''s own platform decides 200 vs 501, never this hint.' }
|
|
41862
|
+
keywords:
|
|
41863
|
+
type: array
|
|
41864
|
+
maxItems: 1000
|
|
41865
|
+
items: { $ref: '#/components/schemas/KeywordEntry' }
|
|
41866
|
+
responses:
|
|
41867
|
+
'200':
|
|
41868
|
+
description: Campaign-level negative keywords replaced
|
|
41869
|
+
content:
|
|
41870
|
+
application/json:
|
|
41871
|
+
schema:
|
|
41872
|
+
type: object
|
|
41873
|
+
properties:
|
|
41874
|
+
created: { type: integer, description: Negative criteria newly created on Google }
|
|
41875
|
+
removed: { type: integer, description: Negative criteria removed from Google }
|
|
41876
|
+
keywords:
|
|
41877
|
+
type: array
|
|
41878
|
+
description: The full negative-keyword set after the replace
|
|
41879
|
+
items:
|
|
41880
|
+
type: object
|
|
41881
|
+
properties:
|
|
41882
|
+
criterionId: { type: string }
|
|
41883
|
+
text: { type: string }
|
|
41884
|
+
matchType: { type: string, enum: [exact, phrase, broad] }
|
|
41885
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
41886
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
41887
|
+
'404': { description: Campaign not found }
|
|
41888
|
+
'429': { description: Google Ads operations budget exhausted; retry later }
|
|
41889
|
+
'501': { description: Only available on Google Ads campaigns }
|
|
41890
|
+
|
|
41630
41891
|
/v1/ads/campaigns/bulk-status:
|
|
41631
41892
|
post:
|
|
41632
41893
|
x-resource-group: "ads"
|
|
@@ -45560,8 +45821,9 @@ paths:
|
|
|
45560
45821
|
items: { type: string, enum: [mobile, desktop] }
|
|
45561
45822
|
audienceId: { type: string, description: Custom audience ID for targeting }
|
|
45562
45823
|
campaignType: { type: string, enum: [display, search], default: display, description: Google only }
|
|
45563
|
-
keywords: { type: array, maxItems: 1000, items: {
|
|
45564
|
-
negativeKeywords: { type: array,
|
|
45824
|
+
keywords: { type: array, maxItems: 1000, items: { $ref: '#/components/schemas/KeywordEntry' }, description: "Google Search only. Keywords on the new ad group; entries are strings (BROAD) or { text, matchType }. Editable later via PUT /v1/ads/{adId} targeting.keywords." }
|
|
45825
|
+
negativeKeywords: { type: array, maxItems: 1000, items: { $ref: '#/components/schemas/KeywordEntry' }, description: "Google Search only; other platforms return 400. Ad-group-level negative keywords on the new ad group. Editable later via PUT /v1/ads/{adId} targeting.negativeKeywords." }
|
|
45826
|
+
campaignNegativeKeywords: { type: array, maxItems: 1000, items: { $ref: '#/components/schemas/KeywordEntry' }, description: "Google Search only; other platforms return 400. Campaign-level negative keywords (campaign_criterion.negative), created alongside the ad group. Editable later via PUT /v1/ads/campaigns/{campaignId}/negative-keywords." }
|
|
45565
45827
|
additionalHeadlines: { type: array, items: { type: string }, description: "Google Search RSA only. Extra headlines." }
|
|
45566
45828
|
additionalDescriptions: { type: array, items: { type: string }, description: "Google Search RSA only. Extra descriptions." }
|
|
45567
45829
|
sitelinks:
|
|
@@ -49010,7 +49272,8 @@ paths:
|
|
|
49010
49272
|
pixel.
|
|
49011
49273
|
|
|
49012
49274
|
NOT idempotent on either platform: each call creates a new pixel (and,
|
|
49013
|
-
for OpenAI, a new Conversions API key
|
|
49275
|
+
for OpenAI, a new Conversions API key plus, with `defaultEventType`, a
|
|
49276
|
+
new conversion event setting). Do not retry blindly on
|
|
49014
49277
|
timeout. Meta (platform `metaads`) and OpenAI Ads (platform
|
|
49015
49278
|
`openaiads`); other platforms return 405.
|
|
49016
49279
|
security:
|
|
@@ -49027,6 +49290,10 @@ paths:
|
|
|
49027
49290
|
properties:
|
|
49028
49291
|
adAccountId: { type: string, description: 'Meta ad account id, e.g. `act_123456789`. Required by this endpoint but ignored for OpenAI Ads.' }
|
|
49029
49292
|
name: { type: string, minLength: 1, maxLength: 200 }
|
|
49293
|
+
defaultEventType:
|
|
49294
|
+
type: string
|
|
49295
|
+
enum: [order_created, lead_created, items_added, contents_viewed, checkout_started, registration_completed, subscription_created, trial_started, appointment_scheduled, page_viewed, app_installed, app_opened]
|
|
49296
|
+
description: 'OpenAI Ads only (ignored by Meta). When set, also provisions a standard conversion event setting wired to the new pixel, so `goal: conversions` ad creates on `POST /v1/ads/create` have an event to reference immediately.'
|
|
49030
49297
|
responses:
|
|
49031
49298
|
'201':
|
|
49032
49299
|
description: Tracking tag created
|
|
@@ -32,6 +32,18 @@ describe 'AdCampaignsApi' do
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# unit tests for add_ad_keywords
|
|
36
|
+
# Add Search keywords to an ad group
|
|
37
|
+
# Adds one or more keyword criteria to an existing Google Search ad group, without touching the keywords already there (unlike the whole-set diff on `PUT /v1/ads/{adId}`, `keywords`/`negativeKeywords` in `platformSpecificData`, which replaces the set). Set `negative: true` to add ad-group-level negatives instead of positive keywords.
|
|
38
|
+
# @param add_ad_keywords_request
|
|
39
|
+
# @param [Hash] opts the optional parameters
|
|
40
|
+
# @return [AddAdKeywords201Response]
|
|
41
|
+
describe 'add_ad_keywords test' do
|
|
42
|
+
it 'should work' do
|
|
43
|
+
# assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
35
47
|
# unit tests for attach_campaign_assets
|
|
36
48
|
# Attach extension assets to a Google Search campaign
|
|
37
49
|
# Attach sitelinks, callouts and/or structured snippets to an already-existing Google Search campaign — the same builders POST /v1/ads/create uses, but without rebuilding the hierarchy. At least one of sitelinks, callouts or structuredSnippets is required. Google-only. Other platforms have no equivalent extension surface and return 501. Approval status is Google-async; poll `asset.policy_summary` after review. Assets stay in the account library even if the campaign is later deleted.
|
|
@@ -319,6 +331,44 @@ describe 'AdCampaignsApi' do
|
|
|
319
331
|
end
|
|
320
332
|
end
|
|
321
333
|
|
|
334
|
+
# unit tests for list_campaign_negative_keywords
|
|
335
|
+
# List campaign-level negative keywords
|
|
336
|
+
# Returns the campaign-level negative keywords (`campaign_criterion.negative`), distinct from the ad-group-level negatives under `GET /v1/ads/keywords`. Read live from Google on every call (not synced to Postgres), and gated by the shared Google Ads operations budget like every other on-demand Google surface. The platform is always discovered from the campaign itself; a non-Google campaign returns 501 rather than 404, whether or not `platform` was passed.
|
|
337
|
+
# @param campaign_id Platform campaign ID
|
|
338
|
+
# @param [Hash] opts the optional parameters
|
|
339
|
+
# @option opts [String] :platform Optional and NOT authoritative: the resolved campaign's own platform decides 200 vs 501, never this hint.
|
|
340
|
+
# @return [ListCampaignNegativeKeywords200Response]
|
|
341
|
+
describe 'list_campaign_negative_keywords test' do
|
|
342
|
+
it 'should work' do
|
|
343
|
+
# assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# unit tests for remove_ad_keyword
|
|
348
|
+
# Remove a Search keyword
|
|
349
|
+
# Removes one keyword criterion (positive or negative) from its ad group (M.140).
|
|
350
|
+
# @param keyword_id Zernio keyword ID (not the Google criterion ID)
|
|
351
|
+
# @param [Hash] opts the optional parameters
|
|
352
|
+
# @return [RemoveAdKeyword200Response]
|
|
353
|
+
describe 'remove_ad_keyword test' do
|
|
354
|
+
it 'should work' do
|
|
355
|
+
# assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# unit tests for replace_campaign_negative_keywords
|
|
360
|
+
# Replace campaign-level negative keywords
|
|
361
|
+
# Replaces the FULL set of campaign-level negative keywords (C.270): the desired list is diffed against what Google already has, and the difference is applied as one `create`/`remove` mutate. Send an empty array to clear every campaign negative. The platform is always discovered from the campaign itself; a non-Google campaign returns 501 rather than 404, whether or not `platform` was sent.
|
|
362
|
+
# @param campaign_id Platform campaign ID
|
|
363
|
+
# @param replace_campaign_negative_keywords_request
|
|
364
|
+
# @param [Hash] opts the optional parameters
|
|
365
|
+
# @return [ReplaceCampaignNegativeKeywords200Response]
|
|
366
|
+
describe 'replace_campaign_negative_keywords test' do
|
|
367
|
+
it 'should work' do
|
|
368
|
+
# assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
322
372
|
# unit tests for update_ad
|
|
323
373
|
# Update ad
|
|
324
374
|
# Patch one or more fields on an ad. Status, budget, targeting, and creative changes are propagated to the platform. Per-platform support: - **Meta** (Facebook + Instagram): all fields supported. - **TikTok**: status, budget, targeting (via `/v2/adgroup/update/`), and creative (via `/v2/ad/update/` patch-style — `headline` is ignored, `body` becomes `ad_text`). - **Google**: status, budget, and KEYWORD edits via `targeting.keywords` / `targeting.negativeKeywords` — each list you send becomes the FULL new set of its kind on the ad group (criteria not in the list are removed); a kind left out is untouched. Any other `targeting` field returns 400: Google cannot mutate broad targeting post-create without recreating the campaign. `creative` returns 501. - **LinkedIn**: status, budget, targeting (geo countries only, applied to the LinkedIn Campaign via PARTIAL_UPDATE), and creative (uploads new media, creates a replacement inline creative on the same campaign, pauses the old one). - **Pinterest / X / OpenAI Ads**: status + budget only. Sending `targeting` or `creative` returns 501 with code `unsupported_platform_operation`. OpenAI Ads budget is lifetime-only (see `budget.type` below).
|
|
@@ -358,6 +408,19 @@ describe 'AdCampaignsApi' do
|
|
|
358
408
|
end
|
|
359
409
|
end
|
|
360
410
|
|
|
411
|
+
# unit tests for update_ad_keyword
|
|
412
|
+
# Pause or enable a Search keyword
|
|
413
|
+
# Changes `ad_group_criterion.status` for one keyword criterion (M.140). Negative keywords have no status on Google and cannot be paused or enabled.
|
|
414
|
+
# @param keyword_id Zernio keyword ID (not the Google criterion ID)
|
|
415
|
+
# @param update_ad_keyword_request
|
|
416
|
+
# @param [Hash] opts the optional parameters
|
|
417
|
+
# @return [UpdateAdKeyword200Response]
|
|
418
|
+
describe 'update_ad_keyword test' do
|
|
419
|
+
it 'should work' do
|
|
420
|
+
# assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
361
424
|
# unit tests for update_ad_set
|
|
362
425
|
# Update an ad set
|
|
363
426
|
# Ad-set-level writes. Use this for ABO budget updates, ad-set-scoped pause/resume, bid-strategy edits, Meta value-rule-set attach/detach, and Meta-only post-launch delivery settings via `platformSpecificData`. At least one updatable field is required. Value rule sets (Meta only, see `/v1/ads/value-rule-sets`): - ATTACH or REPLACE: send `valueRuleSetId`. Attachment is driven by the id's presence, so `valueRulesApplied: true` is optional. Sending a different id replaces the previous association; there is no separate replace call. - DETACH: send `valueRulesApplied: false` and OMIT `valueRuleSetId`. - Sending `valueRulesApplied: false` TOGETHER with `valueRuleSetId` returns 400 `mutually_exclusive_fields`. This is deliberate: Meta attaches the rule set whenever `value_rule_set_id` is present, even with `value_rules_applied` false, so echoing stored state while asking to detach would silently keep the bid adjustments live. - Eligibility: only ad sets on `LOWEST_COST_WITHOUT_CAP` or `COST_CAP`. Meta rejects the rest server-side. - Read back with `GET /v1/ads/ad-sets/{adSetId}?fields=value_rule_set_id`. Meta does not document `value_rules_applied` as a readable ad-set field, so the boolean cannot be read back. Bid strategy compatibility (per Meta's spec): - `LOWEST_COST_WITHOUT_CAP`: no `bidAmount`, no `roasAverageFloor`. - `LOWEST_COST_WITH_BID_CAP` / `COST_CAP`: `bidAmount` REQUIRED (whole currency units). - `LOWEST_COST_WITH_MIN_ROAS`: `roasAverageFloor` REQUIRED (decimal multiplier, e.g. 2.0 = 2.0x ROAS). - Meta only: send `bidAmount` WITHOUT `bidStrategy` to change the cap amount on an ad set under a COST_CAP / LOWEST_COST_WITH_BID_CAP parent campaign, leaving the strategy itself (inherited from the campaign) untouched. `roasAverageFloor` without `bidStrategy` is rejected (it has no meaning outside LOWEST_COST_WITH_MIN_ROAS). Delivery settings are validated by Meta against the campaign objective; incompatible combinations (e.g. a billingEvent the optimization goal doesn't allow) surface as 400s from Meta. When updating `budget` on an ABO campaign: if the parent campaign is CBO, the response is 409 with code BUDGET_LEVEL_MISMATCH — route to PUT /v1/ads/campaigns/{campaignId} instead.
|
|
@@ -388,7 +451,7 @@ describe 'AdCampaignsApi' do
|
|
|
388
451
|
# Pause or resume a single ad
|
|
389
452
|
# Ad-scoped pause/resume — touches ONLY this ad, never its parent ad set or campaign (so sibling ads keep running). Thin wrapper over the `status` field of PUT /v1/ads/{adId}, for callers that want a URL symmetric to /v1/ads/campaigns/{campaignId}/status and /v1/ads/ad-sets/{adSetId}/status. `{adId}` accepts the same identifier dialects as GET/PUT /v1/ads/{adId} (Zernio hex `_id`, Meta numeric `platformAdId`, or the creative's effective story/media IDs). `platform` is inferred from the ad, so it's not required in the body. Ads in terminal statuses (rejected, completed, cancelled) and no-op flips (already in the target state) are skipped.
|
|
390
453
|
# @param ad_id Zernio `_id` (hex), Meta `platformAdId` (numeric), or one of the creative's effective story/media IDs.
|
|
391
|
-
# @param
|
|
454
|
+
# @param update_ad_keyword_request
|
|
392
455
|
# @param [Hash] opts the optional parameters
|
|
393
456
|
# @return [UpdateAdStatus200Response]
|
|
394
457
|
describe 'update_ad_status test' do
|
|
@@ -201,7 +201,7 @@ describe 'ConnectApi' do
|
|
|
201
201
|
# unit tests for get_connect_url
|
|
202
202
|
# Get OAuth connect URL
|
|
203
203
|
# Initiate an OAuth connection flow. Returns an authUrl to redirect the user to. Standard flow: Zernio hosts the selection UI, then redirects to your redirect_url. Headless mode (headless=true): user is redirected to your redirect_url with OAuth data for custom UI. Use the platform-specific selection endpoints to complete.
|
|
204
|
-
# @param platform Social media platform to connect
|
|
204
|
+
# @param platform Social media platform to connect. `snapchat` is a closed beta with no public release date: it returns 403 `PLATFORM_BETA_RESTRICTED` until the account is approved.
|
|
205
205
|
# @param profile_id Your Zernio profile ID (get from /v1/profiles). For WhatsApp, a Zernio-provisioned number can only be connected on the profile it was provisioned to; connecting from any other profile is rejected with a 409.
|
|
206
206
|
# @param [Hash] opts the optional parameters
|
|
207
207
|
# @option opts [String] :redirect_url Your custom redirect URL after connection completes. MUST be an absolute http(s) URL or a custom app scheme for mobile deeplinks (e.g. myapp://callback); a relative path is rejected with 400 INVALID_REDIRECT_URL. Result params are appended with the URL API, so an existing query string is preserved. Standard mode appends connected={platform}&profileId=X&accountId=Y&username=Z. Headless mode appends OAuth data params for platforms requiring selection (e.g. LinkedIn orgs, Facebook pages). If no selection is needed, the account is created directly and the redirect includes accountId. On failure, the browser is sent to the same redirect_url with `error` and `platform` appended. `error` and `platform` are always present. `error_message`, `is_user_fixable`, `reason` and `dashboard_url` are conditional and must be treated as optional. This list is NOT exhaustive and new values may be added at any time. Treat an unrecognized value as a generic failure rather than matching it exhaustively. Existing values are not renamed or removed without notice. OAuth and callback: oauth_denied, invalid_callback, invalid_state, unsupported_platform, connection_failed, internal_error, token_exchange_failed, byok_config_error, personal_account_not_supported, missing_google_permissions, platform_requires_destination, reconnect_account_mismatch, invalid_request Access and limits: profile_not_found, invalid_profile_id, access_denied, account_limit_exceeded, profile_limit_exceeded, payment_required Destination selection: no_facebook_pages, facebook_pages_error, no_google_locations, google_locations_error, google_permission_denied, no_snapchat_public_profiles, snapchat_profiles_error, discord_no_guild, slack_no_team WhatsApp: whatsapp_error, one_whatsapp_per_profile, whatsapp_number_already_connected, whatsapp_number_pinned_to_profile, connection_cancelled Google Ads (platform=googleads): google_ads_auth_failed, google_ads_invalid_state, google_ads_config_error, google_ads_token_failed, google_ads_quota_exhausted, google_ads_callback_error TikTok Ads (platform=tiktokads): tiktok_ads_auth_failed, tiktok_ads_invalid_state, tiktok_ads_access_denied, tiktok_ads_config_error, tiktok_ads_token_failed, tiktok_ads_account_not_found, tiktok_ads_callback_error X Ads (platform=xads): x_ads_denied, x_ads_auth_failed, x_ads_config_error, x_ads_account_not_found, x_ads_state_error, x_ads_token_failed, x_ads_token_missing, x_ads_callback_error Shopify (platform=shopify): shopify_auth_failed, shopify_config_error, shopify_invalid_state, shopify_invalid_hmac, shopify_invalid_shop, shopify_missing_scopes, shopify_callback_error 1. On this endpoint every upstream OAuth error is collapsed into `oauth_denied`. The provider's own value (for example Meta's `access_denied`) is not forwarded. The dedicated ads flows below are different: they use their own denial slugs and `google_ads_auth_failed` and `tiktok_ads_auth_failed` may carry the provider's raw error string in `error_message`. 2. On the tiktok and twitter ads flows `platform` carries the ads platform id (`tiktokads`, `xads`), not the value used in the request path. The googleads and shopify flows report `googleads` and `shopify`.
|
data/spec/api/posts_api_spec.rb
CHANGED
|
@@ -47,7 +47,7 @@ describe 'PostsApi' do
|
|
|
47
47
|
|
|
48
48
|
# unit tests for create_post
|
|
49
49
|
# Create post
|
|
50
|
-
# Create and optionally publish a post. Immediate posts (`publishNow: true`) include `platformPostUrl` in the response. Content is optional when media is attached, all platforms have `customContent`, every platform entry is an X Article (`platformSpecificData.article`), or every platform entry is a LinkedIn text-free reshare (`platformSpecificData.reshareUrl` with no text). See each platform's schema for media constraints. ## Idempotency Two layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post. **1. Same-request idempotency (5-minute window).** Pass an `x-request-id` header to mark a logical request. If a second request arrives with the same `x-request-id` while the first is in-flight (or within ~5 minutes of completion), we return **HTTP 200** with the original post in the `existingPost` field — no new post is created. The official Zernio SDKs auto-generate a unique `x-request-id` per call. If you're using a generic HTTP client (curl, n8n's HTTP node, Zapier, custom code), either: - Set a unique `x-request-id` per logical call (recommended — UUIDv4 is fine) - Or simply omit the header — we'll treat each request as new **Common pitfall**: if your workflow tool uses a single execution-level request ID and reuses it across multiple HTTP nodes (e.g. one ID for the whole run, shared across 6 different platform calls), every call after the first will look like a retry of the first and return its post. Generate a fresh ID per node. **2. Content-hash dedup (24-hour window).** Independently, we hash `(platform, accountId, content + media URLs)` and reject duplicates within 24 hours with **HTTP 409**. This catches genuine \"same content posted twice to the same account\" cases regardless of `x-request-id`. Returns `error`, `accountId`, `platform`, and `existingPostId` so you can find the original. To intentionally re-post identical content within 24h, change something (the caption, the media, the account) — the dedup is keyed on the full content fingerprint. Order: same-`x-request-id` retries (200) are checked first; if no idempotency match, the content-hash dedup (409) runs.
|
|
50
|
+
# Create and optionally publish a post. Immediate posts (`publishNow: true`) include `platformPostUrl` in the response. Content is optional when media is attached, all platforms have `customContent`, every platform entry is an X Article (`platformSpecificData.article`), or every platform entry is a LinkedIn text-free reshare (`platformSpecificData.reshareUrl` with no text). See each platform's schema for media constraints. ## Scheduling Pick one of `scheduledFor` (schedule), `publishNow: true` (publish synchronously) or `queuedFromProfile` (next queue slot). With none of them and `isDraft` unset, the post is saved as a draft. `platforms` is required unless the post is a draft. `isDraft: true` wins over `publishNow` and `scheduledFor` (the post is saved, never published); `publishNow: true` wins over `scheduledFor`. A `scheduledFor` already in the past is not rejected: the post is published synchronously in the same request, exactly like `publishNow`. ## Idempotency Two layers of duplicate-protection apply, so safe-to-retry callers (network blips, n8n / Zapier retries, etc.) don't accidentally double-post. **1. Same-request idempotency (5-minute window).** Pass an `x-request-id` header to mark a logical request. If a second request arrives with the same `x-request-id` while the first is in-flight (or within ~5 minutes of completion), we return **HTTP 200** with the original post in the `existingPost` field — no new post is created. The official Zernio SDKs auto-generate a unique `x-request-id` per call. If you're using a generic HTTP client (curl, n8n's HTTP node, Zapier, custom code), either: - Set a unique `x-request-id` per logical call (recommended — UUIDv4 is fine) - Or simply omit the header — we'll treat each request as new **Common pitfall**: if your workflow tool uses a single execution-level request ID and reuses it across multiple HTTP nodes (e.g. one ID for the whole run, shared across 6 different platform calls), every call after the first will look like a retry of the first and return its post. Generate a fresh ID per node. **2. Content-hash dedup (24-hour window).** Independently, we hash `(platform, accountId, content + media URLs)` and reject duplicates within 24 hours with **HTTP 409**. This catches genuine \"same content posted twice to the same account\" cases regardless of `x-request-id`. Returns `error`, `accountId`, `platform`, and `existingPostId` so you can find the original. To intentionally re-post identical content within 24h, change something (the caption, the media, the account) — the dedup is keyed on the full content fingerprint. Order: same-`x-request-id` retries (200) are checked first; if no idempotency match, the content-hash dedup (409) runs.
|
|
51
51
|
# @param create_post_request
|
|
52
52
|
# @param [Hash] opts the optional parameters
|
|
53
53
|
# @option opts [String] :x_request_id Optional client-generated request identifier for safe retry (idempotency). When two requests carry the same value, the second is treated as a retry of the first and returns the original post (HTTP 200) instead of creating a duplicate. Window is ~5 minutes from the first request. Generate a UUID per logical call. SDKs do this automatically; HTTP clients should set it themselves or omit it. See the operation description for the full idempotency contract.
|
|
@@ -48,7 +48,7 @@ describe 'TrackingTagsApi' do
|
|
|
48
48
|
|
|
49
49
|
# unit tests for create_tracking_tag
|
|
50
50
|
# Create a tracking tag
|
|
51
|
-
# Meta: creates a Meta Pixel on the given ad account (`POST /act_{id}/adspixels` — `name` is the only input). Returns the created tag including its install `code`. The pixel is owned by the Business Manager that owns the ad account; a pixel created on a personal (non-BM) ad account ends up with `ownerBusinessId: null` and can't be shared with other ad accounts. Creating a Meta pixel does NOT install it — install the returned `code` snippet on the site, or send events server-side via `POST /v1/ads/conversions`. The check `installed` is derived from `lastFiredTime`. OpenAI Ads: creates an OpenAI pixel AND provisions a Conversions API key for it in the same call (`adAccountId` is required by this endpoint but ignored — one API key maps to exactly one ad account, so there's nothing to select). Returns 422 (`FEATURE_NOT_AVAILABLE`) if the ad account isn't enabled for pixel management; contact your OpenAI partner representative to enable it. There is no delete API for OpenAI pixels. If the pixel is created but the Conversions API key provisioning then fails, the pixel is left live on OpenAI (it cannot be cleaned up) and the error message names the surviving pixel id and warns against retrying, since a retry would create a second, orphaned pixel. NOT idempotent on either platform: each call creates a new pixel (and, for OpenAI, a new Conversions API key). Do not retry blindly on timeout. Meta (platform `metaads`) and OpenAI Ads (platform `openaiads`); other platforms return 405.
|
|
51
|
+
# Meta: creates a Meta Pixel on the given ad account (`POST /act_{id}/adspixels` — `name` is the only input). Returns the created tag including its install `code`. The pixel is owned by the Business Manager that owns the ad account; a pixel created on a personal (non-BM) ad account ends up with `ownerBusinessId: null` and can't be shared with other ad accounts. Creating a Meta pixel does NOT install it — install the returned `code` snippet on the site, or send events server-side via `POST /v1/ads/conversions`. The check `installed` is derived from `lastFiredTime`. OpenAI Ads: creates an OpenAI pixel AND provisions a Conversions API key for it in the same call (`adAccountId` is required by this endpoint but ignored — one API key maps to exactly one ad account, so there's nothing to select). Returns 422 (`FEATURE_NOT_AVAILABLE`) if the ad account isn't enabled for pixel management; contact your OpenAI partner representative to enable it. There is no delete API for OpenAI pixels. If the pixel is created but the Conversions API key provisioning then fails, the pixel is left live on OpenAI (it cannot be cleaned up) and the error message names the surviving pixel id and warns against retrying, since a retry would create a second, orphaned pixel. NOT idempotent on either platform: each call creates a new pixel (and, for OpenAI, a new Conversions API key plus, with `defaultEventType`, a new conversion event setting). Do not retry blindly on timeout. Meta (platform `metaads`) and OpenAI Ads (platform `openaiads`); other platforms return 405.
|
|
52
52
|
# @param account_id Ads SocialAccount id (platform `metaads` or `openaiads`).
|
|
53
53
|
# @param create_tracking_tag_request
|
|
54
54
|
# @param [Hash] opts the optional parameters
|