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
actionsarrays. Xylo flattens them to top-levelconversions,cost_per_conversion, 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. - Clean targeting — Simplified targeting format compared to Meta's deeply nested structure.
3-Step Getting Started
Step 1: List connected ad accounts
curl https://api.xyloapi.dev/v1/accounts \ -H "x-api-key: xy_sk_your_key"
Step 2: Get campaigns for an account
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 "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.
# 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.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.
/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://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 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.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.
| Plan | Rate Limit |
|---|---|
| Free | 60 rpm |
| Growth | 120 rpm |
| Agency | 600 rpm |
| Enterprise | 6000 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.xyloapi.dev/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": "v25.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.xyloapi.dev/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": "v25.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.xyloapi.dev/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"
},
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.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) |
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
}'{
"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
/v1/campaigns/:idPartial update. Only include the fields you want to change.
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 }'{
"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
/v1/campaigns/:idSets the campaign status to DELETED in Meta. This is not a hard delete.
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
/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.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 }'{
"data": { "campaign": { "from": "120211234567890", "to": "120219876543299" } },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.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.xyloapi.dev/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,
"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
/v1/adsets/:idReturns a single ad set by ID. Same data shape as the list response.
curl https://api.xyloapi.dev/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,
"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
/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 | Targeting spec: { age_min, age_max, geo_locations: { countries: [...] } } |
| 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 |
| start_time | string | optional | ISO 8601 start time |
| end_time | string | optional | ISO 8601 end time |
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
/v1/adsets/:idPartial update. Only include the fields you want to change.
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
/v1/adsets/:idDeletes the ad set in Meta.
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
/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.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 }'{
"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
/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.xyloapi.dev/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": "v25.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.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"
}'{
"data": { "id": "99887766554433" },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.0" }
}Budget Schedules — Delete
/v1/budget-schedules/:idRemove a schedule. Does not affect the underlying campaign/ad-set budget.
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
/v1/adsets/:adset_id/adsReturns all ads within an ad set.
curl https://api.xyloapi.dev/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": "v25.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.
curl https://api.xyloapi.dev/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": "v25.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) |
Creative object fields: page_id (required), headline, body, link, call_to_action, image_url (optional, auto-uploaded).
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
/v1/ads/:idPartial update. Only include the fields you want to change.
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
/v1/ads/:idDeletes the ad in Meta.
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
/v1/insightsRetrieve 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).
| 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: today) |
| 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 metrics (default: spend,impressions,clicks,ctr,cpc,cpm,reach,frequency,conversions,cost_per_conversion,roas) |
| breakdowns | string | optional | age, gender, country, placement, device_platform |
| refresh | boolean | optional | Set to true to bypass cache |
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"
{
"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
/v1/audiences| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results |
| after | string | optional | Pagination cursor |
curl https://api.xyloapi.dev/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": "v25.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 } |
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
/v1/audiences/lookalike| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | required | Lookalike audience name |
| source_audience_id | string | required | Source audience ID to base lookalike on |
| country | string | required | Target country ISO code (e.g. US) |
| ratio | number | required | Lookalike ratio 0.01 - 0.20 (0.01 = top 1%) |
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
/v1/audiences/:idcurl -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 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.xyloapi.dev/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": "v25.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.xyloapi.dev/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": "v25.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.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" }'{
"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
/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 auto-assigned group_id (perceptual-hash based, so variant sizes get grouped together).
| 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.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"
}'{
"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.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..."]
}'{
"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.xyloapi.dev/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.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 https://api.xyloapi.dev/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.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
/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.xyloapi.dev/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.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..."] }'{
"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.xyloapi.dev/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.xyloapi.dev/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": "v25.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.xyloapi.dev/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": "v25.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.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"
{
"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
/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.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"
}'{
"data": { "id": "112233445566778_999888777" },
"meta": { "cached": false, "cache_age_seconds": null, "meta_api_version": "v25.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.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
/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.xyloapi.dev/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.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
/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.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
/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.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
/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.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
/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.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
/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.xyloapi.dev/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.xyloapi.dev/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": "v25.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.xyloapi.dev/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.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"
{
"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
/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.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 — 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.xyloapi.dev/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.xyloapi.dev/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.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
/v1/instagram/media/:mediaIdFetch a single media object by ID.
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
/v1/instagram/media/:mediaId/insightsPer-post insights (impressions, reach, engagement, saves, etc.).
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
/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.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
/v1/instagram/comments/:commentId/replyReply publicly to an IG comment.
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | required | Reply text |
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
/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.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
/v1/instagram/comments/:commentIdPermanently delete a comment.
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
/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.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
/v1/leads/formsList lead generation forms for connected Pages. Requires the x-ad-account header.
curl https://api.xyloapi.dev/v1/leads/forms \ -H "x-api-key: xy_sk_your_key" \ -H "x-ad-account: act_123456789"
{
"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
/v1/leads/forms/:id/leadsGet lead submissions from a specific form.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (default 25) |
| after | string | optional | Pagination cursor from paging.next_cursor |
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"
{
"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
/v1/catalogsList product catalogs. Requires the x-ad-account header.
curl https://api.xyloapi.dev/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": "v25.0" }
}Catalogs — List Products
/v1/catalogs/:id/productsList products in a catalog.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max results (default 25) |
| after | string | optional | Pagination cursor from paging.next_cursor |
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"
{
"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
/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 audienceAI 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 today) |
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" }'{
"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
/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, and TikTok with platform totals and grand totals. Query: platforms, date_from, date_to, levelTeams
Team management with role-based access control. Roles: owner, admin, member, viewer.
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 and ad set events. Max 20 subscriptions per organization. URLs are validated with SSRF protection.
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
}
}
}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".
{
"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".
/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 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.
/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) 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
curl -X GET https://api.xyloapi.dev/v1/accounts \ -H "x-api-key: YOUR_API_KEY"
Step 2: List active 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
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
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
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
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:
{
"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.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"
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"
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:
| 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.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
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
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
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:
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.