Xylo API Documentation
Base URL: https://api.xylomcp.com (production) or http://localhost:3001 (dev)
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.- 1Use the button above to open Claude's custom connector form with Xylo pre-filled.
- 2Review the name and URL, click "Add", then click "Connect" to authenticate through OAuth.
- 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.comOne 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/mcpOnce 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:
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
actionsarrays. Xylo flattens them to top-levelpurchases,leads,cost_per_purchase,cost_per_lead, androasfields. - Single-call ad creation — Pass
image_urlin 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 setstatus: "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 https://api.xylomcp.com/v1/accounts \ -H "x-api-key: xy_sk_your_key"
Step 2: Get campaigns for an account
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 "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.
# 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-api-key | string | required | Your Xylo API key (starts with xy_sk_) |
| x-ad-account | string | required | Meta ad account ID (e.g. act_123456789). NOT required for GET /v1/accounts and POST /v1/connect-sessions. |
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.
/v1/connect-sessions| Parameter | Type | Required | Description |
|---|---|---|---|
| redirect_url | string | optional | URL to redirect user to after OAuth (e.g. https://yourapp.com/callback) |
{
"redirect_url": "https://yourapp.com/callback"
}{
"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 Type | Cache TTL |
|---|---|
| Account structure (campaigns, ad sets, ads) | 5 minutes |
| Performance metrics (insights) | 15 minutes |
| Ad creatives | 30 minutes |
| Account info | 1 hour |
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.
| Plan | Rate Limit |
|---|---|
| Free | 60 rpm |
| Brand | 120 rpm |
| Agency | 600 rpm |
Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
Accounts
List Accounts
/v1/accountsReturns all connected Meta ad accounts. This endpoint does NOT require the x-ad-account header.
curl https://api.xylomcp.com/v1/accounts \ -H "x-api-key: xy_sk_your_key"
{
"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
/v1/accounts/:idSets 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| spend_cap | number | optional | New spending limit in dollars. 0 means no cap. |
| spend_cap_action | string | optional | "reset" restarts counted spend at zero without changing the limit; "delete" removes the limit entirely. |
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 }'{
"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
/v1/campaignsReturns all campaigns for the specified ad account. Supports filtering and pagination.
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | string | optional | Filter: ACTIVE, PAUSED, DELETED, ARCHIVED |
| limit | integer | optional | Max results (default 25, max 100) |
| after | string | optional | Pagination cursor from paging.next_cursor |
| refresh | boolean | optional | Set to true to bypass cache |
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"
{
"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
/v1/campaigns/:idReturns a single campaign by its Meta campaign ID. Same data shape as list.
curl https://api.xylomcp.com/v1/campaigns/120211234567890 \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/campaignsCreates a new campaign. Defaults to status: "PAUSED" as a safety measure. Budget values are in dollars.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Campaign name |
| objective | string | required | OUTCOME_SALES, OUTCOME_TRAFFIC, OUTCOME_AWARENESS, OUTCOME_LEADS, OUTCOME_ENGAGEMENT |
| status | string | optional | ACTIVE or PAUSED (default PAUSED) |
| daily_budget | number | optional | Daily budget in dollars (e.g. 50.00) |
| lifetime_budget | number | optional | Lifetime budget in dollars |
| start_time | string | optional | ISO 8601 start time |
| stop_time | string | optional | ISO 8601 stop time |
| special_ad_categories | array | optional | Special ad categories if applicable |
| buying_type | string | optional | Buying type (e.g. AUCTION) |
| spend_cap | number | optional | Lifetime campaign spend cap in dollars. Meta enforces an account-specific minimum (about $100). |
| is_adset_budget_sharing_enabled | boolean | optional | ABO only (no campaign-level budget). Requires bid_strategy; defaults to true when bid_strategy is set, otherwise false. |
| campaign_optimization_type | string | optional | NONE or ICO_ONLY |
| promoted_object | object | optional | What the campaign promotes, sent verbatim. { product_catalog_id } turns on Advantage+ catalog ads, including omnichannel catalog ads |
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
}'{
"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
/v1/campaigns/:idPartial update. Only include the fields you want to change.
| Parameter | Type | Required | Description |
|---|---|---|---|
| spend_cap | number | null | optional | Lifetime campaign spend cap in dollars. null or 0 removes an existing cap. |
| bid_strategy | string | optional | Campaign bid strategy |
| is_adset_budget_sharing_enabled | boolean | optional | ABO budget sharing across ad sets. Requires the campaign to also carry bid_strategy. |
| adset_budgets | array | optional | Per-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_amounts | object | optional | Per-ad-set manual bid overrides, dollars, keyed by adset_id: { "<adset_id>": 12.50 }. |
| campaign_optimization_type | string | optional | NONE or ICO_ONLY |
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 }'{
"data": { "success": true },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Campaigns — Delete
/v1/campaigns/:idSets the campaign status to DELETED in Meta. This is not a hard delete.
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
/v1/campaigns/:id/duplicateDuplicate 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| new_name | string | optional | Name for the new campaign (defaults to `{source name} (Copy)`) |
| include_children | boolean | optional | Deep duplicate — also copy all ad sets under this campaign |
| include_ads | boolean | optional | When include_children is true, also copy ads (default true) |
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 }'{
"data": { "campaign": { "from": "120211234567890", "to": "120219876543299" } },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Campaigns — Reach & Frequency (Reservation)
/v1/reach-frequency-predictions/v1/reach-frequency-predictions/:id/v1/reach-frequency-predictions/v1/reach-frequency-predictions/:id/reserve/v1/reach-frequency-predictions/:id/cancelA 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| budget | number | optional | Create: lifetime budget in dollars (Meta predicts reach). Send budget OR reach. Reserve: a curve point's budget, with reach and impressions. |
| reach | integer | optional | Create: target unique reach (Meta predicts the budget). Reserve: a curve point's reach. |
| start_time | string | required | Create: ISO 8601 start |
| stop_time | string | required | Create: ISO 8601 end, after 6AM on the last day in the account timezone |
| frequency_cap | integer | required | Create: most impressions per person |
| target_spec | object | required | Create: ad set targeting shape with exactly one country |
| destination_ids | string[] | optional | Create: Page id, plus the Instagram account id for Instagram placements |
| impressions | integer | optional | Reserve: a curve point's impressions, with budget and reach |
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"] }'{
"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
/v1/campaigns/:campaign_id/adsetsReturns all ad sets within a campaign.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (default 25, max 100) |
| after | string | optional | Pagination cursor from paging.next_cursor |
| refresh | boolean | optional | Set to true to bypass cache |
curl https://api.xylomcp.com/v1/campaigns/120211234567890/adsets \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/adsets/:idReturns a single ad set by ID. Same data shape as the list response.
curl https://api.xylomcp.com/v1/adsets/120215556667770 \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/campaigns/:campaign_id/adsetsCreates a new ad set under a campaign. Defaults to status: "PAUSED".
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Ad set name |
| billing_event | string | required | e.g. IMPRESSIONS |
| optimization_goal | string | required | e.g. VALUE, CONVERSIONS, LINK_CLICKS, REACH |
| targeting | object | required | Meta targeting spec (see Targeting Spec): geo_locations, placements, exclusions, locales, devices, flexible_spec, and more |
| status | string | optional | ACTIVE or PAUSED (default PAUSED) |
| daily_budget | number | optional | Daily budget in dollars |
| lifetime_budget | number | optional | Lifetime budget in dollars |
| bid_strategy | string | optional | Bid strategy |
| bid_amount | number | optional | Bid cap or cost cap in dollars |
| bid_constraints | object | optional | { roas_average_floor } with LOWEST_COST_WITH_MIN_ROAS: the minimum ROAS as a multiple (1.5 = 150%) |
| ad_set_goal | object | optional | Customer 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_cap | number | optional | Campaign-budget campaigns only: the most this ad set may spend per day / over its lifetime, in dollars |
| daily_min_spend_target / lifetime_min_spend_target | number | optional | Campaign-budget campaigns only: the least Meta aims to spend on this ad set, in dollars (best effort) |
| promoted_object | object | optional | What 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_type | string | optional | Where 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_time | string | optional | ISO 8601 start time |
| end_time | string | optional | ISO 8601 end time |
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
/v1/adsets/:idPartial 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ad_set_goal | object | optional | Customer Lifecycle Strategy (Sales ad sets only). Omitted exclusion lists keep their current value. |
| daily_spend_cap | number | null | optional | Campaign-budget campaigns only, dollars. null removes. |
| lifetime_spend_cap | number | null | optional | Campaign-budget campaigns only, dollars. null removes. |
| daily_min_spend_target | number | null | optional | Campaign-budget campaigns only, dollars (best effort). null removes. |
| lifetime_min_spend_target | number | null | optional | Campaign-budget campaigns only, dollars (best effort). null removes. |
| bid_amount | number | optional | Bid cap or cost cap in dollars |
| bid_constraints | object | optional | { roas_average_floor } with LOWEST_COST_WITH_MIN_ROAS. {} clears it. |
| optimization_goal | string | optional | e.g. VALUE, CONVERSIONS, LINK_CLICKS, REACH |
| billing_event | string | optional | e.g. IMPRESSIONS |
| promoted_object | object | optional | What the ad set optimizes for |
| pacing_type | string | optional | standard or no_pacing |
destination_type and is_dynamic_creative cannot change after creation.
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
/v1/adsets/:idDeletes the ad set in Meta.
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
/v1/adsets/:id/duplicateDuplicate 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| new_name | string | optional | Name for the new ad set (defaults to `{source name} (Copy)`) |
| include_children | boolean | optional | Deep duplicate — also copy ads in this ad set |
| include_ads | boolean | optional | When include_children is true, also copy ads (default true) |
| target_campaign_id | string | optional | Move the copy into this campaign instead of the source campaign |
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 }'{
"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
/v1/budget-schedulesList all schedules attached to a campaign or ad set.
| Parameter | Type | Required | Description |
|---|---|---|---|
| entity_id | string | required | Campaign or ad set ID |
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"
{
"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
/v1/budget-schedulesCreate a schedule. time_start must be before time_end.
| Parameter | Type | Required | Description |
|---|---|---|---|
| entity_id | string | required | Campaign or ad set ID to apply the schedule to |
| time_start | string | required | ISO 8601 start time of the boost window |
| time_end | string | required | ISO 8601 end time of the boost window |
| budget_value | number | required | Amount in cents (ABSOLUTE) or 100–1000 integer (MULTIPLIER, where 200 = 2.0x) |
| budget_value_type | string | required | ABSOLUTE or MULTIPLIER |
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"
}'{
"data": { "id": "99887766554433" },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Budget Schedules — Delete
/v1/budget-schedules/:idRemove a schedule. Does not affect the underlying campaign/ad-set budget.
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
/v1/ad-studiesList ad studies. Omits each study's cells — get one study for them.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (1-100, default 25) |
| after | string | optional | Pagination cursor |
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
/v1/ad-studies/:idGet one ad study, including each cell's treatment_percentage and assigned ids.
{
"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
/v1/ad-studiesCreate 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Study name |
| type | string | required | SPLIT_TEST or LIFT (LIFT needs Meta account-team setup) |
| start_time | string | required | ISO 8601, must be in the future |
| end_time | string | required | ISO 8601, must be after start_time |
| cells | array | required | Treatment groups; see above for the per-cell rules |
| description | string | optional | Study description |
| confidence_level | number | optional | Confidence level for the power calculation |
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"] }
]
}'{
"data": { "id": "800001", "cell_ids": ["900001", "900002"], "objective_ids": [] },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Ad Studies — End
/v1/ad-studies/:id/endCollapse 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 -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
/v1/adsets/:adset_id/adsReturns all ads within an ad set.
curl https://api.xylomcp.com/v1/adsets/120215556667770/ads \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/ads/:idReturns 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 https://api.xylomcp.com/v1/ads/120218889990001 \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/adsets/:adset_id/adsCreates 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Ad name |
| creative | object | required | Creative object with page_id, headline, body, link, call_to_action. Optionally image_url (Xylo auto-uploads). |
| status | string | optional | ACTIVE or PAUSED (default PAUSED) |
| conversion_domain | string | optional | Bare domain where conversions happen, e.g. example.com. Meta requires it on ads in campaigns that share data with a pixel |
| tracking_specs | object[] | optional | Meta tracking specs, e.g. [{"action.type":["offsite_conversion"],"fb_pixel":["<PIXEL_ID>"]}] |
| adlabels | object[] | optional | Up to 50 labels, each { id } or { name } (a new name creates the label) |
| engagement_audience | boolean | optional | true builds a custom audience of people who engage with this ad |
| allow_duplicate_name | boolean | optional | An 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_time | string | optional | Ad-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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| creative.threads_user_id | string | optional | Threads account the ad runs as on Threads (see Threads). Pair with instagram_actor_id |
| creative.wamo_whatsapp_identity_spec | object | optional | { 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_destinations | string[] | optional | Click-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_message | string | object | optional | Greeting, or Meta's VISUAL_EDITOR object with ice breakers, shown when the chat opens |
| creative.marketing_message_structured_spec | object | optional | { 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 -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
/v1/ads/:idPartial update. Only include the fields you want to change.
| Parameter | Type | Required | Description |
|---|---|---|---|
| name / status | string | optional | Rename, or ACTIVE / PAUSED / ARCHIVED / DELETED |
| conversion_domain | string | optional | Bare domain, e.g. example.com |
| tracking_specs | object[] | optional | Replaces the ad's tracking specs |
| adlabels | object[] | optional | Replaces the ad's labels ({ id } or { name }, up to 50) |
| engagement_audience | boolean | optional | true builds a custom audience of people who engage with this ad |
| creative.link / bodies / headlines | string / string[] / string[] | optional | On 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_run | boolean | optional | Preview 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 }] } | optional | Required 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_time | string | null | optional | Ad-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 -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
/v1/ads/:idDeletes the ad in Meta.
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.
/v1/threads/accounts/v1/threads/accounts/v1/threads/replies/v1/threads/replies/:id/hide/v1/threads/replies/:id/reply| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id / instagram_user_id | string | optional | Accounts: 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_id | string | required | Replies: 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 |
| limit | number | optional | Replies: 1–100 |
| hide | boolean | optional | Hide: true hides a reply, false shows it again |
| text | string | optional | Reply: a text reply as the brand |
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
/v1/insightsRetrieve 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
| level | string | required | account, campaign, adset, or ad |
| date_from | string | optional | Start date YYYY-MM-DD (default: 7 days ago) |
| date_to | string | optional | End date YYYY-MM-DD (default: yesterday) |
| campaign_id | string | optional | Filter by campaign ID |
| adset_id | string | optional | Filter by ad set ID |
| ad_id | string | optional | Filter by ad ID |
| time_granularity | string | optional | all (default, aggregated), daily, weekly, monthly |
| fields | string | optional | Comma-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 |
| breakdowns | string | optional | age, 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 |
| refresh | boolean | optional | Set to true to bypass cache |
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"
{
"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
/v1/audiences| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results |
| after | string | optional | Pagination cursor |
curl https://api.xylomcp.com/v1/audiences \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/audiences| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Audience name |
| type | string | required | Audience type (e.g. website) |
| rule | object | required | Rule definition: { url_contains, retention_days } |
| description | string | optional | Audience description |
| customer_file_source | string | optional | USER_PROVIDED_ONLY, PARTNER_PROVIDED_ONLY, or BOTH_USER_AND_PARTNER_PROVIDED (customer-list audiences) |
| retention_days | number | optional | 1–180 days |
| prefill | boolean | optional | Include people who already match the rule (default true) |
| pixel_id | string | optional | Pixel ID for a website audience rule |
| is_value_based | boolean | optional | Value-based customer list, uploaded with a LOOKALIKE_VALUE column |
| audience_labels | array | optional | CUSTOMERS, HIGH_VALUE_CUSTOMERS, LOW_VALUE_CUSTOMERS, AT_RISK, DISENGAGED, QUALIFIED_LEADS, DISQUALIFIED_LEADS, APP_USERS, TRIAL_USERS, ENGAGED_USERS — create-time only |
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
/v1/audiences/lookalike| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Lookalike audience name |
| source_audience_id | string | optional | Seed: a custom audience (use exactly one of source_audience_id, origin_ids, page_id) |
| origin_ids | array | optional | Seed: campaign or ad set converters |
| page_id | string | optional | Seed: a Page's fans |
| country | string | optional | Ignored. The ad set's locations decide where the lookalike delivers |
| ratio | number | required | Lookalike ratio 0.01 - 0.20 (0.01 = top 1%) |
| starting_ratio | number | optional | Builds a band with ratio, e.g. 0.01–0.02 |
| allow_international_seeds | boolean | optional | Allow seed members outside the account's country |
| is_financial_service | boolean | optional | Marks the lookalike as financial-service audience |
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
/v1/audiences/:idcurl -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
/v1/saved-audiences/v1/saved-audiences/:idSaved 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | List only: max results (1-100, default 25) |
| after | string | optional | List only: pagination cursor |
curl https://api.xylomcp.com/v1/saved-audiences/23851234567891 \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/ad-labels| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results |
| after | string | optional | Pagination cursor |
curl https://api.xylomcp.com/v1/ad-labels \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/ad-labels| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Label name |
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
/v1/ad-labels/:id| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | New label name |
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
/v1/ad-labels/:idcurl -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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| geo_locations / excluded_geo_locations | object | optional | countries, regions, cities (with radius + distance_unit), zips, location_types |
| locales | number[] | optional | Language ids (e.g. 5 = German) |
| age_min / age_max / age_range / genders | number / number[] | optional | Demographics; genders 1 = male, 2 = female |
| publisher_platforms / device_platforms | string[] | optional | facebook, 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_positions | string[] | optional | Manual positions, e.g. feed, stream, story, reels, threads_stream, status |
| placements | "automatic" | optional | Clears every placement key (Advantage+ placements) |
| flexible_spec / exclusions | object[] / object | optional | Detailed targeting groups (AND between groups, OR inside) and detailed exclusions |
| interests / behaviors | object[] | optional | Shortcuts: join the first flexible_spec group on writes; list every group on reads |
| custom_audiences / excluded_custom_audiences | object[] | optional | [{ id }] |
| user_os / user_device / excluded_user_device / wireless_carrier | string[] | optional | Device targeting |
| targeting_automation | object | optional | { advantage_audience: 0 | 1, individual_setting: { age, gender, geo } } |
| brand_safety_content_filter_levels / excluded_publisher_categories / excluded_publisher_list_ids | string[] | optional | Inventory 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 -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 Interests
/v1/targeting/search| Parameter | Type | Required | Description |
|---|---|---|---|
| type | string | required | interests, behaviors, or demographics |
| query | string | required | Search string (e.g. 'yoga') |
curl "https://api.xylomcp.com/v1/targeting/search?type=interests&query=yoga" \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"data": [
{
"id": "6003107904431",
"name": "Yoga",
"audience_size": 150000000
}
],
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Targeting — Search Locations
/v1/targeting/locations| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | required | Search string (e.g. 'New York') |
| type | string | optional | city, country, region, zip, or geo_market |
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"
{
"data": [
{
"name": "New York",
"audience_size": 0
}
],
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Creatives
/v1/creatives/uploadUpload 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| image_url | string | optional | URL of image to download and upload to Meta |
| image_data | string | optional | Base64-encoded image data |
| filename | string | optional | Optional filename for the image |
Provide either image_url or image_data, not both.
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" }'{
"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.
/v1/creatives/upload-videoUpload 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| video_url | string | optional | Public URL of the video to upload |
| video_data | string | optional | Base64-encoded video data |
| source_instagram_media_id | string | optional | Reel audio swap: the Instagram Reel media id to copy (instead of video_url or video_data) |
| audio_asset_id | string | optional | Reel audio swap: Sound Collection track id from GET /v1/creatives/videos/replacement-audio (required with source_instagram_media_id) |
| name | string | optional | Reel audio swap: name of the new video in the library |
/v1/creatives/videos/replacement-audioRoyalty-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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ig_media_id | string | required | The Instagram Reel media id |
| search | string | optional | Keyword search across the Sound Collection (1-120 characters) |
| auto | boolean | optional | true returns only Meta's best-match track for the Reel (not with search) |
| ig_user_id | string | optional | Instagram account that posted the Reel; needed when the token covers several Instagram accounts |
/v1/creatives/videos/:video_idRemove a video, including a Reel audio-swap output, from the ad account's video library.
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
/v1/media/uploadStage 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
| file_data | string | required | Base64-encoded file contents |
| filename | string | required | Original filename (used for grouping) |
| mime_type | string | required | image/jpeg, image/jpg, image/png, video/mp4, or video/quicktime |
| ad_account_id | string | required | Ad account this asset will be finalized to |
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"
}'{
"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
/v1/media/finalizePush 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ad_account_id | string | required | Ad account to finalize to |
| asset_ids | string[] | optional | Specific asset IDs to finalize (default: all unfinalized for this account) |
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..."]
}'{
"data": {
"finalized": 1,
"failed": 0,
"total": 1,
"results": [
{
"id": "asset_01HX9...",
"meta_ref": "a1b2c3d4e5f6...",
"status": "success"
}
]
}
}Media — List
/v1/mediaList staged and finalized assets for an ad account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ad_account_id | string | required | Ad account ID |
| group_id | string | optional | Filter to a single media group |
curl "https://api.xylomcp.com/v1/media?ad_account_id=act_123456789" \ -H "x-api-key: xy_sk_your_key"
Media — Preview
/v1/media/preview/:idPublic 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 https://api.xylomcp.com/v1/media/preview/asset_01HX9...
Media — Rename Group
/v1/media/groups/:groupIdRename every asset in a media group.
| Parameter | Type | Required | Description |
|---|---|---|---|
| group_name | string | required | New name for the group |
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
/v1/media/groups/:groupId/remove/:assetIdDetach a single asset from its group (sets group_id and group_name to null).
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
/v1/media/groups/mergeMerge multiple assets into a single group. Useful when auto-grouping puts related creatives in separate groups.
| Parameter | Type | Required | Description |
|---|---|---|---|
| asset_ids | string[] | required | Asset IDs to merge (minimum 2) |
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..."] }'{
"data": {
"group_id": "grp_new...",
"group_name": "hero",
"asset_ids": ["asset_01...", "asset_02...", "asset_03..."]
}
}Media — Delete
/v1/media/:idDelete a media asset record and its blob.
curl -X DELETE https://api.xylomcp.com/v1/media/asset_01HX9... \ -H "x-api-key: xy_sk_your_key"
Pages
Pages — List
/v1/pagesList Facebook Pages the user manages. This endpoint does NOT require the x-ad-account header.
curl https://api.xylomcp.com/v1/pages \ -H "x-api-key: xy_sk_your_key"
{
"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
/v1/pages/:id/postsList recent posts for a Page. Each post includes engagement counts (likes, reactions, comments, shares).
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (default 25, max 100) |
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"
{
"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
/v1/pages/:id/insightsGet Page performance insights.
| Parameter | Type | Required | Description |
|---|---|---|---|
| metric | string | optional | Comma-separated metrics (e.g. page_impressions, page_engaged_users) |
| period | string | optional | day, week, or month |
| date_from | string | optional | Start date YYYY-MM-DD |
| date_to | string | optional | End date YYYY-MM-DD |
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"
{
"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
/v1/pages/:pageId/postsPublish a post to a Page. At least one of message, link, or image_url is required.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | optional | Post text |
| link | string | optional | URL to share |
| image_url | string | optional | Public image URL to attach |
| published | boolean | optional | If false, creates an unpublished draft |
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"
}'{
"data": { "id": "112233445566778_999888777" },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Pages — Schedule Post
/v1/pages/:pageId/posts/scheduleSchedule a post for future publishing. scheduled_publish_time must be at least 10 minutes in the future and within 30 days.
| Parameter | Type | Required | Description |
|---|---|---|---|
| scheduled_publish_time | string | required | ISO 8601 — between now+10min and now+30d |
| message | string | optional | Post text (one of message/link/image_url required) |
| link | string | optional | URL to share |
| image_url | string | optional | Public image URL to attach |
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
/v1/pages/:pageId/scheduled-postsList posts queued for future publishing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (default 25, max 100) |
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
/v1/pages/posts/:postIdEdit the message on an existing post. page_id must be provided in the body so Xylo can resolve the correct page token.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | required | New post text |
| page_id | string | required | ID of the Page that owns the post |
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
/v1/pages/posts/:postIdDelete a post. Pass page_id in the body.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | required | ID of the Page that owns the post |
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
/v1/pages/posts/:postId/commentsList comments on a Page post.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | required | ID of the Page that owns the post (query param) |
| limit | integer | optional | Max results (default 25, max 100) |
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
/v1/pages/comments/:commentId/replyPost a public reply to a comment.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | required | Reply text |
| page_id | string | required | ID of the Page that owns the comment |
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
/v1/pages/comments/:commentIdDelete a comment.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | required | ID of the Page that owns the comment |
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
/v1/pages/latest-postOne-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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | optional | Page ID (optional — uses default if configured) |
| include_comments | boolean | optional | Include comments on the latest post (default true) |
| comments_limit | integer | optional | Max comments to return (default 25, max 100) |
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 — List Accounts
/v1/instagram/accountsList connected Instagram Business accounts. This endpoint does NOT require the x-ad-account header.
curl https://api.xylomcp.com/v1/instagram/accounts \ -H "x-api-key: xy_sk_your_key"
{
"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
/v1/instagram/connected-accountsIG 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 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
/v1/instagram/:id/insightsGet Instagram account insights.
| Parameter | Type | Required | Description |
|---|---|---|---|
| metric | string | optional | Comma-separated metrics (e.g. impressions, reach, follower_count) |
| period | string | optional | day, week, or month |
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"
{
"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
/v1/instagram/:igAccountId/publishPublish a single IMAGE, VIDEO, REEL, or STORY to an Instagram Business account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| type | string | required | IMAGE, VIDEO, REEL, or STORY |
| image_url | string | optional | Public image URL (required for IMAGE) |
| video_url | string | optional | Public video URL (required for VIDEO, REEL) |
| caption | string | optional | Caption text (max 2200 chars, 30 hashtags) |
| alt_text | string | optional | Accessibility alt text (images only) |
| user_tags | object[] | optional | Array of user tag objects to tag accounts in the media |
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 — Publish Carousel
/v1/instagram/:igAccountId/publish/carouselPublish a multi-item carousel (2–10 items). Each item is either an image or video.
| Parameter | Type | Required | Description |
|---|---|---|---|
| items | object[] | required | Array of 2–10 items; each must have image_url OR video_url |
| caption | string | optional | Caption for the carousel |
curl -X POST https://api.xylomcp.com/v1/instagram/17841400987654321/publish/carousel \
-H "x-api-key: xy_sk_your_key" \
-H "x-ad-account: act_123456789" \
-H "Content-Type: application/json" \
-d '{
"caption": "Our top 3 looks this week",
"items": [
{ "image_url": "https://example.com/1.jpg" },
{ "image_url": "https://example.com/2.jpg" },
{ "image_url": "https://example.com/3.jpg" }
]
}'Instagram — Publishing Limit
/v1/instagram/:igAccountId/publishing-limitCheck the account's current publishing quota. Instagram caps container-based publishing at 50 posts per rolling 24h.
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
/v1/instagram/:igAccountId/mediaList recent media for an IG Business account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results |
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
/v1/instagram/media/:mediaIdFetch a single media object by ID.
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
/v1/instagram/media/:mediaId/insightsPer-post insights (impressions, reach, engagement, saves, etc.).
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
/v1/instagram/media/:mediaId/commentsList comments on an IG media object.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (default 25, max 100) |
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
/v1/instagram/comments/:commentId/replyReply publicly to an IG comment.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | required | Reply text |
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
/v1/instagram/comments/:commentIdHide or unhide a comment. Hidden comments remain visible to the author and their friends but not to the wider audience.
| Parameter | Type | Required | Description |
|---|---|---|---|
| hide | boolean | required | true to hide, false to unhide |
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
/v1/instagram/comments/:commentIdPermanently delete a comment.
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
/v1/instagram/latest-postOne-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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ig_account_id | string | optional | IG Business account ID (optional — uses default if configured) |
| include_comments | boolean | optional | Include comments on the latest post (default true) |
| comments_limit | integer | optional | Max comments (default 25, max 100) |
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
/v1/leads/formsList lead generation forms for connected Pages. Requires the x-ad-account header.
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"
{
"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
/v1/leads/formsCreate 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | required | Page that owns the form and runs the lead ad |
| name | string | required | Form name |
| questions | array | required | [{ type, key?, label?, options?, dependent_conditional_questions? }] — types include FULL_NAME, EMAIL, WORK_EMAIL, PHONE, PHONE_OTP, CUSTOM, DATE_TIME, STORE_LOOKUP |
| privacy_policy | object | optional | { url, link_text } (link_text up to 70 characters) |
| thank_you_page | object | optional | { title, body?, button_type, button_text?, website_url?, business_phone_number? } |
| context_card | object | optional | Intro screen: { title?, style (LIST_STYLE | PARAGRAPH_STYLE), content[], button_text? } |
| custom_disclaimer | object | optional | { title?, body: { text }, checkboxes: [{ text, is_required?, is_checked_by_default? }] } |
| is_optimized_for_quality | boolean | optional | Adds a review-and-confirm step for higher-intent leads |
| is_phone_sms_verify_enabled | boolean | optional | Require SMS verification of the phone number |
| block_display_for_non_targeted_viewer | boolean | optional | Hide the form outside the ad (no organic leads) |
| tracking_parameters | object | optional | Key/value pairs included with every lead |
| locale / follow_up_action_url / question_page_custom_headline / should_enforce_work_email / is_lead_capture_ai_agent_enabled | various | optional | Passed to Meta as documented on the Page leadgen_forms edge |
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" } }'{
"data": { "id": "1234567890123" },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v26.0" }
}Leads — Archive or Reactivate Form
/v1/leads/forms/:idMeta 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | required | Page that owns the form |
| status | string | required | ACTIVE or ARCHIVED |
{
"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
/v1/leads/forms/:id/leadsGet 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| page_id | string | required | Page that owns the form |
| limit | integer | optional | Max results (1-100, default 25) |
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"
{
"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
/v1/catalogsList product catalogs. Requires the x-ad-account header.
curl https://api.xylomcp.com/v1/catalogs \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/catalogs/:id/productsList 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| retailer_ids | string | optional | Comma-separated retailer IDs (feed/SKU IDs) to look up, max 100. When set, limit defaults to 100. |
| limit | integer | optional | Max results (default 25) |
| after | string | optional | Pagination cursor from paging.next_cursor |
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"
{
"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
/v1/google/campaigns— List all campaigns/v1/google/campaigns/:id— Get campaign details/v1/google/campaigns— Create campaign (SEARCH, DISPLAY, SHOPPING, PERFORMANCE_MAX, VIDEO)/v1/google/campaigns/:id— Update campaign/v1/google/campaigns/:id— Delete campaignAd Groups
/v1/google/campaigns/:campaign_id/ad-groups— List ad groups in campaign/v1/google/ad-groups/:id— Get ad group details/v1/google/campaigns/:campaign_id/ad-groups— Create ad group/v1/google/ad-groups/:id— Update ad group/v1/google/ad-groups/:id— Delete ad groupAds
/v1/google/ad-groups/:ad_group_id/ads— List ads in ad group/v1/google/ad-groups/:ad_group_id/ads— Create responsive search/display ad/v1/google/ad-groups/:ad_group_id/ads/:id— Update ad/v1/google/ad-groups/:ad_group_id/ads/:id— Delete adKeywords
/v1/google/ad-groups/:ad_group_id/keywords— List keywords/v1/google/ad-groups/:ad_group_id/keywords— Add keywords (bulk support via keywords array)/v1/google/ad-groups/:ad_group_id/keywords/:id— Update keyword/v1/google/ad-groups/:ad_group_id/keywords/:id— Remove keywordInsights
/v1/google/insights— Performance metrics (level: campaign | ad_group | ad)Audiences
/v1/google/audiences— List remarketing audiences/v1/google/audiences— Create audience/v1/google/audiences/:id— Delete audienceConversions
/v1/google/conversion-actions— List conversion actions/v1/google/conversion-actions— Create conversion action/v1/google/conversions/upload— Upload offline click conversionsAssets
/v1/google/assets— List assets (sitelinks, callouts, structured snippets, images)/v1/google/assets— Create asset/v1/google/campaigns/:id/assets— Link asset to campaignSearch Terms
/v1/google/search-terms— Search term performance reportNegative Keywords
/v1/google/campaigns/:id/negative-keywords— List campaign negative keywords/v1/google/campaigns/:id/negative-keywords— Add negative keywords/v1/google/negative-keyword-lists— List shared negative keyword lists/v1/google/negative-keyword-lists— Create shared listBudgets
/v1/google/budgets— List budgets/v1/google/budgets— Create budget/v1/google/budgets/:id— Update budgetAsset Groups (Performance Max)
/v1/google/asset-groups— List asset groups/v1/google/asset-groups— Create asset group for PMax campaign/v1/google/asset-groups/:id/assets— Link asset to groupExperiments
/v1/google/experiments— List A/B experiments/v1/google/experiments— Create experiment/v1/google/experiments/:id/promote— Promote winning experiment/v1/google/experiments/:id/end— End experimentRecommendations & Reporting
/v1/google/recommendations— List optimization recommendations/v1/google/recommendations/:id/apply— Apply recommendation/v1/google/change-history— Account change history/v1/google/quality-scores— Keyword quality scoresTikTok 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
/v1/tiktok/campaigns— List campaigns/v1/tiktok/campaigns— Create campaign (objectives: REACH, TRAFFIC, VIDEO_VIEWS, CONVERSIONS, etc.)/v1/tiktok/campaigns/:id— Update campaign/v1/tiktok/campaigns/:id— Delete campaignAd Groups
/v1/tiktok/campaigns/:campaign_id/ad-groups— List ad groups/v1/tiktok/campaigns/:campaign_id/ad-groups— Create ad group/v1/tiktok/ad-groups/:id— Update ad group/v1/tiktok/ad-groups/:id— Delete ad groupAds
/v1/tiktok/ad-groups/:ad_group_id/ads— List ads/v1/tiktok/ad-groups/:ad_group_id/ads— Create ad/v1/tiktok/ads/:id— Update ad/v1/tiktok/ads/:id— Delete adInsights
/v1/tiktok/insights— Performance metrics (level: campaign | ad_group | ad)Audiences
/v1/tiktok/audiences— List audiences/v1/tiktok/audiences— Create audience/v1/tiktok/audiences/:id— Delete audienceTikTok 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
/v1/tiktok-shop/analytics/shop— Shop GMV, sales, orders, and trends/v1/tiktok-shop/analytics/shop/products— Product performance/v1/tiktok-shop/analytics/shop/skus— SKU performance/v1/tiktok-shop/analytics/shop/videos— Video performance/v1/tiktok-shop/analytics/shop/lives/overview— LIVE performanceProduct listings
/v1/tiktok-shop/products— Search product listings/v1/tiktok-shop/products/:id— Read one listing and its review versions/v1/tiktok-shop/products/diagnose-optimize— Diagnose listing quality/v1/tiktok-shop/products/:id/partial-edit— Submit a confirmed partial listing editCreator discovery
/v1/tiktok-shop/affiliate/creators/search— Search Creator Marketplace/v1/tiktok-shop/affiliate/creators/:creatorOpenId/performance— Read Marketplace-wide trailing-30-day creator contextAffiliate collaborations
/v1/tiktok-shop/affiliate/open-plans— List open collaborations/v1/tiktok-shop/affiliate/targeted-plans— List target collaborations/v1/tiktok-shop/affiliate/target-collaborations— Create a confirmed target collaboration with sample settingsCreator messages
/v1/tiktok-shop/affiliate/conversations— List creator conversations/v1/tiktok-shop/affiliate/conversations/:conversationId/messages— Read a conversation/v1/tiktok-shop/affiliate/conversations/:conversationId/messages— Send a confirmed text messageX 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
/v1/x/campaigns— List campaigns/v1/x/campaigns— Create a campaign/v1/x/campaigns/:id— Update status, budget, dates, or campaign settings/v1/x/campaigns/batch— Create or update campaigns in a batch/v1/x/line-items— List ad groups/v1/x/line-items— Create an ad group/v1/x/line-items/:id— Update an ad group/v1/x/line-items/batch— Create or update ad groups in a batchAds & creative
/v1/x/promoted-tweets— List promoted tweets/v1/x/promoted-tweets— Create a promoted tweet/v1/x/tweets— Create a nullcast tweet for advertising/v1/x/cards— Create a unified website or app card/v1/x/media/upload— Upload image or video media from a URL or base64 payload/v1/x/media-library— List reusable account mediaTargeting
/v1/x/targeting-options/:type— Search locations, interests, events, devices, conversations, and other targeting types/v1/x/targeting-suggestions— Get account-specific targeting suggestions/v1/x/audience-estimate— Estimate reach before launch/v1/x/targeting-criteria— Add targeting criteria to an ad group/v1/x/targeting-criteria/:id— Remove targeting criteriaAudiences
/v1/x/audiences— List custom audiences/v1/x/audiences— Create a custom audience/v1/x/audiences/:id/users— Add locally hashed CRM identifiers/v1/x/do-not-reach-lists— List do-not-reach exclusions/v1/x/do-not-reach-lists/:id/users— Add users to an exclusion listAnalytics
/v1/x/insights— Synchronous performance analytics/v1/x/insights/jobs— Start a detailed asynchronous analytics job/v1/x/insights/jobs— Read or download asynchronous analytics results/v1/x/insights/active-entities— Find entities with delivery in a period/v1/x/account/history— Read account change historyMeasurement
/v1/x/tracking-tags— List website tracking tags/v1/x/tracking-tags— Create a website tracking tag/v1/x/web-conversions/:pixel_id— Send server-side web conversion events/v1/x/conversion-event— Send a mobile conversion event/v1/x/conversion-attribution— Read conversion-attribution settingsCatalogs & tests
/v1/x/product-catalogs— List product catalogs/v1/x/product-catalogs— Create a product catalog/v1/x/product-catalogs/:id/products— Bulk upsert catalog products/v1/x/product-catalogs/:id/scheduled-feeds— Create a scheduled catalog feed/v1/x/ab-tests— List A/B tests/v1/x/ab-tests— Create an A/B testKlaviyo
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
/v1/klaviyo/accounts— List connected Klaviyo accounts/v1/klaviyo/account— Read live account name, timezone, currency, and contact details/v1/klaviyo/account/context— Read the saved account brief and memoryCampaigns & sending
/v1/klaviyo/campaigns— List email or SMS campaigns/v1/klaviyo/campaigns— Create a campaign draft/v1/klaviyo/campaigns/:id— Update campaign details/v1/klaviyo/campaigns/:id/clone— Clone a campaign as a draft/v1/klaviyo/campaigns/messages/:message_id— Update an email or SMS message definition/v1/klaviyo/campaigns/messages/:message_id/assign-template— Assign a template to a campaign message/v1/klaviyo/campaigns/:id/estimate-recipients— Run a live recipient estimate/v1/klaviyo/campaigns/:id/send— Send after the confirmation-gated preview/v1/klaviyo/campaigns/:id/send— Check campaign send statusFlows
/v1/klaviyo/flows— List flows/v1/klaviyo/flows/:id— Inspect one flow and its definition/v1/klaviyo/flows/:id/actions— List flow actions/v1/klaviyo/flow-actions/:id/messages— List messages for a flow action/v1/klaviyo/flows/:id/status— Pause or activate an existing flowProfiles & audiences
/v1/klaviyo/profiles— List or search profiles/v1/klaviyo/profiles— Create a profile/v1/klaviyo/profiles/:id— Update a profile/v1/klaviyo/lists— List lists/v1/klaviyo/lists— Create a list/v1/klaviyo/segments— List segments/v1/klaviyo/segments— Create a segment/v1/klaviyo/profiles/subscribe— Subscribe profiles with confirmation for bulk changes/v1/klaviyo/profiles/unsubscribe— Unsubscribe profiles with confirmation for bulk changesTemplates, offers & catalog
/v1/klaviyo/templates— Search or list templates/v1/klaviyo/templates— Create an HTML template/v1/klaviyo/templates/:id/render— Render a template for review/v1/klaviyo/images— List reusable images/v1/klaviyo/images— Upload an image from a URL/v1/klaviyo/coupons— List coupons and coupon codes/v1/klaviyo/coupons— Create a coupon/v1/klaviyo/catalogs/items— List catalog items/v1/klaviyo/tags— List tagsEvents, metrics & reports
/v1/klaviyo/events— List customer events/v1/klaviyo/metrics— List account metrics/v1/klaviyo/metrics/aggregate— Aggregate a metric over a time window/v1/klaviyo/reporting/campaigns— Campaign performance and attributed revenue/v1/klaviyo/reporting/flows— Flow performance and attributed revenue/v1/klaviyo/reporting/forms— Form performance/v1/klaviyo/reporting/segments— Segment performanceGoogle 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
/v1/ga4/properties— List connected GA4 properties (no property header needed)/v1/ga4/property— Property details: name, timezone, currency, data retention/v1/ga4/data-streams— List web and app data streams with measurement ids/v1/ga4/key-events— List key events (GA4 conversions)/v1/ga4/custom-dimensions— List custom dimensions/v1/ga4/custom-metrics— List custom metrics/v1/ga4/audiences— List GA4 audiences/v1/ga4/google-ads-links— List linked Google Ads accounts/v1/ga4/annotations— List reporting data annotationsConfiguration management
/v1/ga4/key-events— Register an event as a key event (conversion)/v1/ga4/key-events/:id— Update a key event's counting method or default value/v1/ga4/key-events/:id— Delete a key event (the event keeps firing; it stops counting as a conversion)/v1/ga4/custom-dimensions— Create a custom dimension/v1/ga4/custom-dimensions/:id— Update a custom dimension's display name or description/v1/ga4/custom-dimensions/:id/archive— Archive a custom dimension (irreversible; the Admin API has no delete)/v1/ga4/custom-metrics— Create a custom metric/v1/ga4/custom-metrics/:id— Update a custom metric/v1/ga4/custom-metrics/:id/archive— Archive a custom metric (irreversible)/v1/ga4/audiences— Create an audience (flows to linked Google Ads for remarketing)/v1/ga4/audiences/:id— Update an audience's name, description, or event trigger/v1/ga4/audiences/:id/archive— Archive an audience (irreversible)/v1/ga4/google-ads-links— Link a Google Ads account to the property/v1/ga4/google-ads-links/:id— Unlink a Google Ads account/v1/ga4/annotations— Create a reporting data annotation (dated note on a traffic change)/v1/ga4/annotations/:id— Update an annotation/v1/ga4/annotations/:id— Delete an annotationReports
/v1/ga4/metadata— Every dimension and metric valid for the property, including custom definitions/v1/ga4/reports— Run a report: dimensions x metrics over date ranges, with filters, ordering, and cohorts/v1/ga4/reports/batch— Run up to 5 reports in one call/v1/ga4/reports/pivot— Run a pivot (cross-tab) report/v1/ga4/reports/realtime— Realtime report over the last 30 minutes (never cached)/v1/ga4/reports/funnel— Funnel report with step-by-step completion and abandonment/v1/ga4/reports/compatibility— Check which dimensions and metrics can join the report being composed/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
/v1/ai/audit— Score campaigns 0-100 with findings and recommendations. Body: campaign_id(s), date_from, date_toMorpheus Structural Audit
/v1/ai/morpheus-auditDeep 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| platform | string | required | meta or google |
| date_from | string | optional | ISO date (defaults to 30 days ago) |
| date_to | string | optional | ISO date (defaults to yesterday) |
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" }'{
"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
/v1/ai/optimize-budget— AI budget allocation using ROAS (40%), CPA (30%), CTR (30%) scoring. Body: campaign_ids, total_budget, date_from, date_toReport Generation
/v1/ai/report— Executive summary with key metrics, top/bottom performers, and recommendations. Body: campaign_ids, date_from, date_toCross-Platform Insights
/v1/insights/cross-platform— Unified metrics across Meta, Google, TikTok, and X with platform totals and grand totals. Query: platforms, date_from, date_to, levelReporting 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
/v1/dashboards— List the org's dashboards (id, title, bound accounts, share status). Does not include the widget spec./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.
/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
/v1/dashboards— Body: title, accounts ([{ platform, account_id, name? }], each an active connected account of the org), spec (optional, omit to create empty)./v1/dashboards/:id— Body: title?, accounts?, spec? (each replaces the existing value wholesale). The spec is validated server-side (@xylo/shared) on every write./v1/dashboards/:id— Permanently delete a dashboard.Run Data
/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.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.
Library / Detail
/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.
/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./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./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./v1/creative-store/creatives/:media_key/assets— Live per-asset and Advantage+ enhancement performance for one multi-asset creative. Meta only./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 "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: trueand the full payload.
Weekly Digest
/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
/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 -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:
| Capability | Meta | TikTok | X |
|---|---|---|---|
| Nightly creative sync | Yes | Yes | Not covered |
| AI taxonomy tagging | Yes | Yes, except Spark ads | Not covered |
| Account-relative scores and digest | Yes | Yes | Not covered |
| Stored ad comments and copy performance | Yes | No | Not covered |
| Pre-purchase teaser preview | Yes | No | Not covered |
| Pause / resume from the Creatives tab | Yes, each ad reported on its own | Yes, but one failed ad can affect others in the same batch | Not covered |
| Hook rate measured on | 3-second video plays | 2-second views | Not 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.
/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)./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./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./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
/v1/team/members— List team members with roles/v1/team/invite— Invite user by email (admin/owner only)/v1/team/members/:id— Update member role/v1/team/members/:id— Remove memberInvites
/v1/team/invites— List pending invites/v1/team/invites/:id— Cancel inviteWebhooks
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
/v1/webhooks— List all webhook subscriptions/v1/webhooks/:id— Get subscription detailsCreate Subscription
/v1/webhooks— Create subscription. Body: url, events array, optional secret/v1/webhooks/:id— Update subscription/v1/webhooks/:id— Delete subscriptionBulk Operations
Execute multiple operations in a single request or batch campaign actions.
/v1/bulk/operations— Execute multiple API operations sequentially. Body: operations array of {method, path, body}/v1/bulk/pause-campaigns— Pause up to 100 campaigns at once/v1/bulk/resume-campaigns— Resume up to 100 campaigns at onceError Codes
All errors return an error object with code, message, and optional platform-specific error details (meta_error, google_error, or tiktok_error).
| HTTP | Code | Description |
|---|---|---|
| 401 | MISSING_API_KEY | No x-api-key header provided |
| 401 | INVALID_API_KEY | API key not found or revoked |
| 400 | MISSING_AD_ACCOUNT | No x-ad-account header on an endpoint that requires it |
| 403 | AD_ACCOUNT_NOT_CONNECTED | Ad account not connected to this organization |
| 403 | ACCOUNT_NOT_ACTIVE | Ad account is not activated on your plan |
| 401 | TOKEN_EXPIRED | Meta token expired, user must re-authorize via connect session |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests. Check X-RateLimit-* headers. |
| 400 | VALIDATION_ERROR | Request body or query params failed validation |
| 502 | META_API_ERROR | Meta's API returned an error (includes meta_error object) |
| 429 | META_RATE_LIMITED | Meta is rate-limiting the underlying ad account |
| 502 | GOOGLE_API_ERROR | Google Ads API returned an error (includes google_error object) |
| 429 | GOOGLE_RATE_LIMIT | Google Ads rate limit exceeded |
| 403 | GOOGLE_CUSTOMER_NOT_CONNECTED | Google customer ID not connected to organization |
| 502 | TIKTOK_API_ERROR | TikTok API returned an error (includes tiktok_error object) |
| 429 | TIKTOK_RATE_LIMIT | TikTok rate limit exceeded |
| 403 | TIKTOK_ADVERTISER_NOT_CONNECTED | TikTok advertiser not connected to organization |
| 404 | NOT_FOUND | The requested resource does not exist |
| 500 | INTERNAL_ERROR | Something went wrong on Xylo's end |
{
"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".
{
"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".
/v1/insights/async/start— Start a background insights job. Returns { job_id, status, estimated_rows, estimated_seconds }/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.
/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
curl -X GET https://api.xylomcp.com/v1/accounts \ -H "x-api-key: YOUR_API_KEY"
Step 2: List active 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
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
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
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
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:
{
"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.
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"
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"
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:
| Metric | Warning | Critical |
|---|---|---|
| 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
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
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
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
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:
Never set a campaign, ad set, or ad to ACTIVE without explicit user confirmation.
Never increase a budget by more than 50% in a single change without warning the user of the risk.
Always confirm with the user before deleting any campaign, ad set, ad, or audience.
Special Ad Categories (housing, credit, employment, politics) have legal requirements — always ask the user before setting these.
Alert the user if actual spend exceeds 125% of the intended daily budget (Meta can overspend by up to 25%).
Do not modify campaigns during high-spend hours (typically 10 AM – 2 PM in the account timezone) unless urgent.
When Meta returns an error, explain it in plain language and suggest a fix rather than retrying blindly.
Respect the learning phase — avoid editing ad sets that are in the learning phase unless performance is critically poor.
Handle ACCOUNT_NOT_ACTIVE errors gracefully by guiding the user to reconnect via a connect session.
When filtering ad sets by campaign, apply client-side filtering if the API does not support server-side filtering for that parameter.