Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

All notable changes to `fopost` are documented here.

## Unreleased

### Added

- Ads tracking (`ads` scope): `ads.send_conversions` is the Conversions API send with a
required `idempotency_key` sent as the `Idempotency-Key` header (a reused key answers
`replayed=True`), `ads.conversion_batches` lists stored batches with hashed identifiers,
and `ads.upload_conversions` takes an optional `idempotency_key`. Events accept
`eventId`, `actionSource`, `sourceUrl`, `externalId`, `clientIpAddress`,
`clientUserAgent`, `clickId`, `browserId` and `contentIds`.
- Pixels: `ads.pixels`, `create_pixel` (the answer carries the install `code`),
`get_pixel`, `update_pixel`, `share_pixel`, `pixel_stats` (last 28 days) and
`event_match_quality`.
- Custom conversions: `ads.custom_conversions`, `create_custom_conversion`,
`get_custom_conversion`, `update_custom_conversion`, `delete_custom_conversion`.
- Models `ConversionUploadResult`, `ConversionBatch`, `HashedConversionEvent`, `AdPixel`,
`AdPixelDetail`, `PixelStats`, `PixelEventCount`, `EventMatchQuality`,
`MatchKeyCoverage`, `CustomConversion`, `CustomConversionRule`, and the tuples
`CONVERSION_ACTION_SOURCES`, `CUSTOM_CONVERSION_EVENT_TYPES`, `PIXEL_DATA_USE_SETTINGS`.

### Changed

- `ads.upload_conversions` returns a `ConversionUploadResult` (`id`, `accepted`,
`event_count`, `replayed`) instead of a dict; read `result.accepted` rather than
`result["accepted"]`.

