Pacing and Scheduling

Determines how your ads budget is spent over time. It provides uniform competition at Facebook's ads auction across all advertisers each day and automatically allocates budgets to different ads. Pacing functions the same way for ads created with the API as it does with Facebook tools, see Ads Help Center, Delivery and Pacing.

Pacing tries to provide optimal bids by learning from other ads competing for the same target audience. This formula explains this:

Final bid (per impression) = optimal bid (per impression) * CTR
where optimal bid <= max_bid

CTR is click-through-rate but this formula applies to view through rate (VTR) for impressions and conversion rate (CVR) for conversions. We continually improve this for accuracy and final bids are influenced by factors such as ad type, targeted audience, time of the day, display context and more. Determining optimal bid value is a core part of the pacing algorithm.

You can set three pacing options in pacing_type when you create or update an ad set. With standard pacing, we enter your ad into every relevant auction and adjust your bid over a day to produce smooth, optimal delivery relative to your objective and budget. This is the default pacing. To disable default pacing:

use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;

$adset = new AdSet(<AD_SET_ID>);
$adset->{AdSetFields::PACING_TYPE} = array('no_pacing');
$adset->update();
from facebookads.adobjects.adset import AdSet

adset = AdSet(fbid=<AD_SET_ID>, parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.pacing_type: ['no_pacing'],
})
adset.remote_update()
new AdSet(<AD_SET_ID>, context).update()
  .setPacingType("[\"no_pacing\"]")
  .execute();
curl \
  -F 'pacing_type=["no_pacing"]' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.11/<AD_SET_ID>
use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;

$adset = new AdSet(<AD_SET_ID>);
$adset->{AdSetFields::PACING_TYPE} = array('standard');
$adset->update();
from facebookads.adobjects.adset import AdSet

adset = AdSet(fbid=<AD_SET_ID>, parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.pacing_type: ['standard'],
})
adset.remote_update()
new AdSet(<AD_SET_ID>, context).update()
  .setPacingType("[\"standard\"]")
  .execute();
curl \
  -F 'pacing_type=["standard"]' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.11/<AD_SET_ID>

Accelerated delivery and removes all pacing adjustments from your bid. We enter your ad into all eligible auctions at its full maximum bid. You can achieve maximum delivery with a specified cost and budget. This results in delivery that is not smooth throughout the day; your ad set's budget may be exhausted before the end of the day. To create an ad set with accelerated delivery:

use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;
use FacebookAds\Object\Fields\TargetingFields;
use FacebookAds\Object\Values\AdSetBillingEventValues;
use FacebookAds\Object\Targeting;
use FacebookAds\Object\Values\AdSetOptimizationGoalValues;

$adset = new AdSet(null, 'act_<AD_ACCOUNT_ID>');
$adset->setData(array(
  AdSetFields::NAME => 'Ad Set without pacing',
  AdSetFields::OPTIMIZATION_GOAL => AdSetOptimizationGoalValues::REACH,
  AdSetFields::BILLING_EVENT => AdSetBillingEventValues::IMPRESSIONS,
  AdSetFields::PACING_TYPE => array('no_pacing'),
  AdSetFields::BID_AMOUNT => 2,
  AdSetFields::DAILY_BUDGET => 1000,
  AdSetFields::CAMPAIGN_ID => <CAMPAIGN_ID>,
  AdSetFields::TARGETING => (new Targeting())->setData(array(
    TargetingFields::GEO_LOCATIONS => array(
      'countries' => array('US'),
    ),
  )),
));
$adset->create();
from facebookads.adobjects.adset import AdSet
from facebookads.adobjects.targeting import Targeting

adset = AdSet(parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.name: 'Ad Set without pacing',
    AdSet.Field.optimization_goal: AdSet.OptimizationGoal.reach,
    AdSet.Field.billing_event: AdSet.BillingEvent.impressions,
    AdSet.Field.pacing_type: ['no_pacing'],
    AdSet.Field.bid_amount: 2,
    AdSet.Field.daily_budget: 1000,
    AdSet.Field.campaign_id: <CAMPAIGN_ID>,
    AdSet.Field.targeting: {
        Targeting.Field.geo_locations: {
            'countries': ['US'],
        },
    },
})

