Class: OpenaiAds::AdvertiserApiClient
- Inherits:
-
BaseService
- Object
- BaseService
- OpenaiAds::AdvertiserApiClient
- Defined in:
- app/services/openai_ads/advertiser_api_client.rb
Overview
Service object: advertiser API client.
Constant Summary collapse
- BASE_URL =
URL for base.
'https://api.ads.openai.com/v1'- PAGE_LIMIT =
Page size for the campaigns list endpoint. The API caps at 500,
which is well above the realistic active-campaign count for one
ad account (Google Ads parallel runs ~50 campaigns) so we expect
a single page in the common case. 500
Instance Attribute Summary
Attributes inherited from BaseService
Instance Method Summary collapse
-
#campaign_insights(token:, campaign_id:) ⇒ Array<Hash>
Daily performance rows for ONE campaign.
-
#create_paused_ocpc_product_feed_campaign(token:, name:, description:, product_feed_id:, lifetime_spend_limit_micros:, conversion_event_setting_id:, targeting:) ⇒ Hash
Create the shell for a product-feed oCPC experiment.
-
#list_campaigns(token:) ⇒ Array<Hash>
List all campaigns on the ad account.
Methods inherited from BaseService
#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #process, #tagged_logger
Constructor Details
This class inherits a constructor from BaseService
Instance Method Details
#campaign_insights(token:, campaign_id:) ⇒ Array<Hash>
Daily performance rows for ONE campaign.
Verified against the live API 2026-07-27 — three traps, all load-bearing:
- insights are per campaign (
/campaigns/{id}/insights); there is
no account-level or flat/insightsendpoint (they 404). fieldsmust use indexed query syntax (fields[0]=…) and the
dotted vocabulary; a comma string orfields[]is a 400.start_date/end_dateare accepted and ignored — the endpoint
always returns its full retained window, so callers MUST filter on
readable_timethemselves.
spendis in whole currency units (dollars), NOT micros, and there is no
conversions field in the vocabulary.
120 121 122 123 124 125 126 127 |
# File 'app/services/openai_ads/advertiser_api_client.rb', line 120 def campaign_insights(token:, campaign_id:) fields = %w[campaign.id campaign.spend campaign.clicks campaign.impressions metadata.readable_time] query = fields.each_with_index.map { |f, i| "fields[#{i}]=#{f}" }.join('&') response = connection(token).get("campaigns/#{campaign_id}/insights?#{query}") raise_unless_ok(response, 'campaign_insights') Array(safe_parse(response.body)['data']) end |
#create_paused_ocpc_product_feed_campaign(token:, name:, description:, product_feed_id:, lifetime_spend_limit_micros:, conversion_event_setting_id:, targeting:) ⇒ Hash
Create the shell for a product-feed oCPC experiment. This deliberately
hardcodes paused so an account-management script cannot accidentally
begin spending before the ad group, CPA bid, product set, and ad template
have been reviewed. OpenAI requires exactly one standard conversion event
for conversion bidding; accepting one ID instead of an array preserves
that invariant at the client boundary.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'app/services/openai_ads/advertiser_api_client.rb', line 81 def create_paused_ocpc_product_feed_campaign(token:, name:, description:, product_feed_id:, lifetime_spend_limit_micros:, conversion_event_setting_id:, targeting:) payload = { name:, description:, status: 'paused', mode: 'product_feed', product_feed_id:, budget: { lifetime_spend_limit_micros: }, bidding_type: 'conversions', conversion_event_setting_ids: [conversion_event_setting_id], targeting: } response = connection(token).post('campaigns') { |req| req.body = payload.to_json } raise_unless_ok(response, 'create_paused_ocpc_product_feed_campaign') body = safe_parse(response.body) body['data'] || body end |
#list_campaigns(token:) ⇒ Array<Hash>
List all campaigns on the ad account. Walks the cursor pagination
until has_more is false and returns the concatenated list.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'app/services/openai_ads/advertiser_api_client.rb', line 41 def list_campaigns(token:) campaigns = [] cursor = nil loop do response = connection(token).get('campaigns') do |req| req.params['limit'] = PAGE_LIMIT req.params['after'] = cursor if cursor req.params['order'] = 'asc' end raise_unless_ok(response, 'list_campaigns') body = safe_parse(response.body) page = Array(body['data']) campaigns.concat(page) break unless body['has_more'] cursor = body['last_id'] || page.last&.dig('id') break unless cursor # defensive — `has_more` true with no cursor would loop forever end campaigns end |