# How to Integrate Bolt.new with Facebook Ads

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 35 minutes
- Last updated: April 2026

## TL;DR

Integrate Bolt.new with Facebook Ads using the Meta Marketing API via Next.js API routes. Create a Meta App in Meta for Developers, obtain a Marketing API access token, then fetch campaign, ad set, and ad performance data server-side. Build a custom analytics dashboard showing spend, impressions, clicks, and ROAS. Creating and modifying campaigns also works through the same Graph API. A reviewed Meta App is required for production access.

## Build a Custom Facebook Ads Dashboard in Bolt.new with Meta Marketing API

Meta's Marketing API gives you complete programmatic control over Facebook and Instagram advertising. Rather than living inside Ads Manager — Meta's browser-based interface — you can pull campaign data into your own Bolt-built application and present it exactly how your business needs. A custom dashboard built with the Marketing API can combine ad performance data with your own conversion data, apply your own attribution models, track custom KPIs, or aggregate multiple ad accounts into a single view that Ads Manager cannot provide.

The Marketing API is part of Meta's broader Graph API, which follows a consistent pattern: every endpoint is a node (an ad account, campaign, ad set, or ad) and you request specific fields by appending ?fields= parameters. Performance insights (impressions, clicks, spend, reach, frequency, CPM, CPC, CTR, ROAS) are fetched through the /insights edge with date range and breakdown parameters. This HTTP-REST design means the entire API works via fetch() calls, making it fully compatible with Bolt's WebContainer for outbound requests during development.

There is one important access tier to understand before building. A standard Marketing API access token can read data from ad accounts where the token owner has permission. This is sufficient for building tools for your own business. Building tools that access other people's ad accounts — a SaaS dashboard for clients, an agency tool — requires Meta App Review, where Meta manually reviews your use case before granting permissions like ads_read and ads_management to other users. Plan for a two to four week review process if you are building a multi-account product.

## Before you start

- A Bolt.new account with a Next.js project
- A Facebook account with admin access to a Facebook Business Manager
- At least one Facebook Ad Account with existing campaigns (for reading data)
- A Meta Developer account at developers.facebook.com and a created Meta App
- A Marketing API access token with ads_read permission (ads_management for creating campaigns)

## Step-by-step guide

### 1. Create a Meta App and Get a Marketing API Access Token

Go to developers.facebook.com and click 'My Apps' in the top-right, then 'Create App.' Select 'Other' as the use case, then 'Business' as the app type. Give your app a name, enter a contact email, and optionally associate it with a Business Manager. Click 'Create App' to complete creation.

Once your app is created, you land on the App Dashboard. Click 'Add Product' and find 'Marketing API' — click 'Set Up.' This adds the Marketing API product to your app. In the left sidebar, you will now see Marketing API under your app's products.

For development, the fastest way to get an access token is the Graph API Explorer. Go to developers.facebook.com/tools/explorer. At the top right, select your app from the dropdown. Click 'Generate Access Token' — this opens a permissions dialog. Select the permissions you need: `ads_read` for reading campaign data, `ads_management` for creating and modifying campaigns. Click 'Generate Access Token' and approve the permissions in the popup.

The resulting token is a User Access Token that expires in one to two hours. For a long-lived token, click the information icon next to the token and select 'Extend Access Token' — this exchanges it for a 60-day token. For production use, you need a System User token (created in Business Manager under Users → System Users), which does not expire and does not require a logged-in Facebook user. Go to your Business Manager → Settings → Users → System Users → Create System User, then generate a token for that system user with the required permissions and assign it to your ad account.

Copy the access token and your Ad Account ID (format: `act_XXXXXXXXXX`, found in Ads Manager URL or Business Manager). You will add both to your Bolt project's .env file.

```
// lib/meta.ts
const META_BASE_URL = 'https://graph.facebook.com/v20.0';

interface MetaFetchOptions {
  params?: Record<string, string>;
  method?: 'GET' | 'POST' | 'DELETE';
  body?: Record<string, unknown>;
}

export async function metaFetch<T = unknown>(
  endpoint: string,
  options: MetaFetchOptions = {}
): Promise<T> {
  const accessToken = process.env.META_ACCESS_TOKEN;
  if (!accessToken) throw new Error('META_ACCESS_TOKEN is not configured');

  const url = new URL(`${META_BASE_URL}/${endpoint}`);
  if (options.params) {
    Object.entries(options.params).forEach(([key, value]) => {
      url.searchParams.set(key, value);
    });
  }

  const response = await fetch(url.toString(), {
    method: options.method || 'GET',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  const data = await response.json() as { error?: { message: string; code: number } } & T;

  if (!response.ok || data.error) {
    throw new Error(data.error?.message || `Meta API error: ${response.status}`);
  }

  return data;
}
```