adset.remote_create()
AdSet adSet = new AdAccount(act_<AD_ACCOUNT_ID>, context).createAdSet()
  .setName("Ad Set without pacing")
  .setOptimizationGoal(AdSet.EnumOptimizationGoal.VALUE_REACH)
  .setBillingEvent(AdSet.EnumBillingEvent.VALUE_IMPRESSIONS)
  .setPacingType("[\"no_pacing\"]")
  .setBidAmount(2L)
  .setDailyBudget(1000L)
  .setCampaignId(<CAMPAIGN_ID>)
  .setTargeting(
    new Targeting()
      .setFieldGeoLocations(
        new TargetingGeoLocation()
          .setFieldCountries(Arrays.asList("US"))
      )
  )
  .execute();
String ad_set_id = adSet.getId();
curl \
  -F 'name=Ad Set without pacing' \
  -F 'optimization_goal=REACH' \
  -F 'billing_event=IMPRESSIONS' \
  -F 'pacing_type=["no_pacing"]' \
  -F 'bid_amount=2' \
  -F 'daily_budget=1000' \
  -F 'campaign_id=<CAMPAIGN_ID>' \
  -F 'targeting={"geo_locations":{"countries":["US"]}}' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.11/act_<AD_ACCOUNT_ID>/adsets

You can disable pacing for these scenarios:

  • Advertise flash sales or limited-time promotions
  • Deliver ads with live events such as sporting events, election debates.
  • Maximize delivery during key times of year such as holidays or back-to-school season.

You should not use this option when:

  • We under-deliver your ad because your bid is too low or targeting too narrow. In these cases we already effectively remove budget pacing, so accelerated delivery won't help.
  • When you use automatic bidding and is_autobid is true.

See Ad Set, Pacing Options, Reference

Ad Scheduling

Specify days in a week and hours in a day when your ad set runs in adset_schedule. Your schedule applies to all ad groups under the ad set. See Ad Scheduling, Blog. adset_schedule is an array of JSON objects. Each object represents a schedule for a single day. For example:

use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;
use FacebookAds\Object\Fields\TargetingFields;
use FacebookAds\Object\Values\AdSetBillingEventValues;
use FacebookAds\Object\Targeting;
use FacebookAds\Object\Values\AdSetOptimizationGoalValues;

$adset = new AdSet(null, 'act_<AD_ACCOUNT_ID>');
$adset->setData(array(
  AdSetFields::NAME => 'Ad Set with scheduling',
  AdSetFields::OPTIMIZATION_GOAL => AdSetOptimizationGoalValues::REACH,
  AdSetFields::BILLING_EVENT => AdSetBillingEventValues::IMPRESSIONS,
  AdSetFields::PACING_TYPE => array('day_parting'),
  AdSetFields::LIFETIME_BUDGET => 100000,
  AdSetFields::END_TIME
    => (new \DateTime("+1 week"))->format(\DateTime::ISO8601),
  AdSetFields::ADSET_SCHEDULE => array(
    array(
      'start_minute' => 540,
      'end_minute' => 720,
      'days' => array(1, 2, 3, 4, 5),
    ),
  ),
  AdSetFields::BID_AMOUNT => 2,
  AdSetFields::CAMPAIGN_ID => <CAMPAIGN_ID>,
  AdSetFields::TARGETING => (new Targeting())->setData(array(
    TargetingFields::GEO_LOCATIONS => array(
      'countries' => array('US'),
    ),
  )),
));
$adset->create();
import time
from facebookads.adobjects.adset import AdSet
from facebookads.adobjects.targeting import Targeting

adset = AdSet(parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.name: 'Ad Set without pacing',
    AdSet.Field.optimization_goal: AdSet.OptimizationGoal.reach,
    AdSet.Field.billing_event: AdSet.BillingEvent.impressions,
    AdSet.Field.pacing_type: ['day_parting'],
    AdSet.Field.lifetime_budget: 100000,
    AdSet.Field.end_time: int(time.time() + 7 * 24 * 3600),
    AdSet.Field.adset_schedule: [
        {
            'start_minute': 540,
            'end_minute': 720,
            'days': [1, 2, 3, 4, 5],
        },
    ],
    AdSet.Field.bid_amount: 2,
    AdSet.Field.campaign_id: <CAMPAIGN_ID>,
    AdSet.Field.targeting: {
        Targeting.Field.geo_locations: {
            'countries': ['US'],
        },
    },
})

