import {z} from 'zod' export type BidStrategyType = 'AUTOMATIC_BID' | 'MAX_BID' | 'TARGET_AVG' | 'null' | null; export type ActionType = 'CLICKTHROUGH' | 'IMPRESSION' | 'VIDEO_V_50_MRC'; export type BudgetType = 'DAILY' | 'LIFETIME' | 'CBO_ADGROUP'; export type PinterestLibIntegerFormatType = string; export type AdgroupTrackingFeatureType = 'TRENDS' | 'CLONE_META' | 'BULK_EDITOR' | 'AD_ROTATION'; export type AdgroupTrackingFeatures = {enabled?: Array | undefined} | null; export type AttributionWindows = {click_window_days?: number | undefined, engagement_window_days?: number | undefined, view_window_days?: number | undefined}; export type ConversionEvent = 'PAGE_VISIT' | 'SIGNUP' | 'CHECKOUT' | 'CUSTOM' | 'VIEW_CATEGORY' | 'SEARCH' | 'ADD_TO_CART' | 'WATCH_VIDEO' | 'LEAD' | 'APP_INSTALL'; export type ConversionTagV3GoalMetadata = {attribution_windows?: AttributionWindows | undefined, conversion_event?: ConversionEvent | undefined, conversion_tag_id?: string | undefined, cpa_goal_value_in_micro_currency?: string | undefined, is_roas_optimized?: (boolean | null) | undefined, reporting_event?: string | undefined}; export type FrequencyGoalMetadataTimerange = 'THIRTY_DAY' | 'DAY' | 'SEVEN_DAY' | 'TWENTY_MINUTE' | 'TEN_MINUTE' | 'TWENTY_FOUR_HOUR'; export type FrequencyGoalMetadata = {frequency?: number | undefined, timerange?: FrequencyGoalMetadataTimerange | undefined}; export type ScrollupGoalMetadata = {scrollup_goal_value_in_micro_currency?: (string | null) | undefined}; export type NullableOptimizationGoalMetadata = {conversion_tag_v3_goal_metadata?: ConversionTagV3GoalMetadata | undefined, frequency_goal_metadata?: FrequencyGoalMetadata | undefined, scrollup_goal_metadata?: ScrollupGoalMetadata | undefined} | null; export type PacingDeliveryType = 'STANDARD' | 'ACCELERATED'; export type PerformancePlusCampaignSettings = {boost_prospecting_ad_group_bid?: boolean | undefined, pinner_list_exclusions?: Array | undefined} | null; export type PlacementGroupType = 'ALL' | 'SEARCH' | 'BROWSE' | 'OTHER'; export type PlacementTrafficType = 'ALL' | 'TWO_COLUMN_FEED' | 'FULLSCREEN_FEED' | 'null' | null; export type PromotionApplicationLevel = 'NONE' | 'ITEM' | 'AD_GROUP' | 'null' | null; export type EntityStatus = 'ACTIVE' | 'PAUSED' | 'ARCHIVED' | 'DRAFT' | 'DELETED_DRAFT'; export type TargetingSpecAgeBucket = '18-24' | '19+' | '20+' | '21+' | '25-34' | '35-44' | '45-49' | '50-54' | '55-64' | '65+'; export type TargetingSpecAppType = 'android_mobile' | 'android_tablet' | 'ipad' | 'iphone' | 'web' | 'web_mobile'; export type TargetingSpecGender = 'unknown' | 'male' | 'female'; export type TargetingSpecShoppingRetargeting = {exclusion_window?: number | undefined, lookback_window?: number | undefined, tag_types?: Array | undefined}; export type TargetingStrategy = 'CHOOSE_YOUR_OWN' | 'FIND_NEW_CUSTOMERS' | 'RECONNECT_WITH_USERS'; export type TargetingSpecOptimal = {AGE_BUCKET?: (Array | null) | undefined, APPTYPE?: (Array | null) | undefined, AUDIENCE_EXCLUDE?: (Array | null) | undefined, AUDIENCE_INCLUDE?: (Array | null) | undefined, GENDER?: (Array | null) | undefined, GEO?: (Array | null) | undefined, GEO_EXCLUDE?: Array | undefined, INTEREST?: Array | undefined, LOCALE?: (Array | null) | undefined, LOCATION?: (Array | null) | undefined, LOCATION_EXCLUDE?: Array | undefined, MAXIMUM_AGE?: string | undefined, MINIMUM_AGE?: string | undefined, SHOPPING_RETARGETING?: (Array | null) | undefined, TARGETING_STRATEGY?: (Array | null) | undefined}; export type TargetingSpecListOperation = 'SET' | 'ADD' | 'REMOVE'; export type TargetingSpecOperations = {field: 'AUDIENCE_EXCLUDE', operation: TargetingSpecListOperation, values: Array} | {field: 'AUDIENCE_INCLUDE', operation: TargetingSpecListOperation, values: Array} | {field: 'GEO', operation: TargetingSpecListOperation, values: Array} | {field: 'GEO_EXCLUDE', operation: TargetingSpecListOperation, values: Array} | {field: 'INTEREST', operation: TargetingSpecListOperation, values: Array} | {field: 'LOCATION', operation: TargetingSpecListOperation, values: Array} | {field: 'LOCATION_EXCLUDE', operation: TargetingSpecListOperation, values: Array} | {field: 'LOCALE', operation: TargetingSpecListOperation, values: Array} | {field: 'APPTYPE', operation: TargetingSpecListOperation, values: Array} | {field: 'GENDER', operation: TargetingSpecListOperation, values: Array} | {field: 'AGE_BUCKET', operation: 'SET', values: Array} | {field: 'SHOPPING_RETARGETING', operation: 'SET', values: Array} | {field: 'MAXIMUM_AGE', operation: 'SET', value: string | null} | {field: 'MINIMUM_AGE', operation: 'SET', value: string | null}; export type AdGroupTrackingURLs = {audience_verification?: Array | undefined, buyable_button?: Array | undefined, click?: Array | undefined, engagement?: Array | undefined, impression?: Array | undefined} | null; export type AdGroupUpdateBatchUpdate = {auto_targeting_enabled?: (boolean | null) | undefined, bid_in_micro_currency?: (number | null) | undefined, bid_multiplier?: number | undefined, bid_strategy_type?: BidStrategyType | undefined, billable_event?: ActionType | undefined, budget_in_micro_currency?: (number | null) | undefined, budget_type?: BudgetType | undefined, campaign_id?: string | undefined, customer_segment_id?: PinterestLibIntegerFormatType | undefined, end_time?: (number | null) | undefined, ext_features?: AdgroupTrackingFeatures | undefined, feed_profile_id?: string | undefined, id: string, is_creative_optimization?: (boolean | null) | undefined, is_local_inventory?: boolean | undefined, lifetime_frequency_cap?: number | undefined, local_inventory_radius_in_miles?: number | undefined, name?: string | undefined, optimization_goal_metadata?: (NullableOptimizationGoalMetadata | null) | undefined, pacing_delivery_type?: PacingDeliveryType | undefined, performance_plus_campaign_settings?: (PerformancePlusCampaignSettings | null) | undefined, placement_group?: PlacementGroupType | undefined, placement_traffic_type?: (PlacementTrafficType | null) | undefined, promotion_application_level?: (PromotionApplicationLevel | null) | undefined, promotion_id?: (string | null) | undefined, promotion_ids?: Array | undefined, start_time?: (number | null) | undefined, status?: EntityStatus | undefined, targeting_spec?: TargetingSpecOptimal | undefined, targeting_spec_operations?: Array | undefined, targeting_template_ids?: (Array | null) | undefined, tracking_urls?: AdGroupTrackingURLs | undefined}; export type AdGroupBatchUpdateRequest = Array; export type PatchApiAdAccountsAdAccountIdAdGroupsArgs = {ad_account_id: string, body: AdGroupBatchUpdateRequest}; export type PatchApiAdAccountsAdAccountIdAdGroupsOptions = {baseUrl?: string | undefined, pinterestOauth2?: string | undefined, fetch?: typeof fetch | undefined}; export const bidStrategyType = z.enum(["AUTOMATIC_BID", "MAX_BID", "TARGET_AVG", "null"]).nullable(); export const actionType = z.enum(["CLICKTHROUGH", "IMPRESSION", "VIDEO_V_50_MRC"]); export const conversionLearningModeType = z.enum(["NOT_ACTIVE", "ACTIVE", "null"]).nullable(); export const pinterestLibIntegerFormatType = z.string(); export const adgroupTrackingFeatureType = z.enum(["TRENDS", "CLONE_META", "BULK_EDITOR", "AD_ROTATION"]); export const adgroupTrackingFeatures = z.object({enabled: z.array(adgroupTrackingFeatureType).optional()}).nullable(); export const attributionWindows = z.object({click_window_days: z.number().int().optional(), engagement_window_days: z.number().int().optional(), view_window_days: z.number().int().optional()}); export const conversionEvent = z.enum(["PAGE_VISIT", "SIGNUP", "CHECKOUT", "CUSTOM", "VIEW_CATEGORY", "SEARCH", "ADD_TO_CART", "WATCH_VIDEO", "LEAD", "APP_INSTALL"]); export const conversionTagV3GoalMetadata = z.object({attribution_windows: attributionWindows.optional(), conversion_event: conversionEvent.optional(), conversion_tag_id: z.string().optional(), cpa_goal_value_in_micro_currency: z.string().optional(), is_roas_optimized: z.boolean().nullable().optional(), reporting_event: z.string().optional()}); export const frequencyGoalMetadataTimerange = z.enum(["THIRTY_DAY", "DAY", "SEVEN_DAY", "TWENTY_MINUTE", "TEN_MINUTE", "TWENTY_FOUR_HOUR"]); export const frequencyGoalMetadata = z.object({frequency: z.number().int().gte(2).lte(20).optional(), timerange: frequencyGoalMetadataTimerange.optional()}); export const scrollupGoalMetadata = z.object({scrollup_goal_value_in_micro_currency: z.string().nullable().optional()}); export const nullableOptimizationGoalMetadata = z.object({conversion_tag_v3_goal_metadata: conversionTagV3GoalMetadata.optional(), frequency_goal_metadata: frequencyGoalMetadata.optional(), scrollup_goal_metadata: scrollupGoalMetadata.optional()}).nullable(); export const performancePlusCampaignSettings = z.object({boost_prospecting_ad_group_bid: z.boolean().optional(), pinner_list_exclusions: z.array(z.string()).optional()}).nullable(); export const placementGroupType = z.enum(["ALL", "SEARCH", "BROWSE", "OTHER"]); export const placementTrafficType = z.enum(["ALL", "TWO_COLUMN_FEED", "FULLSCREEN_FEED", "null"]).nullable(); export const promotionApplicationLevel = z.enum(["NONE", "ITEM", "AD_GROUP", "null"]).nullable(); export const entityStatus = z.enum(["ACTIVE", "PAUSED", "ARCHIVED", "DRAFT", "DELETED_DRAFT"]); export const summaryStatus = z.enum(["RUNNING", "PAUSED", "NOT_STARTED", "COMPLETED", "ADVERTISER_DISABLED", "ARCHIVED", "DRAFT", "DELETED_DRAFT"]); export const targetingSpecAgeBucket = z.enum(["18-24", "19+", "20+", "21+", "25-34", "35-44", "45-49", "50-54", "55-64", "65+"]); export const targetingSpecAppType = z.enum(["android_mobile", "android_tablet", "ipad", "iphone", "web", "web_mobile"]); export const targetingSpecGender = z.enum(["unknown", "male", "female"]); export const targetingSpecShoppingRetargeting = z.object({exclusion_window: z.number().int().optional(), lookback_window: z.number().int().optional(), tag_types: z.array(z.number().int()).optional()}); export const targetingStrategy = z.enum(["CHOOSE_YOUR_OWN", "FIND_NEW_CUSTOMERS", "RECONNECT_WITH_USERS"]); export const targetingSpecOptimal = z.object({AGE_BUCKET: z.array(targetingSpecAgeBucket).nullable().optional(), APPTYPE: z.array(targetingSpecAppType).nullable().optional(), AUDIENCE_EXCLUDE: z.array(pinterestLibIntegerFormatType).nullable().optional(), AUDIENCE_INCLUDE: z.array(pinterestLibIntegerFormatType).nullable().optional(), GENDER: z.array(targetingSpecGender).nullable().optional(), GEO: z.array(z.string()).nullable().optional(), GEO_EXCLUDE: z.array(z.string()).optional(), INTEREST: z.array(z.string()).optional(), LOCALE: z.array(z.string()).nullable().optional(), LOCATION: z.array(z.string()).nullable().optional(), LOCATION_EXCLUDE: z.array(z.string()).optional(), MAXIMUM_AGE: z.string().optional(), MINIMUM_AGE: z.string().optional(), SHOPPING_RETARGETING: z.array(targetingSpecShoppingRetargeting).nullable().optional(), TARGETING_STRATEGY: z.array(targetingStrategy).nullable().optional()}); export const adGroupTrackingURLs = z.object({audience_verification: z.array(z.string()).optional(), buyable_button: z.array(z.string()).optional(), click: z.array(z.string()).optional(), engagement: z.array(z.string()).optional(), impression: z.array(z.string()).optional()}).nullable(); export const adGroup = z.object({ad_account_id: z.string().optional(), bid_in_micro_currency: z.number().int().nullable().optional(), bid_strategy_type: bidStrategyType.optional(), billable_event: actionType, budget_in_micro_currency: z.number().int().nullable().optional(), campaign_id: z.string(), conversion_learning_mode_type: conversionLearningModeType, created_time: z.number().int(), customer_segment_id: pinterestLibIntegerFormatType.optional(), dca_assets: z.unknown(), end_time: z.number().int().nullable().optional(), ext_features: adgroupTrackingFeatures.optional(), feed_profile_id: z.string().optional(), id: z.string().max(18), is_creative_optimization: z.boolean().nullable().optional(), is_local_inventory: z.boolean().optional(), lifetime_frequency_cap: z.number().int().optional(), local_inventory_radius_in_miles: z.number().optional(), name: z.string(), optimization_goal_metadata: nullableOptimizationGoalMetadata.nullable().optional(), performance_plus_campaign_settings: performancePlusCampaignSettings.nullable().optional(), placement_group: placementGroupType.optional(), placement_traffic_type: placementTrafficType.nullable().optional(), promotion_application_level: promotionApplicationLevel.nullable().optional(), promotion_id: z.string().nullable().optional(), promotion_ids: z.array(z.string()).optional(), start_time: z.number().int().nullable().optional(), status: entityStatus.optional(), summary_status: summaryStatus, targeting_spec: targetingSpecOptimal.optional(), targeting_template_ids: z.array(pinterestLibIntegerFormatType).nullable().optional(), tracking_urls: adGroupTrackingURLs.optional(), type: z.string(), updated_time: z.number().int()}); export const pinterestLibBatchItemException = z.object({code: z.number().int().optional(), message: z.string()}); export const patchApiAdAccountsAdAccountIdAdGroupsResponse = z.object({items: z.array(z.object({data: adGroup.optional(), exceptions: z.array(pinterestLibBatchItemException).optional()}))}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Update ad groups * * PATCH /ad_accounts/{ad_account_id}/ad_groups */ export const patchApiAdAccountsAdAccountIdAdGroups = async (args: PatchApiAdAccountsAdAccountIdAdGroupsArgs, options?: PatchApiAdAccountsAdAccountIdAdGroupsOptions) => { const {baseUrl = "https://api.pinterest.com/v5", fetch: fetchFn = fetch, pinterestOauth2} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (pinterestOauth2 !== undefined) headers.set('authorization', `Bearer ${pinterestOauth2}`) const url = `${baseUrl.replace(/\/+$/, '')}/ad_accounts/${encodeURIComponent(String(args.ad_account_id))}/ad_groups${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "PATCH", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(patchApiAdAccountsAdAccountIdAdGroupsResponse, await res.json()) };