**Expected result:** The metaFetch helper is ready. You have a valid access token and ad account ID stored in .env. A quick test calling graph.facebook.com/v20.0/me with your token should return your name.

### 2. Build the Campaign Insights API Route

The Marketing API's insights endpoint returns performance metrics for any ad object. You can request insights on a campaign (which aggregates all ad sets and ads beneath it), an ad set, or an individual ad. The endpoint pattern is: `/{ad-object-id}/insights` with a `fields` parameter listing the metrics you want and a `date_preset` or `time_range` parameter controlling the date window.

The most commonly needed fields for a campaign dashboard are: `campaign_name`, `campaign_id`, `objective`, `status`, `spend`, `impressions`, `clicks`, `reach`, `frequency`, `cpm`, `cpc`, `ctr`, `cpp`, `actions` (conversions), `cost_per_action_type`, and `purchase_roas`. The `purchase_roas` field is an array of objects with value strings — you need to parse the float from the first element. The `actions` field is also an array that you filter for the action type you care about (e.g., `omni_purchase`, `lead`).

To get insights for all campaigns in an ad account at once, request them from the ad account node with a `level=campaign` parameter: `/{ad-account-id}/insights?level=campaign&fields=...`. This returns all campaigns in one paginated response rather than requiring individual requests for each campaign ID.

Date presets supported include: `today`, `yesterday`, `last_7d`, `last_14d`, `last_30d`, `last_month`, `this_month`, `last_quarter`, `this_year`. For custom date ranges, use `time_range={since:'2024-01-01',until:'2024-01-31'}` as a JSON string in the query parameter. The response is paginated with a `paging.next` URL for subsequent pages — for most dashboards fetching the first page of up to 25 campaigns is sufficient.

The API route should accept query parameters for the date range, and optionally for filtering by campaign status (ACTIVE, PAUSED, DELETED). Return the campaigns array with parsed numeric values — the API returns spend and ROAS as strings, which should be converted to numbers for chart rendering.