## 0.4.0 - 2026-10-18

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@ except FopostError as err:
| `contacts` | `list`, `get`, `create`, `update`, `delete`, `conversations`, `import_csv`, `list_fields`, `create_field`, `update_field`, `delete_field`, `conversation_analytics` |
| `broadcasts` | `list`, `get`, `create`, `update`, `delete`, `send`, `cancel`, `recipients` |
| `sequences` | `list`, `get`, `create`, `update`, `delete`, `enroll`, `unenroll`, `enrollments` |
| `ads` | `list`, `external`, `boostable`, `connections`, `sources`, `providers`, `authorize`, `delete_connection`, `boost`, `create`, `refresh`, `set_status`, `delete`, `audiences`, `create_audience`, `search_targeting`, `lead_forms`, `create_lead_form`, `leads`, `account_tree`, `create_campaign`, `get_campaign`, `update_campaign`, `delete_campaign`, `duplicate_campaign`, `create_ad_set`, `get_ad_set`, `update_ad_set`, `delete_ad_set`, `duplicate_ad_set`, `create_network_ad`, `get_network_ad`, `update_network_ad`, `delete_network_ad`, `duplicate_network_ad`, `bulk_set_status`, `creatives`, `create_creative`, `get_creative`, `delete_creative`, `get_audience`, `update_audience`, `delete_audience`, `add_audience_users`, `add_audience_companies`, `estimate_reach`, `insights`, `ad_insights`, `get_lead_form`, `archive_lead_form`, `leads_feed`, `lead_pages`, `subscribe_lead_page`, `unsubscribe_lead_page`, `goals`, `catalogs`, `create_catalog`, `get_catalog`, `update_catalog`, `delete_catalog`, `catalog_products`, `write_catalog_products`, `product_feeds`, `create_product_feed`, `delete_product_feed`, `feed_uploads`, `start_feed_upload`, `product_sets`, `create_product_set`, `update_product_set`, `delete_product_set`, `reach_frequency`, `create_reach_frequency`, `get_reach_frequency`, `reserve_reach_frequency`, `cancel_reach_frequency`, `library`, `partnership_creators`, `request_partnership`, `revoke_partnership`, `account_activity`, `labels`, `create_label`, `update_label`, `delete_label`, `apply_label`, `studies`, `create_study`, `get_study`, `delete_study`, `ios_campaign_limits`, `high_demand_periods`, `create_high_demand_period`, `delete_high_demand_period`, `value_rule_sets`, `create_value_rule_set`, `delete_value_rule_set`, `bid_pricing`, `supply_forecast`, `conversion_rules`, `create_conversion_rule`, `get_conversion_rule`, `update_conversion_rule`, `delete_conversion_rule`, `attach_conversion_rule`, `detach_conversion_rule`, `conversion_metrics`, `send_conversion_events` |
| `ads` | `list`, `external`, `boostable`, `connections`, `sources`, `providers`, `authorize`, `delete_connection`, `boost`, `create`, `refresh`, `set_status`, `delete`, `audiences`, `create_audience`, `search_targeting`, `lead_forms`, `create_lead_form`, `leads`, `account_tree`, `create_campaign`, `get_campaign`, `update_campaign`, `delete_campaign`, `duplicate_campaign`, `create_ad_set`, `get_ad_set`, `update_ad_set`, `delete_ad_set`, `duplicate_ad_set`, `create_network_ad`, `get_network_ad`, `update_network_ad`, `delete_network_ad`, `duplicate_network_ad`, `bulk_set_status`, `creatives`, `create_creative`, `get_creative`, `delete_creative`, `get_audience`, `update_audience`, `delete_audience`, `add_audience_users`, `add_audience_companies`, `estimate_reach`, `insights`, `ad_insights`, `get_lead_form`, `archive_lead_form`, `leads_feed`, `lead_pages`, `subscribe_lead_page`, `unsubscribe_lead_page`, `goals`, `catalogs`, `create_catalog`, `get_catalog`, `update_catalog`, `delete_catalog`, `catalog_products`, `write_catalog_products`, `product_feeds`, `create_product_feed`, `delete_product_feed`, `feed_uploads`, `start_feed_upload`, `product_sets`, `create_product_set`, `update_product_set`, `delete_product_set`, `reach_frequency`, `create_reach_frequency`, `get_reach_frequency`, `reserve_reach_frequency`, `cancel_reach_frequency`, `library`, `partnership_creators`, `request_partnership`, `revoke_partnership`, `account_activity`, `labels`, `create_label`, `update_label`, `delete_label`, `apply_label`, `studies`, `create_study`, `get_study`, `delete_study`, `ios_campaign_limits`, `high_demand_periods`, `create_high_demand_period`, `delete_high_demand_period`, `value_rule_sets`, `create_value_rule_set`, `delete_value_rule_set`, `bid_pricing`, `supply_forecast`, `conversion_rules`, `create_conversion_rule`, `get_conversion_rule`, `update_conversion_rule`, `delete_conversion_rule`, `attach_conversion_rule`, `detach_conversion_rule`, `conversion_metrics`, `send_conversion_events`, `upload_conversions`, `send_conversions`, `conversion_batches`, `pixels`, `create_pixel`, `get_pixel`, `update_pixel`, `share_pixel`, `pixel_stats`, `event_match_quality`, `custom_conversions`, `create_custom_conversion`, `get_custom_conversion`, `update_custom_conversion`, `delete_custom_conversion` |
| `knowledge` | `list`, `create`, `update`, `delete`, `sync`, `search` |
| `ads` | `list`, `external`, `boostable`, `connections`, `sources`, `providers`, `authorize`, `delete_connection`, `boost`, `create`, `refresh`, `set_status`, `delete`, `audiences`, `create_audience`, `search_targeting`, `lead_forms`, `create_lead_form`, `leads`, `account_tree`, `create_campaign`, `get_campaign`, `update_campaign`, `delete_campaign`, `duplicate_campaign`, `create_ad_set`, `get_ad_set`, `update_ad_set`, `delete_ad_set`, `duplicate_ad_set`, `create_network_ad`, `get_network_ad`, `update_network_ad`, `delete_network_ad`, `duplicate_network_ad`, `bulk_set_status`, `creatives`, `create_creative`, `get_creative`, `delete_creative`, `get_audience`, `update_audience`, `delete_audience`, `add_audience_users`, `add_audience_companies`, `estimate_reach`, `insights`, `ad_insights`, `get_lead_form`, `archive_lead_form`, `leads_feed`, `lead_pages`, `subscribe_lead_page`, `unsubscribe_lead_page`, `bid_pricing`, `supply_forecast`, `conversion_rules`, `create_conversion_rule`, `get_conversion_rule`, `update_conversion_rule`, `delete_conversion_rule`, `attach_conversion_rule`, `detach_conversion_rule`, `conversion_metrics`, `send_conversion_events` |
| `ads` | `list`, `external`, `boostable`, `connections`, `sources`, `providers`, `authorize`, `delete_connection`, `boost`, `create`, `refresh`, `set_status`, `delete`, `audiences`, `create_audience`, `search_targeting`, `lead_forms`, `create_lead_form`, `leads`, `account_tree`, `create_campaign`, `get_campaign`, `update_campaign`, `delete_campaign`, `duplicate_campaign`, `create_ad_set`, `get_ad_set`, `update_ad_set`, `delete_ad_set`, `duplicate_ad_set`, `create_network_ad`, `get_network_ad`, `update_network_ad`, `delete_network_ad`, `duplicate_network_ad`, `bulk_set_status`, `creatives`, `create_creative`, `get_creative`, `delete_creative`, `get_audience`, `update_audience`, `delete_audience`, `add_audience_users`, `add_audience_companies`, `estimate_reach`, `insights`, `ad_insights`, `get_lead_form`, `archive_lead_form`, `leads_feed`, `lead_pages`, `subscribe_lead_page`, `unsubscribe_lead_page`, `bid_pricing`, `supply_forecast`, `conversion_rules`, `create_conversion_rule`, `get_conversion_rule`, `update_conversion_rule`, `delete_conversion_rule`, `attach_conversion_rule`, `detach_conversion_rule`, `conversion_metrics`, `send_conversion_events`, `upload_conversions`, `send_conversions`, `conversion_batches`, `pixels`, `create_pixel`, `get_pixel`, `update_pixel`, `share_pixel`, `pixel_stats`, `event_match_quality`, `custom_conversions`, `create_custom_conversion`, `get_custom_conversion`, `update_custom_conversion`, `delete_custom_conversion` |
| `google_business` | `get_location`, `update_location`, `get_attributes`, `update_attributes`, `get_menus`, `replace_menus`, `get_services`, `replace_services`, `list_media`, `add_media`, `delete_media`, `list_place_actions`, `create_place_action`, `update_place_action`, `delete_place_action`, `get_verification_options`, `start_verification`, `complete_verification`, `get_performance`, `get_search_keywords`, `assign` |
| `blog` | `get_settings`, `update_settings`, `rotate_website_secret`, `test_website`, `list_keywords`, `suggest_seeds`, `plan_keywords`, `add_keyword`, `update_keyword`, `delete_keyword`, `list_articles`, `request_article`, `get_article`, `update_article`, `regenerate_article`, `submit_article`, `publish_article`, `reject_article`, `delete_article` |
| `blogs` | `list_blogs`, `list_articles`, `get_article`, `create_article`, `update_article`, `delete_article`, `list_products`, `update_product` |
Expand Down
28 changes: 28 additions & 0 deletions src/fopost/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
BLOG_KEYWORD_STATUSES,
BLOG_TARGET_KINDS,
CONNECT_SESSION_STATUSES,
CONVERSION_ACTION_SOURCES,
CUSTOM_CONVERSION_EVENT_TYPES,
PIXEL_DATA_USE_SETTINGS,
PLATFORMS,
POST_STATUSES,
WEBHOOK_EVENTS,
Expand All @@ -63,6 +66,8 @@
AdInsights,
AdInsightsReport,
AdLibraryPage,
AdPixel,
AdPixelDetail,
AdProvider,
AdSet,
AdSetNode,
Expand Down Expand Up @@ -142,8 +147,12 @@
ContentSignal,
ConversationAnalytics,
ConversationAnalyticsRow,
ConversionBatch,
ConversionMetrics,
ConversionRule,
ConversionUploadResult,
CustomConversion,
CustomConversionRule,
DecayBand,
Delivery,
DemographicAccountRef,
Expand All @@ -157,6 +166,7 @@
DiscordRole,
DiscordScheduledEvent,
Enrollment,
EventMatchQuality,
ExternalAd,
FeedLead,
FollowerHistoryPoint,
Expand All @@ -179,6 +189,7 @@
GoogleRecommendationImpact,
GoogleSearchTerm,
GoogleSharedSet,
HashedConversionEvent,
InboxAccount,
InboxAccountRef,
InboxAnalytics,
Expand Down Expand Up @@ -221,6 +232,7 @@
LeadsFeedPage,
LeadsPage,
LinkedInMention,
MatchKeyCoverage,
MediaItem,
MetaGreeting,
MetaGreetingText,
Expand All @@ -237,6 +249,8 @@
Page,
PageMeta,
PinterestBoard,
PixelEventCount,
PixelStats,
Platform,
PlatformMetricRow,
PlatformMetricsBlock,
Expand Down Expand Up @@ -572,6 +586,20 @@
"BidPricing",
"ConversionMetrics",
"ConversionRule",
"ConversionUploadResult",
"HashedConversionEvent",
"ConversionBatch",
"AdPixel",
"AdPixelDetail",
"PixelEventCount",
"PixelStats",
"MatchKeyCoverage",
"EventMatchQuality",
"CustomConversionRule",
"CustomConversion",
"CONVERSION_ACTION_SOURCES",
"CUSTOM_CONVERSION_EVENT_TYPES",
"PIXEL_DATA_USE_SETTINGS",
"ReachEstimate",
"SupplyForecast",
"BLOG_ARTICLE_STATUSES",
Expand Down
14 changes: 9 additions & 5 deletions src/fopost/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import time
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from email.utils import parsedate_to_datetime
from typing import Any
from urllib.parse import urlsplit
Expand Down Expand Up @@ -89,13 +89,15 @@ def request(
params: dict[str, Any] | None = None,
data: dict[str, Any] | None = None,
files: Sequence[tuple[str, tuple[str, bytes, str]]] | None = None,
headers: Mapping[str, str] | None = None,
) -> Any:
"""Send a request, retrying on 429, and return the decoded body.

``data`` and ``files`` switch the body to ``multipart/form-data``.
``data`` and ``files`` switch the body to ``multipart/form-data``;
``headers`` adds per-request headers such as ``Idempotency-Key``.
"""
clean_params = {k: v for k, v in params.items() if v is not None} if params else None
headers = self.headers
headers = {**self.headers, **(headers or {})}
if files is not None:
# httpx sets the multipart boundary itself.
headers = {k: v for k, v in headers.items() if k != "Content-Type"}
Expand Down Expand Up @@ -153,8 +155,10 @@ def _decode(self, response: httpx.Response) -> Any:
def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
return self.request("GET", path, params=params)