adset.remote_create()
AdSet adSet = new AdAccount(act_<AD_ACCOUNT_ID>, context).createAdSet()
  .setName("Ad Set with scheduling")
  .setOptimizationGoal(AdSet.EnumOptimizationGoal.VALUE_REACH)
  .setBillingEvent(AdSet.EnumBillingEvent.VALUE_IMPRESSIONS)
  .setPacingType("[\"day_parting\"]")
  .setLifetimeBudget(100000L)
  .setEndTime(end_time)
  .setAdsetSchedule("[{\"start_minute\":\"540\",\"end_minute\":\"720\",\"days\":[\"1\",\"2\",\"3\",\"4\",\"5\"]}]")
  .setBidAmount(2L)
  .setCampaignId(<CAMPAIGN_ID>)
  .setTargeting(
    new Targeting()
      .setFieldGeoLocations(
        new TargetingGeoLocation()
          .setFieldCountries(Arrays.asList("US"))
      )
  )
  .execute();
String ad_set_id = adSet.getId();
curl \
  -F 'name=Ad Set with scheduling' \
  -F 'optimization_goal=REACH' \
  -F 'billing_event=IMPRESSIONS' \
  -F 'pacing_type=["day_parting"]' \
  -F 'lifetime_budget=100000' \
  -F 'end_time=2018-02-06T04:45:17+0000' \
  -F 'adset_schedule=[ 
    { 
      "start_minute": 540, 
      "end_minute": 720, 
      "days": [ 
        1, 
        2, 
        3, 
        4, 
        5 
      ] 
    } 
  ]' \
  -F 'bid_amount=2' \
  -F 'campaign_id=<CAMPAIGN_ID>' \
  -F 'targeting={"geo_locations":{"countries":["US"]}}' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.11/act_<AD_ACCOUNT_ID>/adsets

To update ad scheduling:

use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;

$adset = new AdSet(<AD_SET_ID>);

$adset->setData(array(
  AdSetFields::DAILY_BUDGET => null,
  AdSetFields::LIFETIME_BUDGET => 100000,
  AdSetFields::END_TIME
    => (new \DateTime("+1 week"))->format(\DateTime::ISO8601),
  AdSetFields::PACING_TYPE => array('day_parting'),
  AdSetFields::ADSET_SCHEDULE => array(
    array(
      'start_minute' => 720,
      'end_minute' => 840,
      'days' => array(1, 2, 3, 4, 5),
    ),
  ),
));
$adset->update();
import time
from facebookads.adobjects.adset import AdSet

adset = AdSet(fbid=<AD_SET_ID>, parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.daily_budget: None,
    AdSet.Field.lifetime_budget: 100000,
    AdSet.Field.end_time: int(time.time() + 7 * 24 * 3600),
    AdSet.Field.pacing_type: ['day_parting'],
    AdSet.Field.adset_schedule: [
        {
            'start_minute': 540,
            'end_minute': 720,
            'days': [1, 2, 3, 4, 5],
        },
    ],
})
adset.remote_update()
curl \
  -F 'lifetime_budget=100000' \
  -F 'end_time=2016-07-21T20:42:08+0000' \
  -F 'pacing_type=["day_parting"]' \
  -F 'adset_schedule=[ 
    { 
      "start_minute": 720, 
      "end_minute": 840, 
      "days": [ 
        1, 
        2, 
        3, 
        4, 
        5 
      ] 
    } 
  ]' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.7/<AD_SET_ID>

To turning ad scheduling off:

use FacebookAds\Object\AdSet;
use FacebookAds\Object\Fields\AdSetFields;

$adset = new AdSet(<AD_SET_ID>);

$adset->setData(array(
  AdSetFields::PACING_TYPE => array('standard'),
  AdSetFields::ADSET_SCHEDULE => array(),
));
$adset->update();
from facebookads.adobjects.adset import AdSet

adset = AdSet(fbid=<AD_SET_ID>, parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.pacing_type: ['standard'],
    AdSet.Field.adset_schedule: [],
})
adset.remote_update()
new AdSet(<AD_SET_ID>, context).update()
  .setPacingType("[\"standard\"]")
  .setAdsetSchedule("[]")
  .execute();
curl \
  -F 'pacing_type=["standard"]' \
  -F 'adset_schedule=[]' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.11/<AD_SET_ID>

To get ad scheduling information:

