Skip to main content

Facebook Ads API Reference

Documentación completa de la API de Facebook utilizada en el sistema de sincronización de audiencias.

API Version

Version: v18.0 Base URL: https://graph.facebook.com/v18.0/ Documentation: Facebook Marketing API

Authentication

Access Token

Todos los requests requieren un Access Token con permisos específicos.

Required Permissions:

  • ads_management - Crear y gestionar audiencias
  • ads_read - Leer información de audiencias
  • business_management - Acceso a cuentas publicitarias

Token Duration: 60 días (long-lived token)

Header Format:

Authorization: Bearer {ACCESS_TOKEN}

Debug Token

GET /debug_token
?input_token={TOKEN_TO_INSPECT}
&access_token={APP_ACCESS_TOKEN}

Response:

{
"data": {
"app_id": "123456789",
"type": "USER",
"application": "My App",
"expires_at": 1699999999,
"is_valid": true,
"scopes": [
"ads_management",
"ads_read",
"business_management"
]
}
}

Ad Accounts

Get Ad Account Info

GET /{ad_account_id}
?fields=id,name,account_status,currency,timezone_name,business

Example:

curl -X GET \
"https://graph.facebook.com/v18.0/act_323379512251780" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-d "fields=id,name,account_status,currency"

Response:

{
"id": "act_323379512251780",
"name": "Kangoo Ads Account",
"account_status": 1,
"currency": "USD"
}

List Ad Accounts

GET /me/adaccounts
?fields=id,name,account_status

Custom Audiences

Create Custom Audience

POST /{ad_account_id}/customaudiences

Request Body:

{
"name": "CDP - Kangoo System - 2024-10",
"subtype": "CUSTOM",
"description": "Audiencia sincronizada desde Nerdistan CDP",
"customer_file_source": "USER_PROVIDED_ONLY"
}

Example:

curl -X POST \
"https://graph.facebook.com/v18.0/act_323379512251780/customaudiences" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "CDP - Kangoo System - 2024-10",
"subtype": "CUSTOM",
"description": "Audiencia sincronizada desde Nerdistan CDP",
"customer_file_source": "USER_PROVIDED_ONLY"
}'

Response:

{
"id": "120210000000000001",
"success": true
}

Get Custom Audience

GET /{custom_audience_id}
?fields=id,name,approximate_count,delivery_status,time_created,time_updated

Example:

curl -X GET \
"https://graph.facebook.com/v18.0/120210000000000001" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-d "fields=id,name,approximate_count,delivery_status,time_created,time_updated"

Response:

{
"id": "120210000000000001",
"name": "CDP - Kangoo System - 2024-10",
"approximate_count": 25000,
"delivery_status": {
"code": 200,
"description": "This audience is ready for use."
},
"time_created": "2024-10-01T10:30:00+0000",
"time_updated": "2024-10-01T10:45:00+0000"
}

Add Users to Custom Audience

POST /{custom_audience_id}/users

Request Body:

{
"payload": {
"schema": ["EMAIL", "PHONE", "FN", "LN"],
"data": [
[
"7d793037a0760186574b0282f2f435e7",
"8c4a7f68f47c99c0e3b8e3d8c4a7f68f",
"96a3be3cf272e017046d1b2674a52bd3",
"b8d14f5a3f4e4c9e2d1c0b9a8f7e6d5c"
],
[
"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1",
"e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
"t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5"
]
]
}
}

Important Notes:

  • Data must be hashed with SHA-256
  • Maximum 10,000 users per request
  • Normalize data before hashing (lowercase, trim spaces)

Example:

curl -X POST \
"https://graph.facebook.com/v18.0/120210000000000001/users" \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"schema": ["EMAIL", "PHONE", "FN", "LN"],
"data": [
["7d793037a0760186574b0282f2f435e7", "...", "...", "..."]
]
}
}'

Response:

{
"audience_id": "120210000000000001",
"session_id": "1234567890",
"num_received": 10000,
"num_invalid_entries": 150,
"invalid_entry_samples": {
"EMAIL": ["invalid@", "not-an-email"]
}
}