def post(self, path: str, json: Any | None = None) -> Any:
return self.request("POST", path, json=json)
def post(
self, path: str, json: Any | None = None, *, headers: Mapping[str, str] | None = None
) -> Any:
return self.request("POST", path, json=json, headers=headers)

def put(self, path: str, json: Any | None = None) -> Any:
return self.request("PUT", path, json=json)
Expand Down
130 changes: 130 additions & 0 deletions src/fopost/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1779,6 +1779,136 @@ class ConversionMetrics(FopostModel):
cost_per_conversion_minor: int | None = None


CONVERSION_ACTION_SOURCES = (
"website",
"app",
"email",
"phone_call",
"chat",
"physical_store",
"system_generated",
"business_messaging",
"other",
)

CUSTOM_CONVERSION_EVENT_TYPES = (
"ADD_PAYMENT_INFO",
"ADD_TO_CART",
"ADD_TO_WISHLIST",
"COMPLETE_REGISTRATION",
"CONTENT_VIEW",
"INITIATED_CHECKOUT",
"LEAD",
"PURCHASE",
"SEARCH",
"CONTACT",
"CUSTOMIZE_PRODUCT",
"DONATE",
"FIND_LOCATION",
"SCHEDULE",
"START_TRIAL",
"SUBMIT_APPLICATION",
"SUBSCRIBE",
"OTHER",
)

