Cost Per Action Ads

Cost Per Actions (CPA) allows you to specify conversion events and get charged by the amount of conversions. CPA for video views is called CPV.

An alternative to CPA is oCPM, which charges per impressions served.

How It Works

Your bid is defined for an ad set. These fields must follow the restrictions below:

Name Description

billing_event

Defines the action you pay on. Set to APP_INSTALLS, LINK_CLICKS, PAGE_LIKES, OFFER_CLAIMS, or THRUPLAY.

optimization_goal

Defines the action you optimize for. Set to same value as your billing_event, APP_INSTALLS, LINK_CLICKS, PAGE_LIKES, POST_ENGAGEMENT, OFFER_CLAIMS, or THRUPLAY.

bid_amount

Value you place on the objective, specified in cents, minimum 1 cent. For example, bid_amount=150 means you would like to bid $1.50 on that action. This should represent the maximum value you are willing to pay for this action.

targeting

For those ads which optimize for connections including page_like it is required to use the excluded_connections field in the targeting in order to exclude users who have already performed the one-time conversion for the destination object. See the examples in the section below for how to specify the appropriate excluded connections.

Limitations

  • The offsite link click action is only supported for links to offsite domains and Facebook hosted app domains.
  • CPA billing for non-video view ads are based on one day click through conversions. CPV, or CPA for video view, ads are billed on 10-second video views.
  • Offsite link click billing is additionally restricted to link clicks within the advertisement, which are known as inline.
  • You will not be able to buy CPA for mobile app installs unless you have already reported back some installs and your account id-app id-mobile store trio has been deemed non-fraudulent. In the meantime, your ad creation call will fail.

Starting on v9, CPA billing for app ads is deprecated, you cannot set both billing event and optimization goal to APP_INSTALLS. Instead, we recommend using the impression billing events. You can still specify APP_INSTALLS under billing_event or optimization_goal, but not both at the same time.

See ad set document on allowed updates to ad sets.

Examples

Create

The example below creates a CPA bidded ad set. Note that for CPA ad sets, you must set a promoted_object.

