import { encodableSchema, type Encodable } from "@automate.ax/codec" import { getMetaAdsApi, META_ADS_ACCOUNT_SCHEMA, META_ADS_AD_SCHEMA, META_ADS_AD_SET_SCHEMA, META_ADS_AUDIENCE_SCHEMA, META_ADS_BUSINESS_SCHEMA, META_ADS_CAMPAIGN_SCHEMA, META_ADS_CONVERSION_RESULT_SCHEMA, META_ADS_CREATIVE_SCHEMA, META_ADS_ID_SCHEMA, META_ADS_INSIGHT_SCHEMA, META_ADS_LEAD_FORM_SCHEMA, META_ADS_LEAD_SCHEMA, META_ADS_MUTATION_RESULT_SCHEMA, META_ADS_PIXEL_SCHEMA, META_ADS_SUBSCRIPTION_CREATE_RESULT_SCHEMA, META_ADS_SUBSCRIPTION_SCHEMA, } from "@automate.ax/integration-contracts/meta-ads" import * as z from "zod" import { defineAction } from "../../automation/actions" import { META_ADS_BUSINESS_READ_SCOPE, META_ADS_BUSINESS_SCOPE, META_ADS_LEADS_READ_SCOPE, META_ADS_PAGE_INPUT_SCHEMA, META_ADS_READ_SCOPE, META_ADS_WRITE_SCOPE, metaAdsAccountRequirement, metaAdsPageSchema, } from "./lib" const ACCOUNT = "meta-ads" const RECORD_SCHEMA: z.ZodType> = z.record( z.string(), encodableSchema, ) const FIELDS_SCHEMA = z.string().array().min(1).optional() const PAGE_SCHEMA = META_ADS_PAGE_INPUT_SCHEMA const ENTITY_ID = { id: META_ADS_ID_SCHEMA } const AD_ACCOUNT_ID = { adAccountId: META_ADS_ID_SCHEMA } const CREATION_STATUS_SCHEMA = z.enum(["ACTIVE", "PAUSED"]) const SUBSCRIPTION_EVENT_TYPE_SCHEMA = z.enum([ "INSIGHTS_MILESTONE_REACHED", "INSIGHTS_UPDATED", "OBJECT_CREATED", "OBJECT_UPDATED", ]) const SUBSCRIPTION_OPERATOR_SCHEMA = z.enum([ "ALL", "ANY", "CONTAIN", "EQUAL", "GREATER_THAN", "IN", "IN_RANGE", "LESS_THAN", "NONE", "NOT_CONTAIN", "NOT_IN", "NOT_IN_RANGE", ]) interface MetaAdsActionAccount { connectionMethodId: string secret: Record serviceId: "meta-ads" } /** * Resolves the shared Graph client from one action account. * * @param account - Resolved Meta Ads action account. * @param account.connectionMethodId - OAuth connection method ID. * @param account.secret - Stored OAuth secret. * @param account.serviceId - Meta Ads service discriminator. */ function api(account: MetaAdsActionAccount) { return getMetaAdsApi(account) } /** * Resolves the Page access token required by Lead Ads endpoints. * * @param account - Resolved Meta Ads action account. * @param pageId - Facebook Page that owns the lead data. */ async function pageApi(account: MetaAdsActionAccount, pageId: string) { const { accessToken } = await api(account).call(pageId, { query: { fields: ["access_token"] }, responseSchema: z.object({ accessToken: z.string() }), }) return getMetaAdsApi({ connectionMethodId: "oauth", secret: { accessToken, expiresAt: null, tokenType: "bearer" }, serviceId: "meta-ads", }) } /** * Adds Meta's ad-account prefix when the caller omitted it. * * @param id - Public ad-account ID. */ function accountPath(id: string) { return id.startsWith("act_") ? id : `act_${id}` } /** * Applies shared cursor and field selection to one Graph edge. * * @param input - Public page and field selection. * @param input.after - Forward cursor. * @param input.before - Backward cursor. * @param input.fields - Optional field override. * @param input.limit - Result limit. * @param defaultFields - Default provider field projection. */ function entityListQuery( input: { after?: string before?: string fields?: string[] limit: number }, defaultFields: string[], ) { return { after: input.after, before: input.before, fields: fieldSelection(input.fields, defaultFields), limit: input.limit, } } /** * Preserves fields required by the output contract and adds caller selections. * * @param additionalFields - Extra provider fields requested by the caller. * @param requiredFields - Fields required by the typed output schema. */ function fieldSelection( additionalFields: string[] | undefined, requiredFields: string[], ) { return [...new Set([...requiredFields, ...(additionalFields ?? [])])] } /** Lists businesses visible to the connected Meta identity. */ export const listMetaAdsBusinesses = defineAction("List Meta Ads businesses") .describe("Lists Meta Business portfolios visible to the connected identity.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_BUSINESS_SCOPE)) .input(z.object({ fields: FIELDS_SCHEMA, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_BUSINESS_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page("me/businesses", { query: entityListQuery(input, ["id", "name", "verification_status"]), responseSchema: META_ADS_BUSINESS_SCHEMA.array(), }), ) /** Lists ad accounts assigned to the connected Meta identity. */ export const listMetaAdsAdAccounts = defineAction("List Meta Ads ad accounts") .describe("Lists ad accounts assigned directly to the connected identity.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input( z.object({ fields: FIELDS_SCHEMA, ...PAGE_SCHEMA, }), ) .output(metaAdsPageSchema(META_ADS_ACCOUNT_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page("me/adaccounts", { query: entityListQuery(input, [ "id", "account_id", "name", "currency", "timezone_id", "timezone_name", ]), responseSchema: META_ADS_ACCOUNT_SCHEMA.array(), }), ) /** Lists owned or client ad accounts for one Meta Business portfolio. */ export const listMetaAdsBusinessAdAccounts = defineAction( "List Meta Ads business ad accounts", ) .describe("Lists owned or client ad accounts for one Business portfolio.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_BUSINESS_READ_SCOPE)) .input( z.object({ businessId: META_ADS_ID_SCHEMA, fields: FIELDS_SCHEMA, relationship: z.enum(["client", "owned"]), ...PAGE_SCHEMA, }), ) .output(metaAdsPageSchema(META_ADS_ACCOUNT_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page( `${input.businessId}/${input.relationship === "client" ? "client_ad_accounts" : "owned_ad_accounts"}`, { query: entityListQuery(input, [ "id", "account_id", "name", "currency", "timezone_id", "timezone_name", "business", ]), responseSchema: META_ADS_ACCOUNT_SCHEMA.array(), }, ), ) /** Retrieves one Meta ad account. */ export const getMetaAdsAdAccount = defineAction("Get Meta Ads ad account") .describe( "Retrieves account identity, currency, timezone, balance, and spend.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, fields: FIELDS_SCHEMA })) .output(META_ADS_ACCOUNT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(accountPath(input.adAccountId), { query: { fields: fieldSelection(input.fields, [ "id", "account_id", "name", "currency", "timezone_id", "timezone_name", "balance", "amount_spent", ]), }, responseSchema: META_ADS_ACCOUNT_SCHEMA, }), ) const CAMPAIGN_FIELDS = [ "id", "name", "objective", "buying_type", "status", "configured_status", "effective_status", "special_ad_categories", ] /** Lists campaigns in an ad account. */ export const listMetaAdsCampaigns = defineAction("List Meta Ads campaigns") .describe( "Lists campaigns in an ad account with optional effective-status filtering.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input( z.object({ ...AD_ACCOUNT_ID, effectiveStatus: z.string().array().optional(), fields: FIELDS_SCHEMA, ...PAGE_SCHEMA, }), ) .output(metaAdsPageSchema(META_ADS_CAMPAIGN_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/campaigns`, { query: { ...entityListQuery(input, CAMPAIGN_FIELDS), effectiveStatus: input.effectiveStatus ? JSON.stringify(input.effectiveStatus) : undefined, }, responseSchema: META_ADS_CAMPAIGN_SCHEMA.array(), }), ) /** Retrieves one campaign. */ export const getMetaAdsCampaign = defineAction("Get Meta Ads campaign") .describe("Retrieves one campaign by ID.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...ENTITY_ID, fields: FIELDS_SCHEMA })) .output(META_ADS_CAMPAIGN_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(input.id, { query: { fields: fieldSelection(input.fields, CAMPAIGN_FIELDS) }, responseSchema: META_ADS_CAMPAIGN_SCHEMA, }), ) /** Creates a campaign. */ export const createMetaAdsCampaign = defineAction("Create Meta Ads campaign") .describe("Creates a campaign in an ad account.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input( z .object({ ...AD_ACCOUNT_ID, bidStrategy: z .enum([ "COST_CAP", "LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "LOWEST_COST_WITH_MIN_ROAS", ]) .optional(), buyingType: z.string().optional(), dailyBudget: z.number().int().positive().optional(), isAdsetBudgetSharingEnabled: z.boolean().prefault(false), lifetimeBudget: z.number().int().positive().optional(), name: z.string().trim().min(1), objective: z.string().trim().min(1), specialAdCategories: z.string().array().prefault([]), status: CREATION_STATUS_SCHEMA.prefault("PAUSED"), }) .refine((input) => !(input.dailyBudget && input.lifetimeBudget), { message: "Set dailyBudget or lifetimeBudget, not both.", }), ) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { adAccountId, ...body } }) => api(account).call(`${accountPath(adAccountId)}/campaigns`, { body, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Updates a campaign. */ export const updateMetaAdsCampaign = defineAction("Update Meta Ads campaign") .describe( "Updates a campaign name, status, budget, or remaining provider fields.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...ENTITY_ID, changes: RECORD_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: input.changes, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Archives a campaign. */ export const archiveMetaAdsCampaign = defineAction("Archive Meta Ads campaign") .describe("Archives a campaign without deleting its reporting history.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object(ENTITY_ID)) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: { status: "ARCHIVED" }, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) const AD_SET_FIELDS = [ "id", "name", "campaign_id", "status", "configured_status", "effective_status", "billing_event", "optimization_goal", "bid_amount", "daily_budget", "lifetime_budget", "start_time", "end_time", "targeting", ] /** Lists ad sets in an ad account. */ export const listMetaAdsAdSets = defineAction("List Meta Ads ad sets") .describe("Lists ad sets in an ad account.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_AD_SET_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/adsets`, { query: entityListQuery(input, AD_SET_FIELDS), responseSchema: META_ADS_AD_SET_SCHEMA.array(), }), ) /** Retrieves one ad set. */ export const getMetaAdsAdSet = defineAction("Get Meta Ads ad set") .describe("Retrieves one ad set by ID.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...ENTITY_ID, fields: FIELDS_SCHEMA })) .output(META_ADS_AD_SET_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(input.id, { query: { fields: fieldSelection(input.fields, AD_SET_FIELDS) }, responseSchema: META_ADS_AD_SET_SCHEMA, }), ) /** Creates an ad set. */ export const createMetaAdsAdSet = defineAction("Create Meta Ads ad set") .describe( "Creates an ad set with budget, delivery, schedule, and targeting configuration.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input( z .object({ ...AD_ACCOUNT_ID, bidAmount: z.number().int().nonnegative().optional(), billingEvent: z.string().trim().min(1), campaignId: META_ADS_ID_SCHEMA, dailyBudget: z.number().int().positive().optional(), endTime: z.iso.datetime({ offset: true }).optional(), lifetimeBudget: z.number().int().positive().optional(), name: z.string().trim().min(1), optimizationGoal: z.string().trim().min(1), promotedObject: RECORD_SCHEMA.optional(), startTime: z.iso.datetime({ offset: true }).optional(), status: CREATION_STATUS_SCHEMA.prefault("PAUSED"), targeting: RECORD_SCHEMA, }) .refine((input) => !(input.dailyBudget && input.lifetimeBudget), { message: "Set dailyBudget or lifetimeBudget, not both.", }) .refine((input) => !input.lifetimeBudget || input.endTime, { message: "Set endTime when using lifetimeBudget.", }), ) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { adAccountId, ...body } }) => api(account).call(`${accountPath(adAccountId)}/adsets`, { body, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Updates an ad set. */ export const updateMetaAdsAdSet = defineAction("Update Meta Ads ad set") .describe( "Updates mutable delivery, targeting, schedule, budget, or status fields.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...ENTITY_ID, changes: RECORD_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: input.changes, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Archives an ad set. */ export const archiveMetaAdsAdSet = defineAction("Archive Meta Ads ad set") .describe("Archives an ad set without deleting reporting history.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object(ENTITY_ID)) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: { status: "ARCHIVED" }, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) const AD_FIELDS = [ "id", "name", "campaign_id", "adset_id", "creative", "status", "configured_status", "effective_status", ] /** Lists ads in an ad account. */ export const listMetaAdsAds = defineAction("List Meta Ads ads") .describe("Lists ads in an ad account.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_AD_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/ads`, { query: entityListQuery(input, AD_FIELDS), responseSchema: META_ADS_AD_SCHEMA.array(), }), ) /** Retrieves one ad. */ export const getMetaAdsAd = defineAction("Get Meta Ads ad") .describe("Retrieves one ad by ID.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...ENTITY_ID, fields: FIELDS_SCHEMA })) .output(META_ADS_AD_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(input.id, { query: { fields: fieldSelection(input.fields, AD_FIELDS) }, responseSchema: META_ADS_AD_SCHEMA, }), ) /** Creates an ad. */ export const createMetaAdsAd = defineAction("Create Meta Ads ad") .describe("Creates an ad from an ad set and existing creative.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input( z.object({ ...AD_ACCOUNT_ID, adsetId: META_ADS_ID_SCHEMA, creativeId: META_ADS_ID_SCHEMA, name: z.string().trim().min(1), status: CREATION_STATUS_SCHEMA.prefault("PAUSED"), }), ) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(`${accountPath(input.adAccountId)}/ads`, { body: { adsetId: input.adsetId, creative: { creativeId: input.creativeId }, name: input.name, status: input.status, }, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Updates an ad. */ export const updateMetaAdsAd = defineAction("Update Meta Ads ad") .describe( "Updates an ad name, creative, status, or remaining mutable fields.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...ENTITY_ID, changes: RECORD_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: input.changes, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Archives an ad. */ export const archiveMetaAdsAd = defineAction("Archive Meta Ads ad") .describe("Archives an ad without deleting reporting history.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object(ENTITY_ID)) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: { status: "ARCHIVED" }, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) const CREATIVE_FIELDS = [ "id", "account_id", "name", "title", "body", "object_story_id", "effective_object_story_id", "status", ] /** Lists ad creatives in an ad account. */ export const listMetaAdsCreatives = defineAction("List Meta Ads creatives") .describe("Lists ad creatives in an ad account.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_CREATIVE_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/adcreatives`, { query: entityListQuery(input, CREATIVE_FIELDS), responseSchema: META_ADS_CREATIVE_SCHEMA.array(), }), ) /** Retrieves one ad creative. */ export const getMetaAdsCreative = defineAction("Get Meta Ads creative") .describe("Retrieves one ad creative by ID.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...ENTITY_ID, fields: FIELDS_SCHEMA })) .output(META_ADS_CREATIVE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(input.id, { query: { fields: fieldSelection(input.fields, CREATIVE_FIELDS) }, responseSchema: META_ADS_CREATIVE_SCHEMA, }), ) /** Creates an ad creative. */ export const createMetaAdsCreative = defineAction("Create Meta Ads creative") .describe("Creates an ad creative from provider-supported creative fields.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, creative: RECORD_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(`${accountPath(input.adAccountId)}/adcreatives`, { body: input.creative, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Updates an ad creative. */ export const updateMetaAdsCreative = defineAction("Update Meta Ads creative") .describe("Updates mutable fields on an ad creative.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...ENTITY_ID, changes: RECORD_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: input.changes, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Deletes an ad creative. */ export const deleteMetaAdsCreative = defineAction("Delete Meta Ads creative") .describe("Deletes an ad creative that is not in use.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object(ENTITY_ID)) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { method: "DELETE", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Lists custom audiences in an ad account. */ export const listMetaAdsAudiences = defineAction("List Meta Ads audiences") .describe("Lists custom audiences in an ad account.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_AUDIENCE_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/customaudiences`, { query: entityListQuery(input, [ "id", "account_id", "name", "description", "subtype", "approximate_count_lower_bound", "approximate_count_upper_bound", ]), responseSchema: META_ADS_AUDIENCE_SCHEMA.array(), }), ) /** Retrieves one custom audience. */ export const getMetaAdsAudience = defineAction("Get Meta Ads audience") .describe("Retrieves one custom audience by ID.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...ENTITY_ID, fields: FIELDS_SCHEMA })) .output(META_ADS_AUDIENCE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(input.id, { query: { fields: fieldSelection(input.fields, [ "id", "account_id", "name", "description", "subtype", ]), }, responseSchema: META_ADS_AUDIENCE_SCHEMA, }), ) /** Creates a custom audience. */ export const createMetaAdsAudience = defineAction("Create Meta Ads audience") .describe( "Creates a custom audience using a customer-file or provider-supported subtype.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input( z.object({ ...AD_ACCOUNT_ID, customerFileSource: z.string().optional(), description: z.string().optional(), name: z.string().trim().min(1), subtype: z.string().prefault("CUSTOM"), }), ) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { adAccountId, ...body } }) => api(account).call(`${accountPath(adAccountId)}/customaudiences`, { body, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Updates a custom audience. */ export const updateMetaAdsAudience = defineAction("Update Meta Ads audience") .describe( "Updates a custom audience name, description, or provider-supported fields.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...ENTITY_ID, changes: RECORD_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { body: input.changes, method: "POST", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) /** Deletes a custom audience. */ export const deleteMetaAdsAudience = defineAction("Delete Meta Ads audience") .describe("Deletes a custom audience.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object(ENTITY_ID)) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(input.id, { method: "DELETE", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }), ) const AUDIENCE_USERS_INPUT = z.object({ audienceId: META_ADS_ID_SCHEMA, payload: z.object({ data: z.array(z.array(z.string())).min(1), isRaw: z.boolean().optional(), schema: z.string().array().min(1), }), }) /** Adds hashed users to a custom audience. */ export const addMetaAdsAudienceUsers = defineAction( "Add Meta Ads audience users", ) .describe( "Adds normalized and SHA-256 hashed user rows to a custom audience.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(AUDIENCE_USERS_INPUT) .output( z.object({ audienceId: z.string().optional(), numReceived: z.number().optional(), }), ) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(`${input.audienceId}/users`, { body: { payload: input.payload }, method: "POST", responseSchema: z.object({ audienceId: z.string().optional(), numReceived: z.number().optional(), }), }), ) /** Removes hashed users from a custom audience. */ export const removeMetaAdsAudienceUsers = defineAction( "Remove Meta Ads audience users", ) .describe( "Removes normalized and SHA-256 hashed user rows from a custom audience.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(AUDIENCE_USERS_INPUT) .output( z.object({ audienceId: z.string().optional(), numReceived: z.number().optional(), }), ) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call(`${input.audienceId}/users`, { body: { payload: input.payload }, method: "DELETE", responseSchema: z.object({ audienceId: z.string().optional(), numReceived: z.number().optional(), }), }), ) /** Lists lead forms owned by a Page. */ export const listMetaAdsLeadForms = defineAction("List Meta Ads lead forms") .describe("Lists lead-generation forms owned by a Facebook Page.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_LEADS_READ_SCOPE)) .input( z.object({ pageId: META_ADS_ID_SCHEMA, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA, }), ) .output(metaAdsPageSchema(META_ADS_LEAD_FORM_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => (await pageApi(account, input.pageId)).page( `${input.pageId}/leadgen_forms`, { query: entityListQuery(input, ["id", "name", "locale", "status"]), responseSchema: META_ADS_LEAD_FORM_SCHEMA.array(), }, ), ) /** Lists leads captured by a lead form. */ export const listMetaAdsLeads = defineAction("List Meta Ads leads") .describe("Lists leads captured by a lead-generation form.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_LEADS_READ_SCOPE)) .input( z.object({ formId: META_ADS_ID_SCHEMA, pageId: META_ADS_ID_SCHEMA, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA, }), ) .output(metaAdsPageSchema(META_ADS_LEAD_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => (await pageApi(account, input.pageId)).page(`${input.formId}/leads`, { query: entityListQuery(input, [ "id", "created_time", "ad_id", "ad_name", "adset_id", "adset_name", "campaign_id", "campaign_name", "form_id", "field_data", "is_organic", ]), responseSchema: META_ADS_LEAD_SCHEMA.array(), }), ) /** Retrieves one lead by ID. */ export const getMetaAdsLead = defineAction("Get Meta Ads lead") .describe("Retrieves one lead and its captured field values.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_LEADS_READ_SCOPE)) .input( z.object({ leadId: META_ADS_ID_SCHEMA, pageId: META_ADS_ID_SCHEMA, fields: FIELDS_SCHEMA, }), ) .output(META_ADS_LEAD_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => (await pageApi(account, input.pageId)).call(input.leadId, { query: { fields: fieldSelection(input.fields, [ "id", "created_time", "field_data", "form_id", "ad_id", ]), }, responseSchema: META_ADS_LEAD_SCHEMA, }), ) /** Reads Marketing API insights for an account, campaign, ad set, or ad. */ export const getMetaAdsInsights = defineAction("Get Meta Ads insights") .describe( "Reads paginated delivery insights with level, date, breakdown, and field controls.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input( z.object({ objectId: META_ADS_ID_SCHEMA, level: z.enum(["account", "campaign", "adset", "ad"]).optional(), datePreset: z.string().optional(), timeRange: z.object({ since: z.string(), until: z.string() }).optional(), breakdowns: z.string().array().optional(), fields: FIELDS_SCHEMA, ...PAGE_SCHEMA, }), ) .output(metaAdsPageSchema(META_ADS_INSIGHT_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${input.objectId}/insights`, { query: { after: input.after, before: input.before, breakdowns: input.breakdowns, datePreset: input.datePreset, fields: fieldSelection(input.fields, [ "account_id", "account_name", "campaign_id", "campaign_name", "adset_id", "adset_name", "ad_id", "ad_name", "impressions", "reach", "clicks", "spend", "cpc", "cpm", "ctr", "date_start", "date_stop", ]), level: input.level, limit: input.limit, timeRange: input.timeRange ? JSON.stringify(input.timeRange) : undefined, }, responseSchema: META_ADS_INSIGHT_SCHEMA.array(), }), ) /** Lists Meta pixels attached to an ad account. */ export const listMetaAdsPixels = defineAction("List Meta Ads pixels") .describe("Lists Meta pixels attached to an ad account.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, fields: FIELDS_SCHEMA, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_PIXEL_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/adspixels`, { query: entityListQuery(input, [ "id", "name", "code", "last_fired_time", "is_unavailable", ]), responseSchema: META_ADS_PIXEL_SCHEMA.array(), }), ) /** Sends server events to a Meta pixel through the Conversions API. */ export const sendMetaAdsConversionEvents = defineAction( "Send Meta Ads conversion events", ) .describe( "Sends a batch of server events to a Meta pixel with optional test-event validation.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input( z.object({ pixelId: META_ADS_ID_SCHEMA, events: z .object({ actionSource: z.string().trim().min(1), customData: RECORD_SCHEMA.optional(), eventId: z.string().optional(), eventName: z.string().trim().min(1), eventSourceUrl: z.url().optional(), eventTime: z.number().int().positive(), userData: RECORD_SCHEMA, }) .array() .min(1) .max(1000), testEventCode: z.string().optional(), }), ) .output(META_ADS_CONVERSION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call(`${input.pixelId}/events`, { body: { data: input.events, testEventCode: input.testEventCode }, method: "POST", responseSchema: META_ADS_CONVERSION_RESULT_SCHEMA, }), ) /** Lists event subscriptions configured on an ad account. */ export const listMetaAdsSubscriptions = defineAction( "List Meta Ads subscriptions", ) .describe( "Lists Marketing API event subscriptions configured on an ad account.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, ...PAGE_SCHEMA })) .output(metaAdsPageSchema(META_ADS_SUBSCRIPTION_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).page(`${accountPath(input.adAccountId)}/subscriptions`, { query: { after: input.after, before: input.before, limit: input.limit, }, responseSchema: META_ADS_SUBSCRIPTION_SCHEMA.array(), }), ) /** Retrieves one event subscription configured on an ad account. */ export const getMetaAdsSubscription = defineAction("Get Meta Ads subscription") .describe("Retrieves one Marketing API event subscription by ID.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_READ_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, subscriptionId: META_ADS_ID_SCHEMA })) .output(META_ADS_SUBSCRIPTION_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input }) => api(account).call( `${accountPath(input.adAccountId)}/subscriptions/${input.subscriptionId}`, { responseSchema: META_ADS_SUBSCRIPTION_SCHEMA }, ), ) /** Creates an event subscription on an ad account. */ export const createMetaAdsSubscription = defineAction( "Create Meta Ads subscription", ) .describe( "Creates a filtered campaign, ad-set, ad, or insights event subscription.", ) .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input( z .object({ ...AD_ACCOUNT_ID, eventType: SUBSCRIPTION_EVENT_TYPE_SCHEMA, field: z.string().trim().min(1).optional(), filters: z .object({ field: z.string().trim().min(1), operator: SUBSCRIPTION_OPERATOR_SCHEMA, value: z.string().trim().min(1), }) .array() .min(1) .max(20), operator: SUBSCRIPTION_OPERATOR_SCHEMA.optional(), status: z.enum(["DISABLED", "ENABLED"]).optional(), value: z.string().trim().min(1).optional(), }) .superRefine((input, context) => { const requiresCondition = input.eventType.startsWith("INSIGHTS_") if (input.eventType !== "OBJECT_CREATED" && !input.field) { context.addIssue({ code: "custom", message: `${input.eventType} requires field.`, path: ["field"], }) } if (requiresCondition && input.value === undefined) { context.addIssue({ code: "custom", message: `${input.eventType} requires value.`, path: ["value"], }) } if (requiresCondition && !input.operator) { context.addIssue({ code: "custom", message: `${input.eventType} requires operator.`, path: ["operator"], }) } if ( requiresCondition && !input.filters.some(({ field }) => field === "time_preset") ) { context.addIssue({ code: "custom", message: `${input.eventType} requires a time_preset filter.`, path: ["filters"], }) } if ( input.eventType === "INSIGHTS_MILESTONE_REACHED" && input.operator !== "EQUAL" ) { context.addIssue({ code: "custom", message: "INSIGHTS_MILESTONE_REACHED requires EQUAL.", path: ["operator"], }) } }), ) .output(META_ADS_SUBSCRIPTION_CREATE_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { adAccountId, ...body } }) => api(account).call(`${accountPath(adAccountId)}/subscriptions`, { body, method: "POST", responseSchema: META_ADS_SUBSCRIPTION_CREATE_RESULT_SCHEMA, }), ) /** Deletes an ad-account event subscription. */ export const deleteMetaAdsSubscription = defineAction( "Delete Meta Ads subscription", ) .describe("Deletes one ad-account event subscription.") .account(ACCOUNT, metaAdsAccountRequirement(META_ADS_WRITE_SCOPE)) .input(z.object({ ...AD_ACCOUNT_ID, subscriptionId: META_ADS_ID_SCHEMA })) .output(META_ADS_MUTATION_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input }) => api(account).call( `${accountPath(input.adAccountId)}/subscriptions/${input.subscriptionId}`, { method: "DELETE", responseSchema: META_ADS_MUTATION_RESULT_SCHEMA, }, ), )