promoted-ruby-client
Ruby client designed for calling Promoted's Delivery and Metrics API.
More information at http://www.promoted.ai
Installation
gem 'promoted-ruby-client'
Local Development
- Clone or fork the repo on your local machine
cd promoted-ruby-clientbundle- To test interactively:
irb -Ilib -rpromoted/ruby/client
Dependencies
HTTP client for calling Promoted.
Faraday binding (provides connection pool support)
Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to deliver
Creating a Client
client = Promoted::Ruby::Client::PromotedClient.newThis client will suffice for building log requests. To send actually send traffing to the API, some configuration is required.
client = Promoted::Ruby::Client::PromotedClient.new({
:metrics_endpoint => "https://<get this from Promoted>",
:delivery_endpoint => "https://<get this from Promoted>",
:metrics_api_key => "<get this from Promoted>",
:delivery_api_key => "<get this from Promoted>"
})Client Configuration Parameters
| Name | Type | Description |
|---|---|---|
:delivery_endpoint |
String | POST URL for the Promoted Delivery API (get this from Promoted) |
:metrics_endpoint |
String | POST URL for the Promoted Metrics API (get this from Promoted) |
:metrics_api_key |
String | Used as the x-api-key header on Metrics API requests to Promoted (get this value from Promoted) |
:delivery_api_key |
String | Used as the x-api-key header on Delivery API requests to Promoted (get this value from Promoted) |
:delivery_timeout_millis |
Number | Timeout on the Delivery API call. Defaults to 3000. |
:metrics_timeout_millis |
Number | Timeout on the Metrics API call. Defaults to 3000. |
:perform_checks |
Boolean | Whether or not to perform detailed input validation, defaults to true but may be disabled for performance |
:logger |
Ruby Logger-compatible logger | Defaults to nil (no logging). Example: Logger.new(STDERR, :progname => 'promotedai')
|
:shadow_traffic_delivery_percent |
Number between 0 and 1 | % of deliver traffic that gets directed to Delivery API as "shadow traffic". Defaults to 0 (no shadow traffic). |
:send_shadow_traffic_for_control |
Boolean | If true, the deliver method will send shadow traffic for users in the CONTROL arm of an experiment. Defaults to true. |
:default_request_headers |
Hash | Additional headers to send on the request beyond x-api-key. Defaults to {} |
:default_only_log |
Boolean | If true, the deliver method will not direct traffic to Delivery API but rather return a request suitable for logging. Defaults to false. |
:should_apply_treatment_func |
Proc | Called during delivery, accepts an experiment and returns a Boolean indicating whether the request should be considered part of the control group (false) or in the experiment (true). If nil, the default behavior of checking the experiement :arm is applied. |
:warmup |
Boolean | If true, the client will prime the Net::HTTP::Persistent connection pool on construction; this can make the first few calls to Promoted complete faster. Defaults to false. |
:max_request_insertions |
Number | Maximum number of request insertions that will be passed to Delivery API on a single request (any more will be truncated by the SDK). Defaults to 1000. |
Data Types
UserInfo
Basic information about the request user.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:user_id |
String | Yes | The platform user id, cleared from Promoted logs. |
:anon_user_id |
String | Yes | A different user id (presumably a UUID) disconnected from the platform user id, good for working with unauthenticated users or implementing right-to-be-forgotten. |
:is_internal_user |
Boolean | Yes | If this user is a test user or not, defaults to false. |
:ignore_usage |
Boolean | Yes | If you want to ignore usage from this user, defaults to false. |
CohortMembership
Useful fields for experimentation during the delivery phase.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:user_info |
UserInfo | Yes | The user info structure. |
:arm |
String | Yes | 'CONTROL' or one of the TREATMENT values (see constants.rb). |
Properties
Properties bag. Has the structure:
:struct => {
:product => {
"id": "product3",
"title": "Product 3",
"url": "www.mymarket.com/p/3"
# other key-value pairs...
}
}Insertion
Content being served at a certain position.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:user_info |
UserInfo | Yes | The user info structure. |
:insertion_id |
String | Yes | Generated by the SDK (do not set) |
:request_id |
String | Yes | Generated by the SDK when needed (do not set) |
:content_id |
String | No | Identifier for the content to be shown, must be set. |
:retrieval_rank |
Number | Yes | Optional original ranking of this content item. |
:retrieval_score |
Number | Yes | Optional original quality score of this content item. |
:properties |
Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties. |
Size
User's screen dimensions.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:width |
Integer | No | Screen width |
:height |
Integer | No | Screen height |
Screen
State of the screen including scaling.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:size |
Size | Yes | Screen size |
:scale |
Float | Yes | Current screen scaling factor |
ClientHints
Alternative to user-agent strings. See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:is_mobile |
Boolean | Yes | Mobile flag |
:brand |
Array of ClientBrandHint | Yes | |
:architecture |
String | Yes | |
:model |
String | Yes | |
:platform |
String | Yes | |
:platform_version |
String | Yes | |
:ua_full_version |
String | Yes |
ClientBrandHint
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:brand |
String | Yes | Mobile flag |
:version |
String | Yes |
Location
Information about the user's location.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:latitude |
Float | No | Location latitude |
:longitude |
Float | No | Location longitude |
:accuracy_in_meters |
Integer | Yes | Location accuracy if available |
Browser
Information about the user's browser.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:user_agent |
String | Yes | Browser user agent string |
:viewport_size |
Size | Yes | Size of the browser viewport |
:client_hints |
ClientHints | Yes | HTTP client hints structure |
referrer |
String | Yes | Request referrer |
Device
Information about the user's device.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:device_type |
one of (UNKNOWN_DEVICE_TYPE, DESKTOP, MOBILE, TABLET) |
Yes | Type of device |
:brand |
String | Yes | "Apple, "google", Samsung", etc. |
:manufacturer |
String | Yes | "Apple", "HTC", Motorola", "HUAWEI", etc. |
:identifier |
String | Yes | Android: android.os.Build.MODEL; iOS: iPhoneXX,YY, etc. |
:screen |
Screen | Yes | Screen dimensions |
:ip_address |
String | Yes | Originating IP address |
:location |
Location | Yes | Location information |
:browser |
Browser | Yes | Browser information |
Paging
Paging parameters.
DeliveryRequest.retrieval_insertion_offset also impacts paging. That field indicate the offset of the retrieved insertions that are passed into Request.insertion. See detailed documentation on paging and retrieval_insertion_offset.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:offset |
Integer | Yes | The 0-based, starting index for the response page. This should be the global position. |
:size |
Integer | Yes | The number of items to return in a response page. |
Request
A request for content insertions.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:user_info |
UserInfo | Yes | The user info structure. |
:request_id |
String | Yes | Generated by the SDK when needed (do not set) |
:use_case |
String | Yes | One of the use case values, i.e. 'FEED' (see constants.rb). |
:properties |
Properties | Yes | Any additional custom properties to associate. |
:paging |
Paging | Yes | Paging parameters |
:device |
Device | Yes | Device information (as available) |
:disable_personalization |
Boolean | Yes | If true, disables personalized inputs into Delivery algorithm. |
DeliveryRequest
Input to deliver
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:experiment |
CohortMembership | Yes | A cohort to evaluation in experimentation. |
:request |
Request | No | The underlying request for content. |
:only_log |
Boolean | Yes | Defaults to false. Set to true to override whether Delivery API is called for this request. |
:retrieval_insertion_offset |
Integer | Yes | The index of the retrieved insertions set on Request.insertion list. If just sending the top-N from your retrieval, this is 0. If this is the next batch (e.g. 500 to 999), then the value is 500. This can be used to send multiple groups of retrieved insertions. This interacts with the Paging fields. More detailed documentation on paging. |
LogRequest
A log object that is sent as a RPC request to Promoted's Metrics API log endpoint. This is outputted from the deliver SDK call. Callers need to either input it into the send_log_request method or send it to the Metrics API directly. Clients should avoid manipulating this object directly.
ClientResponse
Output of deliver, includes the insertions as well as a suitable LogRequest for forwarding to Metrics API.
| Field Name | Type | Optional? | Description |
|---|---|---|---|
:insertion |
[] of Insertion | No | The paged insertions, which are from Delivery API (when deliver was called, i.e. we weren't either only-log or part of an experiment) or the input insertions (when the other conditions don't hold). |
:log_request |
LogRequest | Yes | A message suitable for logging to Metrics API via send_log_request. If the call to deliver was made (i.e. the request was not part of the CONTROL arm of an experiment or marked to only log), :log_request will not be set, as you can assume logging was performed on the server-side by Promoted. |
:client_request_id |
String | Yes | Client-generated request id sent to Delivery API and may be useful for logging and debugging. |
:execution_server |
one of 'API' or 'SDK' | Yes | Indicates if response insertions on a delivery request came from the API or the SDK. |
PromotedClient
| Method | Input | Output | Description |
|---|---|---|---|
send_log_request |
LogRequest | n/a | Forwards a LogRequest to Promoted using an HTTP client. |
deliver |
DeliveryRequest | ClientResponse | Depending on flags, either (1) makes a request (subject to experimentation) to Delivery API for insertions, which are then returned along with a LogRequest or (2) implements SDK-side paging and prepares log records that can be logged to Promoted using send_log_request or by using this structure to make the call yourself. Optionally, based on client configuration may send a random subset of requests to Delivery API as shadow traffic for integration purposes. |
close |
n/a | n/a | Closes down the client at shutdown, currently this is just to drain the thread pool that handles shadow traffic. |
Metrics API
Expected flow for Metrics logging
# Retrieve a list of content (i.e. products)
# products = fetch_my_marketplace_products()
products = [
{
id: "123",
type: "SHOE",
name: "Blue shoe",
total_sales: 1000
},
{
id: "124",
type: "SHIRT",
name: "Green shirt",
total_sales: 800
},
{
id: "125",
type: "DRESS",
name: "Red dress",
total_sales: 1200
}
]
# Transform them into an [] of Insertions.
insertions = products.map { |product|
{
:content_id => product[:id],
:properties => {
:struct => {
:type => product[:type],
:name => product[:name]
# etc
}
}
}
}
# Form a MetricsRequest
metrics_request = {
:request => {
:user_info => { :user_id => "912", :anon_user_id => "912191"},
:use_case => "FEED",
:paging => {
:offset => 0,
:size => 5
},
:properties => {
:struct => {
:active => true
}
},
:insertion => insertions
},
:only_log => true,
}
# Create a client
client = Promoted::Ruby::Client::PromotedClient.new
# Build a log request
client_response = client.deliver(metrics_request)
# Log (assuming you have configured your client with a :metrics_endpoint)
client.send_log_request(client_response[:log_request]) if client_response[:log_request]Delivery API
Expected flow for Delivery
# (continuing from the above example for Metrics)
# Form a DeliveryRequest
delivery_request = {
:request => {
:user_info => { :user_id => "912", :anon_user_id => "912191"},
:use_case => "FEED",
:paging => {
:offset => 0,
:size => 5
},
:properties => {
:struct => {
:active => true
}
},
:insertion => insertions,
},
:only_log => false
}
# Request insertions from Delivery API
client_response = client.deliver(delivery_request)
# Use the resulting insertions
client_response[:insertion]
# Log if a log request was provided (if not, deliver was called successfully
# and Promoted logged on the server-side).)
client.send_log_request(client_response[:log_request]) if client_response[:log_request]