```
// app/api/facebook/campaigns/route.ts
import { NextResponse } from 'next/server';
import { metaFetch } from '@/lib/meta';

const INSIGHTS_FIELDS = [
  'campaign_name',
  'campaign_id',
  'objective',
  'status',
  'spend',
  'impressions',
  'clicks',
  'reach',
  'cpm',
  'cpc',
  'ctr',
  'purchase_roas',
  'actions',
  'cost_per_action_type',
].join(',');

interface MetaInsight {
  campaign_id: string;
  campaign_name: string;
  objective: string;
  status?: string;
  spend: string;
  impressions: string;
  clicks: string;
  reach: string;
  cpm: string;
  cpc: string;
  ctr: string;
  purchase_roas?: Array<{ action_type: string; value: string }>;
  actions?: Array<{ action_type: string; value: string }>;
}

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const datePreset = searchParams.get('datePreset') || 'last_30d';
  const status = searchParams.get('status') || 'ACTIVE';

  const adAccountId = process.env.META_AD_ACCOUNT_ID;
  if (!adAccountId) {
    return NextResponse.json({ error: 'META_AD_ACCOUNT_ID is not configured' }, { status: 500 });
  }

  try {
    const data = await metaFetch<{ data: MetaInsight[] }>(
      `${adAccountId}/insights`,
      {
        params: {
          level: 'campaign',
          fields: INSIGHTS_FIELDS,
          date_preset: datePreset,
          filtering: JSON.stringify([{ field: 'campaign.delivery_info', operator: 'IN', value: [status] }]),
          limit: '50',
        },
      }
    );

    const campaigns = (data.data || []).map((insight) => ({
      id: insight.campaign_id,
      name: insight.campaign_name,
      objective: insight.objective,
      spend: parseFloat(insight.spend || '0'),
      impressions: parseInt(insight.impressions || '0', 10),
      clicks: parseInt(insight.clicks || '0', 10),
      reach: parseInt(insight.reach || '0', 10),
      cpm: parseFloat(insight.cpm || '0'),
      cpc: parseFloat(insight.cpc || '0'),
      ctr: parseFloat(insight.ctr || '0'),
      roas: insight.purchase_roas?.[0]
        ? parseFloat(insight.purchase_roas[0].value)
        : null,
      purchases: insight.actions?.find((a) => a.action_type === 'omni_purchase')
        ? parseInt(insight.actions.find((a) => a.action_type === 'omni_purchase')!.value, 10)
        : 0,
    }));

    campaigns.sort((a, b) => b.spend - a.spend);

    const totals = campaigns.reduce(
      (acc, c) => ({
        spend: acc.spend + c.spend,
        impressions: acc.impressions + c.impressions,
        clicks: acc.clicks + c.clicks,
        purchases: acc.purchases + c.purchases,
      }),
      { spend: 0, impressions: 0, clicks: 0, purchases: 0 }
    );

    return NextResponse.json({ campaigns, totals, datePreset });
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Failed to fetch campaigns';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Expected result:** The API route returns campaign performance data as a JSON array. You can verify it by calling /api/facebook/campaigns?datePreset=last_7d in the browser and seeing your real campaign data with parsed numeric values.

### 3. Build the Analytics Dashboard React Component

With the API route returning structured data, build the React dashboard components that display it. A well-designed Facebook Ads dashboard typically has three sections: a summary header with total spend, total ROAS, and date range; a top-level metrics bar with impressions, clicks, CTR, and CPM; and a data table or card grid showing individual campaign performance.

For ROAS (Return on Ad Spend), calculate it as total purchase revenue divided by total ad spend. The Marketing API returns ROAS as a multiplier — a value of 3.5 means every dollar spent returned $3.50 in revenue. Display it as '3.5x' with green coloring for values above 2x and red for below 1x. For campaigns without purchase tracking (awareness or traffic objectives), ROAS will be null — handle this gracefully by showing 'N/A'.

The date range picker should offer preset options (Last 7 days, Last 30 days, Last quarter, This year) that map to Meta's date_preset values. When the user changes the date range, the component re-fetches data by updating the query parameter on the API route call.

For charts, Recharts works well in Bolt-generated Next.js projects. A bar chart comparing spend across campaigns, a line chart showing daily spend trend (requires a separate sparklines API call with time_increment=1), and a scatter plot of CPC vs CTR for identifying efficient campaigns are all high-value visualizations. For the spend trend, use the same insights endpoint but add `time_increment=1` to get daily breakdowns instead of aggregated totals.

**Expected result:** The dashboard renders with real campaign data, a functional date range selector, and a bar chart of campaign spend. The table sorts by clicking column headers, with ROAS values color-coded by performance.

### 4. Add Campaign Creation Endpoint

Creating campaigns via the Marketing API requires the `ads_management` permission in addition to `ads_read`. The creation flow involves three sequential API calls: create a Campaign object (the top-level container with objective and spending limits), create an Ad Set (targeting, budget, schedule, and bidding), and create an Ad (the creative that users actually see). Each step depends on the ID returned by the previous step.

For the Campaign, the key fields are `name`, `objective` (OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_TRAFFIC, OUTCOME_AWARENESS, OUTCOME_ENGAGEMENT, OUTCOME_APP_PROMOTION), `status` (PAUSED is safest for initial creation), and optionally `special_ad_categories` (required if running housing, employment, or credit ads). Set status to PAUSED initially so you can review the campaign before it starts spending.

The Ad Set specifies where and when the campaign runs and how much it spends. Key fields: `campaign_id` (from the campaign just created), `name`, `daily_budget` or `lifetime_budget` (in cents, so $50/day = 5000), `start_time` and `end_time` (ISO 8601 format), `targeting` (an object specifying geo, age, interests, behaviors), `billing_event` (IMPRESSIONS is standard), and `optimization_goal` (OFFSITE_CONVERSIONS for purchase tracking, LINK_CLICKS for traffic).

The targeting object has many fields. A minimal targeting object includes `geo_locations` (countries array, e.g., `{countries: ['US']}`), `age_min` (18-65), and `age_max` (18-65). Interest-based targeting uses `flexible_spec` with interest IDs that you look up via the Targeting Search API. For a working MVP, use broad targeting with just geo and age — detailed interest targeting can be added later.

Important: the Meta Marketing API performs validation and may return errors for budget minimums (typically $1/day minimum), missing required fields, or campaigns in restricted categories. Always handle the error response and display Meta's error message directly — it is usually specific and actionable.

```
// app/api/facebook/create-campaign/route.ts
import { NextResponse } from 'next/server';
import { metaFetch } from '@/lib/meta';

