Class: OpenaiAds::ApiClient

Inherits:
BaseService show all
Defined in:
app/services/openai_ads/api_client.rb

Overview

Service object: API client.

Constant Summary collapse

BASE_URL =

URL for base.

'https://bzr.openai.com/v1'

Instance Attribute Summary

Attributes inherited from BaseService

#options

Instance Method Summary collapse

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

#send_events(pixel_id:, token:, events:, validate_only: false) ⇒ Hash

Send one or more conversion events.

Parameters:

  • pixel_id (String)

    the OpenAI Ads Pixel ID (passed as ?pid=).

  • token (String)

    the OpenAI Ads CAPI bearer token.

  • events (Array<Hash>)

    event objects per the OpenAI Ads spec
    (id, type, timestamp_ms, oppref?, source_url?,
    action_source?, user?, data).

  • validate_only (Boolean) (defaults to: false)

    when true, OpenAI validates the payload
    without persisting events — used during canary rollout to verify
    schema acceptance before flipping to real sends.

Returns:

  • (Hash)

    { status: :reported|:validate_only_ok|:rate_limited|:failed, http_status:, error: }



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/services/openai_ads/api_client.rb', line 27

def send_events(pixel_id:, token:, events:, validate_only: false)
  # `pid` is a query param per OpenAI's docs, set via `req.params`.
  # Faraday's `post(path, body)` 2nd positional is the request body,
  # not the query string — passing `{ pid: pixel_id }` there put the
  # pixel id into the body where it then got overwritten by the JSON
  # `req.body = ...` assignment below, leaving the URL without a pid
  # and triggering OpenAI's `{type: 'missing', loc: ('query', 'pid')}`
  # rejection (AppSignal #5214). Setting `req.params` inside the
  # block is the correct shape.
  response = connection(token).post('events') do |req|
    req.params[:pid] = pixel_id
    req.body = { validate_only: validate_only, events: events }.to_json
  end

  case response.status
  when 200
    if validate_only
      Rails.logger.info 'OpenaiAds::ApiClient: validate_only payload accepted (HTTP 200)'
      { status: :validate_only_ok, http_status: 200 }
    else
      Rails.logger.info 'OpenaiAds::ApiClient: events accepted (HTTP 200)'
      { status: :reported, http_status: 200 }
    end
  when 429
    error_msg = 'Rate limited (HTTP 429)'
    Rails.logger.warn "OpenaiAds::ApiClient: #{error_msg}"
    { status: :rate_limited, http_status: 429, error: error_msg }
  else
    body      = safe_parse(response.body)
    error_msg = body['message'] || body['error'] || "HTTP #{response.status}"
    Rails.logger.error "OpenaiAds::ApiClient: Failed -- #{error_msg}"
    { status: :failed, http_status: response.status, error: error_msg }
  end
rescue Faraday::TimeoutError => e
  # Keep status :failed so ConversionRetrySweepWorker still re-enqueues it
  # (it selects result == 'failed'); the `timeout` flag lets the reporter
  # skip AppSignal noise for these transient, already-retried failures (#5214).
  { status: :failed, http_status: nil, error: "Timeout: #{e.message}", timeout: true }
rescue Faraday::Error => e
  { status: :failed, http_status: nil, error: e.message }
end