Owlmetry
API Reference

Ads

Advertising-insights API — rank campaigns, ad groups, keywords, and ads by lifetime USD revenue from attributed users.

The Ads API surfaces Advertising Insights — a ranked view of ad campaigns, ad groups, keywords, and ads by the lifetime USD revenue they've driven. The data is computed at query time from app_users.properties (attribution) joined to app_users.total_revenue_usd_cents (RevenueCat lifetime revenue).

All endpoints are project-scoped and require users:read for read paths or users:write for the manual sync trigger.

Common types

interface AdsRow {
  id: string;          // Network-specific ID (e.g. ASA campaign ID)
  name: string | null; // Resolved name; null when not yet enriched
  user_count: number;
  paying_user_count: number;
  total_revenue_usd: number;
  arpu: number;        // total_revenue_usd / user_count, 0 when no users
}

attribution_source query parameter values currently supported:

ValueDescription
apple_search_adsDefault. Captured by the Swift SDK + backfilled by RevenueCat.

The hierarchy is identical across networks (campaign → ad group → keyword | ad), implemented via ATTRIBUTION_NETWORK_DIMENSIONS in shared. Future networks plug in there.

GET /v1/projects/:projectId/ads/campaigns

List campaigns ranked by total_revenue_usd desc, then user_count desc, then id asc.

Auth required: Yes (users:read)

Query parameters

ParameterTypeDescription
attribution_sourcestringDefaults to apple_search_ads.
app_idstringScope to users acquired into a single app.
limitnumberMax rows (1–500, default 100).

Response

{
  "attribution_source": "apple_search_ads",
  "campaigns": [
    {
      "id": "542370539",
      "name": "Holiday US Campaign",
      "user_count": 1234,
      "paying_user_count": 218,
      "total_revenue_usd": 4598.12,
      "arpu": 3.73
    }
  ],
  "total_user_count": 1234,
  "total_paying_user_count": 218,
  "total_revenue_usd": 4598.12,
  "revenue_synced_at": "2026-05-01T03:00:00.000Z"
}

revenue_synced_at is the most recent app_users.revenue_synced_at across the project — surfaces "as of" freshness without per-row timestamps.

GET /v1/projects/:projectId/ads/campaigns/:campaignId/ad-groups

Drill into a campaign. Same row shape as campaigns.

Auth required: Yes (users:read)

Query parameters

Same as campaigns.

Response

{
  "attribution_source": "apple_search_ads",
  "campaign_id": "542370539",
  "campaign_name": "Holiday US Campaign",
  "ad_groups": [ /* AdsRow[] */ ]
}

GET /v1/projects/:projectId/ads/campaigns/:campaignId/ad-groups/:adGroupId/leaves

Within a single ad group, returns both keywords and ads arrays side-by-side. Apple Search Ads attributes each user to one or the other.

Auth required: Yes (users:read)

Query parameters

Same as campaigns. (No leaf_type filter — the response always includes both.)

Response

{
  "attribution_source": "apple_search_ads",
  "campaign_id": "542370539",
  "campaign_name": "Holiday US Campaign",
  "ad_group_id": "111222333",
  "ad_group_name": "Holiday App Install",
  "keywords": [ /* AdsRow[] */ ],
  "ads": [ /* AdsRow[] */ ]
}

POST /v1/projects/:projectId/ads/sync

Manually trigger a refresh of advertising insights for the project. Fires revenuecat_sync (refreshes lifetime revenue per user) and apple_ads_sync (resolves any unresolved ASA IDs to readable names) in parallel.

Auth required: Yes (users:write, admin role)

Response

{
  "syncing": true,
  "revenuecat_job_run_id": "uuid",
  "apple_ads_job_run_id": "uuid"
}

The daily revenuecat_sync cron (03:00 UTC) runs automatically across every project — manual sync is for forcing freshness between cron runs or after onboarding a new RevenueCat integration.

Ready to get started?

Connect your agent via MCP or CLI and start tracking.