export async function POST(request: Request) {
  const adAccountId = process.env.META_AD_ACCOUNT_ID;
  if (!adAccountId) {
    return NextResponse.json({ error: 'META_AD_ACCOUNT_ID is not configured' }, { status: 500 });
  }

  const {
    name,
    objective = 'OUTCOME_TRAFFIC',
    dailyBudgetCents = 500, // $5.00
    targetCountries = ['US'],
    ageMin = 18,
    ageMax = 65,
  } = await request.json();

  try {
    // Step 1: Create Campaign
    const campaign = await metaFetch<{ id: string }>(
      `${adAccountId}/campaigns`,
      {
        method: 'POST',
        body: {
          name,
          objective,
          status: 'PAUSED', // Always start paused
          special_ad_categories: [],
        },
      }
    );

    // Step 2: Create Ad Set
    const startTime = new Date();
    startTime.setHours(startTime.getHours() + 1);

    const adSet = await metaFetch<{ id: string }>(
      `${adAccountId}/adsets`,
      {
        method: 'POST',
        body: {
          campaign_id: campaign.id,
          name: `${name} - Ad Set`,
          daily_budget: dailyBudgetCents,
          billing_event: 'IMPRESSIONS',
          optimization_goal: 'LINK_CLICKS',
          bid_strategy: 'LOWEST_COST_WITHOUT_CAP',
          targeting: {
            geo_locations: { countries: targetCountries },
            age_min: ageMin,
            age_max: ageMax,
          },
          start_time: startTime.toISOString(),
          status: 'PAUSED',
        },
      }
    );

    return NextResponse.json({
      success: true,
      campaignId: campaign.id,
      adSetId: adSet.id,
      message: `Campaign "${name}" created with status PAUSED. Add creatives before activating.`,
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Campaign creation failed';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Expected result:** The create campaign endpoint returns a campaign ID and ad set ID. You can verify the campaign exists in Facebook Ads Manager under its PAUSED status, with the correct objective and targeting settings.

### 5. Deploy and Configure for Production Access

During development in Bolt's WebContainer, all outbound Marketing API calls work correctly — you can read campaign data and test the dashboard with real numbers. The one integration feature that requires deployment is webhook-based data: Lead Ads form submissions and real-time event notifications use Meta's webhook system, which sends POST requests to your server. Since the WebContainer cannot receive incoming connections, these webhooks only function after deployment.

Deploy to Netlify via Settings → Applications → Connect Netlify, or use Bolt Cloud (click Publish). After deployment, go to your Netlify or Bolt Cloud dashboard and add environment variables: `META_ACCESS_TOKEN` and `META_AD_ACCOUNT_ID` with your production values.

For production use with other users' ad accounts (building a multi-tenant dashboard), your Meta App must go through App Review. In Meta for Developers, under your app, go to App Review → Permissions and Features. Request the `ads_read` permission (and `ads_management` if you need write access). You must provide a screencast demonstrating your integration, a privacy policy URL, and a business verification. Meta typically completes review in two to four weeks. Without App Review, your app can only access data for users who have a role in your Meta App (developers, testers, admins).

For a single-tenant dashboard (your own ad account only), App Review is not required. Use a System User token from Business Manager, which does not require any end-user OAuth flow and does not expire. This is the production-ready approach for internal tools and agency dashboards accessing specific accounts with explicit permission.

```
# Environment variables to add in Netlify dashboard or Bolt Cloud
# Settings → Environment Variables (Netlify) or Secrets (Bolt Cloud)

META_ACCESS_TOKEN=EAAxxxxxxxxxxxxxxxxxx
META_AD_ACCOUNT_ID=act_1234567890

# Optional: restrict to specific ad account for security
# NEXT_PUBLIC_APP_URL=https://your-app.netlify.app
```

**Expected result:** The app is deployed and the Meta API connection works with production credentials. The health check endpoint returns your Meta user ID and confirms the ad account is accessible.

## Best practices

- Use System User tokens from Business Manager for production deployments — they do not expire and do not require a logged-in Facebook user, making them ideal for automated dashboards and server-side only access.
- Cache Marketing API responses for at least 5 minutes on your API route — campaign insights do not update in real time and the API has per-ad-account hourly rate limits that vary based on account spend level.
- Store META_ACCESS_TOKEN and META_AD_ACCOUNT_ID as server-side environment variables without the NEXT_PUBLIC_ prefix to prevent them from appearing in client-side JavaScript bundles.
- Always create campaigns with status PAUSED initially, and require explicit user confirmation before changing status to ACTIVE — active campaigns spend budget immediately.
- Request only the specific fields you need using the fields parameter rather than fetching all available fields — this reduces response size and API quota consumption significantly.
- Handle Meta's nested error format explicitly: error responses have a JSON body with error.message and error.code, not a plain text body, so ensure your error handling reads the JSON before extracting the message.
- For multi-tenant applications where users connect their own ad accounts, Meta App Review is required for ads_read and ads_management permissions — budget 2-4 weeks for the review process and prepare a video demonstration of your integration.
- Use the /insights endpoint with level=campaign for account-level aggregation rather than fetching each campaign individually — one API call for all campaigns is far more efficient than N calls for N campaigns.

## Use cases

### Campaign Performance Analytics Dashboard

Build a custom analytics dashboard that displays Facebook and Instagram campaign performance across all active campaigns. Show spend, impressions, clicks, CPM, CPC, CTR, and ROAS for any date range. Drill down from campaigns to ad sets to individual ads. Compare performance across time periods to spot trends that Ads Manager's default views obscure.

Prompt example:

```
Build a Facebook Ads analytics dashboard. Create a Next.js API route at /api/facebook/campaigns that calls Meta's Marketing API to fetch all campaigns in an ad account with their insights (spend, impressions, clicks, cpm, ctr, purchase_roas) for a given date range. Accept adAccountId and dateRange as query params. Build a React dashboard with a date range picker, a summary row of total spend and ROAS, and a sortable table of campaigns. Store META_ACCESS_TOKEN and META_AD_ACCOUNT_ID in process.env.
```

### Ad Creative Performance Analyzer

Pull ad-level performance data to identify which creatives are driving results and which are fatiguing. Compare hook rates, scroll-stop rates (measured via thumb-stop ratio), and conversion rates across ad creative variants to inform your next creative sprint with data rather than guesswork.

Prompt example:

```
Create an ad creative analyzer using Facebook Marketing API. Build a Next.js API route at /api/facebook/ads that fetches all active ads in an ad account with ad creative thumbnails, ad names, and insights (impressions, clicks, spend, ctr, cpc, purchase_roas, cost_per_unique_click). Create a React grid view showing each ad's thumbnail, key metrics, and a performance score badge. Highlight top performers in green and underperformers in red. Use META_ACCESS_TOKEN from process.env.
```

### Campaign Creator and Budget Manager

Build a simplified campaign creation interface that lets non-technical team members launch new Facebook ad campaigns without navigating Ads Manager. Set budgets, schedules, targeting parameters, and ad creatives through a clean form interface that calls the Marketing API to create the campaign objects.

Prompt example:

```
Build a Facebook ad campaign creator. Create Next.js API routes at /api/facebook/create-campaign (POST) and /api/facebook/update-budget (PATCH). The create endpoint should create a campaign, ad set with targeting, and ad via the Meta Marketing API using META_ACCESS_TOKEN from process.env. The update endpoint should update daily or lifetime budget on an existing ad set. Build a React form with campaign name, objective (OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_TRAFFIC), daily budget, start/end date, and a targeting section. Show a success message with the campaign ID after creation.
```

## Troubleshooting

### Error 190: Invalid OAuth access token or Session has expired

Cause: The META_ACCESS_TOKEN has expired. User Access Tokens from Graph API Explorer expire in 1-2 hours. Even 'extended' tokens expire after 60 days.

Solution: Generate a new access token in Graph API Explorer and update your .env file. For a permanent solution, create a System User in Business Manager → Settings → Users → System Users, generate a token there, and use it instead — System User tokens do not expire.

### Error 200 or 'Permissions error' — missing ads_read or ads_management permission

Cause: The access token was generated without including the required permissions. Graph API Explorer requires you to explicitly check each permission checkbox when generating a token.

Solution: Return to developers.facebook.com/tools/explorer, select your app, click 'Generate Access Token,' and explicitly check ads_read (and ads_management if needed) before generating. Permissions are not inherited — you must request them each time.

### API returns empty data array even though campaigns exist in Ads Manager

Cause: The ad account ID format is incorrect, or the token does not have access to that specific ad account. The ad account ID must include the 'act_' prefix.

Solution: Verify the ad account ID includes the 'act_' prefix (act_1234567890, not 1234567890). Also confirm the token's user has at least 'Analyst' access to the ad account in Business Manager → Ad Accounts → People.

```
// Ensure act_ prefix is included:
const adAccountId = process.env.META_AD_ACCOUNT_ID; // Should be: act_1234567890
// If stored without prefix, add it:
const formattedId = adAccountId?.startsWith('act_') ? adAccountId : `act_${adAccountId}`;
```

### Webhooks for Lead Ads form submissions are not arriving at the API route

Cause: Webhooks cannot reach Bolt's WebContainer preview URL. Meta sends an HTTP POST to your webhook URL, which requires a publicly accessible server.

Solution: Deploy to Netlify or Bolt Cloud to get a stable HTTPS URL. In Meta for Developers, navigate to your app → Webhooks → Add Subscription, and enter your deployed URL as the callback. Meta requires your endpoint to respond to a GET verification request with the hub.challenge parameter before activating the webhook.

```
// Webhook verification handler (GET request from Meta):
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const mode = searchParams.get('hub.mode');
  const token = searchParams.get('hub.verify_token');
  const challenge = searchParams.get('hub.challenge');
  if (mode === 'subscribe' && token === process.env.META_WEBHOOK_VERIFY_TOKEN) {
    return new Response(challenge, { status: 200 });
  }
  return new Response('Forbidden', { status: 403 });
}
```

## Frequently asked questions

### Does Bolt.new have a native Facebook Ads integration?

No — Bolt.new does not have a native Meta Marketing API connector. The integration requires creating Next.js API routes that call Meta's Graph API. Bolt's AI assistant can generate this boilerplate from a prompt, which significantly reduces setup time even without a native connector.

### Can I test the Facebook Ads dashboard in Bolt's preview without deploying?

Yes — all outbound calls to Meta's Marketing API work in Bolt's WebContainer preview since they are standard HTTPS requests. You can read campaign data, build the dashboard, and test performance metrics entirely in the preview. The only feature that requires deployment is incoming webhooks, such as Lead Ads form submission notifications.

### Do I need Meta App Review to read my own ad account data?

No — App Review is only required when your app accesses other users' ad accounts (building a SaaS product or agency tool). For accessing your own ad account data, or accounts where you have an admin or analyst role in Business Manager, App Review is not needed. Use a System User token from Business Manager for long-term access without expiry.

### Why is the Marketing API returning an empty data array even though I have active campaigns?

The most common cause is an incorrect ad account ID format — it must include the 'act_' prefix (act_1234567890). The second common cause is insufficient permissions on the access token — verify it includes ads_read. The third is that the token's associated user does not have at least Analyst access to the specific ad account in Business Manager.

### How do I get ROAS data from the Meta Marketing API?

Include 'purchase_roas' in your fields parameter. The API returns it as an array of objects like [{action_type: 'omni_purchase', value: '3.45'}]. Parse the float from the first element's value property. Note that ROAS only appears for campaigns tracking purchase conversions — awareness and traffic campaigns will return an empty or absent purchase_roas field.

### What is the rate limit for the Meta Marketing API?

Rate limits are calculated per ad account based on the account's ad spend tier. Higher-spending accounts receive more API calls per hour. Typically, accounts get several hundred calls per hour. If you hit limits (error code 17 or 613), the response includes a Retry-After header. Caching responses for 5-15 minutes eliminates most rate limit issues for dashboard use cases.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/facebook-ads
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/facebook-ads
