From d03d55ac66241f0e9bb590c323a0ea017508f78d Mon Sep 17 00:00:00 2001 From: Jon Frisby Date: Thu, 9 Apr 2026 16:14:50 -0700 Subject: [PATCH 1/2] Add S2S analytics events API documentation (C-247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document the new POST /v2/analytics_events and /v2/analytics_events/batch endpoints for server-to-server custom analytics event ingestion. - Single and batch endpoint docs with full parameter descriptions - Geo resolution priority documented: ip_address > country_code > last SDK session - Success, error (400/404/429) response examples - Added to nav.adoc ## Human-Claude Interaction Log ### Human prompts (VERBATIM): 1. "I've already updated the Linear issue's status. I've cut a branch in ../server-api-docs, so you can update the docs there." → Claude: Explored server-api-docs repo structure, read existing endpoint docs (player_properties) for conventions, created matching AsciiDoc page and nav entry. ### Key decisions made: - Human guided: docs go in server-api-docs repo on pre-cut branch - Claude discovered: repo uses Antora with AsciiDoc, followed player_properties doc conventions exactly 🤖 Generated with Claude Code Co-Authored-By: Claude --- modules/ROOT/nav.adoc | 1 + .../ROOT/pages/other/v2_analytics_events.adoc | 235 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 modules/ROOT/pages/other/v2_analytics_events.adoc diff --git a/modules/ROOT/nav.adoc b/modules/ROOT/nav.adoc index 8edb393..8015112 100644 --- a/modules/ROOT/nav.adoc +++ b/modules/ROOT/nav.adoc @@ -5,6 +5,7 @@ * xref:page$other/v2_player_properties.adoc[Player Properties] * xref:page$rewards/endpoint.adoc[Teak Reward Endpoint] * xref:page$rewards/claiming.adoc[Claiming Teak Rewards] +* xref:page$other/v2_analytics_events.adoc[Analytics Events] * xref:page$other/v2_purchase.adoc[Purchase Reporting] * xref:page$other/v2_opt_out_categories.adoc[Opt Out Category Management] * xref:page$other/v2_email.adoc[Email Address Management] diff --git a/modules/ROOT/pages/other/v2_analytics_events.adoc b/modules/ROOT/pages/other/v2_analytics_events.adoc new file mode 100644 index 0000000..1046779 --- /dev/null +++ b/modules/ROOT/pages/other/v2_analytics_events.adoc @@ -0,0 +1,235 @@ += Analytics Events + +Use this endpoint to send custom analytics events to Teak from your game server. Events submitted through this API are usable in segmentation the same way SDK-reported events are. + +This is primarily useful for game architectures where events are generated server-side and forwarded to third parties, rather than being generated by the game client. + +== Sending a Single Event +:endpoint: v2/analytics_events +:rate_limit: 100 +[cols="1h,3a", frame="none", grid="none"] +|=== +| Endpoint +| https://api.gocarrot.com/{endpoint} +| Request Type +| POST +| Content-Type +| application/json or application/x-www-form-urlencoded +| Rate Limiting +| {rate_limit} requests per second +|=== + +=== Required Parameters +[cols="1,3a", stripes="even"] +|=== +|Name | Description + +| game_id +| Your Teak App ID +| secret_key +| Your Teak Server Secret +| user_id +| The Game Assigned Player ID of the player this event is for. This must match the value provided by the game client's Teak SDK integration. +| action_type +| The event type string (e.g. "level_up", "purchase"). Corresponds to the `action_type` parameter in the SDK's `incrementEvent`. +| object_type +| The event value string (e.g. "character", "item"). Corresponds to the `object_type` parameter in the SDK's `incrementEvent`. +|=== + +=== Optional Parameters +[cols="1,3a", stripes="even"] +|=== +|Name | Description + +| object_instance_id +| Additional context for the event (e.g. "warrior", "sword"). Corresponds to the `object_instance_id` parameter in the SDK's `incrementEvent`. +| event_count +| The number of times this event occurred. Used for aggregate event reporting. +| sum_of_squares +| The sum of squared values for this event. Used for statistical calculations (variance/standard deviation). +| duration +| Duration associated with this event, in seconds. +| ip_address +| The player's IP address. Used for geographic segmentation. Takes highest priority for geo resolution. If both `ip_address` and `country_code` are provided, `ip_address` is used. +| country_code +| ISO 3166-1 alpha-2 country code for the player (e.g. "US", "GB"). Used for geographic segmentation when `ip_address` is not provided. If neither `ip_address` nor `country_code` is provided, the player's location from their most recent SDK session is used automatically. +|=== + +=== Success Response +[cols="1,3a"] +|=== +| Status Code +| 200 +| Response Body +| JSON dictionary. +[cols="1,3a", stripes="even"] +!=== +! status +! 'ok' +!=== +| Example +| [source, json] +---- +{ + "status": "ok" +} +---- +|=== + +=== Error Responses + +==== Not Found + +:response-code: 404 +:response-body: JSON dictionary with 'status' and 'errors' keys. 'status' will be 'error'. 'errors' will be a dictionary indicating which resource was not found. +:response-example: {"status":"error","errors":{"user_id":["could not be found"]}} +include::partial$response.adoc[] + +==== Rate Limit Response + +:response-code: 429 +:response-body: JSON dictionary with 'status' and 'errors' keys. 'status' will be 'rate_limit'. 'errors' will contain the key 'rate_limit' +:response-example: {"status":"rate_limit","errors":{"rate_limit":["/{endpoint} may only be called {rate_limit} times per second. Please wait a second and try again"]}} +include::partial$response.adoc[] + +== Batch Sending +:batch_endpoint: v2/analytics_events/batch +:batch_rate_limit: 100 +[cols="1h,3a", frame="none", grid="none"] +|=== +| Endpoint +| https://api.gocarrot.com/{batch_endpoint} +| Request Type +| POST +| Content-Type +| application/json or application/x-www-form-urlencoded +| Rate Limiting +| {batch_rate_limit} requests per second +| Batch Size +| Up to 100 events per request +|=== + +Use this endpoint to send multiple analytics events in a single request. This is more efficient than making individual requests when you need to report many events at once. + +=== Required Parameters +[cols="1,3a", stripes="even"] +|=== +|Name | Description + +| game_id +| Your Teak App ID +| secret_key +| Your Teak Server Secret +| events +| An array of event objects. Maximum of 100 events per request. +|=== + +=== Event Object Structure +[cols="1,3a", stripes="even"] +|=== +|Name | Description + +| user_id +| *Required.* The Game Assigned Player ID of the player this event is for. +| action_type +| *Required.* The event type string. +| object_type +| *Required.* The event value string. +| object_instance_id +| Additional context for the event. +| event_count +| The number of times this event occurred. +| sum_of_squares +| The sum of squared values for this event. +| duration +| Duration associated with this event, in seconds. +| ip_address +| The player's IP address for geographic segmentation. Takes highest priority for geo resolution. +| country_code +| ISO 3166-1 alpha-2 country code for geographic segmentation. Used when `ip_address` is not provided. Falls back to last SDK session location if neither is specified. +|=== + +=== Example Request +[source, json] +---- +{ + "game_id": "your_teak_app_id", + "secret_key": "your_server_secret", + "events": [ + { + "user_id": "player_123", + "action_type": "level_up", + "object_type": "character", + "object_instance_id": "warrior", + "event_count": 1 + }, + { + "user_id": "player_456", + "action_type": "purchase", + "object_type": "item", + "object_instance_id": "sword", + "event_count": 3, + "country_code": "US" + } + ] +} +---- + +=== Success Response +[cols="1,3a"] +|=== +| Status Code +| 200 +| Response Body +| JSON dictionary. +[cols="1,3a", stripes="even"] +!=== +! status +! 'ok' +! results +! An array of result objects, one for each event in the request. Each result contains `status`, `user_id`, and optionally `errors`. +! summary +! A summary object with `total_events`, `successful_events`, and `failed_events` counts. +!=== +| Example +| [source, json] +---- +{ + "status": "ok", + "results": [ + { + "status": "ok", + "user_id": "player_123" + }, + { + "status": "error", + "user_id": "unknown_player", + "errors": {"user_id": ["could not be found"]} + } + ], + "summary": { + "total_events": 2, + "successful_events": 1, + "failed_events": 1 + } +} +---- +|=== + +NOTE: Individual event failures do not prevent other events in the batch from being processed. Check the `results` array to determine which events were processed successfully. + +=== Error Responses + +==== Bad Request + +:response-code: 400 +:response-body: JSON dictionary with 'status' and 'errors' keys. 'status' will be 'error'. 'errors' will contain validation errors for the request. +:response-example: {"status":"error","errors":{"events":["must be a non-empty array (max 100 items)"]}} +include::partial$response.adoc[] + +==== Rate Limit Response + +:response-code: 429 +:response-body: JSON dictionary with 'status' and 'errors' keys. 'status' will be 'rate_limit'. 'errors' will contain the key 'rate_limit' +:response-example: {"status":"rate_limit","errors":{"rate_limit":["/{batch_endpoint} may only be called {batch_rate_limit} times per second. Please wait a second and try again"]}} +include::partial$response.adoc[] From faf1eabf2b8cc097fc3377c31a942fcd32310ffe Mon Sep 17 00:00:00 2001 From: Jon Frisby Date: Fri, 10 Apr 2026 16:56:50 -0700 Subject: [PATCH 2/2] Address PR review: rename params, fix descriptions, update examples (C-247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename ip_address → client_ip for consistency with v2/identify - Rename duration → value with corrected description (arbitrary numeric value, not time duration — OLAP column reuse) - Move object_type from required to optional (both endpoints) - Add "Defaults to 1 if omitted" for event_count, value, sum_of_squares - Replace batch example with coin sink use case demonstrating value and sum_of_squares fields ## Human-Claude Interaction Log ### Human prompts (VERBATIM): 1. "We're going to make some fixes to both this branch of the carrot repo, and a corresponding branch of the server-api-docs repo (feat/c-247-server-to-server-api-for-custom-analytics-events)." → Claude: Explored both repos to understand current state 2. "Take a look at PR #6 on the server-api-docs repo. The discussion there outlines what we need to do." → Claude: Read PR comments from AlexSc, identified 6 action items 3. "Mostly. I'd also like to rename the `duration` parameter to `value` in order to keep the public-facing API from becoming confusing." → Claude: Added duration→value rename to the plan, implemented all changes 4. "Looks good! Please commit your work on both branches. It looks like for the carrot repo you've made a new branch called claude/epic-ride. Please merge that into the feat/c-247-server-to-server-api-for-custom-analytics-events branch." → Claude: Committing and merging ### Key decisions made: - Human guided: rename duration→value for public API clarity - Human guided: merge worktree branch into feature branch - Claude discovered: object_type had no required validation in controller already 🤖 Generated with Claude Code Co-Authored-By: Claude --- .../ROOT/pages/other/v2_analytics_events.adoc | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/modules/ROOT/pages/other/v2_analytics_events.adoc b/modules/ROOT/pages/other/v2_analytics_events.adoc index 1046779..5b8c9eb 100644 --- a/modules/ROOT/pages/other/v2_analytics_events.adoc +++ b/modules/ROOT/pages/other/v2_analytics_events.adoc @@ -32,8 +32,6 @@ This is primarily useful for game architectures where events are generated serve | The Game Assigned Player ID of the player this event is for. This must match the value provided by the game client's Teak SDK integration. | action_type | The event type string (e.g. "level_up", "purchase"). Corresponds to the `action_type` parameter in the SDK's `incrementEvent`. -| object_type -| The event value string (e.g. "character", "item"). Corresponds to the `object_type` parameter in the SDK's `incrementEvent`. |=== === Optional Parameters @@ -41,18 +39,20 @@ This is primarily useful for game architectures where events are generated serve |=== |Name | Description +| object_type +| The event category string (e.g. "character", "item"). Corresponds to the `object_type` parameter in the SDK's `incrementEvent`. | object_instance_id | Additional context for the event (e.g. "warrior", "sword"). Corresponds to the `object_instance_id` parameter in the SDK's `incrementEvent`. | event_count -| The number of times this event occurred. Used for aggregate event reporting. +| The number of times this event occurred. Used for aggregate event reporting. Defaults to 1 if omitted. +| value +| An arbitrary numeric value associated with this event. For example, the total coin cost across all occurrences. Defaults to 1 if omitted. | sum_of_squares -| The sum of squared values for this event. Used for statistical calculations (variance/standard deviation). -| duration -| Duration associated with this event, in seconds. -| ip_address -| The player's IP address. Used for geographic segmentation. Takes highest priority for geo resolution. If both `ip_address` and `country_code` are provided, `ip_address` is used. +| The sum of squared per-occurrence values for this event. Used for statistical calculations (variance/standard deviation). Defaults to 1 if omitted. +| client_ip +| The player's IP address. Used for geographic segmentation. Takes highest priority for geo resolution. If both `client_ip` and `country_code` are provided, `client_ip` is used. | country_code -| ISO 3166-1 alpha-2 country code for the player (e.g. "US", "GB"). Used for geographic segmentation when `ip_address` is not provided. If neither `ip_address` nor `country_code` is provided, the player's location from their most recent SDK session is used automatically. +| ISO 3166-1 alpha-2 country code for the player (e.g. "US", "GB"). Used for geographic segmentation when `client_ip` is not provided. If neither `client_ip` nor `country_code` is provided, the player's location from their most recent SDK session is used automatically. |=== === Success Response @@ -134,19 +134,19 @@ Use this endpoint to send multiple analytics events in a single request. This is | action_type | *Required.* The event type string. | object_type -| *Required.* The event value string. +| The event category string. | object_instance_id | Additional context for the event. | event_count -| The number of times this event occurred. +| The number of times this event occurred. Defaults to 1 if omitted. +| value +| An arbitrary numeric value associated with this event. For example, the total coin cost across all occurrences. Defaults to 1 if omitted. | sum_of_squares -| The sum of squared values for this event. -| duration -| Duration associated with this event, in seconds. -| ip_address +| The sum of squared per-occurrence values for this event. Defaults to 1 if omitted. +| client_ip | The player's IP address for geographic segmentation. Takes highest priority for geo resolution. | country_code -| ISO 3166-1 alpha-2 country code for geographic segmentation. Used when `ip_address` is not provided. Falls back to last SDK session location if neither is specified. +| ISO 3166-1 alpha-2 country code for geographic segmentation. Used when `client_ip` is not provided. Falls back to last SDK session location if neither is specified. |=== === Example Request @@ -165,16 +165,20 @@ Use this endpoint to send multiple analytics events in a single request. This is }, { "user_id": "player_456", - "action_type": "purchase", - "object_type": "item", - "object_instance_id": "sword", - "event_count": 3, + "action_type": "spin", + "object_type": "Mr. Bling Bling's Slots", + "object_instance_id": "bet_5000", + "event_count": 10, + "value": 50000, + "sum_of_squares": 250000000, "country_code": "US" } ] } ---- +In the second event, the player spun a slot machine ten times at 5,000 coins per spin. The `value` is the total coin cost (50,000) and `sum_of_squares` is 5000^2^ x 10 = 250,000,000. + === Success Response [cols="1,3a"] |===