Xylo API Documentation

Base URL: https://api.xylomcp.com (production) or http://localhost:3001 (dev)

Get Xylo alerts through ClaudeSend Meta webhook alerts and Meta, Google, or TikTok budget-pacing alerts to any writable connector with scheduled checks, real-time webhooks, or the Claude Agent SDK.

Connect via MCP

Most users drive Xylo through its Model Context Protocol (MCP) connector rather than raw REST. One connector, https://xylomcp.com/api/mcp, carries 300+ ad operations across Meta (Facebook & Instagram), Google, TikTok, and X, approved TikTok Shop commerce workflows, Klaviyo email and SMS, plus Google Analytics (GA4) reporting through a compact set of tools. Install the Xylo plugin in Codex, or add the connector in Claude, ChatGPT, Cursor, or Claude Code, then authorize once through OAuth. Meta, Google, and TikTok Ads entity routes use the channel parameter; TikTok Shop uses commerce; X, Klaviyo, and Google Analytics use their dedicated dispatchers. Pick your app below for the current setup steps.

Steps

Add Xylo to ClaudeOpens Claude with Xylo's name and connector URL pre-filled. Review the details, confirm, then connect with OAuth.
  1. 1Use the button above to open Claude's custom connector form with Xylo pre-filled.
  2. 2Review the name and URL, click "Add", then click "Connect" to authenticate through OAuth.
  3. 3For Team or Enterprise workspaces, an Owner may need to add Xylo from Organization settings → Connectors first.

Upload creative from Claude

Optional. Add this domain in Claude's Additional allowed domains so it can send uploaded creative to Xylo. Open allowed domains

api.xylomcp.com
The Xylo connector

One URL for everything: 300+ ad operations across Meta, Google, TikTok & X, plus Klaviyo email and SMS workflows, through tools that load instantly in any AI client.

https://xylomcp.com/api/mcp
Your first prompt

Once connected, have your AI learn your accounts one brand at a time. Swap in your brand name and website below; it studies your winners, copy, budgets and conventions, then saves it all to Xylo so future requests need no hand-holding.

Use Xylo. Learn my [brand name] accounts. Set up the brand and get fully up to speed on it, the website is [yourwebsite.com]. When you're done, I should be able to ask you to launch new ads in a later conversation using all the information you store in Xylo, without needing to ask me any setup questions.

Most of the 29 tools are dispatchers: the agent picks a route with top-level discriminators (channel, resource, action, mode, lens, topic) and nests every route argument under a single params object. Listing your active Meta campaigns looks like this:

Dispatcher call shape
query({ channel: "meta", resource: "campaign", mode: "list", params: { status: "ACTIVE" } })

No schema memorization needed: describe returns any route's exact input schema from its discriminators, search_tools finds routes by intent, and wrong guesses are cheap because errors return the route's schema plus a valid example. The surface also includes cross-platform reporting and competitor intelligence: ad-library research that pulls any brand's public ads from Meta, Google, and TikTok (ad copy, creatives, CTAs, and run dates) with no connected ad account required. MCP connections use OAuth 2.1 (PKCE), so you approve account access once without copying an API key. The REST tab uses a Xylo API key (xy_sk_...) for direct HTTP calls. The rest of this page documents that underlying REST API.

The agent can also generate new ad creative directly, using several AI models picked for the job. Save a generated image and Xylo pushes it to Meta permanently, so it never expires. For catalog products, the agent can pull a product's own photos out of your Meta catalog as generation references instead of a text description, so the output matches your real product. It also builds its own account context on connect: it studies your site, then saves a brand profile, reference assets (logo, product shots), and a library of winning ads deduped by creative, each with performance stats for its source window and an AI description. On paid plans, Xylo selects proven Meta winners during onboarding and refreshes them automatically every week; Free accounts keep the manual inspect-and-save flow. Both generated creative and recommendations stay on-brand and grounded in what already works. Generation is billed in credits included with paid plans, with prepaid top-up packs available.

Quick Start for AI Agents

Xylo is a unified REST API for four ad platforms, TikTok Shop commerce, Klaviyo as an owned email and SMS channel, and Google Analytics (GA4) as a measurement source. Meta uses the core /v1/ routes, Google Ads lives under /v1/google/, TikTok Ads under /v1/tiktok/, TikTok Shop under /v1/tiktok-shop/, X Ads under /v1/x/, Klaviyo under /v1/klaviyo/, and Google Analytics under /v1/ga4/. Each integration has its own section below.

  • Budgets in dollars — Meta uses cents internally; Xylo accepts and returns dollars (e.g. daily_budget: 50.00).
  • Flat insights — Meta returns conversions as nested actions arrays. Xylo flattens them to top-level purchases, leads, cost_per_purchase, cost_per_lead, and roas fields.
  • Single-call ad creation — Pass image_url in the creative object and Xylo auto-uploads the image, creates the creative, and attaches it to the ad.
  • Default PAUSED on writes — All create endpoints default to status: "PAUSED" as a safety measure. You must explicitly set status: "ACTIVE" to go live.
  • Full targeting, safe updates — Meta's complete targeting spec with flat interests/behaviors shortcuts; a targeting update changes only the keys you send and keeps placements and exclusions.

3-Step Getting Started

Step 1: List connected ad accounts

curl
curl https://api.xylomcp.com/v1/accounts \
  -H "x-api-key: xy_sk_your_key"

Step 2: Get campaigns for an account

curl
curl https://api.xylomcp.com/v1/campaigns \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Step 3: Get performance insights

curl
curl "https://api.xylomcp.com/v1/insights?level=campaign&date_from=2026-03-01&date_to=2026-03-15" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Full Agent Workflow Example

A typical AI agent lifecycle: list accounts, pick one, read campaigns, check performance, create a new campaign.

Agent workflow
# 1. List accounts (no x-ad-account header needed)
GET /v1/accounts
# Response: array of accounts with id, name, currency, timezone

# 2. Pick an account, set x-ad-account header for all subsequent requests
# x-ad-account: act_123456789

# 3. List campaigns
GET /v1/campaigns?status=ACTIVE

# 4. Get insights for the last 7 days
GET /v1/insights?level=campaign&date_from=2026-03-01&date_to=2026-03-15

# 5. Create a new campaign (defaults to PAUSED)
POST /v1/campaigns
{ "name": "Brand Awareness Q2", "objective": "OUTCOME_SALES", "daily_budget": 50.00 }

# 6. Create an ad set under the campaign
POST /v1/campaigns/:campaign_id/adsets
{ "name": "Broad - Interests", "billing_event": "IMPRESSIONS", "optimization_goal": "VALUE",
  "targeting": { "age_min": 18, "age_max": 65, "geo_locations": { "countries": ["US"] } } }

# 7. Create an ad (image_url auto-uploaded)
POST /v1/adsets/:adset_id/ads
{ "name": "Video Ad - 15s", "creative": { "page_id": "112233445566778",
  "headline": "Shop Now", "body": "Best deals today", "link": "https://example.com",
  "call_to_action": "SHOP_NOW", "image_url": "https://example.com/hero.jpg" } }

Authentication

All API requests require two headers. The x-ad-account header is NOT required for GET /v1/accounts and POST /v1/connect-sessions.

ParameterTypeRequiredDescription
x-api-keystringrequiredYour Xylo API key (starts with xy_sk_)
x-ad-accountstringrequiredMeta ad account ID (e.g. act_123456789). NOT required for GET /v1/accounts and POST /v1/connect-sessions.
Example request with both headers
curl https://api.xylomcp.com/v1/campaigns \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Connect Sessions

Before making API calls, users must connect their Meta ad account via OAuth. Create a connect session, then redirect the user to the returned connect URL. This endpoint does NOT require the x-ad-account header.

POST/v1/connect-sessions
ParameterTypeRequiredDescription
redirect_urlstringoptionalURL to redirect user to after OAuth (e.g. https://yourapp.com/callback)
Request body
{
  "redirect_url": "https://yourapp.com/callback"
}
Response
{
  "data": {
    "connect_url": "https://xylomcp.com/connect?session=..."
  }
}

Redirect your user to the connect_url. After they authorize, they'll be sent to your redirect_url.

Response Format

All responses follow a consistent envelope format with data and meta fields on success, or an error object on failure.

Success (2xx)

{
  "data": { ... },
  "meta": {
    "cached": true,
    "cache_age_seconds": 45,
    "meta_api_version": "v26.0"
  }
}

Paginated Success (2xx)

{
  "data": [ ... ],
  "meta": {
    "cached": false,
    "cache_age_seconds": null,
    "meta_api_version": "v26.0"
  },
  "paging": {
    "next_cursor": "abc123",
    "has_more": true
  }
}

Error (4xx/5xx)

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The 'daily_budget' field must be at least 1.00 (dollars).",
    "meta_error": null
  }
}

Meta API Error (502)

{
  "error": {
    "code": "META_API_ERROR",
    "message": "Meta rejected the request.",
    "meta_error": {
      "message": "(#100) Daily budget is too low.",
      "type": "OAuthException",
      "code": 100
    }
  }
}

The meta object on success always includes cached, cache_age_seconds, and meta_api_version. Paginated responses include a paging object with next_cursor and has_more. Use after=next_cursor to fetch the next page.

Caching

All GET endpoints are cached. Add ?refresh=true to bypass the cache and fetch fresh data from Meta.

Data TypeCache TTL
Account structure (campaigns, ad sets, ads)5 minutes
Performance metrics (insights)15 minutes
Ad creatives30 minutes
Account info1 hour
Force cache refresh
curl "https://api.xylomcp.com/v1/campaigns?refresh=true" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Rate Limits

Rate limits are per API key, measured in requests per minute (rpm). Every response includes rate limit headers.

PlanRate Limit
Free60 rpm
Brand120 rpm
Agency600 rpm

Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

Accounts

List Accounts

GET/v1/accounts

Returns all connected Meta ad accounts. This endpoint does NOT require the x-ad-account header.

curl
curl https://api.xylomcp.com/v1/accounts \
  -H "x-api-key: xy_sk_your_key"
