Pacing and Scheduling

Pacing is control logic that 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. It is a core part of optimization that helps provide return on investment, known as ROI. 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 also 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 many different 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, which includes feedback systems to keep pacing on track.

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 the course of the day in order to produce smooth, optimal delivery relative to your objective and budget. This is the default pacing mechanism. For example to disable defaultl pacing for an existing ad set:

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.9/<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.9/<AD_SET_ID>

This is known as accelerated delivery and removes all pacing adjustments from your bid as your ad enter the auction. 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 exhaused 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.adaccount import AdAccount
from facebookads.adobjects.adset import AdSet
from facebookads.adobjects.targeting import Targeting

ad_account = AdAccount(fbid='act_<AD_ACCOUNT_ID>')

params = {
    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.Field.status: AdSet.Status.active,
}
adset = ad_account.create_ad_set(params=params)
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.9/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.adaccount import AdAccount
from facebookads.adobjects.adset import AdSet
from facebookads.adobjects.targeting import Targeting

ad_account = AdAccount(fbid='act_<AD_ACCOUNT_ID>')

params = {
    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.Field.status: AdSet.Status.active,
}
adset = ad_account.create_ad_set(params=params)
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=2017-07-19T00:39:45+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.9/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.9/<AD_SET_ID>

To get ad scheduling information:

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

$adset = new AdSet(<AD_SET_ID>);
$adset->read(array(
  AdSetFields::ADSET_SCHEDULE,
));
from facebookads.adobjects.adset import AdSet

adset = AdSet(fbid=<AD_SET_ID>)
adset.remote_read(fields=[AdSet.Field.adset_schedule])
AdSet adSet2 = new AdSet(<AD_SET_ID>, context).get()
  .requestAdsetScheduleField()
  .execute();
curl -G \
  -d 'fields=adset_schedule' \
  -d 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.9/<AD_SET_ID>

Each array object must have:

Field NameDescriptionType

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. An sample day part is below. For Reach and Frequency, day parts must be at least 4 hours:

 [{'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. Imagine your ad account time zone is ET, but your ads targets people in California in PST. when you schedule ads from 6pm-9pm, we deliver them to people in California 6pm-9pm PST not 6pm-9pm 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 secure 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 the support portal.

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

A: Ad campaign setting do not influence pacing. Since individual ad sets have budget and schedule, pacing is 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 the 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 does not affect pacing as much as 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 should not be 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 somewhat. 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 anaudience. Pacing takes this schedule into account when 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.