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 audienciasads_read- Leer información de audienciasbusiness_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
- Remove leading/trailing whitespace
- Convert to lowercase
- 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
- Remove all non-numeric characters
- Keep country code if present
- 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
- Remove leading/trailing whitespace
- Convert to lowercase
- Remove punctuation
- 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
| Code | Error | Description | Solution |
|---|---|---|---|
| 190 | Invalid OAuth access token | Token expired or invalid | Regenerate token |
| 100 | Invalid parameter | Incorrect request format | Check request body |
| 200 | Permission denied | Missing permissions | Add required scopes |
| 80004 | API Too Many Calls | Rate limit exceeded | Wait 1 hour |
| 2650 | Ad account does not exist | Invalid ad account ID | Verify 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
| Endpoint | Limit | Window |
|---|---|---|
| Create Audience | 25 | 1 hour |
| Add Users | 200 | 1 hour |
| Get Audience | 200 | 1 hour |
Best Practices
- Batch Requests: Send up to 10,000 users per request
- Implement Backoff: Use exponential backoff on rate limit errors
- Monitor Usage: Track API calls to stay under limits
- 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
- Official Documentation: Facebook Marketing API
- Custom Audiences: Custom Audiences Guide
- Error Codes: Error Reference
- Rate Limits: Rate Limiting Guide
- Data Hashing: Advanced Matching