Xylo API Documentation

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

Quick Start for AI Agents

Xylo is a unified REST API for Meta (Facebook) Ads. It wraps the Meta Marketing API with these key simplifications:

  • Budgets in dollars — Meta uses cents internally; Xylo accepts and returns dollars (e.g. daily_budget: 50.00).
  • Flat insights — Meta returns conversions as nested actions arrays. Xylo flattens them to top-level conversions, cost_per_conversion, and roas fields.
  • Single-call ad creation — Pass image_url in the creative object and Xylo auto-uploads the image, creates the creative, and attaches it to the ad.
  • Default PAUSED on writes — All create endpoints default to status: "PAUSED" as a safety measure. You must explicitly set status: "ACTIVE" to go live.
  • Clean targeting — Simplified targeting format compared to Meta's deeply nested structure.

3-Step Getting Started

Step 1: List connected ad accounts

curl
curl https://api.xyloapi.dev/v1/accounts \
  -H "x-api-key: xy_sk_your_key"

Step 2: Get campaigns for an account

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

Step 3: Get performance insights

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

Full Agent Workflow Example

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

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

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

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

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

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

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

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

Authentication

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

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

Connect Sessions

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

POST/v1/connect-sessions
ParameterTypeRequiredDescription
redirect_urlstringoptionalURL to redirect user to after OAuth (e.g. https://yourapp.com/callback)
Request body
{
  "redirect_url": "https://yourapp.com/callback"
}
Response
{
  "data": {
    "connect_url": "https://xyloapi.dev/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": "v25.0"
  }
}

Paginated Success (2xx)

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

Error (4xx/5xx)

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

Meta API Error (502)

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

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

Caching

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

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

Rate Limits

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

PlanRate Limit
Free60 rpm
Growth120 rpm
Agency600 rpm
Enterprise6000 rpm

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

Accounts

List Accounts

GET/v1/accounts

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

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

Campaigns

Campaigns — List

GET/v1/campaigns

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

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

Campaigns — Read

GET/v1/campaigns/:id

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

curl
curl https://api.xyloapi.dev/v1/campaigns/120211234567890 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": {
    "id": "120211234567890",
    "name": "Summer Sale 2026",
    "status": "ACTIVE",
    "objective": "OUTCOME_SALES",
    "daily_budget": 50.00,
    "lifetime_budget": null,
    "budget_remaining": 50.00,
    "spend_today": null,
    "start_time": "2026-03-01T00:00:00-0800",
    "stop_time": null,
    "created_time": "2026-03-01T09:00:00-0800",
    "updated_time": "2026-03-15T12:00:00-0800"
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.0" }
}

Campaigns — Create

POST/v1/campaigns

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

ParameterTypeRequiredDescription
namestringrequiredCampaign name
objectivestringrequiredOUTCOME_SALES, OUTCOME_TRAFFIC, OUTCOME_AWARENESS, OUTCOME_LEADS, OUTCOME_ENGAGEMENT
statusstringoptionalACTIVE or PAUSED (default PAUSED)
daily_budgetnumberoptionalDaily budget in dollars (e.g. 50.00)
lifetime_budgetnumberoptionalLifetime budget in dollars
start_timestringoptionalISO 8601 start time
stop_timestringoptionalISO 8601 stop time
special_ad_categoriesarrayoptionalSpecial ad categories if applicable
buying_typestringoptionalBuying type (e.g. AUCTION)
curl
curl -X POST https://api.xyloapi.dev/v1/campaigns \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer Campaign",
    "objective": "OUTCOME_SALES",
    "daily_budget": 75.00
  }'
