li_get_budget_pacing
Calculate budget utilization for active LinkedIn campaigns by comparing spend over a custom period against total budget. Identifies under-delivery below 80% or over-pacing above 100% for mid-flight pacing checks.
Instructions
Calculate budget utilization for active LinkedIn campaigns. Compares spend over the specified period_days window against total or estimated period budget, and returns a utilization_pct for each campaign. Useful for mid-flight pacing checks: if utilization is below 80% near the end of a month, the campaign may be under-delivering; above 100% means it is over-pacing. Accepts optional campaign_ids to limit scope; defaults to all ACTIVE campaigns in the account.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ad_account_id | No | ||
| campaign_ids | No | Limit pacing report to these campaigns. Omit to report all active campaigns in the account. | |
| period_days | No | Number of days to look back for spend. Should match your budget period (e.g., 30 for monthly). |
Implementation Reference
- src/tools/analytics.ts:379-480 (handler)The main handler function `getBudgetPacing` that fetches active LinkedIn campaigns (or specified campaign_ids), retrieves spend data via analytics API, and computes utilization_pct against total/daily budget.
export async function getBudgetPacing(args: { ad_account_id?: string; campaign_ids?: string[]; period_days?: number; }) { const account = resolveAdAccount(args.ad_account_id); const accountId = unwrapURN(account); const periodDays = args.period_days ?? 30; // Fetch campaign list + spend in parallel const campaignParams: Record<string, string | number> = { q: "search", pageSize: 100, search: "(status:(values:List(ACTIVE)))", }; let campaigns: Array<Record<string, unknown>> = []; if (args.campaign_ids && args.campaign_ids.length > 0) { // Fetch each specified campaign campaigns = await Promise.all( args.campaign_ids.map((id) => { const campaignId = id.startsWith("urn:li:") ? id.split(":").pop()! : id; return liGet<Record<string, unknown>>(`/adAccounts/${accountId}/adCampaigns/${campaignId}`); }) ); } else { const res = await liGet<{ elements?: Array<Record<string, unknown>> }>( `/adAccounts/${accountId}/adCampaigns`, campaignParams ); campaigns = res.elements ?? []; } // Fetch spend for the period const spendStart = `${periodDays}daysAgo`; const campaignUrns = campaigns.map((c) => { const id = c["id"] as string | undefined ?? ""; return urn("sponsoredCampaign", id); }); const start = resolveDate(spendStart); const end = resolveDate(DEFAULT_END); const spendUrl = buildAnalyticsUrl({ pivot: "CAMPAIGN", timeGranularity: "ALL", start, end, fields: "costInUsd,costInLocalCurrency", campaignUrns: campaignUrns.filter(Boolean), accountUrn: undefined, }); const spendData = await liGetRaw<AnalyticsResponse>(spendUrl); const spendMap = new Map<string, { usd: number; local: number }>(); for (const row of spendData.elements ?? []) { const key = (row.pivotValues?.[0] ?? "") as string; spendMap.set(key, { usd: Number(row["costInUsd"] ?? 0), local: Number(row["costInLocalCurrency"] ?? 0), }); } const pacing = campaigns.map((c) => { const id = c["id"] as string ?? ""; const campaignUrn = urn("sponsoredCampaign", id); const spend = spendMap.get(campaignUrn) ?? { usd: 0, local: 0 }; // totalBudget is a {amount, currencyCode} object const totalBudget = c["totalBudget"] as { amount?: string; currencyCode?: string } | undefined; const dailyBudget = c["dailyBudget"] as { amount?: string; currencyCode?: string } | undefined; const budgetAmount = totalBudget?.amount ? Number(totalBudget.amount) : null; const dailyBudgetAmount = dailyBudget?.amount ? Number(dailyBudget.amount) : null; const estimatedPeriodBudget = dailyBudgetAmount ? dailyBudgetAmount * periodDays : null; const effectiveBudget = budgetAmount ?? estimatedPeriodBudget; const utilizationPct = effectiveBudget && effectiveBudget > 0 ? Math.round((spend.usd / effectiveBudget) * 10000) / 100 : null; return { campaign_id: id, campaign_urn: campaignUrn, name: c["name"], status: c["status"], total_budget_usd: budgetAmount, daily_budget_usd: dailyBudgetAmount, estimated_period_budget_usd: estimatedPeriodBudget, spend_usd: spend.usd, spend_local: spend.local, utilization_pct: utilizationPct, period_days: periodDays, }; }); return { period_days: periodDays, ad_account_id: accountId, campaigns: pacing, }; } - src/tools/analytics.ts:364-377 (schema)Zod schema defining the input parameters: ad_account_id (optional), campaign_ids (optional array of strings), period_days (optional int, default 30, min 1, max 365).
export const getBudgetPacingSchema = { ad_account_id: z.string().optional(), campaign_ids: z .array(z.string()) .optional() .describe("Limit pacing report to these campaigns. Omit to report all active campaigns in the account."), period_days: z .number() .int() .min(1) .max(365) .default(30) .describe("Number of days to look back for spend. Should match your budget period (e.g., 30 for monthly)."), }; - src/index.ts:139-144 (registration)Registration of the 'li_get_budget_pacing' tool using server.tool() with its schema and handler.
server.tool( "li_get_budget_pacing", "Calculate budget utilization for active LinkedIn campaigns. Compares spend over the specified period_days window against total or estimated period budget, and returns a utilization_pct for each campaign. Useful for mid-flight pacing checks: if utilization is below 80% near the end of a month, the campaign may be under-delivering; above 100% means it is over-pacing. Accepts optional campaign_ids to limit scope; defaults to all ACTIVE campaigns in the account.", getBudgetPacingSchema, async (args) => { try { return ok(await getBudgetPacing(args)); } catch (e) { return err(e); } } ); - src/index.ts:21-22 (registration)Import of the getBudgetPacing handler and schema from analytics.ts module.
getBudgetPacing, getBudgetPacingSchema, } from "./tools/analytics.js";