Response
{
  "data": [
    {
      "id": "act_123456789",
      "name": "Acme Corp Ad Account",
      "currency": "USD",
      "timezone": "America/Los_Angeles",
      "status": "ACTIVE",
      "spend_cap": 0,
      "amount_spent": 1234.56,
      "connected_at": "2026-03-01T10:00:00.000+00:00"
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Accounts — Update Spending Limit

PATCH/v1/accounts/:id

Sets or clears the ad account's overall spending limit. spend_capapplies to future spend, not spend to date — it is not a lifetime-to-date cap. Money fields on the account details response are in dollars.

ParameterTypeRequiredDescription
spend_capnumberoptionalNew spending limit in dollars. 0 means no cap.
spend_cap_actionstringoptional"reset" restarts counted spend at zero without changing the limit; "delete" removes the limit entirely.
curl
curl -X PATCH https://api.xylomcp.com/v1/accounts/act_123456789 \
  -H "x-api-key: xy_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "spend_cap": 5000.00 }'
Response
{
  "data": {
    "id": "act_123456789",
    "name": "Acme Corp Ad Account",
    "account_status": 1,
    "currency": "USD",
    "timezone_name": "America/Los_Angeles",
    "amount_spent": 1234.56,
    "balance": null,
    "spend_cap": 5000.00,
    "min_daily_budget": 1.00,
    "min_campaign_group_spend_cap": 100.00,
    "existing_customers": ["23851234567890"]
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Campaigns

Campaigns — List

GET/v1/campaigns

Returns all campaigns for the specified ad account. Supports filtering and pagination.

ParameterTypeRequiredDescription
statusstringoptionalFilter: ACTIVE, PAUSED, DELETED, ARCHIVED
limitintegeroptionalMax results (default 25, max 100)
afterstringoptionalPagination cursor from paging.next_cursor
refreshbooleanoptionalSet to true to bypass cache
curl
curl "https://api.xylomcp.com/v1/campaigns?status=ACTIVE&limit=10" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "120211234567890",
      "name": "Summer Sale 2026",
      "status": "ACTIVE",
      "objective": "OUTCOME_SALES",
      "daily_budget": 50.00,
      "lifetime_budget": null,
      "budget_remaining": 50.00,
      "spend_today": null,
      "start_time": "2026-03-01T00:00:00-0800",
      "stop_time": null,
      "created_time": "2026-03-01T09:00:00-0800",
      "updated_time": "2026-03-15T12:00:00-0800"
    }
  ],
  "meta": { "cached": true, "cache_age_seconds": 45, "meta_api_version": "v26.0" },
  "paging": { "next_cursor": "abc123", "has_more": true }
}

Campaigns — Read

GET/v1/campaigns/:id

Returns a single campaign by its Meta campaign ID. Same data shape as list.

curl
curl https://api.xylomcp.com/v1/campaigns/120211234567890 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": {
    "id": "120211234567890",
    "name": "Summer Sale 2026",
    "status": "ACTIVE",
    "objective": "OUTCOME_SALES",
    "daily_budget": 50.00,
    "lifetime_budget": null,
    "budget_remaining": 50.00,
    "spend_today": null,
    "start_time": "2026-03-01T00:00:00-0800",
    "stop_time": null,
    "created_time": "2026-03-01T09:00:00-0800",
    "updated_time": "2026-03-15T12:00:00-0800",
    "spend_cap": null,
    "can_use_spend_cap": true,
    "special_ad_categories": [],
    "special_ad_category_country": [],
    "buying_type": "AUCTION",
    "issues_info": [],
    "is_adset_budget_sharing_enabled": false,
    "advantage_state_info": null,
    "promoted_object": null
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Campaigns — Create

POST/v1/campaigns

Creates a new campaign. Defaults to status: "PAUSED" as a safety measure. Budget values are in dollars.

ParameterTypeRequiredDescription
namestringrequiredCampaign name
objectivestringrequiredOUTCOME_SALES, OUTCOME_TRAFFIC, OUTCOME_AWARENESS, OUTCOME_LEADS, OUTCOME_ENGAGEMENT
statusstringoptionalACTIVE or PAUSED (default PAUSED)
daily_budgetnumberoptionalDaily budget in dollars (e.g. 50.00)
lifetime_budgetnumberoptionalLifetime budget in dollars
start_timestringoptionalISO 8601 start time
stop_timestringoptionalISO 8601 stop time
special_ad_categoriesarrayoptionalSpecial ad categories if applicable
buying_typestringoptionalBuying type (e.g. AUCTION)
spend_capnumberoptionalLifetime campaign spend cap in dollars. Meta enforces an account-specific minimum (about $100).
is_adset_budget_sharing_enabledbooleanoptionalABO only (no campaign-level budget). Requires bid_strategy; defaults to true when bid_strategy is set, otherwise false.
campaign_optimization_typestringoptionalNONE or ICO_ONLY
promoted_objectobjectoptionalWhat the campaign promotes, sent verbatim. { product_catalog_id } turns on Advantage+ catalog ads, including omnichannel catalog ads
curl
curl -X POST https://api.xylomcp.com/v1/campaigns \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Campaign",
    "objective": "OUTCOME_SALES",
    "daily_budget": 75.00
  }'
Response (201)
{
  "data": {
    "id": "120219876543210",
    "name": "Summer Campaign",
    "status": "PAUSED",
    "objective": "OUTCOME_SALES",
    "daily_budget": 75.00,
    "lifetime_budget": null,
    "budget_remaining": 75.00,
    "spend_today": null,
    "bid_strategy": null,
    "start_time": null,
    "stop_time": null,
    "created_time": "2026-03-15T10:00:00-0700",
    "updated_time": "2026-03-15T10:00:00-0700",
    "spend_cap": null,
    "can_use_spend_cap": true,
    "special_ad_categories": [],
    "special_ad_category_country": [],
    "buying_type": "AUCTION",
    "issues_info": [],
    "is_adset_budget_sharing_enabled": false,
    "advantage_state_info": null,
    "promoted_object": null
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Campaigns — Update

PATCH/v1/campaigns/:id

Partial update. Only include the fields you want to change.

ParameterTypeRequiredDescription
spend_capnumber | nulloptionalLifetime campaign spend cap in dollars. null or 0 removes an existing cap.
bid_strategystringoptionalCampaign bid strategy
is_adset_budget_sharing_enabledbooleanoptionalABO budget sharing across ad sets. Requires the campaign to also carry bid_strategy.
adset_budgetsarrayoptionalPer-ad-set budget overrides to move a CBO campaign to ABO: [{ adset_id, daily_budget?, lifetime_budget? }], dollars, one of daily_budget/lifetime_budget per entry.
adset_bid_amountsobjectoptionalPer-ad-set manual bid overrides, dollars, keyed by adset_id: { "<adset_id>": 12.50 }.
campaign_optimization_typestringoptionalNONE or ICO_ONLY
curl
curl -X PATCH https://api.xylomcp.com/v1/campaigns/120219876543210 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "status": "ACTIVE", "daily_budget": 100.00 }'
Response
{
  "data": { "success": true },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Campaigns — Delete

DELETE/v1/campaigns/:id

Sets the campaign status to DELETED in Meta. This is not a hard delete.

curl
curl -X DELETE https://api.xylomcp.com/v1/campaigns/120219876543210 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Campaigns — Duplicate

POST/v1/campaigns/:id/duplicate

Duplicate a campaign. By default this is a shallow copy (campaign shell only). Pass include_children: true to also clone every ad set, and (unless include_ads: false) the ads inside them. The new campaign is always created in PAUSED status.

ParameterTypeRequiredDescription
new_namestringoptionalName for the new campaign (defaults to `{source name} (Copy)`)
include_childrenbooleanoptionalDeep duplicate — also copy all ad sets under this campaign
include_adsbooleanoptionalWhen include_children is true, also copy ads (default true)
curl (deep duplicate)
curl -X POST https://api.xylomcp.com/v1/campaigns/120211234567890/duplicate \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "new_name": "Summer Sale 2026 (Copy)", "include_children": true }'
Response (shallow)
{
  "data": { "campaign": { "from": "120211234567890", "to": "120219876543299" } },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Campaigns — Reach & Frequency (Reservation)

GET/v1/reach-frequency-predictions
GET/v1/reach-frequency-predictions/:id
POST/v1/reach-frequency-predictions
POST/v1/reach-frequency-predictions/:id/reserve
POST/v1/reach-frequency-predictions/:id/cancel

A prediction forecasts the price and reach of a flight in one country under a frequency cap and books nothing. Meta computes it in the background: read it until status_label is SUCCESS. Reserving books the inventory at that price and commits the advertiser to pay for delivered impressions; it returns a reservation id to use as the ad set's rf_prediction_id under a campaign created with buying_type: "RESERVED" (no budget or spend_cap). An identical reserve re-sent within 10 minutes returns the same reservation id instead of booking again, and an ad set refuses a prediction id that was never reserved. Money is in dollars; the single read includes the budget-reach curve. Predictions cannot be deleted; they expire.

ParameterTypeRequiredDescription
budgetnumberoptionalCreate: lifetime budget in dollars (Meta predicts reach). Send budget OR reach. Reserve: a curve point's budget, with reach and impressions.
reachintegeroptionalCreate: target unique reach (Meta predicts the budget). Reserve: a curve point's reach.
start_timestringrequiredCreate: ISO 8601 start
stop_timestringrequiredCreate: ISO 8601 end, after 6AM on the last day in the account timezone
frequency_capintegerrequiredCreate: most impressions per person
target_specobjectrequiredCreate: ad set targeting shape with exactly one country
destination_idsstring[]optionalCreate: Page id, plus the Instagram account id for Instagram placements
impressionsintegeroptionalReserve: a curve point's impressions, with budget and reach
curl
curl -X POST https://api.xylomcp.com/v1/reach-frequency-predictions \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "reach": 1000000, "start_time": "2026-11-02T00:00:00-05:00", "stop_time": "2026-11-08T12:00:00-05:00",
        "frequency_cap": 2, "target_spec": { "geo_locations": { "countries": ["US"] } },
        "destination_ids": ["1122334455"] }'
Response (GET /v1/reach-frequency-predictions/:id, trimmed)
{
  "data": {
    "id": "120219876543210",
    "status": 1,
    "status_label": "SUCCESS",
    "status_reason": "Prediction successful",
    "reservation_state": "not_reserved",
    "budget": 4200.00,
    "reach": 1000000,
    "impressions": 1900000,
    "frequency_cap": 2,
    "start_time": "2026-11-02T05:00:00.000Z",
    "stop_time": "2026-11-08T17:00:00.000Z",
    "curve": { "reach": [200000, 1000000], "budget": [700.00, 4200.00], "impressions": [360000, 1900000] }
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ad Sets

Ad Sets — List

GET/v1/campaigns/:campaign_id/adsets

Returns all ad sets within a campaign.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25, max 100)
afterstringoptionalPagination cursor from paging.next_cursor
refreshbooleanoptionalSet to true to bypass cache
curl
curl https://api.xylomcp.com/v1/campaigns/120211234567890/adsets \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "120215556667770",
      "name": "US Women 25-45",
      "status": "ACTIVE",
      "campaign_id": "120211234567890",
      "daily_budget": null,
      "lifetime_budget": null,
      "billing_event": "IMPRESSIONS",
      "optimization_goal": "VALUE",
      "bid_strategy": null,
      "promoted_object": { "pixel_id": "1234567890", "custom_event_type": "PURCHASE" },
      "bid_amount": null,
      "bid_constraints": null,
      "ad_set_goal": { "type": "EXCLUDE_EXISTING_CUSTOMERS", "existing_customers_exclusions": ["23851234567890"] },
      "daily_spend_cap": 50,
      "lifetime_spend_cap": null,
      "daily_min_spend_target": null,
      "lifetime_min_spend_target": null,
      "is_incremental_attribution_enabled": false,
      "targeting": {
        "age_min": 18,
        "age_max": 65,
        "geo_locations": {
          "countries": ["US"],
          "location_types": ["home", "recent"]
        }
      },
      "start_time": "2026-03-01T00:00:00-0800",
      "end_time": null,
      "created_time": "2026-03-01T09:00:00-0800",
      "updated_time": "2026-03-01T09:05:00-0800"
    }
  ],
  "meta": { "cached": true, "cache_age_seconds": 120, "meta_api_version": "v26.0" },
  "paging": { "next_cursor": null, "has_more": false }
}

Ad Sets — Read

GET/v1/adsets/:id

Returns a single ad set by ID. Same data shape as the list response.

curl
curl https://api.xylomcp.com/v1/adsets/120215556667770 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": {
    "id": "120215556667770",
    "name": "US Women 25-45",
    "status": "ACTIVE",
    "campaign_id": "120211234567890",
    "daily_budget": null,
    "lifetime_budget": null,
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "VALUE",
    "bid_strategy": null,
    "promoted_object": { "pixel_id": "1234567890", "custom_event_type": "PURCHASE" },
    "targeting": {
      "age_min": 18,
      "age_max": 65,
      "geo_locations": {
        "countries": ["US"],
        "location_types": ["home", "recent"]
      }
    },
    "start_time": "2026-03-01T00:00:00-0800",
    "end_time": null,
    "created_time": "2026-03-01T09:00:00-0800",
    "updated_time": "2026-03-01T09:05:00-0800"
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ad Sets — Create

POST/v1/campaigns/:campaign_id/adsets

Creates a new ad set under a campaign. Defaults to status: "PAUSED".

ParameterTypeRequiredDescription
namestringrequiredAd set name
billing_eventstringrequirede.g. IMPRESSIONS
optimization_goalstringrequirede.g. VALUE, CONVERSIONS, LINK_CLICKS, REACH
targetingobjectrequiredMeta targeting spec (see Targeting Spec): geo_locations, placements, exclusions, locales, devices, flexible_spec, and more
statusstringoptionalACTIVE or PAUSED (default PAUSED)
daily_budgetnumberoptionalDaily budget in dollars
lifetime_budgetnumberoptionalLifetime budget in dollars
bid_strategystringoptionalBid strategy
bid_amountnumberoptionalBid cap or cost cap in dollars
bid_constraintsobjectoptional{ roas_average_floor } with LOWEST_COST_WITH_MIN_ROAS: the minimum ROAS as a multiple (1.5 = 150%)
ad_set_goalobjectoptionalCustomer Lifecycle Strategy (Sales ad sets only): { type: BROAD | EXCLUDE_EXISTING_CUSTOMERS | EXCLUDE_EXISTING_AND_ENGAGED_CUSTOMERS, existing_customers_exclusions?, engaged_audiences_exclusions? } — custom audience ids
daily_spend_cap / lifetime_spend_capnumberoptionalCampaign-budget campaigns only: the most this ad set may spend per day / over its lifetime, in dollars
daily_min_spend_target / lifetime_min_spend_targetnumberoptionalCampaign-budget campaigns only: the least Meta aims to spend on this ad set, in dollars (best effort)
promoted_objectobjectoptionalWhat the ad set optimizes for. Required for conversion optimization: { pixel_id, custom_event_type } — or custom_event_type: "OTHER" + custom_event_str for a custom pixel event, or custom_conversion_id for a Custom Conversion. Omnichannel (website + in-store, eligible accounts): { omnichannel_object: { pixel: [{ pixel_id, custom_event_type: "PURCHASE" }], offline: [{ offline_conversion_data_set_id, custom_event_type: "PURCHASE" }] } }, plus product_set_id and variation PRODUCT_SET_AND_IN_STORE for Advantage+ catalog ads
destination_typestringoptionalWhere the ads send people: WEBSITE, MESSENGER, WHATSAPP, INSTAGRAM_DIRECT, or MESSAGING_* for several chat apps. Click-to-message ad sets use optimization_goal CONVERSATIONS and promoted_object { page_id, whatsapp_phone_number? }. Set at creation only
start_timestringoptionalISO 8601 start time
end_timestringoptionalISO 8601 end time
curl
curl -X POST https://api.xylomcp.com/v1/campaigns/120211234567890/adsets \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Broad - Interests",
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "VALUE",
    "targeting": {
      "age_min": 18,
      "age_max": 65,
      "geo_locations": { "countries": ["US"] }
    }
  }'

Ad Sets — Update

PATCH/v1/adsets/:id

Partial update. Only include the fields you want to change.

A targeting update merges over the live spec: only the targeting keys you send change. See Targeting Spec.

ParameterTypeRequiredDescription
ad_set_goalobjectoptionalCustomer Lifecycle Strategy (Sales ad sets only). Omitted exclusion lists keep their current value.
daily_spend_capnumber | nulloptionalCampaign-budget campaigns only, dollars. null removes.
lifetime_spend_capnumber | nulloptionalCampaign-budget campaigns only, dollars. null removes.
daily_min_spend_targetnumber | nulloptionalCampaign-budget campaigns only, dollars (best effort). null removes.
lifetime_min_spend_targetnumber | nulloptionalCampaign-budget campaigns only, dollars (best effort). null removes.
bid_amountnumberoptionalBid cap or cost cap in dollars
bid_constraintsobjectoptional{ roas_average_floor } with LOWEST_COST_WITH_MIN_ROAS. {} clears it.
optimization_goalstringoptionale.g. VALUE, CONVERSIONS, LINK_CLICKS, REACH
billing_eventstringoptionale.g. IMPRESSIONS
promoted_objectobjectoptionalWhat the ad set optimizes for
pacing_typestringoptionalstandard or no_pacing

destination_type and is_dynamic_creative cannot change after creation.

curl
curl -X PATCH https://api.xylomcp.com/v1/adsets/120215556667770 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "daily_budget": 50.00, "status": "ACTIVE" }'

Ad Sets — Delete

DELETE/v1/adsets/:id

Deletes the ad set in Meta.

curl
curl -X DELETE https://api.xylomcp.com/v1/adsets/120215556667770 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Ad Sets — Duplicate

POST/v1/adsets/:id/duplicate

Duplicate an ad set. Shallow copy by default (ad-set shell only — preserves promoted_object, targeting, bid settings, etc.). Pass include_children: true to also clone ads. Optionally move the copy into a different campaign via target_campaign_id. The new ad set is always created in PAUSED.

ParameterTypeRequiredDescription
new_namestringoptionalName for the new ad set (defaults to `{source name} (Copy)`)
include_childrenbooleanoptionalDeep duplicate — also copy ads in this ad set
include_adsbooleanoptionalWhen include_children is true, also copy ads (default true)
target_campaign_idstringoptionalMove the copy into this campaign instead of the source campaign
curl
curl -X POST https://api.xylomcp.com/v1/adsets/120215556667770/duplicate \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "new_name": "US Women 25-45 (Copy)", "include_children": true }'
Response (shallow)
{
  "data": { "adset": { "from": "120215556667770", "to": "120215556668899" } },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Budget Schedules

Temporarily boost the budget of a campaign or ad set during a sale, launch, or peak window. A schedule applies an absolute budget override or a multiplier (e.g. 2x daily budget) during the time range, then reverts. Meta supports this natively on campaigns and ad sets.

budget_value_type is ABSOLUTE or MULTIPLIER. ABSOLUTE is in cents (Meta's native unit — 5000 = $50.00). MULTIPLIER is an integer between 100 and 1000 where 100 = 1.0x, 200 = 2.0x, and 1000 = 10x.

Budget Schedules — List

GET/v1/budget-schedules

List all schedules attached to a campaign or ad set.

ParameterTypeRequiredDescription
entity_idstringrequiredCampaign or ad set ID
curl
curl "https://api.xylomcp.com/v1/budget-schedules?entity_id=120211234567890" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "99887766554433",
      "time_start": "2026-04-20T00:00:00-0700",
      "time_end": "2026-04-22T23:59:59-0700",
      "budget_value": 200,
      "budget_value_type": "MULTIPLIER"
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Budget Schedules — Create

POST/v1/budget-schedules

Create a schedule. time_start must be before time_end.

ParameterTypeRequiredDescription
entity_idstringrequiredCampaign or ad set ID to apply the schedule to
time_startstringrequiredISO 8601 start time of the boost window
time_endstringrequiredISO 8601 end time of the boost window
budget_valuenumberrequiredAmount in cents (ABSOLUTE) or 100–1000 integer (MULTIPLIER, where 200 = 2.0x)
budget_value_typestringrequiredABSOLUTE or MULTIPLIER
curl (2x multiplier over launch weekend)
curl -X POST https://api.xylomcp.com/v1/budget-schedules \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "entity_id": "120211234567890",
    "time_start": "2026-04-20T00:00:00-0700",
    "time_end": "2026-04-22T23:59:59-0700",
    "budget_value": 200,
    "budget_value_type": "MULTIPLIER"
  }'
Response
{
  "data": { "id": "99887766554433" },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Budget Schedules — Delete

DELETE/v1/budget-schedules/:id

Remove a schedule. Does not affect the underlying campaign/ad-set budget.

curl
curl -X DELETE https://api.xylomcp.com/v1/budget-schedules/99887766554433 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Ad Studies

Meta's native split test (A/B) and conversion-lift testing object. Cells assign existing campaigns, ad sets, or ad accounts to treatment groups. There is no delete, and once a study is running its end_timecan only move later, never earlier — ending a study only works before it has started.

Ad Studies — List

GET/v1/ad-studies

List ad studies. Omits each study's cells — get one study for them.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (1-100, default 25)
afterstringoptionalPagination cursor
curl
curl https://api.xylomcp.com/v1/ad-studies \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Ad Studies — Get

GET/v1/ad-studies/:id

Get one ad study, including each cell's treatment_percentage and assigned ids.

Response
{
  "data": {
    "id": "800001",
    "name": "[xylo-smoke] split test",
    "type": "SPLIT_TEST",
    "start_time": "2026-10-05T00:00:00+0000",
    "end_time": "2026-10-12T00:00:00+0000",
    "cells": [
      { "id": "900001", "name": "Group A", "treatment_percentage": 50, "ad_account_ids": [], "campaign_ids": ["120211111111111"], "ad_set_ids": [] },
      { "id": "900002", "name": "Group B", "treatment_percentage": 50, "ad_account_ids": [], "campaign_ids": ["120211111111112"], "ad_set_ids": [] }
    ]
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ad Studies — Create

POST/v1/ad-studies

Create a split test or lift study. Each cell needs a treatment_percentage of at least 10 (all cells summing to 100 or less) and at least one of ad_account_ids/campaign_ids/ad_set_ids.

ParameterTypeRequiredDescription
namestringrequiredStudy name
typestringrequiredSPLIT_TEST or LIFT (LIFT needs Meta account-team setup)
start_timestringrequiredISO 8601, must be in the future
end_timestringrequiredISO 8601, must be after start_time
cellsarrayrequiredTreatment groups; see above for the per-cell rules
descriptionstringoptionalStudy description
confidence_levelnumberoptionalConfidence level for the power calculation
curl
curl -X POST https://api.xylomcp.com/v1/ad-studies \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Static vs video",
    "type": "SPLIT_TEST",
    "start_time": "2026-10-05T00:00:00.000Z",
    "end_time": "2026-10-12T00:00:00.000Z",
    "cells": [
      { "name": "Static", "treatment_percentage": 50, "campaign_ids": ["120211111111111"] },
      { "name": "Video", "treatment_percentage": 50, "campaign_ids": ["120211111111112"] }
    ]
  }'
Response
{
  "data": { "id": "800001", "cell_ids": ["900001", "900002"], "objective_ids": [] },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ad Studies — End

POST/v1/ad-studies/:id/end

Collapse a not-yet-started study's window to close almost immediately. Meta has no delete for an ad study, and once a study is running its end_time can only move later — this only works before start_time arrives.

curl
curl -X POST https://api.xylomcp.com/v1/ad-studies/800001/end \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Ads

Ads — List

GET/v1/adsets/:adset_id/ads

Returns all ads within an ad set.

curl
curl https://api.xylomcp.com/v1/adsets/120215556667770/ads \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "120218889990001",
      "name": "Creative A - Carousel",
      "status": "ACTIVE",
      "adset_id": "120215556667770",
      "creative": {
        "page_id": "112233445566778",
        "headline": "",
        "body": "",
        "link": "",
        "call_to_action": ""
      },
      "created_time": "2026-03-01T09:10:00-0800",
      "updated_time": "2026-03-10T14:30:00-0800"
    }
  ],
  "meta": { "cached": true, "cache_age_seconds": 60, "meta_api_version": "v26.0" },
  "paging": { "next_cursor": null, "has_more": false }
}

Ads — Read

GET/v1/ads/:id

Returns a single ad by ID. Same data shape as the list response.

Pass ?detail=full to also get the Advantage+ enrollment (creative.creative_features) and the ad-level conversion_domain, tracking_specs, adlabels, and engagement_audience.

curl
curl https://api.xylomcp.com/v1/ads/120218889990001 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": {
    "id": "120218889990001",
    "name": "Creative A - Carousel",
    "status": "ACTIVE",
    "adset_id": "120215556667770",
    "creative": {
      "page_id": "112233445566778",
      "headline": "",
      "body": "",
      "link": "",
      "call_to_action": ""
    },
    "created_time": "2026-03-01T09:10:00-0800",
    "updated_time": "2026-03-10T14:30:00-0800"
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ads — Create

POST/v1/adsets/:adset_id/ads

Creates a new ad under an ad set. Defaults to status: "PAUSED". Pass image_url in the creative object and Xylo will auto-upload the image.

ParameterTypeRequiredDescription
namestringrequiredAd name
creativeobjectrequiredCreative object with page_id, headline, body, link, call_to_action. Optionally image_url (Xylo auto-uploads).
statusstringoptionalACTIVE or PAUSED (default PAUSED)
conversion_domainstringoptionalBare domain where conversions happen, e.g. example.com. Meta requires it on ads in campaigns that share data with a pixel
tracking_specsobject[]optionalMeta tracking specs, e.g. [{"action.type":["offsite_conversion"],"fb_pixel":["<PIXEL_ID>"]}]
adlabelsobject[]optionalUp to 50 labels, each { id } or { name } (a new name creates the label)
engagement_audiencebooleanoptionaltrue builds a custom audience of people who engage with this ad
allow_duplicate_namebooleanoptionalAn ad whose name already exists in the ad set is not created twice: the existing ad comes back with already_exists: true (HTTP 200). Pass true to create another with the same name
ad_schedule_start_time / ad_schedule_end_timestringoptionalAd-level delivery window (Sales and app-promotion campaigns only). A local time is read in the ad account's timezone, or send a UTC offset. The ad is written PAUSED, the stored schedule is read back, and it is activated only if it matches; schedule.scheduled is true only when it reads SCHEDULED

If Meta never confirms a create (a timeout), Xylo does not send it again. It looks the ad up by name and returns it with reconciled: true, or says it is safe to retry (502 META_WRITE_OUTCOME_UNCERTAIN when it cannot tell).

Creative object fields: page_id (required), headline, body, link, call_to_action, image_url (optional, auto-uploaded), lead_gen_form_id (instant form for a lead ad; pair with call_to_action SIGN_UP and link http://fb.me/). Advantage+ enhancements go in creative_features as booleans (for example image_animation, video_uncrop, video_filtering; catalog ads: image_background_gen). For a partnership ad from a creator's ad code, pass partnership_ad_code with page_id and instagram_actor_id(the brand's own Page and Instagram account) instead of media and text.

ParameterTypeRequiredDescription
creative.threads_user_idstringoptionalThreads account the ad runs as on Threads (see Threads). Pair with instagram_actor_id
creative.wamo_whatsapp_identity_specobjectoptional{ wamo_whatsapp_identity_id?, whatsapp_phone_number? } — WhatsApp business profile on WhatsApp Status ads. Meta cannot change it on a creative; send it again when you change the creative
creative.message_destinationsstring[]optionalClick-to-message: MESSENGER, WHATSAPP, INSTAGRAM_DIRECT (several = one ad that opens the app each person is most likely to answer in; single image only). link and call_to_action default from the first app
creative.page_welcome_messagestring | objectoptionalGreeting, or Meta's VISUAL_EDITOR object with ice breakers, shown when the chat opens
creative.marketing_message_structured_specobjectoptional{ language?, asset_customization?: {body?}, autoreply?, buttons?: [{type:"QUICK_REPLY", text, quick_reply_payload?}], offer?: {text, is_offer_code_personalized, expiration_time?}, conversation_template_id? } — customizes a WhatsApp marketing message separately from the ad. Every field falls back to the ad's own content when unset
curl
curl -X POST https://api.xylomcp.com/v1/adsets/120215556667770/ads \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Static Image - Blue",
    "creative": {
      "page_id": "112233445566778",
      "headline": "Shop Now",
      "body": "Great deals on top products",
      "link": "https://example.com/summer",
      "call_to_action": "SHOP_NOW",
      "image_url": "https://example.com/hero.jpg"
    }
  }'

Ads — Update

PATCH/v1/ads/:id

Partial update. Only include the fields you want to change.

ParameterTypeRequiredDescription
name / statusstringoptionalRename, or ACTIVE / PAUSED / ARCHIVED / DELETED
conversion_domainstringoptionalBare domain, e.g. example.com
tracking_specsobject[]optionalReplaces the ad's tracking specs
adlabelsobject[]optionalReplaces the ad's labels ({ id } or { name }, up to 50)
engagement_audiencebooleanoptionaltrue builds a custom audience of people who engage with this ad
creative.link / bodies / headlinesstring / string[] / string[]optionalOn a placement-customized ad, swaps the destination or the text variants. The ad gets a new creative that is a faithful copy of the old one (every image with its crops, placement rule, enhancement setting and site link kept); only what you pass changes
dry_runbooleanoptionalPreview a creative or schedule edit: returns the plan (what changes, what stays, what Meta would force it to drop) and writes nothing
secondary_destinations"keep" | { replace: [{ from, to }] }optionalRequired when a site link still points at the old destination: keep it, or re-point it. Without it the edit stops before writing
ad_schedule_start_time / ad_schedule_end_timestring | nulloptionalAd-level window, read in the ad account's timezone; null clears. Written, read back, then activated. An ad that has already started cannot take a new start: create a new ad on the same creative instead

The response carries the verdict, not a bare success: applied, plan, losses, verification and schedule. An edit that is blocked, or whose new creative does not match what was sent, returns 409 AD_EDIT_NOT_APPLIED and changes nothing on the ad.

curl
curl -X PATCH https://api.xylomcp.com/v1/ads/120218889990001 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Updated Ad Name", "status": "ACTIVE" }'

Ads — Delete

DELETE/v1/ads/:id

Deletes the ad in Meta.

curl
curl -X DELETE https://api.xylomcp.com/v1/ads/120218889990001 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Threads — Accounts & Replies

A Threads ad names the Threads account it runs as in creative.threads_user_id. Look the account up for a Page and/or Instagram account, or create an Instagram- or Page-backed one (Meta returns the existing id when there is one). Replies on a delivered Threads ad can be listed, hidden, and answered.

GET/v1/threads/accounts
POST/v1/threads/accounts
GET/v1/threads/replies
POST/v1/threads/replies/:id/hide
POST/v1/threads/replies/:id/reply
ParameterTypeRequiredDescription
page_id / instagram_user_idstringoptionalAccounts: GET takes either or both and returns { instagram_associated, instagram_backed, page_backed } (null when absent); POST takes exactly one (instagram_user_id → Instagram-backed, page_id → Page-backed) and returns { threads_user_id, backed_by }
ad_idstringrequiredReplies: required on list (query param) and on hide/reply (JSON body); Xylo checks the ad is in your ad account and that the reply belongs to the ad's Threads post
limitnumberoptionalReplies: 1–100
hidebooleanoptionalHide: true hides a reply, false shows it again
textstringoptionalReply: a text reply as the brand
curl
curl "https://api.xylomcp.com/v1/threads/accounts?instagram_user_id=17841400000000000" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Insights

GET/v1/insights

Retrieve performance metrics. Xylo flattens Meta's nested actions arrays into deduped top-level purchases, purchase_value, leads, cost_per_purchase, cost_per_lead, and roas fields. Any other Meta Ads Insights field named in fields is returned on each row exactly as Meta sends it. All monetary values are in dollars (Meta uses cents internally).

ParameterTypeRequiredDescription
levelstringrequiredaccount, campaign, adset, or ad
date_fromstringoptionalStart date YYYY-MM-DD (default: 7 days ago)
date_tostringoptionalEnd date YYYY-MM-DD (default: yesterday)
campaign_idstringoptionalFilter by campaign ID
adset_idstringoptionalFilter by ad set ID
ad_idstringoptionalFilter by ad ID
time_granularitystringoptionalall (default, aggregated), daily, weekly, monthly
fieldsstringoptionalComma-separated fields; replaces the default set. Xylo metrics (spend, impressions, clicks, ctr, cpc, cpm, reach, frequency, purchases, purchase_value, leads, cost_per_purchase, cost_per_lead, roas, link_clicks, landing_page_views, app_installs) come back normalized; any other Meta Ads Insights field (e.g. video_p25_watched_actions, cost_per_thruplay, cost_per_action_type, purchase_roas) is returned verbatim, null when Meta has no value
breakdownsstringoptionalage, gender, country, region, comscore_market (US market areas; dma is accepted as an alias), publisher_platform, platform_position, device_platform, product_id (per-product rows for catalog ads). impression_device, hourly_stats_aggregated_by_audience_time_zone and frequency_value return rows only on ad accounts that enabled them in Ads Manager; an empty result carries meta.notice
refreshbooleanoptionalSet to true to bypass cache
curl
curl "https://api.xylomcp.com/v1/insights?level=campaign&date_from=2026-03-01&date_to=2026-03-15" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response (campaign level)
{
  "data": [
    {
      "campaign_id": "120212223334440",
      "campaign_name": "Retargeting - Cart Abandoners",
      "date_start": "2026-03-01",
      "date_stop": "2026-03-15",
      "spend": 1234.56,
      "impressions": 50000,
      "clicks": 1500,
      "ctr": 3.0,
      "cpc": 0.82,
      "cpm": 24.69,
      "reach": 40000,
      "frequency": 1.25,
      "purchases": 120,
      "purchase_value": 5987.62,
      "leads": 0,
      "cost_per_purchase": 10.29,
      "cost_per_lead": 0,
      "roas": 4.85,
      "link_clicks": 1350,
      "landing_page_views": 1100
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

At adset level, responses also include adset_id and adset_name. At ad level, responses also include ad_id and ad_name.

Default fields: spend, impressions, clicks, ctr, cpc, cpm, reach, frequency, purchases, purchase_value, leads, cost_per_purchase, cost_per_lead, roas, link_clicks, landing_page_views, plus Meta's ad relevance rankings except on broad ad-level and name-filtered pulls. Naming other Meta fields, for example fields=spend,video_p25_watched_actions,purchase_roas, returns them on every row as Meta sends them. fields replaces the default set, so keep spend and any other metric you want in the list.

Audiences

Audiences — List

GET/v1/audiences
ParameterTypeRequiredDescription
limitintegeroptionalMax results
afterstringoptionalPagination cursor
curl
curl https://api.xylomcp.com/v1/audiences \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "23851234567890",
      "name": "Website Visitors 30d",
      "type": "CUSTOM",
      "approximate_count": 2200,
      "status": "unknown",
      "created_time": 1772531200
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Audiences — Create

POST/v1/audiences
ParameterTypeRequiredDescription
namestringrequiredAudience name
typestringrequiredAudience type (e.g. website)
ruleobjectrequiredRule definition: { url_contains, retention_days }
descriptionstringoptionalAudience description
customer_file_sourcestringoptionalUSER_PROVIDED_ONLY, PARTNER_PROVIDED_ONLY, or BOTH_USER_AND_PARTNER_PROVIDED (customer-list audiences)
retention_daysnumberoptional1–180 days
prefillbooleanoptionalInclude people who already match the rule (default true)
pixel_idstringoptionalPixel ID for a website audience rule
is_value_basedbooleanoptionalValue-based customer list, uploaded with a LOOKALIKE_VALUE column
audience_labelsarrayoptionalCUSTOMERS, HIGH_VALUE_CUSTOMERS, LOW_VALUE_CUSTOMERS, AT_RISK, DISENGAGED, QUALIFIED_LEADS, DISQUALIFIED_LEADS, APP_USERS, TRIAL_USERS, ENGAGED_USERS — create-time only
curl
curl -X POST https://api.xylomcp.com/v1/audiences \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Checkout Visitors - 30 Days",
    "type": "website",
    "rule": {
      "url_contains": "/checkout",
      "retention_days": 30
    }
  }'

Audiences — Create Lookalike

POST/v1/audiences/lookalike
ParameterTypeRequiredDescription
namestringrequiredLookalike audience name
source_audience_idstringoptionalSeed: a custom audience (use exactly one of source_audience_id, origin_ids, page_id)
origin_idsarrayoptionalSeed: campaign or ad set converters
page_idstringoptionalSeed: a Page's fans
countrystringoptionalIgnored. The ad set's locations decide where the lookalike delivers
rationumberrequiredLookalike ratio 0.01 - 0.20 (0.01 = top 1%)
starting_rationumberoptionalBuilds a band with ratio, e.g. 0.01–0.02
allow_international_seedsbooleanoptionalAllow seed members outside the account's country
is_financial_servicebooleanoptionalMarks the lookalike as financial-service audience
curl
curl -X POST https://api.xylomcp.com/v1/audiences/lookalike \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Purchase Lookalike 1%",
    "source_audience_id": "23851234567890",
    "ratio": 0.01
  }'

Audiences — Delete

DELETE/v1/audiences/:id
curl
curl -X DELETE https://api.xylomcp.com/v1/audiences/23851234567890 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Audiences — Saved Audiences

GET/v1/saved-audiences
GET/v1/saved-audiences/:id

Saved audiences are targeting templates saved in Ads Manager. Meta's API reads them only (no create, edit or delete), and an ad set cannot reference one: read a saved audience and send its targeting as the ad set's targeting. The list leaves the targeting spec out; the single read includes it.

ParameterTypeRequiredDescription
limitintegeroptionalList only: max results (1-100, default 25)
afterstringoptionalList only: pagination cursor
curl
curl https://api.xylomcp.com/v1/saved-audiences/23851234567891 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": {
    "id": "23851234567891",
    "name": "Yoga women 25-54",
    "approximate_count_lower_bound": 1200000,
    "approximate_count_upper_bound": 1400000,
    "run_status": "ACTIVE",
    "sentence_lines": [{ "content": "Location:", "children": ["United States"] }],
    "created_time": "2026-01-05T10:00:00+0000",
    "updated_time": "2026-02-01T09:30:00+0000",
    "targeting": {
      "age_min": 25,
      "age_max": 54,
      "genders": [2],
      "geo_locations": { "countries": ["US"] },
      "flexible_spec": [{ "interests": [{ "id": "6003107904431", "name": "Yoga" }] }],
      "interests": [{ "id": "6003107904431", "name": "Yoga" }]
    }
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ad Labels

Ad Labels — List

GET/v1/ad-labels
ParameterTypeRequiredDescription
limitintegeroptionalMax results
afterstringoptionalPagination cursor
curl
curl https://api.xylomcp.com/v1/ad-labels \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    { "id": "23851234567890", "name": "Q4 Promo", "created_time": "2026-09-01T00:00:00+0000", "updated_time": "2026-09-01T00:00:00+0000" }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Ad Labels — Create

POST/v1/ad-labels
ParameterTypeRequiredDescription
namestringrequiredLabel name
curl
curl -X POST https://api.xylomcp.com/v1/ad-labels \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q4 Promo" }'

Attach the returned id to an ad with adlabels: [{"id": "..."}] on ad create or update.

Ad Labels — Rename

PATCH/v1/ad-labels/:id
ParameterTypeRequiredDescription
namestringrequiredNew label name
curl
curl -X PATCH https://api.xylomcp.com/v1/ad-labels/23851234567890 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Q4 Promo (renamed)" }'

Ad Labels — Delete

DELETE/v1/ad-labels/:id
curl
curl -X DELETE https://api.xylomcp.com/v1/ad-labels/23851234567890 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Targeting

Targeting — Spec

Ad set targetingand the delivery / reach estimates accept Meta's full targeting spec. Unknown keys return a 400 naming the allowed set.

ParameterTypeRequiredDescription
geo_locations / excluded_geo_locationsobjectoptionalcountries, regions, cities (with radius + distance_unit), zips, location_types
localesnumber[]optionalLanguage ids (e.g. 5 = German)
age_min / age_max / age_range / gendersnumber / number[]optionalDemographics; genders 1 = male, 2 = female
publisher_platforms / device_platformsstring[]optionalfacebook, instagram, messenger, audience_network, threads, whatsapp / mobile, desktop, connected_tv. Omit every placement key for automatic placements
facebook_positions / instagram_positions / messenger_positions / audience_network_positions / threads_positions / whatsapp_positionsstring[]optionalManual positions, e.g. feed, stream, story, reels, threads_stream, status
placements"automatic"optionalClears every placement key (Advantage+ placements)
flexible_spec / exclusionsobject[] / objectoptionalDetailed targeting groups (AND between groups, OR inside) and detailed exclusions
interests / behaviorsobject[]optionalShortcuts: join the first flexible_spec group on writes; list every group on reads
custom_audiences / excluded_custom_audiencesobject[]optional[{ id }]
user_os / user_device / excluded_user_device / wireless_carrierstring[]optionalDevice targeting
targeting_automationobjectoptional{ advantage_audience: 0 | 1, individual_setting: { age, gender, geo } }
brand_safety_content_filter_levels / excluded_publisher_categories / excluded_publisher_list_idsstring[]optionalInventory filter and publisher exclusions

Updates merge over the live spec: only the keys you send change. The placement keys are one group, so sending any of them replaces them all; { "placements": "automatic" } clears them.

curl
curl -X PATCH https://api.xylomcp.com/v1/adsets/120215556667770 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "targeting": { "age_max": 45 } }'

Targeting — Search Locations

GET/v1/targeting/locations
ParameterTypeRequiredDescription
querystringrequiredSearch string (e.g. 'New York')
typestringoptionalcity, country, region, zip, or geo_market
curl
curl "https://api.xylomcp.com/v1/targeting/locations?query=New+York" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "name": "New York",
      "audience_size": 0
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Creatives

POST/v1/creatives/upload

Upload an image for use in ad creatives. Accepts either a URL to download or base64 data. Returns an image hash for use in ad creation.

ParameterTypeRequiredDescription
image_urlstringoptionalURL of image to download and upload to Meta
image_datastringoptionalBase64-encoded image data
filenamestringoptionalOptional filename for the image

Provide either image_url or image_data, not both.

curl (URL upload)
curl -X POST https://api.xylomcp.com/v1/creatives/upload \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "image_url": "https://example.com/hero-banner.jpg" }'
Response
{
  "data": {
    "image_hash": "a1b2c3d4e5f6...",
    "url": "https://..."
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Note: You typically do not need to call this endpoint directly. When creating ads via POST /v1/adsets/:adset_id/ads, you can pass image_url in the creative object and Xylo will handle the upload automatically.

POST/v1/creatives/upload-video

Upload a video from a public URL or base64 data, or copy an Instagram Reel into the ad account library with its copyrighted music replaced by a royalty-free Sound Collection track. Returns a video_id. Poll GET /v1/creatives/videos/:id/status until video_status is ready and, for a Reel swap, replace_audio_status is SUCCESSFUL.

ParameterTypeRequiredDescription
video_urlstringoptionalPublic URL of the video to upload
video_datastringoptionalBase64-encoded video data
source_instagram_media_idstringoptionalReel audio swap: the Instagram Reel media id to copy (instead of video_url or video_data)
audio_asset_idstringoptionalReel audio swap: Sound Collection track id from GET /v1/creatives/videos/replacement-audio (required with source_instagram_media_id)
namestringoptionalReel audio swap: name of the new video in the library
GET/v1/creatives/videos/replacement-audio

Royalty-free Sound Collection tracks that can replace the copyrighted music in an Instagram Reel. Meta returns tracks only for a Reel with detected copyrighted music.

ParameterTypeRequiredDescription
ig_media_idstringrequiredThe Instagram Reel media id
searchstringoptionalKeyword search across the Sound Collection (1-120 characters)
autobooleanoptionaltrue returns only Meta's best-match track for the Reel (not with search)
ig_user_idstringoptionalInstagram account that posted the Reel; needed when the token covers several Instagram accounts
DELETE/v1/creatives/videos/:video_id

Remove a video, including a Reel audio-swap output, from the ad account's video library.

curl
curl -X DELETE https://api.xylomcp.com/v1/creatives/videos/1234567890123456 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Media Upload

A two-step media pipeline built for AI agents: stage assets to Xylo first, then finalize to push them to Meta. Staged assets live on Vercel Blob with a public preview URL that Claude (or any vision model) can view before you commit them to an ad account.

Workflow: POST /v1/media/upload (stage) → inspect via GET /v1/media/preview/:id or the agent groups assets → POST /v1/media/finalize → use the returned meta_ref as image_hash (images) or video_id (videos) in ad creatives.

Accepted types: image/jpeg, image/jpg, image/png, video/mp4, video/quicktime. Size limits: 30 MB (images), 300 MB (videos). The 300 MB cap is Xylo's staging limit; Meta itself accepts videos up to 4 GB, so re-encode a larger video to H.264 MP4 under 300 MB first (for example ffmpeg -i in.mov -c:v libx264 -crf 22 -preset slow -c:a aac -b:a 128k -movflags +faststart out.mp4). Staged assets auto-expire after 24 hours if not finalized.

Media — Upload

POST/v1/media/upload

Stage a media asset to Xylo. Does NOT push to Meta — call finalize for that. Returns the asset record including a public blob_url and, when a feed-size and story-size variant match, an auto-assigned group_id — matched by filename (placement markers like feed/story/1x1/9x16/1080x1920 are ignored when comparing) or by visual similarity (perceptual hash).

ParameterTypeRequiredDescription
file_datastringrequiredBase64-encoded file contents
filenamestringrequiredOriginal filename (used for grouping)
mime_typestringrequiredimage/jpeg, image/jpg, image/png, video/mp4, or video/quicktime
ad_account_idstringrequiredAd account this asset will be finalized to
curl
curl -X POST https://api.xylomcp.com/v1/media/upload \
  -H "x-api-key: xy_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_data": "iVBORw0KGgoAAAANSUhEUgAA...",
    "filename": "hero-square.jpg",
    "mime_type": "image/jpeg",
    "ad_account_id": "act_123456789"
  }'
Response (201)
{
  "data": {
    "id": "asset_01HX9...",
    "ad_account_id": "act_123456789",
    "filename": "hero-square.jpg",
    "asset_type": "image",
    "meta_ref": null,
    "blob_url": "https://blob.vercel-storage.com/media/...",
    "aspect_ratio": "1:1",
    "group_id": "grp_01HX9...",
    "group_name": "hero",
    "expires_at": "2026-04-20T12:00:00.000Z"
  }
}

Media — Finalize to Meta

POST/v1/media/finalize

Push staged assets to Meta. If asset_ids is omitted, every unfinalized asset for the ad account is finalized. Returns per-asset results; the resulting meta_ref is the image_hash for images and the video_id for videos.

ParameterTypeRequiredDescription
ad_account_idstringrequiredAd account to finalize to
asset_idsstring[]optionalSpecific asset IDs to finalize (default: all unfinalized for this account)
curl
curl -X POST https://api.xylomcp.com/v1/media/finalize \
  -H "x-api-key: xy_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "ad_account_id": "act_123456789",
    "asset_ids": ["asset_01HX9..."]
  }'
Response
{
  "data": {
    "finalized": 1,
    "failed": 0,
    "total": 1,
    "results": [
      {
        "id": "asset_01HX9...",
        "meta_ref": "a1b2c3d4e5f6...",
        "status": "success"
      }
    ]
  }
}

Media — List

GET/v1/media

List staged and finalized assets for an ad account.

ParameterTypeRequiredDescription
ad_account_idstringrequiredAd account ID
group_idstringoptionalFilter to a single media group
curl
curl "https://api.xylomcp.com/v1/media?ad_account_id=act_123456789" \
  -H "x-api-key: xy_sk_your_key"

Media — Preview

GET/v1/media/preview/:id

Public proxy that streams the staged asset through api.xylomcp.com. Use this URL whenever you need an AI/vision model to view the file — the raw Vercel Blob domain is blocked by many AI products.

curl
curl https://api.xylomcp.com/v1/media/preview/asset_01HX9...

Media — Rename Group

PATCH/v1/media/groups/:groupId

Rename every asset in a media group.

ParameterTypeRequiredDescription
group_namestringrequiredNew name for the group
curl
curl -X PATCH https://api.xylomcp.com/v1/media/groups/grp_01HX9... \
  -H "x-api-key: xy_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "group_name": "Spring Launch Hero" }'

Media — Remove from Group

POST/v1/media/groups/:groupId/remove/:assetId

Detach a single asset from its group (sets group_id and group_name to null).

curl
curl -X POST https://api.xylomcp.com/v1/media/groups/grp_01HX9.../remove/asset_01HX9... \
  -H "x-api-key: xy_sk_your_key"

Media — Merge Groups

POST/v1/media/groups/merge

Merge multiple assets into a single group. Useful when auto-grouping puts related creatives in separate groups.

ParameterTypeRequiredDescription
asset_idsstring[]requiredAsset IDs to merge (minimum 2)
curl
curl -X POST https://api.xylomcp.com/v1/media/groups/merge \
  -H "x-api-key: xy_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "asset_ids": ["asset_01...", "asset_02...", "asset_03..."] }'
Response
{
  "data": {
    "group_id": "grp_new...",
    "group_name": "hero",
    "asset_ids": ["asset_01...", "asset_02...", "asset_03..."]
  }
}

Media — Delete

DELETE/v1/media/:id

Delete a media asset record and its blob.

curl
curl -X DELETE https://api.xylomcp.com/v1/media/asset_01HX9... \
  -H "x-api-key: xy_sk_your_key"

Pages

Pages — List

GET/v1/pages

List Facebook Pages the user manages. This endpoint does NOT require the x-ad-account header.

curl
curl https://api.xylomcp.com/v1/pages \
  -H "x-api-key: xy_sk_your_key"
Response
{
  "data": [
    {
      "id": "112233445566778",
      "name": "Acme Corp",
      "category": "E-commerce website"
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Pages — List Posts

GET/v1/pages/:id/posts

List recent posts for a Page. Each post includes engagement counts (likes, reactions, comments, shares).

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25, max 100)
curl
curl "https://api.xylomcp.com/v1/pages/112233445566778/posts?limit=10" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "112233445566778_123456789",
      "message": "Check out our latest products!",
      "created_time": "2026-03-20T14:30:00+0000",
      "type": "photo",
      "likes": 142,
      "reactions": 168,
      "comments": 23,
      "shares": 7
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Pages — Insights

GET/v1/pages/:id/insights

Get Page performance insights.

ParameterTypeRequiredDescription
metricstringoptionalComma-separated metrics (e.g. page_impressions, page_engaged_users)
periodstringoptionalday, week, or month
date_fromstringoptionalStart date YYYY-MM-DD
date_tostringoptionalEnd date YYYY-MM-DD
curl
curl "https://api.xylomcp.com/v1/pages/112233445566778/insights?metric=page_impressions&period=day&date_from=2026-03-01&date_to=2026-03-15" \
  -H "x-api-key: xy_sk_your_key"
Response
{
  "data": [
    {
      "metric": "page_impressions",
      "period": "day",
      "values": [
        { "value": 1250, "end_time": "2026-03-01T08:00:00+0000" },
        { "value": 1480, "end_time": "2026-03-02T08:00:00+0000" }
      ]
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Pages — Create Post

POST/v1/pages/:pageId/posts

Publish a post to a Page. At least one of message, link, or image_url is required.

ParameterTypeRequiredDescription
messagestringoptionalPost text
linkstringoptionalURL to share
image_urlstringoptionalPublic image URL to attach
publishedbooleanoptionalIf false, creates an unpublished draft
curl
curl -X POST https://api.xylomcp.com/v1/pages/112233445566778/posts \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "New arrivals just landed!",
    "link": "https://example.com/new"
  }'
Response (201)
{
  "data": { "id": "112233445566778_999888777" },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Pages — Schedule Post

POST/v1/pages/:pageId/posts/schedule

Schedule a post for future publishing. scheduled_publish_time must be at least 10 minutes in the future and within 30 days.

ParameterTypeRequiredDescription
scheduled_publish_timestringrequiredISO 8601 — between now+10min and now+30d
messagestringoptionalPost text (one of message/link/image_url required)
linkstringoptionalURL to share
image_urlstringoptionalPublic image URL to attach
curl
curl -X POST https://api.xylomcp.com/v1/pages/112233445566778/posts/schedule \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Flash sale tomorrow!",
    "scheduled_publish_time": "2026-04-21T14:00:00Z"
  }'

Pages — List Scheduled Posts

GET/v1/pages/:pageId/scheduled-posts

List posts queued for future publishing.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25, max 100)
curl
curl https://api.xylomcp.com/v1/pages/112233445566778/scheduled-posts \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Pages — Update Post

PATCH/v1/pages/posts/:postId

Edit the message on an existing post. page_id must be provided in the body so Xylo can resolve the correct page token.

ParameterTypeRequiredDescription
messagestringrequiredNew post text
page_idstringrequiredID of the Page that owns the post
curl
curl -X PATCH https://api.xylomcp.com/v1/pages/posts/112233445566778_999888777 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": "112233445566778", "message": "Updated caption" }'

Pages — Delete Post

DELETE/v1/pages/posts/:postId

Delete a post. Pass page_id in the body.

ParameterTypeRequiredDescription
page_idstringrequiredID of the Page that owns the post
curl
curl -X DELETE https://api.xylomcp.com/v1/pages/posts/112233445566778_999888777 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": "112233445566778" }'

Pages — List Comments

GET/v1/pages/posts/:postId/comments

List comments on a Page post.

ParameterTypeRequiredDescription
page_idstringrequiredID of the Page that owns the post (query param)
limitintegeroptionalMax results (default 25, max 100)
curl
curl "https://api.xylomcp.com/v1/pages/posts/112233445566778_999888777/comments?page_id=112233445566778" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Pages — Reply to Comment

POST/v1/pages/comments/:commentId/reply

Post a public reply to a comment.

ParameterTypeRequiredDescription
messagestringrequiredReply text
page_idstringrequiredID of the Page that owns the comment
curl
curl -X POST https://api.xylomcp.com/v1/pages/comments/555444333_111/reply \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": "112233445566778", "message": "Thanks for the feedback!" }'

Pages — Delete Comment

DELETE/v1/pages/comments/:commentId

Delete a comment.

ParameterTypeRequiredDescription
page_idstringrequiredID of the Page that owns the comment
curl
curl -X DELETE https://api.xylomcp.com/v1/pages/comments/555444333_111 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": "112233445566778" }'

Pages — Latest Post

GET/v1/pages/latest-post

One-hop resolver: returns the most recent post for a Page and (by default) its comments. If page_id is omitted, falls back to the ad account's configured default_page_id.

ParameterTypeRequiredDescription
page_idstringoptionalPage ID (optional — uses default if configured)
include_commentsbooleanoptionalInclude comments on the latest post (default true)
comments_limitintegeroptionalMax comments to return (default 25, max 100)
curl
curl "https://api.xylomcp.com/v1/pages/latest-post?page_id=112233445566778" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram

Instagram — List Accounts

GET/v1/instagram/accounts

List connected Instagram Business accounts. This endpoint does NOT require the x-ad-account header.

curl
curl https://api.xylomcp.com/v1/instagram/accounts \
  -H "x-api-key: xy_sk_your_key"
Response
{
  "data": [
    {
      "id": "17841400987654321",
      "username": "acme_store",
      "name": "Acme Store",
      "followers_count": 10000,
      "media_count": 250
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Instagram — Connected Accounts

GET/v1/instagram/connected-accounts

IG accounts specifically connected to the current ad account (i.e. usable for ad creation). This is different from /v1/instagram/accounts, which lists every IG account the user has access to across their Pages.

curl
curl https://api.xylomcp.com/v1/instagram/connected-accounts \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — Account Insights

GET/v1/instagram/:id/insights

Get Instagram account insights.

ParameterTypeRequiredDescription
metricstringoptionalComma-separated metrics (e.g. impressions, reach, follower_count)
periodstringoptionalday, week, or month
curl
curl "https://api.xylomcp.com/v1/instagram/17841400987654321/insights?metric=impressions,reach&period=day" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "metric": "impressions",
      "period": "day",
      "values": [
        { "value": 8540, "end_time": "2026-03-20T08:00:00+0000" },
        { "value": 9120, "end_time": "2026-03-21T08:00:00+0000" }
      ]
    },
    {
      "metric": "reach",
      "period": "day",
      "values": [
        { "value": 6230, "end_time": "2026-03-20T08:00:00+0000" },
        { "value": 6780, "end_time": "2026-03-21T08:00:00+0000" }
      ]
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Instagram — Publish Media

POST/v1/instagram/:igAccountId/publish

Publish a single IMAGE, VIDEO, REEL, or STORY to an Instagram Business account.

ParameterTypeRequiredDescription
typestringrequiredIMAGE, VIDEO, REEL, or STORY
image_urlstringoptionalPublic image URL (required for IMAGE)
video_urlstringoptionalPublic video URL (required for VIDEO, REEL)
captionstringoptionalCaption text (max 2200 chars, 30 hashtags)
alt_textstringoptionalAccessibility alt text (images only)
user_tagsobject[]optionalArray of user tag objects to tag accounts in the media
curl
curl -X POST https://api.xylomcp.com/v1/instagram/17841400987654321/publish \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "IMAGE",
    "image_url": "https://example.com/hero.jpg",
    "caption": "Spring drop just landed. #newarrivals"
  }'

Instagram — Publishing Limit

GET/v1/instagram/:igAccountId/publishing-limit

Check the account's current publishing quota. Instagram caps container-based publishing at 50 posts per rolling 24h.

curl
curl https://api.xylomcp.com/v1/instagram/17841400987654321/publishing-limit \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — List Media

GET/v1/instagram/:igAccountId/media

List recent media for an IG Business account.

ParameterTypeRequiredDescription
limitintegeroptionalMax results
curl
curl "https://api.xylomcp.com/v1/instagram/17841400987654321/media?limit=10" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — Get Media

GET/v1/instagram/media/:mediaId

Fetch a single media object by ID.

curl
curl https://api.xylomcp.com/v1/instagram/media/18023456789012345 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — Media Insights

GET/v1/instagram/media/:mediaId/insights

Per-post insights (impressions, reach, engagement, saves, etc.).

curl
curl https://api.xylomcp.com/v1/instagram/media/18023456789012345/insights \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — List Comments

GET/v1/instagram/media/:mediaId/comments

List comments on an IG media object.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25, max 100)
curl
curl https://api.xylomcp.com/v1/instagram/media/18023456789012345/comments \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — Reply to Comment

POST/v1/instagram/comments/:commentId/reply

Reply publicly to an IG comment.

ParameterTypeRequiredDescription
messagestringrequiredReply text
curl
curl -X POST https://api.xylomcp.com/v1/instagram/comments/17895555000/reply \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Thanks! DM us for details 🙌" }'

Instagram — Hide / Unhide Comment

PATCH/v1/instagram/comments/:commentId

Hide or unhide a comment. Hidden comments remain visible to the author and their friends but not to the wider audience.

ParameterTypeRequiredDescription
hidebooleanrequiredtrue to hide, false to unhide
curl
curl -X PATCH https://api.xylomcp.com/v1/instagram/comments/17895555000 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "hide": true }'

Instagram — Delete Comment

DELETE/v1/instagram/comments/:commentId

Permanently delete a comment.

curl
curl -X DELETE https://api.xylomcp.com/v1/instagram/comments/17895555000 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Instagram — Latest Post

GET/v1/instagram/latest-post

One-hop resolver: returns the most recent media object and (by default) its comments. Falls back to the ad account's configured default_instagram_id if ig_account_id is omitted.

ParameterTypeRequiredDescription
ig_account_idstringoptionalIG Business account ID (optional — uses default if configured)
include_commentsbooleanoptionalInclude comments on the latest post (default true)
comments_limitintegeroptionalMax comments (default 25, max 100)
curl
curl "https://api.xylomcp.com/v1/instagram/latest-post?ig_account_id=17841400987654321" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Leads

Leads — List Forms

GET/v1/leads/forms

List lead generation forms for connected Pages. Requires the x-ad-account header.

curl
curl https://api.xylomcp.com/v1/leads/forms?page_id=1234567890 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "1234567890",
      "name": "Summer Sale Lead Form",
      "status": "ACTIVE",
      "created_time": "2026-03-01T10:00:00+0000"
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Leads — Create Form

POST/v1/leads/forms

Create an instant form on a Facebook Page for lead ads. Use the returned id as creative.lead_gen_form_id when creating an ad in an OUTCOME_LEADS campaign whose ad set uses destination_type: ON_AD. Requires the x-ad-account header.

ParameterTypeRequiredDescription
page_idstringrequiredPage that owns the form and runs the lead ad
namestringrequiredForm name
questionsarrayrequired[{ type, key?, label?, options?, dependent_conditional_questions? }] — types include FULL_NAME, EMAIL, WORK_EMAIL, PHONE, PHONE_OTP, CUSTOM, DATE_TIME, STORE_LOOKUP
privacy_policyobjectoptional{ url, link_text } (link_text up to 70 characters)
thank_you_pageobjectoptional{ title, body?, button_type, button_text?, website_url?, business_phone_number? }
context_cardobjectoptionalIntro screen: { title?, style (LIST_STYLE | PARAGRAPH_STYLE), content[], button_text? }
custom_disclaimerobjectoptional{ title?, body: { text }, checkboxes: [{ text, is_required?, is_checked_by_default? }] }
is_optimized_for_qualitybooleanoptionalAdds a review-and-confirm step for higher-intent leads
is_phone_sms_verify_enabledbooleanoptionalRequire SMS verification of the phone number
block_display_for_non_targeted_viewerbooleanoptionalHide the form outside the ad (no organic leads)
tracking_parametersobjectoptionalKey/value pairs included with every lead
locale / follow_up_action_url / question_page_custom_headline / should_enforce_work_email / is_lead_capture_ai_agent_enabledvariousoptionalPassed to Meta as documented on the Page leadgen_forms edge
curl
curl -X POST https://api.xylomcp.com/v1/leads/forms \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": "1234567890", "name": "Quote request",
        "questions": [{ "type": "FULL_NAME" }, { "type": "EMAIL" }, { "type": "PHONE" }],
        "privacy_policy": { "url": "https://example.com/privacy", "link_text": "Privacy policy" } }'
Response
{
  "data": { "id": "1234567890123" },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Leads — Archive or Reactivate Form

PATCH/v1/leads/forms/:id

Meta forms cannot be deleted. ARCHIVED retires a form (an archived form can't be attached to an ad); ACTIVE restores it. Status is the only change this endpoint makes.

ParameterTypeRequiredDescription
page_idstringrequiredPage that owns the form
statusstringrequiredACTIVE or ARCHIVED
Response
{
  "data": { "id": "1234567890123", "name": "Quote request", "status": "ARCHIVED", "created_time": "2026-09-27T10:00:00+0000" },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Leads — Get Form Leads

GET/v1/leads/forms/:id/leads

Get lead submissions from a form. The form must belong to page_id, and the caller must have access to that Page; a form on another Page returns 404.

ParameterTypeRequiredDescription
page_idstringrequiredPage that owns the form
limitintegeroptionalMax results (1-100, default 25)
curl
curl "https://api.xylomcp.com/v1/leads/forms/1234567890/leads?page_id=1122334455&limit=10" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "9876543210",
      "created_time": "2026-03-20T09:15:00+0000",
      "fields": { "email": "user@example.com", "full_name": "Alex Smith" }
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Catalogs

Catalogs — List

GET/v1/catalogs

List product catalogs. Requires the x-ad-account header.

curl
curl https://api.xylomcp.com/v1/catalogs \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "1122334455",
      "name": "Main Product Catalog",
      "product_count": 250,
      "created_time": "2026-01-15T12:00:00+0000"
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Catalogs — List Products

GET/v1/catalogs/:id/products

List products in a catalog, or look up specific products by retailer_id — the merchant's feed/SKU ID, which is also what the insights product_id breakdown reports. Pull insights with breakdowns=product_id, then resolve the winning rows here to see which products drive spend and purchases.

ParameterTypeRequiredDescription
retailer_idsstringoptionalComma-separated retailer IDs (feed/SKU IDs) to look up, max 100. When set, limit defaults to 100.
limitintegeroptionalMax results (default 25)
afterstringoptionalPagination cursor from paging.next_cursor
curl
curl "https://api.xylomcp.com/v1/catalogs/1122334455/products?retailer_ids=45015356375236" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "prod_001",
      "retailer_id": "45015356375236",
      "name": "Classic T-Shirt",
      "price": "29.99",
      "currency": "USD",
      "availability": "in stock",
      "image_url": "https://example.com/tshirt.jpg"
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" },
  "paging": { "next_cursor": "def456", "has_more": true }
}

Google Ads

Google Ads endpoints require the x-google-customer-id header (your Google Ads customer ID) in addition to the standard x-api-key header. All endpoints are under the /v1/google/ prefix.

Campaigns

GET/v1/google/campaigns— List all campaigns
GET/v1/google/campaigns/:id— Get campaign details
POST/v1/google/campaigns— Create campaign (SEARCH, DISPLAY, SHOPPING, PERFORMANCE_MAX, VIDEO)
PATCH/v1/google/campaigns/:id— Update campaign
DELETE/v1/google/campaigns/:id— Delete campaign

Keywords

GET/v1/google/ad-groups/:ad_group_id/keywords— List keywords
POST/v1/google/ad-groups/:ad_group_id/keywords— Add keywords (bulk support via keywords array)
PATCH/v1/google/ad-groups/:ad_group_id/keywords/:id— Update keyword
DELETE/v1/google/ad-groups/:ad_group_id/keywords/:id— Remove keyword

Insights

GET/v1/google/insights— Performance metrics (level: campaign | ad_group | ad)

Audiences

GET/v1/google/audiences— List remarketing audiences
POST/v1/google/audiences— Create audience
DELETE/v1/google/audiences/:id— Delete audience

Conversions

GET/v1/google/conversion-actions— List conversion actions
POST/v1/google/conversion-actions— Create conversion action
POST/v1/google/conversions/upload— Upload offline click conversions

Assets

GET/v1/google/assets— List assets (sitelinks, callouts, structured snippets, images)
POST/v1/google/assets— Create asset
POST/v1/google/campaigns/:id/assets— Link asset to campaign

Search Terms

GET/v1/google/search-terms— Search term performance report

Negative Keywords

GET/v1/google/campaigns/:id/negative-keywords— List campaign negative keywords
POST/v1/google/campaigns/:id/negative-keywords— Add negative keywords
GET/v1/google/negative-keyword-lists— List shared negative keyword lists
POST/v1/google/negative-keyword-lists— Create shared list

Budgets

GET/v1/google/budgets— List budgets
POST/v1/google/budgets— Create budget
PATCH/v1/google/budgets/:id— Update budget

Asset Groups (Performance Max)

GET/v1/google/asset-groups— List asset groups
POST/v1/google/asset-groups— Create asset group for PMax campaign
POST/v1/google/asset-groups/:id/assets— Link asset to group

Experiments

GET/v1/google/experiments— List A/B experiments
POST/v1/google/experiments— Create experiment
POST/v1/google/experiments/:id/promote— Promote winning experiment
POST/v1/google/experiments/:id/end— End experiment

Recommendations & Reporting

GET/v1/google/recommendations— List optimization recommendations
POST/v1/google/recommendations/:id/apply— Apply recommendation
GET/v1/google/change-history— Account change history
GET/v1/google/quality-scores— Keyword quality scores

TikTok Ads

TikTok endpoints require the x-tiktok-advertiser-id header in addition to the standard x-api-key header. All endpoints are under the /v1/tiktok/ prefix.

Campaigns

GET/v1/tiktok/campaigns— List campaigns
POST/v1/tiktok/campaigns— Create campaign (objectives: REACH, TRAFFIC, VIDEO_VIEWS, CONVERSIONS, etc.)
PATCH/v1/tiktok/campaigns/:id— Update campaign
DELETE/v1/tiktok/campaigns/:id— Delete campaign

Ad Groups

GET/v1/tiktok/campaigns/:campaign_id/ad-groups— List ad groups
POST/v1/tiktok/campaigns/:campaign_id/ad-groups— Create ad group
PATCH/v1/tiktok/ad-groups/:id— Update ad group
DELETE/v1/tiktok/ad-groups/:id— Delete ad group

Ads

GET/v1/tiktok/ad-groups/:ad_group_id/ads— List ads
POST/v1/tiktok/ad-groups/:ad_group_id/ads— Create ad
PATCH/v1/tiktok/ads/:id— Update ad
DELETE/v1/tiktok/ads/:id— Delete ad

Insights

GET/v1/tiktok/insights— Performance metrics (level: campaign | ad_group | ad)

Audiences

GET/v1/tiktok/audiences— List audiences
POST/v1/tiktok/audiences— Create audience
DELETE/v1/tiktok/audiences/:id— Delete audience

TikTok Shop

TikTok Shop is Xylo's commerce integration for analytics, product listing optimization, creator discovery, affiliate collaborations, sample settings, and creator text messages. REST routes live under /v1/tiktok-shop/ and use the x-tiktok-shop-id header. AI agents use the same approved surface through the commerce dispatcher. Listing edits, collaboration invitations, and outbound messages require explicit confirmation.

Analytics

GET/v1/tiktok-shop/analytics/shop— Shop GMV, sales, orders, and trends
GET/v1/tiktok-shop/analytics/shop/products— Product performance
GET/v1/tiktok-shop/analytics/shop/skus— SKU performance
GET/v1/tiktok-shop/analytics/shop/videos— Video performance
GET/v1/tiktok-shop/analytics/shop/lives/overview— LIVE performance

Product listings

GET/v1/tiktok-shop/products— Search product listings
GET/v1/tiktok-shop/products/:id— Read one listing and its review versions
POST/v1/tiktok-shop/products/diagnose-optimize— Diagnose listing quality
POST/v1/tiktok-shop/products/:id/partial-edit— Submit a confirmed partial listing edit

Creator discovery

POST/v1/tiktok-shop/affiliate/creators/search— Search Creator Marketplace
GET/v1/tiktok-shop/affiliate/creators/:creatorOpenId/performance— Read Marketplace-wide trailing-30-day creator context

Affiliate collaborations

GET/v1/tiktok-shop/affiliate/open-plans— List open collaborations
GET/v1/tiktok-shop/affiliate/targeted-plans— List target collaborations
POST/v1/tiktok-shop/affiliate/target-collaborations— Create a confirmed target collaboration with sample settings

Creator messages

GET/v1/tiktok-shop/affiliate/conversations— List creator conversations
GET/v1/tiktok-shop/affiliate/conversations/:conversationId/messages— Read a conversation
POST/v1/tiktok-shop/affiliate/conversations/:conversationId/messages— Send a confirmed text message

X Ads

X Ads endpoints require the x-x-ads-account-id header in addition to x-api-key. The REST surface lives under /v1/x/. AI agents can access the same 111 operations through the x_ads MCP tool; see the complete generated route reference for every action and schema.

Campaigns & ad groups

GET/v1/x/campaigns— List campaigns
POST/v1/x/campaigns— Create a campaign
PATCH/v1/x/campaigns/:id— Update status, budget, dates, or campaign settings
POST/v1/x/campaigns/batch— Create or update campaigns in a batch
GET/v1/x/line-items— List ad groups
POST/v1/x/line-items— Create an ad group
PATCH/v1/x/line-items/:id— Update an ad group
POST/v1/x/line-items/batch— Create or update ad groups in a batch

Ads & creative

GET/v1/x/promoted-tweets— List promoted tweets
POST/v1/x/promoted-tweets— Create a promoted tweet
POST/v1/x/tweets— Create a nullcast tweet for advertising
POST/v1/x/cards— Create a unified website or app card
POST/v1/x/media/upload— Upload image or video media from a URL or base64 payload
GET/v1/x/media-library— List reusable account media

Targeting

GET/v1/x/targeting-options/:type— Search locations, interests, events, devices, conversations, and other targeting types
GET/v1/x/targeting-suggestions— Get account-specific targeting suggestions
POST/v1/x/audience-estimate— Estimate reach before launch
POST/v1/x/targeting-criteria— Add targeting criteria to an ad group
DELETE/v1/x/targeting-criteria/:id— Remove targeting criteria

Audiences

GET/v1/x/audiences— List custom audiences
POST/v1/x/audiences— Create a custom audience
POST/v1/x/audiences/:id/users— Add locally hashed CRM identifiers
GET/v1/x/do-not-reach-lists— List do-not-reach exclusions
POST/v1/x/do-not-reach-lists/:id/users— Add users to an exclusion list

Analytics

GET/v1/x/insights— Synchronous performance analytics
POST/v1/x/insights/jobs— Start a detailed asynchronous analytics job
GET/v1/x/insights/jobs— Read or download asynchronous analytics results
GET/v1/x/insights/active-entities— Find entities with delivery in a period
GET/v1/x/account/history— Read account change history

Measurement

GET/v1/x/tracking-tags— List website tracking tags
POST/v1/x/tracking-tags— Create a website tracking tag
POST/v1/x/web-conversions/:pixel_id— Send server-side web conversion events
POST/v1/x/conversion-event— Send a mobile conversion event
GET/v1/x/conversion-attribution— Read conversion-attribution settings

Catalogs & tests

GET/v1/x/product-catalogs— List product catalogs
POST/v1/x/product-catalogs— Create a product catalog
PUT/v1/x/product-catalogs/:id/products— Bulk upsert catalog products
POST/v1/x/product-catalogs/:id/scheduled-feeds— Create a scheduled catalog feed
GET/v1/x/ab-tests— List A/B tests
POST/v1/x/ab-tests— Create an A/B test

Klaviyo

Klaviyo is Xylo's owned email and SMS channel. Its endpoints require the x-klaviyo-account header in addition to x-api-key. The REST surface lives under /v1/klaviyo/. AI agents access the same surface through the klaviyo MCP tool; see the complete generated route reference for every action and schema. Campaign sends always require a recipient estimate and explicit confirmation.

Accounts

GET/v1/klaviyo/accounts— List connected Klaviyo accounts
GET/v1/klaviyo/account— Read live account name, timezone, currency, and contact details
GET/v1/klaviyo/account/context— Read the saved account brief and memory

Campaigns & sending

GET/v1/klaviyo/campaigns— List email or SMS campaigns
POST/v1/klaviyo/campaigns— Create a campaign draft
PATCH/v1/klaviyo/campaigns/:id— Update campaign details
POST/v1/klaviyo/campaigns/:id/clone— Clone a campaign as a draft
PATCH/v1/klaviyo/campaigns/messages/:message_id— Update an email or SMS message definition
POST/v1/klaviyo/campaigns/messages/:message_id/assign-template— Assign a template to a campaign message
POST/v1/klaviyo/campaigns/:id/estimate-recipients— Run a live recipient estimate
POST/v1/klaviyo/campaigns/:id/send— Send after the confirmation-gated preview
GET/v1/klaviyo/campaigns/:id/send— Check campaign send status

Flows

GET/v1/klaviyo/flows— List flows
GET/v1/klaviyo/flows/:id— Inspect one flow and its definition
GET/v1/klaviyo/flows/:id/actions— List flow actions
GET/v1/klaviyo/flow-actions/:id/messages— List messages for a flow action
PATCH/v1/klaviyo/flows/:id/status— Pause or activate an existing flow

Profiles & audiences

GET/v1/klaviyo/profiles— List or search profiles
POST/v1/klaviyo/profiles— Create a profile
PATCH/v1/klaviyo/profiles/:id— Update a profile
GET/v1/klaviyo/lists— List lists
POST/v1/klaviyo/lists— Create a list
GET/v1/klaviyo/segments— List segments
POST/v1/klaviyo/segments— Create a segment
POST/v1/klaviyo/profiles/subscribe— Subscribe profiles with confirmation for bulk changes
POST/v1/klaviyo/profiles/unsubscribe— Unsubscribe profiles with confirmation for bulk changes

Templates, offers & catalog

GET/v1/klaviyo/templates— Search or list templates
POST/v1/klaviyo/templates— Create an HTML template
POST/v1/klaviyo/templates/:id/render— Render a template for review
GET/v1/klaviyo/images— List reusable images
POST/v1/klaviyo/images— Upload an image from a URL
GET/v1/klaviyo/coupons— List coupons and coupon codes
POST/v1/klaviyo/coupons— Create a coupon
GET/v1/klaviyo/catalogs/items— List catalog items
GET/v1/klaviyo/tags— List tags

Events, metrics & reports

GET/v1/klaviyo/events— List customer events
GET/v1/klaviyo/metrics— List account metrics
POST/v1/klaviyo/metrics/aggregate— Aggregate a metric over a time window
POST/v1/klaviyo/reporting/campaigns— Campaign performance and attributed revenue
POST/v1/klaviyo/reporting/flows— Flow performance and attributed revenue
POST/v1/klaviyo/reporting/forms— Form performance
POST/v1/klaviyo/reporting/segments— Segment performance

Google Analytics (GA4)

Google Analytics is Xylo's site-measurement source: traffic, engagement, key events, and revenue as GA4 measures them on the website, plus Analytics configuration management (key events, custom definitions, audiences, Google Ads links, annotations). Reporting data itself is read-only. Its endpoints require the x-ga4-property-id header (the numeric property id) in addition to x-api-key; only GET /v1/ga4/properties works without it. The REST surface lives under /v1/ga4/. AI agents use the ga4 MCP tool; see the complete generated route reference for every action and schema. Report responses return flattened rows, report bodies accept snake_case or Google's camelCase, and reports cache for 15 minutes (?refresh=truebypasses) to protect GA4's shared per-property quota.

Properties & config

GET/v1/ga4/properties— List connected GA4 properties (no property header needed)
GET/v1/ga4/property— Property details: name, timezone, currency, data retention
GET/v1/ga4/data-streams— List web and app data streams with measurement ids
GET/v1/ga4/key-events— List key events (GA4 conversions)
GET/v1/ga4/custom-dimensions— List custom dimensions
GET/v1/ga4/custom-metrics— List custom metrics
GET/v1/ga4/audiences— List GA4 audiences
GET/v1/ga4/google-ads-links— List linked Google Ads accounts
GET/v1/ga4/annotations— List reporting data annotations

Configuration management

POST/v1/ga4/key-events— Register an event as a key event (conversion)
PATCH/v1/ga4/key-events/:id— Update a key event's counting method or default value
DELETE/v1/ga4/key-events/:id— Delete a key event (the event keeps firing; it stops counting as a conversion)
POST/v1/ga4/custom-dimensions— Create a custom dimension
PATCH/v1/ga4/custom-dimensions/:id— Update a custom dimension's display name or description
POST/v1/ga4/custom-dimensions/:id/archive— Archive a custom dimension (irreversible; the Admin API has no delete)
POST/v1/ga4/custom-metrics— Create a custom metric
PATCH/v1/ga4/custom-metrics/:id— Update a custom metric
POST/v1/ga4/custom-metrics/:id/archive— Archive a custom metric (irreversible)
POST/v1/ga4/audiences— Create an audience (flows to linked Google Ads for remarketing)
PATCH/v1/ga4/audiences/:id— Update an audience's name, description, or event trigger
POST/v1/ga4/audiences/:id/archive— Archive an audience (irreversible)
POST/v1/ga4/google-ads-links— Link a Google Ads account to the property
DELETE/v1/ga4/google-ads-links/:id— Unlink a Google Ads account
POST/v1/ga4/annotations— Create a reporting data annotation (dated note on a traffic change)
PATCH/v1/ga4/annotations/:id— Update an annotation
DELETE/v1/ga4/annotations/:id— Delete an annotation

Reports

GET/v1/ga4/metadata— Every dimension and metric valid for the property, including custom definitions
POST/v1/ga4/reports— Run a report: dimensions x metrics over date ranges, with filters, ordering, and cohorts
POST/v1/ga4/reports/batch— Run up to 5 reports in one call
POST/v1/ga4/reports/pivot— Run a pivot (cross-tab) report
POST/v1/ga4/reports/realtime— Realtime report over the last 30 minutes (never cached)
POST/v1/ga4/reports/funnel— Funnel report with step-by-step completion and abandonment
POST/v1/ga4/reports/compatibility— Check which dimensions and metrics can join the report being composed
GET/v1/ga4/insights— Normalized daily rows powering GA4 dashboard cards (account, channel, or source level)

AI Features

AI-powered campaign analysis and optimization. These endpoints analyze performance data and return actionable recommendations.

Campaign Audit

POST/v1/ai/audit— Score campaigns 0-100 with findings and recommendations. Body: campaign_id(s), date_from, date_to

Morpheus Structural Audit

POST/v1/ai/morpheus-audit

Deep structural audit of an entire ad account. Goes beyond per-campaign scoring to evaluate account organization, keyword hygiene, retargeting overlap, billing structure, and audience strategy. Returns an overall 0–100 score with findings grouped by category and severity. Use this for quarterly-review-level audits; use /v1/ai/audit for lightweight per-campaign scoring.

ParameterTypeRequiredDescription
platformstringrequiredmeta or google
date_fromstringoptionalISO date (defaults to 30 days ago)
date_tostringoptionalISO date (defaults to yesterday)
curl
curl -X POST https://api.xylomcp.com/v1/ai/morpheus-audit \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "platform": "meta" }'
Response
{
  "data": {
    "score": 72,
    "platform": "meta",
    "date_from": "2026-03-20",
    "date_to": "2026-04-19",
    "findings": [
      {
        "category": "account_organization",
        "severity": "high",
        "title": "Overlapping retargeting audiences across 3 ad sets",
        "detail": "...",
        "recommendation": "..."
      }
    ]
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}

Budget Optimization

POST/v1/ai/optimize-budget— AI budget allocation using ROAS (40%), CPA (30%), CTR (30%) scoring. Body: campaign_ids, total_budget, date_from, date_to

Report Generation

POST/v1/ai/report— Executive summary with key metrics, top/bottom performers, and recommendations. Body: campaign_ids, date_from, date_to

Cross-Platform Insights

GET/v1/insights/cross-platform— Unified metrics across Meta, Google, TikTok, and X with platform totals and grand totals. Query: platforms, date_from, date_to, level

Reporting Dashboards

Live, block-based reporting dashboards composed from a declarative spec. Available on Brand and Agency plans (not Free). Each dashboard is bound to a set of connected accounts, holds KPI, time-series, bar, donut, table, and text blocks on a 12-column grid, and can be shared as a public, logged-out, unindexed link. Org-level, not ad-account scoped: these endpoints take no x-ad-account header.

List / Get

GET/v1/dashboards— List the org's dashboards (id, title, bound accounts, share status). Does not include the widget spec.
GET/v1/dashboards/:id— Get one dashboard, including its full widget spec and bound accounts.

Metric Catalog

Grounds every widget in metrics the account actually has. Call this before binding any conversion metric, never guess an action name. Returns core metrics plus, per platform, Meta action types and custom conversions or Google conversion actions, along with the valid levels, breakdowns, and filters.

GET/v1/dashboards/metric-catalog— Query: platform (meta, google, or tiktok), account_id, refresh. Cached; a degraded upstream response is served but not cached.

Create / Update

POST/v1/dashboards— Body: title, accounts ([{ platform, account_id, name? }], each an active connected account of the org), spec (optional, omit to create empty).
PATCH/v1/dashboards/:id— Body: title?, accounts?, spec? (each replaces the existing value wholesale). The spec is validated server-side (@xylo/shared) on every write.
DELETE/v1/dashboards/:id— Permanently delete a dashboard.

Run Data

POST/v1/dashboards/:id/data— Execute the spec's widget queries and return live data. Body: widget_ids? (omit to run every widget). Ungated: free to call, only editing is plan-gated.

Share / Unshare

POST/v1/dashboards/:id/share— Publish to a public, unauthenticated share URL (https://xylomcp.com/share/:token). Re-sharing rotates the token; old links stop working.
DELETE/v1/dashboards/:id/share— Revoke the public share link. The dashboard stays accessible to the org.

Creative Intelligence

A rolling 90-day per-account creative library: every creative the ad account ran in the last 90 days, deduped, scored against the account's own history, and tagged by AI, with nightly Meta archives for ad comments and primary-text/headline performance. Creative Intelligence is a per-account add-on billed on top of a Brand or Agency plan: $25/month per ad account on Brand, $15/month per ad account on Agency. It is not available on Free. Enable it per account in the dashboard under Creatives. The library fills on the next nightly sync and tags land on the run after that, because sync and tagging are separate nightly phases, so expect a populated, tagged tab within two days of enabling rather than immediately.

Account-scoped: every endpoint below takes the platform's account header (x-ad-account for Meta, x-tiktok-advertiser-id for TikTok). Through MCP the same capability is six actions on existing dispatchers, not new tools: creative({action:"library"|"digest"|"comments"|"copy"|"assets"}) and update({resource:"creative_tags"}).

The Creative Store

The store holds a rolling 90 daysof creative-day history and nothing older; a longer account history is not backfilled, and the 90-day cap on a query window is that horizon rather than a page size. A nightly cron walks each enabled ad account and writes one row per deduped creative plus a row per creative per day. Deduplication is by the creative's own media, so the same video running in six ad sets is one library row with six sibling ads, not six rows. Metrics come from the ad account's own insights, and a creative's results are counted on the optimized event of the ad set that served it.

Two consequences worth knowing before you divide anything. Windows end yesterday, because that is the last day the nightly sync could have filled; including today would report a hole as a decline. And a creative that ran under two ad sets optimizing for two different events sums two different events, so its cost per result has no nameable unit and is suppressed rather than guessed. A result of null means unverifiable, never zero.

Scores are percentiles across the whole account, computed before any filter is applied, so "top decile hook rate" means top decile in this account rather than against a generic benchmark. A weekly pass runs deep creative analysis on a standing set: the account's winners plus up to five notable losers.

Taxonomy Tags

Tagging runs through the Gemini Batch API on a fixed two-layer vocabulary. The surface layer covers asset type, visual format, hook tactic, headline tactic, messaging angle, offer type, seasonality, and intended persona. The psych layer covers awareness level, Life Force 8, mindstate, regulatory focus, dominant emotion, point of view, product visibility, CTA clarity, and timing markers. Each tagged dimension carries a confidence value.

Human corrections always win. An override is stored separately from the model output and applied on read, so re-tagging can never clobber it, and clearing an override falls back to the AI value rather than deleting the dimension.

Library / Detail

GET/v1/creative-store/creatives— List the account's creatives. Query: date_from, date_to (default the 30 days ending yesterday, max 90-day span), status (active | paused | all), min_spend, tag_filters (JSON object of dimension to value), group_by (any taxonomy dimension), sort, dir, limit (default 50, max 200), offset, detail (lean | full).

group_by returns spend-weighted rollups per tag value instead of rows. Group on a categorical dimension (hook tactic, visual format, messaging angle, offer type, awareness level, Life Force 8, mindstate, and the like); the four timing dimensions hold raw seconds, so grouping on one produces a bucket per distinct value rather than a useful rollup. A creative on a multi-valued dimension counts in every group it belongs to, so group spend can exceed account spend; untagged creatives stay visible in an untagged bucket rather than vanishing from the report.

GET/v1/creative-store/creatives/:media_key— One creative in depth: day-by-day rows, window-summed quartile retention (null for a static image), the full tag set with overrides applied, the sibling ads with their statuses, and the stored deep analysis when one exists.
GET/v1/creative-store/creatives/:media_key/comments— Stored Facebook and Instagram ad comments and replies across every sibling ad using this creative, with verified advertiser replies, freshness, filters, and paging. Meta only.
GET/v1/creative-store/creatives/:media_key/copy— Stored daily primary-text and headline performance across every sibling ad using this creative. Identical wording is combined; query date_from/date_to within the rolling 90-day window. Meta only.
GET/v1/creative-store/creatives/:media_key/assets— Live per-asset and Advantage+ enhancement performance for one multi-asset creative. Meta only.
GET/v1/creative-store/teaser— The pre-purchase preview for an account whose plan supports Creative Intelligence but which has not enabled the add-on. Live 30-day pull folded to creative grain, no tags, no scores, nothing added to your library. Meta only.
curl
curl "https://api.xylomcp.com/v1/creative-store/creatives?group_by=hook_tactic&min_spend=100" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"

Gating is tri-state

  • Plan has no Creative Intelligence: 403 FEATURE_NOT_AVAILABLE.
  • Plan supports it, the account has not enabled it: reads return 200 { data: { available: false, reason: "ADDON_NOT_ENABLED", teaser_available } }. That is a successful call, not an error. An agent should offer to enable the add-on rather than retry.
  • Add-on enabled: data.available: true and the full payload.

Weekly Digest

GET/v1/creative-store/digest— One call, one week: period-over-period movers, top and bottom creatives on the optimized event, new launches, and fatigue candidates. Query: period_days (1 to 90, default 7); the period ends yesterday and is compared against the equal period before it.

Every list in the response is capped at 10 entries, which keeps the payload small enough for a scheduled agent run. The dashboard's Creatives tab header reads this exact endpoint, so an agent and the tab cannot tell two different stories about the same week. See the Claude automation guide for the Monday-report recipe.

Tag Overrides

PATCH/v1/creative-store/tags— Correct a creative's tags. Body: media_key, overrides (dimension to corrected value; null clears that dimension's override and falls back to the AI value). Writes only the override field, never the model's own tags.

Unlike the reads, the write is refused with 403 ADDON_NOT_ENABLED when the add-on is off, and it needs a write grant on the account. Values are validated against the same dimension table the tagging model answers into, so an editor cannot submit a value the API rejects.

curl
curl -X PATCH https://api.xylomcp.com/v1/creative-store/tags \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "media_key": "video:120345678901234", "overrides": { "hook_tactic": "bold_claim" } }'

Platform Coverage

Meta is deep and TikTok follows. Both platforms sync, tag, score, and support pausing or resuming an ad from the Creatives tab. The differences are real and worth stating rather than glossing:

CapabilityMetaTikTokX
Nightly creative syncYesYesNot covered
AI taxonomy taggingYesYes, except Spark adsNot covered
Account-relative scores and digestYesYesNot covered
Stored ad comments and copy performanceYesNoNot covered
Pre-purchase teaser previewYesNoNot covered
Pause / resume from the Creatives tabYes, each ad reported on its ownYes, but one failed ad can affect others in the same batchNot covered
Hook rate measured on3-second video plays2-second viewsNot covered

A TikTok Spark ad's creative lives in a TikTok post whose bytes Xylo cannot resolve, so a Spark ad is stored as its own row, is not deduped across ad groups, and carries no tags. Because Meta's hook rate counts 3-second plays and TikTok's counts 2-second views, the two numbers are not interchangeable and no report should present them as one metric. X Ads has no creative store, no tagging, and no digest.

Instagram Organic Posts

Organic Creative Intelligence is a separate per-account add-on on Brand and Agency, not available on Free. It applies the same nightly refresh, the same taxonomy tags and the same account-relative percentile scores to the brand's own unpaid Instagram feed posts and Reels. An Instagram account is its own connection in Xylo — connect one from your existing Meta connection and switch the add-on on in the dashboard under Organic Intel. No ad account is required, so a brand can use Xylo for organic alone. There is no spend, no ROAS and no optimized event here, so these numbers are never blended into paid reporting: organic is judged on attention earned, paid on what it drives. Scoped by x-instagram-account (the Instagram user id); Instagram only.

GET/v1/organic/posts— List the profile's posts. Query: date_from, date_to (default the 30 days ending yesterday), media_product_type (FEED | REELS), tag_filters (JSON object of dimension to value), group_by (any taxonomy dimension), sort, dir, limit (default 50, max 200), offset, detail (lean | full).
GET/v1/organic/posts/:media_id— One post in depth: full caption, the complete tag set with overrides applied, lifetime totals, window metrics, the ids of any ads boosting it, and days[] with the daily delta for each stored snapshot.
GET/v1/organic/digest— One call, one week: period totals, movers, top and bottom posts, new launches, and fatigue candidates. Query: period_days (default 7); the period ends yesterday and is compared against the equal period before it. Organic has no spend, so movers rank on reach.
PATCH/v1/organic/tags— Correct a post's tags. Body: media_id, overrides (dimension to corrected value; null clears that dimension's override). Writes only the override field, and never publishes anything to Instagram.

The daily series is built, not fetched

Instagram reports a post's lifetime totals only, so Xylo manufactures the daily series by differencing consecutive nightly snapshots. Three consequences worth stating to anyone reading these numbers: a post's first snapshot has delta: null because there is nothing to difference it against; there is no backfill, so a post published before the add-on was switched on has exactly one data point; and a post under about seven days old, or whose lifetime reach is still under the account's own reach floor, comes back maturing: true with scores: null, shown but deliberately unscored until it settles. A scores: null with maturing: false means something else again: the account has fewer than 10 matured posts, so percentiles are suppressed rather than presented as confident. Every rate is null on a zero denominator rather than a flattering zero.

Gating matches the paid store: no plan feature is 403 FEATURE_NOT_AVAILABLE, plan-but-no-add-on returns 200 { data: { available: false, reason: "ADDON_NOT_ENABLED" } } on the reads, and the tag write is refused with 403 ADDON_NOT_ENABLED. Through MCP the same capability is four more actions on the existing dispatchers, not new tools: creative({action:"organic_library"|"organic_post"|"organic_digest"}) and update({resource:"organic_tags"}).

Teams

Team management with role-based access control. Roles: owner, admin, member, viewer. Owners and admins manage everything, members are scoped to the ad accounts you grant them, and viewers read every account in the organization without being able to change anything.

Members

GET/v1/team/members— List team members with roles
POST/v1/team/invite— Invite user by email (admin/owner only)
PATCH/v1/team/members/:id— Update member role
DELETE/v1/team/members/:id— Remove member

Invites

GET/v1/team/invites— List pending invites
DELETE/v1/team/invites/:id— Cancel invite

Webhooks

Subscribe to campaign.status_changed, campaign.budget_updated, and live notification.created events. Max 20 subscriptions per organization. URLs are validated with SSRF protection. See the Claude notifications guide for signature verification and delivery recipes.

List Subscriptions

GET/v1/webhooks— List all webhook subscriptions
GET/v1/webhooks/:id— Get subscription details

Create Subscription

POST/v1/webhooks— Create subscription. Body: url, events array, optional secret
PATCH/v1/webhooks/:id— Update subscription
DELETE/v1/webhooks/:id— Delete subscription

Bulk Operations

Execute multiple operations in a single request or batch campaign actions.

POST/v1/bulk/operations— Execute multiple API operations sequentially. Body: operations array of {method, path, body}
POST/v1/bulk/pause-campaigns— Pause up to 100 campaigns at once
POST/v1/bulk/resume-campaigns— Resume up to 100 campaigns at once

Error Codes

All errors return an error object with code, message, and optional platform-specific error details (meta_error, google_error, or tiktok_error).

HTTPCodeDescription
401MISSING_API_KEYNo x-api-key header provided
401INVALID_API_KEYAPI key not found or revoked
400MISSING_AD_ACCOUNTNo x-ad-account header on an endpoint that requires it
403AD_ACCOUNT_NOT_CONNECTEDAd account not connected to this organization
403ACCOUNT_NOT_ACTIVEAd account is not activated on your plan
401TOKEN_EXPIREDMeta token expired, user must re-authorize via connect session
429RATE_LIMIT_EXCEEDEDToo many requests. Check X-RateLimit-* headers.
400VALIDATION_ERRORRequest body or query params failed validation
502META_API_ERRORMeta's API returned an error (includes meta_error object)
429META_RATE_LIMITEDMeta is rate-limiting the underlying ad account
502GOOGLE_API_ERRORGoogle Ads API returned an error (includes google_error object)
429GOOGLE_RATE_LIMITGoogle Ads rate limit exceeded
403GOOGLE_CUSTOMER_NOT_CONNECTEDGoogle customer ID not connected to organization
502TIKTOK_API_ERRORTikTok API returned an error (includes tiktok_error object)
429TIKTOK_RATE_LIMITTikTok rate limit exceeded
403TIKTOK_ADVERTISER_NOT_CONNECTEDTikTok advertiser not connected to organization
404NOT_FOUNDThe requested resource does not exist
500INTERNAL_ERRORSomething went wrong on Xylo's end
Example: META_API_ERROR with meta_error details
{
  "error": {
    "code": "META_API_ERROR",
    "message": "Meta rejected the request.",
    "meta_error": {
      "message": "(#100) Daily budget is too low.",
      "type": "OAuthException",
      "code": 100
    }
  }
}

Data Warehouse

Data warehousing is an optional add-on on the Brand and Agency plans, billed per account. An account with the add-on enabled gets a local Postgres mirror of its Meta ad state, async insights for heavy queries, and an ad integrity checker designed to run on a schedule. Every response that resolves from the local mirror includes a data_freshness field so agents can decide whether to trust the cached result or force a live pull with refresh=true.

Daily Scheduled Bulk Pulls

Once per day Xylo fetches the full state of each warehoused ad account — campaigns, ad sets, ads with creatives, and daily-granularity insights on a rolling 90-day window — and writes it to local snapshot tables. Read endpoints (list campaigns, list ads, read insights) answer from this snapshot when one is available, which makes warehoused reads dramatically faster and avoids Meta rate limits entirely. Insights for any date range within the 90-day window are aggregated from the daily rows at query time, so last_7d, last_14d, last_30d, last_60d, last_90d, yesterday, and explicit date_from/date_to all serve from the snapshot. Write endpoints always go straight to Meta.

The sync is incremental: each daily run only re-pulls the last 7 days of insights (to catch Meta's late-arriving conversion attribution) plus one older 30-day chunk if we haven't yet backfilled the full 90 days. A brand-new account reaches full 90-day depth after ~3 runs. Steady-state daily load is small enough to stay well under Meta's Business Use Case rate limits.

data_freshness Response Field

When an insights response is served from the local snapshot, the meta envelope includes a data_freshness field set to "local_snapshot" along with a snapshot_at timestamp. Live Meta calls (an account without the add-on, or a warehoused account with refresh=true) return "live".

Example: warehoused insights response
{
  "data": [ /* ... */ ],
  "meta": {
    "cached": false,
    "data_freshness": "local_snapshot",
    "snapshot_at": "2026-04-20T08:00:00Z",
    "meta_api_version": "v26.0"
  }
}

Async Insights Jobs

Very large insights queries (long date ranges, ad-level breakdowns across thousands of ads) will time out if run synchronously. Warehoused accounts get two MCP routes for these: insights({ channel: "meta", lens: "job", mode: "job_create" }) kicks off a background job and returns a job_id; mode: "job_check" polls that job and returns results once complete.

Synchronous insights endpoints auto-route to async when the estimated row count exceeds ~15,000, and refuse outright above ~5,000,000 rows. Agents should poll roughly every 5–15 seconds until status is "completed" or "failed".

POST/v1/insights/async/start— Start a background insights job. Returns { job_id, status, estimated_rows, estimated_seconds }
GET/v1/insights/async/:job_id— Poll job status. Running: { status, percent }. Complete: { status: 'complete', rows, actual_rows }.

Ad Integrity Checker

The insights({ channel: "meta", lens: "ad_integrity" }) MCP route (and its REST counterpart GET /v1/ai/integrity-check) scans every active ad on the account and flags anomalies that usually indicate a hijacked creative, a wrong Page selection, or a misconfigured landing page. It is designed to be run once a day on a schedule.

What it flags

  • Ads linking to a domain that is not the account's business website (and not on the approved list).
  • Ads running from an unknown Facebook Page or Instagram account.
  • Ads with no destination URL at all.
GET/v1/ai/integrity-check— Run the integrity scan. Returns { total_active_ads, anomalies_found, anomalies, reference }

Business Website & Approved Domains

Each connected account has a business website field, set in the dashboard. The integrity checker uses this as the reference domain; subdomains of the business website are automatically accepted (e.g. setting example.com also accepts shop.example.com).

When a new domain shows up on an ad, the checker flags it. If the domain is legitimate (a checkout partner, a tracking redirect, a co-branded landing page), open the account's settings page in the dashboard and add it to the Approved domains list. Future scans will stop flagging ads that link to approved domains.

Agent Playbook

A comprehensive guide for AI agents managing Meta (Facebook & Instagram), Google, TikTok, and X advertising, plus Google Analytics (GA4) measurement, through Xylo. The playbooks cover account discovery, daily monitoring routines, campaign management actions, and safety rules to follow.

For the complete playbook, see llms-full.txt or agents.txt. For the full list of 300+ MCP ad operations, see mcp-tools.md.

Overview

When an AI agent first connects to a Xylo-managed ad account, it should perform a structured discovery sequence to understand the account state, then establish daily monitoring routines. All campaign modifications follow a safe creation pattern: create in a PAUSED state, confirm with the user, then activate. The safety rules below must always be respected to prevent accidental budget overspend or destructive actions.

Account Discovery

When onboarding a new ad account, run the following 6-step discovery sequence to build a complete picture of the account:

Step 1: List connected ad accounts

GET /v1/accounts
curl -X GET https://api.xylomcp.com/v1/accounts \
  -H "x-api-key: YOUR_API_KEY"

Step 2: List active campaigns

GET /v1/campaigns
curl -X GET "https://api.xylomcp.com/v1/campaigns?status=ACTIVE" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

Step 3: Pull account-level insights

GET /v1/insights
curl -X GET "https://api.xylomcp.com/v1/insights?level=account&date_from=2026-03-18&date_to=2026-03-25" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

Step 4: List saved audiences

GET /v1/audiences
curl -X GET https://api.xylomcp.com/v1/audiences \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

Step 5: List pages and Instagram accounts

GET /v1/pages & GET /v1/instagram/accounts
curl -X GET https://api.xylomcp.com/v1/pages \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

curl -X GET https://api.xylomcp.com/v1/instagram/accounts \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

Step 6: List lead forms and catalogs

GET /v1/leads/forms & GET /v1/catalogs
curl -X GET https://api.xylomcp.com/v1/leads/forms \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

curl -X GET https://api.xylomcp.com/v1/catalogs \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

After discovery, save a memory structure like the following so the agent can reference it on subsequent interactions:

Agent Memory Structure (JSON)
{
  "account_id": "act_123456",
  "account_name": "My Business",
  "currency": "USD",
  "timezone": "America/New_York",
  "active_campaigns": 5,
  "total_daily_budget": 250.00,
  "pages": ["Page A (id: 111)", "Page B (id: 222)"],
  "instagram_accounts": ["@mybusiness (id: 333)"],
  "saved_audiences": 3,
  "lead_forms": 2,
  "catalogs": 1,
  "last_7d_spend": 1450.00,
  "last_7d_roas": 3.2,
  "discovery_date": "2026-03-25"
}

Daily Monitoring

Run this morning check sequence daily to surface anomalies and keep the user informed about account health.

Morning Check: Account Insights (last 24h)
curl -X GET "https://api.xylomcp.com/v1/insights?level=account&date_from=2026-03-24&date_to=2026-03-25" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"
Morning Check: Campaign-level Breakdown
curl -X GET "https://api.xylomcp.com/v1/insights?level=campaign&date_from=2026-03-24&date_to=2026-03-25" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"
Morning Check: Ad-set-level Breakdown
curl -X GET "https://api.xylomcp.com/v1/insights?level=adset&date_from=2026-03-24&date_to=2026-03-25" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456"

Compare results against the following alert thresholds:

MetricWarningCritical
Cost per conversion>25% above avg>50% above avg
ROAS<20% below target<50% below target
CTR<30% below avg<50% below avg
Frequency (prospecting)>2.5>3.5

Campaign Actions

Always follow the 4-step safe creation sequence when building new campaigns. Never create campaigns in an ACTIVE state directly.

Step 1: Create campaign in PAUSED state

POST /v1/campaigns
curl -X POST https://api.xylomcp.com/v1/campaigns \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring Sale - Conversions",
    "objective": "OUTCOME_SALES",
    "status": "PAUSED",
    "special_ad_categories": []
  }'

Step 2: Create ad set under the campaign

POST /v1/adsets
curl -X POST https://api.xylomcp.com/v1/adsets \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "CAMPAIGN_ID",
    "name": "Broad - 25-54 - Purchase",
    "daily_budget": 5000,
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "OFFSITE_CONVERSIONS",
    "targeting": {
      "age_min": 25,
      "age_max": 54,
      "geo_locations": { "countries": ["US"] }
    },
    "status": "PAUSED"
  }'

Step 3: Create ad within the ad set

POST /v1/ads
curl -X POST https://api.xylomcp.com/v1/ads \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456" \
  -H "Content-Type: application/json" \
  -d '{
    "adset_id": "ADSET_ID",
    "name": "Spring Sale - Image Ad",
    "creative_id": "CREATIVE_ID",
    "status": "PAUSED"
  }'

Step 4: Activate after user confirmation

PATCH /v1/campaigns/:id
curl -X PATCH https://api.xylomcp.com/v1/campaigns/CAMPAIGN_ID \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-ad-account: act_123456" \
  -H "Content-Type: application/json" \
  -d '{ "status": "ACTIVE" }'

Scaling guidance:When scaling budgets, increase by 20-30% at a time. Wait 48-72 hours between increases to allow Meta's learning phase to stabilize. Never increase a budget by more than 50% in a single change without explicit user approval.

Safety Rules

Agents must follow these 10 rules at all times when managing ad accounts through the Xylo API:

1.

Never set a campaign, ad set, or ad to ACTIVE without explicit user confirmation.

2.

Never increase a budget by more than 50% in a single change without warning the user of the risk.

3.

Always confirm with the user before deleting any campaign, ad set, ad, or audience.

4.

Special Ad Categories (housing, credit, employment, politics) have legal requirements — always ask the user before setting these.

5.

Alert the user if actual spend exceeds 125% of the intended daily budget (Meta can overspend by up to 25%).

6.

Do not modify campaigns during high-spend hours (typically 10 AM – 2 PM in the account timezone) unless urgent.

7.

When Meta returns an error, explain it in plain language and suggest a fix rather than retrying blindly.

8.

Respect the learning phase — avoid editing ad sets that are in the learning phase unless performance is critically poor.

9.

Handle ACCOUNT_NOT_ACTIVE errors gracefully by guiding the user to reconnect via a connect session.

10.

When filtering ad sets by campaign, apply client-side filtering if the API does not support server-side filtering for that parameter.