Response (201)
{
  "data": {
    "id": "120219876543210",
    "name": "Summer Campaign",
    "status": "PAUSED",
    "objective": "OUTCOME_SALES",
    "daily_budget": 75.00,
    "lifetime_budget": null,
    "budget_remaining": 75.00,
    "spend_today": null,
    "start_time": null,
    "stop_time": null,
    "created_time": "2026-03-15T10:00:00-0700",
    "updated_time": "2026-03-15T10:00:00-0700"
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.0" }
}

Campaigns — Update

PATCH/v1/campaigns/:id

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

curl
curl -X PATCH https://api.xyloapi.dev/v1/campaigns/120219876543210 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "status": "ACTIVE", "daily_budget": 100.00 }'
Response
{
  "data": {
    "id": "120219876543210",
    "name": "Summer Campaign",
    "status": "ACTIVE",
    "objective": "OUTCOME_SALES",
    "daily_budget": 100.00,
    "lifetime_budget": null,
    "budget_remaining": 100.00,
    "spend_today": null,
    "start_time": null,
    "stop_time": null,
    "created_time": "2026-03-15T10:00:00-0700",
    "updated_time": "2026-03-15T12:00:00-0700"
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.0" }
}

Campaigns — Delete

DELETE/v1/campaigns/:id

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

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

Campaigns — Duplicate

POST/v1/campaigns/:id/duplicate

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

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

Ad Sets

Ad Sets — List

GET/v1/campaigns/:campaign_id/adsets

Returns all ad sets within a campaign.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25, max 100)
afterstringoptionalPagination cursor from paging.next_cursor
refreshbooleanoptionalSet to true to bypass cache
curl
curl https://api.xyloapi.dev/v1/campaigns/120211234567890/adsets \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "120215556667770",
      "name": "US Women 25-45",
      "status": "ACTIVE",
      "campaign_id": "120211234567890",
      "daily_budget": null,
      "lifetime_budget": null,
      "billing_event": "IMPRESSIONS",
      "optimization_goal": "VALUE",
      "bid_strategy": null,
      "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": "v25.0" },
  "paging": { "next_cursor": null, "has_more": false }
}

Ad Sets — Read

GET/v1/adsets/:id

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

curl
curl https://api.xyloapi.dev/v1/adsets/120215556667770 \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": {
    "id": "120215556667770",
    "name": "US Women 25-45",
    "status": "ACTIVE",
    "campaign_id": "120211234567890",
    "daily_budget": null,
    "lifetime_budget": null,
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "VALUE",
    "bid_strategy": null,
    "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": "v25.0" }
}

Ad Sets — Create

POST/v1/campaigns/:campaign_id/adsets

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

ParameterTypeRequiredDescription
namestringrequiredAd set name
billing_eventstringrequirede.g. IMPRESSIONS
optimization_goalstringrequirede.g. VALUE, CONVERSIONS, LINK_CLICKS, REACH
targetingobjectrequiredTargeting spec: { age_min, age_max, geo_locations: { countries: [...] } }
statusstringoptionalACTIVE or PAUSED (default PAUSED)
daily_budgetnumberoptionalDaily budget in dollars
lifetime_budgetnumberoptionalLifetime budget in dollars
bid_strategystringoptionalBid strategy
start_timestringoptionalISO 8601 start time
end_timestringoptionalISO 8601 end time
curl
curl -X POST https://api.xyloapi.dev/v1/campaigns/120211234567890/adsets \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Broad - Interests",
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "VALUE",
    "targeting": {
      "age_min": 18,
      "age_max": 65,
      "geo_locations": { "countries": ["US"] }
    }
  }'

Ad Sets — Update

PATCH/v1/adsets/:id

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

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

Ad Sets — Delete

DELETE/v1/adsets/:id

Deletes the ad set in Meta.

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

Ad Sets — Duplicate

POST/v1/adsets/:id/duplicate

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

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

Budget Schedules

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

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

Budget Schedules — List

GET/v1/budget-schedules

List all schedules attached to a campaign or ad set.

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

Budget Schedules — Create

POST/v1/budget-schedules

Create a schedule. time_start must be before time_end.

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

Budget Schedules — Delete

DELETE/v1/budget-schedules/:id

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

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