Delete Custom Audience

DELETE /{custom_audience_id}

Example:

curl -X DELETE \
"https://graph.facebook.com/v18.0/120210000000000001" \
-H "Authorization: Bearer {ACCESS_TOKEN}"

Response:

{
"success": true
}

Data Hashing

SHA-256 Normalization Rules

Facebook requiere que todos los datos personales sean hasheados con SHA-256 siguiendo reglas específicas:

Email

  1. Remove leading/trailing whitespace
  2. Convert to lowercase
  3. Hash
import hashlib

def hash_email(email: str) -> str:
normalized = email.lower().strip()
return hashlib.sha256(normalized.encode('utf-8')).hexdigest()

# Example
hash_email("John.Doe@Example.COM ")
# Output: "7d793037a0760186574b0282f2f435e7"

Phone

  1. Remove all non-numeric characters
  2. Keep country code if present
  3. Hash
def hash_phone(phone: str) -> str:
# Remove non-numeric
normalized = ''.join(filter(str.isdigit, phone))
return hashlib.sha256(normalized.encode('utf-8')).hexdigest()

# Example
hash_phone("+1 (555) 123-4567")
# Output: "8c4a7f68f47c99c0e3b8e3d8c4a7f68f"

First Name / Last Name

  1. Remove leading/trailing whitespace
  2. Convert to lowercase
  3. Remove punctuation
  4. Hash
import re

def hash_name(name: str) -> str:
# Remove punctuation
normalized = re.sub(r'[^\w\s]', '', name)
normalized = normalized.lower().strip()
return hashlib.sha256(normalized.encode('utf-8')).hexdigest()

# Example
hash_name("O'Connor")
# Output: "96a3be3cf272e017046d1b2674a52bd3"

Full Hashing Example

import hashlib
import re

class FacebookDataHasher:
@staticmethod
def hash_value(value: str) -> str:
if not value:
return ""
return hashlib.sha256(value.encode('utf-8')).hexdigest()

@staticmethod
def normalize_email(email: str) -> str:
return email.lower().strip()

@staticmethod
def normalize_phone(phone: str) -> str:
return ''.join(filter(str.isdigit, phone))

@staticmethod
def normalize_name(name: str) -> str:
normalized = re.sub(r'[^\w\s]', '', name)
return normalized.lower().strip()

def hash_customer_data(self, customer: dict) -> list:
"""Hash customer data for Facebook API"""
return [
self.hash_value(self.normalize_email(customer.get('email', ''))),
self.hash_value(self.normalize_phone(customer.get('phone', ''))),
self.hash_value(self.normalize_name(customer.get('firstname', ''))),
self.hash_value(self.normalize_name(customer.get('lastname', '')))
]

# Usage
hasher = FacebookDataHasher()
customer = {
'email': 'John.Doe@Example.COM',
'phone': '+1 (555) 123-4567',
'firstname': "John",
'lastname': "O'Connor"
}

hashed = hasher.hash_customer_data(customer)
# ['7d793037a0760186574b0282f2f435e7', '8c4a7f68f47c99c0e3b8e3d8c4a7f68f', ...]

Error Handling

Common Error Codes

CodeErrorDescriptionSolution
190Invalid OAuth access tokenToken expired or invalidRegenerate token
100Invalid parameterIncorrect request formatCheck request body
200Permission deniedMissing permissionsAdd required scopes
80004API Too Many CallsRate limit exceededWait 1 hour
2650Ad account does not existInvalid ad account IDVerify account ID format

Error Response Format

{
"error": {
"message": "Invalid OAuth access token",
"type": "OAuthException",
"code": 190,
"fbtrace_id": "AGq9rEiVcMF"
}
}

Retry Strategy

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=10)
)
def facebook_api_call(endpoint, payload):
"""API call with automatic retry"""
try:
response = requests.post(
f"https://graph.facebook.com/v18.0/{endpoint}",
headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
json=payload
)
response.raise_for_status()
return response.json()