curl -X POST \ -F 'name="A CPA Ad Set"' \ -F 'campaign_id="<AD_CAMPAIGN_ID>"' \ -F 'daily_budget=5000' \ -F 'start_time="2024-03-25T22:08:40-0700"' \ -F 'end_time="2024-04-01T22:08:40-0700"' \ -F 'billing_event="IMPRESSIONS"' \ -F 'optimization_goal="REACH"' \ -F 'bid_amount=1000' \ -F 'promoted_object={ "page_id": "<PAGE_ID>" }' \ -F 'targeting={ "facebook_positions": [ "feed" ], "geo_locations": { "countries": [ "US" ] } }' \ -F 'user_os="iOS"' \ -F 'publisher_platforms="facebook"' \ -F 'device_platforms="mobile"' \ -F 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v19.0/act_<AD_ACCOUNT_ID>/adsets
'use strict'; const bizSdk = require('facebook-nodejs-business-sdk'); const AdAccount = bizSdk.AdAccount; const AdSet = bizSdk.AdSet; const access_token = '<ACCESS_TOKEN>'; const app_secret = '<APP_SECRET>'; const app_id = '<APP_ID>'; const id = '<AD_ACCOUNT_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 = [ ]; params = { 'name' : 'A CPA Ad Set', 'campaign_id' : '<adCampaignLinkClicksID>', 'daily_budget' : '5000', 'start_time' : '2024-03-20T14:31:58-0700', 'end_time' : '2024-03-27T14:31:58-0700', 'billing_event' : 'IMPRESSIONS', 'optimization_goal' : 'REACH', 'bid_amount' : '1000', 'promoted_object' : {'page_id':'<pageID>'}, 'targeting' : {'facebook_positions':['feed'],'geo_locations':{'countries':['US']}}, 'user_os' : 'iOS', 'publisher_platforms' : 'facebook', 'device_platforms' : 'mobile', }; const adsets = (new AdAccount(id)).createAdSet( fields, params ); logApiCallResult('adsets api call complete.', adsets);
require __DIR__ . '/vendor/autoload.php'; use FacebookAds\Object\AdAccount; use FacebookAds\Object\AdSet; use FacebookAds\Api; use FacebookAds\Logger\CurlLogger; $access_token = '<ACCESS_TOKEN>'; $app_secret = '<APP_SECRET>'; $app_id = '<APP_ID>'; $id = '<AD_ACCOUNT_ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( ); $params = array( 'name' => 'A CPA Ad Set', 'campaign_id' => '<adCampaignLinkClicksID>', 'daily_budget' => '5000', 'start_time' => '2024-03-20T14:31:58-0700', 'end_time' => '2024-03-27T14:31:58-0700', 'billing_event' => 'IMPRESSIONS', 'optimization_goal' => 'REACH', 'bid_amount' => '1000', 'promoted_object' => array('page_id' => '<pageID>'), 'targeting' => array('facebook_positions' => array('feed'),'geo_locations' => array('countries' => array('US'))), 'user_os' => 'iOS', 'publisher_platforms' => 'facebook', 'device_platforms' => 'mobile', ); echo json_encode((new AdAccount($id))->createAdSet( $fields, $params )->exportAllData(), JSON_PRETTY_PRINT);
from facebook_business.adobjects.adaccount import AdAccount 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 = '<AD_ACCOUNT_ID>' FacebookAdsApi.init(access_token=access_token) fields = [ ] params = { 'name': 'A CPA Ad Set', 'campaign_id': '<adCampaignLinkClicksID>', 'daily_budget': '5000', 'start_time': '2024-03-20T14:31:58-0700', 'end_time': '2024-03-27T14:31:58-0700', 'billing_event': 'IMPRESSIONS', 'optimization_goal': 'REACH', 'bid_amount': '1000', 'promoted_object': {'page_id':'<pageID>'}, 'targeting': {'facebook_positions':['feed'],'geo_locations':{'countries':['US']}}, 'user_os': 'iOS', 'publisher_platforms': 'facebook', 'device_platforms': 'mobile', } print AdAccount(id).create_ad_set( 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 = \"<AD_ACCOUNT_ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new AdAccount(id, context).createAdSet() .setName(\"A CPA Ad Set\") .setCampaignId(\"<adCampaignLinkClicksID>\") .setDailyBudget(5000L) .setStartTime(\"2024-03-20T14:31:58-0700\") .setEndTime(\"2024-03-27T14:31:58-0700\") .setBillingEvent(AdSet.EnumBillingEvent.VALUE_IMPRESSIONS) .setOptimizationGoal(AdSet.EnumOptimizationGoal.VALUE_REACH) .setBidAmount(1000L) .setPromotedObject(\"{\\"page_id\\":\\"<pageID>\\"}\") .setTargeting( new Targeting() .setFieldFacebookPositions(Arrays.asList(\"feed\")) .setFieldGeoLocations( new TargetingGeoLocation() .setFieldCountries(Arrays.asList(\"US\")) ) ) .setParam(\"user_os\", \"iOS\") .setParam(\"publisher_platforms\", \"facebook\") .setParam(\"device_platforms\", \"mobile\") .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<AD_ACCOUNT_ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end ad_account = FacebookAds::AdAccount.get(id) adsets = ad_account.adsets.create({ name: 'A CPA Ad Set', campaign_id: '<adCampaignLinkClicksID>', daily_budget: '5000', start_time: '2024-03-20T14:31:58-0700', end_time: '2024-03-27T14:31:58-0700', billing_event: 'IMPRESSIONS', optimization_goal: 'REACH', bid_amount: '1000', promoted_object: {'page_id':'<pageID>'}, targeting: {'facebook_positions':['feed'],'geo_locations':{'countries':['US']}}, user_os: 'iOS', publisher_platforms: 'facebook', device_platforms: 'mobile', })

After that, you can create ads and put them into this ad set following the creation flow here.

Update

Change the bid to a CPA ad set:

curl -X POST \ -F 'billing_event="IMPRESSIONS"' \ -F 'optimization_goal="LINK_CLICKS"' \ -F 'bid_amount=200' \ -F 'targeting={ "geo_locations": { "countries": [ "US" ] }, "facebook_positions": [ "feed" ] }' \ -F 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v19.0/<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 = '<AD_SET_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 = [ ]; params = { 'billing_event' : 'IMPRESSIONS', 'optimization_goal' : 'LINK_CLICKS', 'bid_amount' : '200', 'targeting' : {'geo_locations':{'countries':['US']},'facebook_positions':['feed']}, }; const sample_code = (new AdSet(id)).update( 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 = '<AD_SET_ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( ); $params = array( 'billing_event' => 'IMPRESSIONS', 'optimization_goal' => 'LINK_CLICKS', 'bid_amount' => '200', 'targeting' => array('geo_locations' => array('countries' => array('US')),'facebook_positions' => array('feed')), ); echo json_encode((new AdSet($id))->updateSelf( $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 = '<AD_SET_ID>' FacebookAdsApi.init(access_token=access_token) fields = [ ] params = { 'billing_event': 'IMPRESSIONS', 'optimization_goal': 'LINK_CLICKS', 'bid_amount': '200', 'targeting': {'geo_locations':{'countries':['US']},'facebook_positions':['feed']}, } print AdSet(id).update( 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 = \"<AD_SET_ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new AdSet(id, context).update() .setBillingEvent(AdSet.EnumBillingEvent.VALUE_IMPRESSIONS) .setOptimizationGoal(AdSet.EnumOptimizationGoal.VALUE_LINK_CLICKS) .setBidAmount(200L) .setTargeting( new Targeting() .setFieldFacebookPositions(Arrays.asList(\"feed\")) .setFieldGeoLocations( new TargetingGeoLocation() .setFieldCountries(Arrays.asList(\"US\")) ) ) .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<AD_SET_ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end ad_set = FacebookAds::AdSet.get(id) ad_set.billing_event = 'IMPRESSIONS'ad_set.optimization_goal = 'LINK_CLICKS'ad_set.bid_amount = '200'ad_set.targeting = {'geo_locations':{'countries':['US']},'facebook_positions':['feed']} ad_set.save

CPV example

To create an ad bidding CPV, or CPA for video views, first create an ad campaign with objective=VIDEO_VIEWS.

curl -X POST \ -F 'name="Video Views campaign"' \ -F 'objective="OUTCOME_ENGAGEMENT"' \ -F 'status="PAUSED"' \ -F 'special_ad_categories=[]' \ -F 'access_token=<ACCESS_TOKEN>' \ https://graph.facebook.com/v19.0/act_<AD_ACCOUNT_ID>/campaigns
'use strict'; const bizSdk = require('facebook-nodejs-business-sdk'); const AdAccount = bizSdk.AdAccount; const Campaign = bizSdk.Campaign; const access_token = '<ACCESS_TOKEN>'; const app_secret = '<APP_SECRET>'; const app_id = '<APP_ID>'; const id = '<AD_ACCOUNT_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 = [ ]; params = { 'name' : 'Video Views campaign', 'objective' : 'OUTCOME_ENGAGEMENT', 'status' : 'PAUSED', 'special_ad_categories' : [], }; const campaigns = (new AdAccount(id)).createCampaign( fields, params ); logApiCallResult('campaigns api call complete.', campaigns);
require __DIR__ . '/vendor/autoload.php'; use FacebookAds\Object\AdAccount; use FacebookAds\Object\Campaign; use FacebookAds\Api; use FacebookAds\Logger\CurlLogger; $access_token = '<ACCESS_TOKEN>'; $app_secret = '<APP_SECRET>'; $app_id = '<APP_ID>'; $id = '<AD_ACCOUNT_ID>'; $api = Api::init($app_id, $app_secret, $access_token); $api->setLogger(new CurlLogger()); $fields = array( ); $params = array( 'name' => 'Video Views campaign', 'objective' => 'OUTCOME_ENGAGEMENT', 'status' => 'PAUSED', 'special_ad_categories' => array(), ); echo json_encode((new AdAccount($id))->createCampaign( $fields, $params )->exportAllData(), JSON_PRETTY_PRINT);
from facebook_business.adobjects.adaccount import AdAccount from facebook_business.adobjects.campaign import Campaign from facebook_business.api import FacebookAdsApi access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<AD_ACCOUNT_ID>' FacebookAdsApi.init(access_token=access_token) fields = [ ] params = { 'name': 'Video Views campaign', 'objective': 'OUTCOME_ENGAGEMENT', 'status': 'PAUSED', 'special_ad_categories': [], } print AdAccount(id).create_campaign( 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 = \"<AD_ACCOUNT_ID>\"; APIContext context = new APIContext(access_token).enableDebug(true); new AdAccount(id, context).createCampaign() .setName(\"Video Views campaign\") .setObjective(Campaign.EnumObjective.VALUE_OUTCOME_ENGAGEMENT) .setStatus(Campaign.EnumStatus.VALUE_PAUSED) .setParam(\"special_ad_categories\", \"[]\") .execute(); } }
require 'facebook_ads' access_token = '<ACCESS_TOKEN>' app_secret = '<APP_SECRET>' app_id = '<APP_ID>' id = '<AD_ACCOUNT_ID>' FacebookAds.configure do |config| config.access_token = access_token config.app_secret = app_secret end ad_account = FacebookAds::AdAccount.get(id) campaigns = ad_account.campaigns.create({ name: 'Video Views campaign', objective: 'OUTCOME_ENGAGEMENT', status: 'PAUSED', special_ad_categories: [], })

Then, set the CPA for video views bid_info at the ad set:

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

$adset = new AdSet(null, 'act_<AD_ACCOUNT_ID>');
$adset->setData(array(
  AdSetFields::NAME => 'A CPV Ad Set',
  AdSetFields::CAMPAIGN_ID => <CAMPAIGN_ID>,
  AdSetFields::DAILY_BUDGET => 500,
  AdSetFields::START_TIME =>
    (new \DateTime("+1 week"))->format(\DateTime::ISO8601),
  AdSetFields::END_TIME =>
    (new \DateTime("+2 week"))->format(\DateTime::ISO8601),
  AdSetFields::BILLING_EVENT => AdSetBillingEventValues::VIDEO_VIEWS,
  AdSetFields::OPTIMIZATION_GOAL => AdSetOptimizationGoalValues::VIDEO_VIEWS,
  AdSetFields::BID_AMOUNT => 100,
  AdSetFields::TARGETING => (new Targeting())->setData(array(
    TargetingFields::GEO_LOCATIONS => array(
      'countries' => array(
        'US',
      ),
    ),
    TargetingFields::PUBLISHER_PLATFORMS => array('facebook'),
    TargetingFields::DEVICE_PLATFORMS => array('mobile'),
  )),
));

$adset->create(array(
  AdSet::STATUS_PARAM_NAME => AdSet::STATUS_PAUSED,
));
import time
from facebookads.adobjects.adset import AdSet

adset = AdSet(parent_id='act_<AD_ACCOUNT_ID>')
adset.update({
    AdSet.Field.name: 'A CPV Ad Set',
    AdSet.Field.campaign_id: <CAMPAIGN_ID>,
    AdSet.Field.daily_budget: 500,
    AdSet.Field.start_time: int(time.time()),
    AdSet.Field.end_time: int(time.time() + 100000),
    AdSet.Field.optimization_goal: AdSet.OptimizationGoal.video_views,
    AdSet.Field.billing_event: AdSet.BillingEvent.video_views,
    AdSet.Field.bid_amount: 100,
    AdSet.Field.targeting: {
        'geo_locations': {
            'countries': ['US'],
        },
        Targeting.Field.publisher_platforms: ['facebook'],
        Targeting.Field.device_platforms: ['mobile'],
    },
})

adset.remote_create(params={
    'status': AdSet.Status.paused,
})
AdSet adSet = new AdAccount(act_<AD_ACCOUNT_ID>, context).createAdSet()
  .setName("A CPV Ad Set")
  .setCampaignId(<CAMPAIGN_ID>)
  .setDailyBudget(500L)
  .setStartTime(start_time)
  .setEndTime(end_time)
  .setBillingEvent(AdSet.EnumBillingEvent.VALUE_VIDEO_VIEWS)
  .setOptimizationGoal(AdSet.EnumOptimizationGoal.VALUE_VIDEO_VIEWS)
  .setBidAmount(100L)
  .setTargeting(
    new Targeting()
      .setFieldDevicePlatforms(Arrays.asList(Targeting.EnumDevicePlatforms.VALUE_MOBILE))
      .setFieldGeoLocations(
        new TargetingGeoLocation()
          .setFieldCountries(Arrays.asList("US"))
      )
      .setFieldPublisherPlatforms(Arrays.asList("facebook"))
  )
  .setStatus(AdSet.EnumStatus.VALUE_PAUSED)
  .execute();
String ad_set_id = adSet.getId();
curl \
  -F 'name=A CPV Ad Set' \
  -F 'campaign_id=<CAMPAIGN_ID>' \
  -F 'daily_budget=500' \
  -F 'start_time=2018-02-06T04:45:29+0000' \
  -F 'end_time=2018-02-13T04:45:29+0000' \
  -F 'billing_event=VIDEO_VIEWS' \
  -F 'optimization_goal=VIDEO_VIEWS' \
  -F 'bid_amount=100' \
  -F 'targeting={ 
    "device_platforms": ["mobile"], 
    "geo_locations": {"countries":["US"]}, 
    "publisher_platforms": ["facebook"] 
  }' \
  -F 'status=PAUSED' \
  -F 'access_token=<ACCESS_TOKEN>' \
  https://graph.facebook.com/v2.11/act_<AD_ACCOUNT_ID>/adsets