Ads

Ads — List

GET/v1/adsets/:adset_id/ads

Returns all ads within an ad set.

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

Ads — Read

GET/v1/ads/:id

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

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

Ads — Create

POST/v1/adsets/:adset_id/ads

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

ParameterTypeRequiredDescription
namestringrequiredAd name
creativeobjectrequiredCreative object with page_id, headline, body, link, call_to_action. Optionally image_url (Xylo auto-uploads).
statusstringoptionalACTIVE or PAUSED (default PAUSED)

Creative object fields: page_id (required), headline, body, link, call_to_action, image_url (optional, auto-uploaded).

curl
curl -X POST https://api.xyloapi.dev/v1/adsets/120215556667770/ads \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Static Image - Blue",
    "creative": {
      "page_id": "112233445566778",
      "headline": "Shop Now",
      "body": "Great deals on top products",
      "link": "https://example.com/summer",
      "call_to_action": "SHOP_NOW",
      "image_url": "https://example.com/hero.jpg"
    }
  }'

Ads — Update

PATCH/v1/ads/:id

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

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

Ads — Delete

DELETE/v1/ads/:id

Deletes the ad in Meta.

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

Insights

GET/v1/insights

Retrieve performance metrics. Xylo flattens Meta's nested actions arrays into top-level conversions, cost_per_conversion, and roas fields. All monetary values are in dollars (Meta uses cents internally).