except requests.exceptions.HTTPError as e:
if e.response.status_code == 429: # Rate limit
raise # Retry
elif e.response.status_code >= 500: # Server error
raise # Retry
else:
# Client error - don't retry
logger.error(f"API error: {e.response.json()}")
raise

except Exception as e:
logger.error(f"Unexpected error: {e}")
raise

Rate Limits

API Rate Limits

EndpointLimitWindow
Create Audience251 hour
Add Users2001 hour
Get Audience2001 hour

Best Practices

  1. Batch Requests: Send up to 10,000 users per request
  2. Implement Backoff: Use exponential backoff on rate limit errors
  3. Monitor Usage: Track API calls to stay under limits
  4. Cache Results: Cache audience info to reduce API calls
# Example: Batch processing
def add_users_in_batches(audience_id, users_data, batch_size=10000):
"""Add users to audience in batches"""

for i in range(0, len(users_data), batch_size):
batch = users_data[i:i+batch_size]

payload = {
"payload": {
"schema": ["EMAIL", "PHONE", "FN", "LN"],
"data": batch
}
}

response = facebook_api_call(
f"{audience_id}/users",
payload
)

logger.info(f"Batch {i//batch_size + 1}: {response['num_received']} users added")

# Rate limit protection
if (i + batch_size) < len(users_data):
time.sleep(2) # 2 second delay between batches

Webhooks (Future)

Custom Audience Webhooks

Facebook puede enviar webhooks cuando una audiencia cambia de estado.

Endpoint: /webhooks/facebook

Payload Example:

{
"entry": [
{
"id": "120210000000000001",
"time": 1699999999,
"changes": [
{
"field": "delivery_status",
"value": {
"code": 200,
"description": "This audience is ready for use."
}
}
]
}
]
}

Testing

Test Custom Audience Creation

#!/bin/bash
# test_facebook_audience.sh

ACCESS_TOKEN="your_access_token"
AD_ACCOUNT_ID="act_323379512251780"

# 1. Create audience
AUDIENCE_RESPONSE=$(curl -s -X POST \
"https://graph.facebook.com/v18.0/${AD_ACCOUNT_ID}/customaudiences" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Audience - CDP",
"subtype": "CUSTOM",
"description": "Test audience from CDP integration",
"customer_file_source": "USER_PROVIDED_ONLY"
}')

AUDIENCE_ID=$(echo $AUDIENCE_RESPONSE | jq -r '.id')
echo "Created audience: $AUDIENCE_ID"

# 2. Add test users
curl -X POST \
"https://graph.facebook.com/v18.0/${AUDIENCE_ID}/users" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"schema": ["EMAIL"],
"data": [
["7d793037a0760186574b0282f2f435e7"],
["a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"]
]
}
}'

# 3. Get audience info
curl -X GET \
"https://graph.facebook.com/v18.0/${AUDIENCE_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-d "fields=id,name,approximate_count,delivery_status"

# 4. Delete test audience
curl -X DELETE \
"https://graph.facebook.com/v18.0/${AUDIENCE_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"

SDK Examples

Python SDK

from facebook_business.api import FacebookAdsApi
from facebook_business.adobjects.customaudience import CustomAudience

# Initialize API
FacebookAdsApi.init(access_token=ACCESS_TOKEN)

# Create audience
audience = CustomAudience(parent_id=AD_ACCOUNT_ID)
audience.update({
CustomAudience.Field.name: 'CDP - Test Audience',
CustomAudience.Field.subtype: CustomAudience.Subtype.custom,
CustomAudience.Field.description: 'Test from Python SDK',
CustomAudience.Field.customer_file_source: CustomAudience.CustomerFileSource.user_provided_only
})

# Add users
audience.add_users(
schema=['EMAIL', 'PHONE'],
users=[
['hash1', 'hash2'],
['hash3', 'hash4']
]
)

Resources

Próximos Pasos