curl -X GET \ -d 'fields="adset_schedule"' \ -d 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v3.2/<AD_SET_ID>/
'use strict'; const bizSdk = require('facebook-nodejs-business-sdk'); const AdSet = bizSdk.AdSet; const access_token = '<ACCESS_TOKEN>'; const app_secret = '<APP_SECRET>'; const app_id = '<APP_ID>'; const id = '<ID>'; const api = bizSdk.FacebookAdsApi.init(access_token); const showDebugingInfo = true; // Setting this to true shows more debugging info. if (showDebugingInfo) { api.setDebug(true); } const logApiCallResult = (apiCallName, data) => { console.log(apiCallName); if (showDebugingInfo) { console.log('Data:' + JSON.stringify(data)); } }; let fields, params; fields = [ 'adset_schedule', ]; params = { }; const sample_code = (new AdSet(id)).get( fields, params ); logApiCallResult('sample_code api call complete.', sample_code);
require __DIR__ . '/vendor/autoload.php'; use FacebookAds\Object\AdSet; use FacebookAds\Api; use FacebookAds\Logger\CurlLogger; $access_token = '<ACCESS_TOKEN>'; $app_secret = '<APP_SECRET>'; $app_id = '<APP_ID>'; $id = '<ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( 'adset_schedule', ); $params = array( ); echo json_encode((new AdSet($id))->getSelf( $fields, $params )->exportAllData(), JSON_PRETTY_PRINT);
from facebook_business.adobjects.adset import AdSet from facebook_business.api import FacebookAdsApi access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAdsApi.init(access_token=access_token) fields = [ 'adset_schedule', ] params = { } print AdSet(id).get( fields=fields, params=params, )
import com.facebook.ads.sdk.*; import java.io.File; import java.util.Arrays; public class SAMPLE_CODE_EXAMPLE { public static void main (String args[]) throws APIException { String access_token = \"<ACCESS_TOKEN>\"; String app_secret = \"<APP_SECRET>\"; String app_id = \"<APP_ID>\"; String id = \"<ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new AdSet(id, context).get() .requestAdsetScheduleField() .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end ad_set = FacebookAds::AdSet.get(id ,'adset_schedule')

Each array must have:

Field Name Description Type

start_minute

0 based minute of day. When schedule starts

int

end_minute

0 based minute of day. When schedule ends

int

days

Days schedule active. Valid values: 0-6 where 0 is Sunday, 1 is Monday, and 6 is Saturday.

Array of ints

start_minute and end_minute must be on the hour and must be at least one hour apart. For Reach and Frequency, day parts must be at least 4 hours. For example:

[{'start_minute':540,'end_minute':720,'days':[1,2,3,4,5]},{'start_minute':180, 'end_minute':360,'days':[0,6]}]

The following restrictions apply:

  • Only use ad scheduling with lifetime budgets.
  • Ad scheduling applies to a target audience's time zone for ads in a set, not an ad account's time zone. If your ad account time zone is ET, but your ads target people in California in PST, when you schedule ads from 6PM-9PM, we deliver them to people in California 6PM-9PM PST not ET.

FAQ

Q: My ads are not pacing correctly, what do I do?

A: For under-delivery, your bid price might be too low or your audience too narrow. Your bid should be in the suggested bid range so your ads win auctions and get placement. With competitive target audiences, you may need to bid above the suggested bid range. Or your targeting is too narrow.

If we over-deliver your ad, you might have a very large audience that quickly exhausts budget. If you believe that is not the case, contact us at Facebook Advertising Help.

Q: Is pacing at the ad set or ad campaign level?

A: If you're using campaign budget optimization, budget pacing is at the campaign level. Otherwise, budget pacing is done at the ad set level.

Q: When I change my budget, will it impact pacing?

A: When you change budget, our systems have to learn the new optimal bid which takes time. During this time, your bids are not optimal and we can't maximize ROI. Therefore you should not change bid and budget frequently.

Q: When should I change bid or budget?

A: If you have to change these parameters, limit yourself to 2-3 times a day and only the early part of the day. This impacts pacing less than changing it often or later in a day.

Q: What about campaigns that run only a day or shorter?

A: Facebook optimizes pacing within a day, so this is not a problem.

Q: I have ads with billing_event as IMPRESSIONS and I switched billing_event to LINK_CLICKS. Will this affect pacing?

A: Pacing may change. Since you switch from view-based billing to click-based billing, we re-adjust pacing.

Q: I don't see max_bid for different bid types, where is it?

A: Max bid is bid_amount of an ad set you specify regardless of its optimization goal.

Q: How does day parting and pacing work together?

A: With ad scheduling, you schedule hours in a day and days in a week when your ads display to a target audience. You can have your ads display when they are most relevant to an audience. Pacing takes this schedule into account to calculate your effective, optimal bid. See ad scheduling.

Q: How does Facebook spend ad set budgets over partial days?

A: From April 9th, 2014, we change the way budgets are spent on partial days at the beginning and end of ad set schedules. For ad sets with daily budgets, we adjust the first and last day spend based on the number of hours we have to deliver ads on those days. For example, if your ad set starts at 6PM, we try to deliver only 25% of daily budget between 6 PM and midnight.