ParameterTypeRequiredDescription
levelstringrequiredaccount, campaign, adset, or ad
date_fromstringoptionalStart date YYYY-MM-DD (default: 7 days ago)
date_tostringoptionalEnd date YYYY-MM-DD (default: today)
campaign_idstringoptionalFilter by campaign ID
adset_idstringoptionalFilter by ad set ID
ad_idstringoptionalFilter by ad ID
time_granularitystringoptionalall (default, aggregated), daily, weekly, monthly
fieldsstringoptionalComma-separated metrics (default: spend,impressions,clicks,ctr,cpc,cpm,reach,frequency,conversions,cost_per_conversion,roas)
breakdownsstringoptionalage, gender, country, placement, device_platform
refreshbooleanoptionalSet to true to bypass cache
curl
curl "https://api.xyloapi.dev/v1/insights?level=campaign&date_from=2026-03-01&date_to=2026-03-15" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response (campaign level)
{
  "data": [
    {
      "campaign_id": "120212223334440",
      "campaign_name": "Retargeting - Cart Abandoners",
      "date_start": "2026-03-01",
      "date_stop": "2026-03-15",
      "spend": 1234.56,
      "impressions": 50000,
      "clicks": 1500,
      "ctr": 3.0,
      "cpc": 0.82,
      "cpm": 24.69,
      "reach": 40000,
      "frequency": 1.25,
      "conversions": 120,
      "cost_per_conversion": 10.29,
      "roas": 4.85,
      "link_clicks": 1350,
      "landing_page_views": 1100
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.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, conversions, cost_per_conversion, roas. Additional fields available: link_clicks, landing_page_views.

Audiences

Audiences — List

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

Audiences — Create

POST/v1/audiences
ParameterTypeRequiredDescription
namestringrequiredAudience name
typestringrequiredAudience type (e.g. website)
ruleobjectrequiredRule definition: { url_contains, retention_days }
curl
curl -X POST https://api.xyloapi.dev/v1/audiences \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Checkout Visitors - 30 Days",
    "type": "website",
    "rule": {
      "url_contains": "/checkout",
      "retention_days": 30
    }
  }'

Audiences — Create Lookalike

POST/v1/audiences/lookalike
ParameterTypeRequiredDescription
namestringrequiredLookalike audience name
source_audience_idstringrequiredSource audience ID to base lookalike on
countrystringrequiredTarget country ISO code (e.g. US)
rationumberrequiredLookalike ratio 0.01 - 0.20 (0.01 = top 1%)
curl
curl -X POST https://api.xyloapi.dev/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",
    "country": "US",
    "ratio": 0.01
  }'

Audiences — Delete

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

Targeting

Targeting — Search Locations

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

Creatives

POST/v1/creatives/upload

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

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

Provide either image_url or image_data, not both.

curl (URL upload)
curl -X POST https://api.xyloapi.dev/v1/creatives/upload \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789" \
  -H "Content-Type: application/json" \
  -d '{ "image_url": "https://example.com/hero-banner.jpg" }'
Response
{
  "data": {
    "image_hash": "a1b2c3d4e5f6...",
    "url": "https://..."
  },
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.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.

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). Staged assets auto-expire after 24 hours if not finalized.

Media — Upload

POST/v1/media/upload

Stage a media asset to Xylo. Does NOT push to Meta — call finalize for that. Returns the asset record including a public blob_url and auto-assigned group_id (perceptual-hash based, so variant sizes get grouped together).

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

Media — Finalize to Meta

POST/v1/media/finalize

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

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

Media — List

GET/v1/media

List staged and finalized assets for an ad account.

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

Media — Preview

GET/v1/media/preview/:id

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

curl
curl https://api.xyloapi.dev/v1/media/preview/asset_01HX9...

Media — Rename Group

PATCH/v1/media/groups/:groupId

Rename every asset in a media group.

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

Media — Remove from Group

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

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

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

Media — Merge Groups

POST/v1/media/groups/merge

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

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

Media — Delete

DELETE/v1/media/:id

Delete a media asset record and its blob.

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

Pages

Pages — List

GET/v1/pages

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

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

Pages — List Posts

GET/v1/pages/:id/posts

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

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

Pages — Insights

GET/v1/pages/:id/insights

Get Page performance insights.

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

Pages — Create Post

POST/v1/pages/:pageId/posts

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

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

Pages — Schedule Post

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

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

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

Pages — List Scheduled Posts

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

List posts queued for future publishing.

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

Pages — Update Post

PATCH/v1/pages/posts/:postId

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

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

Pages — Delete Post

DELETE/v1/pages/posts/:postId

Delete a post. Pass page_id in the body.

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

Pages — List Comments

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

List comments on a Page post.

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

Pages — Reply to Comment

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

Post a public reply to a comment.

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

Pages — Delete Comment

DELETE/v1/pages/comments/:commentId

Delete a comment.

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

Pages — Latest Post

GET/v1/pages/latest-post

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

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

Instagram

Instagram — List Accounts

GET/v1/instagram/accounts

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

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

Instagram — Connected Accounts

GET/v1/instagram/connected-accounts

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

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

Instagram — Account Insights

GET/v1/instagram/:id/insights

Get Instagram account insights.

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

Instagram — Publish Media

POST/v1/instagram/:igAccountId/publish

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

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

Instagram — Publishing Limit

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

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

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

Instagram — List Media

GET/v1/instagram/:igAccountId/media

List recent media for an IG Business account.

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

Instagram — Get Media

GET/v1/instagram/media/:mediaId

Fetch a single media object by ID.

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

Instagram — Media Insights

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

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

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

Instagram — List Comments

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

List comments on an IG media object.

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

Instagram — Reply to Comment

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

Reply publicly to an IG comment.

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

Instagram — Hide / Unhide Comment

PATCH/v1/instagram/comments/:commentId

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

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

Instagram — Delete Comment

DELETE/v1/instagram/comments/:commentId

Permanently delete a comment.

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

Instagram — Latest Post

GET/v1/instagram/latest-post

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

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

Leads

Leads — List Forms

GET/v1/leads/forms

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

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

Leads — Get Form Leads

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

Get lead submissions from a specific form.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25)
afterstringoptionalPagination cursor from paging.next_cursor
curl
curl "https://api.xyloapi.dev/v1/leads/forms/1234567890/leads?limit=10" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "9876543210",
      "created_time": "2026-03-20T09:15:00+0000",
      "field_data": [
        { "name": "email", "values": ["user@example.com"] },
        { "name": "full_name", "values": ["Alex Smith"] }
      ]
    }
  ],
  "meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.0" },
  "paging": { "next_cursor": "xyz789", "has_more": true }
}