PIXEL_DATA_USE_SETTINGS = ("advertising_and_analytics", "analytics_only")


class ConversionUploadResult(FopostModel):
"""What the API answers to a conversion batch; ``replayed`` marks a reused key."""

id: str | None = None
accepted: int = 0
event_count: int = 0
replayed: bool = False


class HashedConversionEvent(FopostModel):
"""A stored event. Identifiers are kept as hashes only."""

event_name: str
occurred_at: str | None = None
event_id: str | None = None
action_source: str | None = None
email_hash: str | None = None
phone_hash: str | None = None
external_id_hash: str | None = None
value_minor: int | None = None
currency: str | None = None
order_id: str | None = None


class ConversionBatch(FopostModel):
id: str
workspace_id: str | None = None
connection_id: str | None = None
ad_account_id: str | None = None
pixel_id: str | None = None
idempotency_key: str | None = None
event_count: int = 0
accepted: int = 0
events: list[HashedConversionEvent] = []
created_at: str | None = None


class AdPixel(FopostModel):
id: str
name: str


class AdPixelDetail(AdPixel):
code: str | None = None
created_at: str | None = None
last_fired_at: str | None = None
is_unavailable: bool = False
data_use_setting: str | None = None


class PixelEventCount(FopostModel):
event_name: str
count: int = 0


class PixelStats(FopostModel):
"""Event counts over the last 28 days."""

since: str | None = None
until: str | None = None
events: list[PixelEventCount] = []


class MatchKeyCoverage(FopostModel):
identifier: str
coverage_percent: float = 0


class EventMatchQuality(FopostModel):
event_name: str
score: float | None = None
match_keys: list[MatchKeyCoverage] = []


class CustomConversionRule(FopostModel):
event_name: str | None = None
url_contains: str | None = None
url_equals: str | None = None


class CustomConversion(FopostModel):
id: str
name: str
pixel_id: str | None = None
event_type: str | None = None
rule: CustomConversionRule | None = None
description: str | None = None
default_value_minor: int | None = None
is_archived: bool = False
is_unavailable: bool = False
created_at: str | None = None
last_fired_at: str | None = None


class LeadFormDetail(LeadForm):
page_id: str | None = None
privacy_policy_url: str | None = None
Expand Down
Loading
Loading