Catalogs

Catalogs — List

GET/v1/catalogs

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

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

Catalogs — List Products

GET/v1/catalogs/:id/products

List products in a catalog.

ParameterTypeRequiredDescription
limitintegeroptionalMax results (default 25)
afterstringoptionalPagination cursor from paging.next_cursor
curl
curl "https://api.xyloapi.dev/v1/catalogs/1122334455/products?limit=10" \
  -H "x-api-key: xy_sk_your_key" \
  -H "x-ad-account: act_123456789"
Response
{
  "data": [
    {
      "id": "prod_001",
      "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": "v25.0" },
  "paging": { "next_cursor": "def456", "has_more": true }
}

Google Ads

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

Campaigns

GET/v1/google/campaignsList all campaigns
GET/v1/google/campaigns/:idGet campaign details
POST/v1/google/campaignsCreate campaign (SEARCH, DISPLAY, SHOPPING, PERFORMANCE_MAX, VIDEO)
PATCH/v1/google/campaigns/:idUpdate campaign
DELETE/v1/google/campaigns/:idDelete campaign

Keywords

GET/v1/google/ad-groups/:ad_group_id/keywordsList keywords
POST/v1/google/ad-groups/:ad_group_id/keywordsAdd keywords (bulk support via keywords array)
PATCH/v1/google/ad-groups/:ad_group_id/keywords/:idUpdate keyword
DELETE/v1/google/ad-groups/:ad_group_id/keywords/:idRemove keyword

Insights

GET/v1/google/insightsPerformance metrics (level: campaign | ad_group | ad)

Audiences

GET/v1/google/audiencesList remarketing audiences
POST/v1/google/audiencesCreate audience
DELETE/v1/google/audiences/:idDelete audience

Conversions

GET/v1/google/conversion-actionsList conversion actions
POST/v1/google/conversion-actionsCreate conversion action
POST/v1/google/conversions/uploadUpload offline click conversions

Assets

GET/v1/google/assetsList assets (sitelinks, callouts, structured snippets, images)
POST/v1/google/assetsCreate asset
POST/v1/google/campaigns/:id/assetsLink asset to campaign

Search Terms

GET/v1/google/search-termsSearch term performance report

Negative Keywords

GET/v1/google/campaigns/:id/negative-keywordsList campaign negative keywords
POST/v1/google/campaigns/:id/negative-keywordsAdd negative keywords
GET/v1/google/negative-keyword-listsList shared negative keyword lists
POST/v1/google/negative-keyword-listsCreate shared list

Budgets

GET/v1/google/budgetsList budgets
POST/v1/google/budgetsCreate budget
PATCH/v1/google/budgets/:idUpdate budget

Asset Groups (Performance Max)

GET/v1/google/asset-groupsList asset groups
POST/v1/google/asset-groupsCreate asset group for PMax campaign
POST/v1/google/asset-groups/:id/assetsLink asset to group

Experiments

GET/v1/google/experimentsList A/B experiments
POST/v1/google/experimentsCreate experiment
POST/v1/google/experiments/:id/promotePromote winning experiment
POST/v1/google/experiments/:id/endEnd experiment

Recommendations & Reporting

GET/v1/google/recommendationsList optimization recommendations
POST/v1/google/recommendations/:id/applyApply recommendation
GET/v1/google/change-historyAccount change history
GET/v1/google/quality-scoresKeyword quality scores

TikTok Ads

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

Campaigns

GET/v1/tiktok/campaignsList campaigns
POST/v1/tiktok/campaignsCreate campaign (objectives: REACH, TRAFFIC, VIDEO_VIEWS, CONVERSIONS, etc.)
PATCH/v1/tiktok/campaigns/:idUpdate campaign
DELETE/v1/tiktok/campaigns/:idDelete campaign

Ad Groups

GET/v1/tiktok/campaigns/:campaign_id/ad-groupsList ad groups
POST/v1/tiktok/campaigns/:campaign_id/ad-groupsCreate ad group
PATCH/v1/tiktok/ad-groups/:idUpdate ad group
DELETE/v1/tiktok/ad-groups/:idDelete ad group

Ads

GET/v1/tiktok/ad-groups/:ad_group_id/adsList ads
POST/v1/tiktok/ad-groups/:ad_group_id/adsCreate ad
PATCH/v1/tiktok/ads/:idUpdate ad
DELETE/v1/tiktok/ads/:idDelete ad

Insights

GET/v1/tiktok/insightsPerformance metrics (level: campaign | ad_group | ad)

Audiences

GET/v1/tiktok/audiencesList audiences
POST/v1/tiktok/audiencesCreate audience
DELETE/v1/tiktok/audiences/:idDelete audience

AI Features

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

Campaign Audit

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

Morpheus Structural Audit

POST/v1/ai/morpheus-audit

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

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

Budget Optimization

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

Report Generation

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

Cross-Platform Insights

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

Teams

Team management with role-based access control. Roles: owner, admin, member, viewer.

Members

GET/v1/team/membersList team members with roles
POST/v1/team/inviteInvite user by email (admin/owner only)
PATCH/v1/team/members/:idUpdate member role
DELETE/v1/team/members/:idRemove member

Invites

GET/v1/team/invitesList pending invites
DELETE/v1/team/invites/:idCancel invite

Webhooks

Subscribe to campaign and ad set events. Max 20 subscriptions per organization. URLs are validated with SSRF protection.

List Subscriptions

GET/v1/webhooksList all webhook subscriptions
GET/v1/webhooks/:idGet subscription details

Create Subscription

POST/v1/webhooksCreate subscription. Body: url, events array, optional secret
PATCH/v1/webhooks/:idUpdate subscription
DELETE/v1/webhooks/:idDelete subscription

Bulk Operations

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

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

Error Codes

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

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

Enterprise Tier

Enterprise accounts get a local Postgres mirror of their 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 enabled enterprise 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 enterprise 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 (free plan, or enterprise with refresh=true) return "live".

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

Async Insights Jobs

Very large insights queries (long date ranges, ad-level breakdowns across thousands of ads) will time out if run synchronously. Enterprise accounts get two MCP tools for these: meta_start_insights_job kicks off a background job and returns a job_id; meta_check_insights_job polls that job and returns results once complete.

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

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

Ad Integrity Checker

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

What it flags

  • Ads linking to a domain that is not the account's business website (and not on the approved list).
  • Ads running from an unknown Facebook Page or Instagram account.
  • Ads with no destination URL at all.
GET/v1/ai/integrity-checkRun 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) advertising through the Xylo API. This playbook covers account discovery, daily monitoring routines, campaign management actions, and safety rules to follow.

For the complete playbook, see llms-full.txt or agents.txt.

Overview

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

Account Discovery

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

Step 1: List connected ad accounts

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

Step 2: List active campaigns

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

Step 3: Pull account-level insights

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

Step 4: List saved audiences

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

Step 5: List pages and Instagram accounts

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

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

Step 6: List lead forms and catalogs

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

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

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

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

Daily Monitoring

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

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

Compare results against the following alert thresholds:

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

Campaign Actions

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

Step 1: Create campaign in PAUSED state

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

Step 2: Create ad set under the campaign

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

Step 3: Create ad within the ad set

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

Step 4: Activate after user confirmation

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

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

Safety Rules

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

1.

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

2.

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

3.

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

4.

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

5.

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

6.

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

7.

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

8.

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

9.

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

10.

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