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
14 changes: 10 additions & 4 deletions docs/config/swagger-merger-ingress-config.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"swagger": "2.0",
"info": {
"title": "Stash Managed Catalog API",
"description": "Read-only API for querying the Stash managed product catalog. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/sdk/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely — you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/sdk/studio/{shop_id}/products\"\n```",
"title": "Stash Studio Read API",
"description": "Read-only APIs for querying a shop's managed product catalog and loyalty program. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/sdk/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely; you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/sdk/studio/{shop_id}/products\"\n```",
"version": "1.0.0",
"contact": {
"name": "API Support",
Expand All @@ -17,13 +17,19 @@
{
"name": "Managed Catalog",
"description": "Read-only endpoints for querying the managed product catalog"
},
{
"name": "Loyalty",
"description": "Read-only endpoints for querying a shop's loyalty program"
}
],
"paths": {
"$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#paths"
"$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#paths",
"$ref": "../../gen/openapiv2/server/ingress/loyalty/v1/service.swagger.json#paths"
},
"definitions": {
"$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#definitions"
"$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#definitions",
"$ref": "../../gen/openapiv2/server/ingress/loyalty/v1/service.swagger.json#definitions"
},
"securityDefinitions": {
"$ref": "../../gen/openapiv2/server/ingress/server.swagger.json#securityDefinitions"
Expand Down
1,032 changes: 1,030 additions & 2 deletions docs/gen/swagger.ingress.v1.json

Large diffs are not rendered by default.

518 changes: 518 additions & 0 deletions docs/gen/swagger.v1.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions server/egress/shop/purchase/v1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "server/egress/shop/purchase/v1/enums.proto";
import "server/egress/shop/v1/enums.proto";
import "server/egress/shop/v1/loyalty.proto";
import "validate/validate.proto";

option go_package = "github.com/stashgg/public-api/gen/proto/server/egress/shop/purchase/v1";
Expand Down Expand Up @@ -162,6 +163,9 @@ message RegisterPaymentRequest {
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Region/country code using ISO-3166-1 alpha-2 code (e.g., 'US', 'CA', 'GB')"}
];
// Additive, optional. Present only for shops with an active loyalty campaign;
// absent otherwise, so the request is byte-identical to before for non-loyalty shops.
server.egress.shop.v1.PlayerLoyaltyState loyalty = 15 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign."}];
}

message RegisterPaymentResponse {
Expand Down Expand Up @@ -443,6 +447,10 @@ message ConfirmPaymentRequest {
];
}
repeated BonusItem bonus_items = 19 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Optional list of bonus items to be granted with the purchase"}];

// Additive, optional. Present only for shops with an active loyalty campaign;
// absent otherwise, so the request is byte-identical to before for non-loyalty shops.
server.egress.shop.v1.PlayerLoyaltyState loyalty = 25 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign."}];
}

message ConfirmPaymentResponse {
Expand Down
9 changes: 9 additions & 0 deletions server/egress/shop/v1/enums.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ enum PaymentEnvironment {
PAYMENT_ENVIRONMENT_TEST = 1;
PAYMENT_ENVIRONMENT_PRODUCTION = 2;
}

enum LoyaltyRewardType {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {description: "The kind of reward granted at a loyalty milestone."};
LOYALTY_REWARD_TYPE_UNSPECIFIED = 0;
LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY = 1;
LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY = 2;
LOYALTY_REWARD_TYPE_LOYALTY_POINTS = 3;
LOYALTY_REWARD_TYPE_SKU_ITEM = 4;
}
64 changes: 64 additions & 0 deletions server/egress/shop/v1/loyalty.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
syntax = "proto3";

package server.egress.shop.v1;

import "protoc-gen-openapiv2/options/annotations.proto";
import "server/egress/shop/v1/enums.proto";

option go_package = "github.com/stashgg/public-api/gen/proto/server/egress/shop/v1";

// PlayerLoyaltyState is the loyalty program sub-payload describing a player's
// standing in a shop's active loyalty campaign at a point in time, embedded on
// purchase requests to report the standing that applied to a purchase. It is a
// snapshot: every value is scoped to the live campaign identified by campaign_id.
// Its Milestone and MilestoneRewardItem messages are nested so this egress
// payload is self-contained and shares nothing with other APIs.
message PlayerLoyaltyState {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema: {
title: "Player Loyalty State"
description: "A player's standing in a shop's active loyalty campaign at a point in time."
}
};
string campaign_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the live loyalty campaign (season) this snapshot is scoped to."}];
uint32 total_points = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty points balance for this campaign."}];
int32 points_delta = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change."}];
optional string current_tier_id = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance."}];
optional string previous_tier_id = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change."}];
optional uint32 points_to_next_tier = 6 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier."}];
optional uint32 points_to_next_milestone = 7 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone."}];
Milestone next_milestone = 8 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone."}];
// points_multiplier_permille is a fixed-point ratio expressed in permille
// (thousandths) so partners can compute in-game bonuses with exact,
// deterministic integer arithmetic across platforms. Divide by 1000 to get the
// multiplier: 1000 = 1.0x, 1500 = 1.5x, 3000 = 3.0x.
uint32 points_multiplier_permille = 9 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration."}];
Comment thread
cursor[bot] marked this conversation as resolved.

// MilestoneRewardItem describes a single reward granted at a loyalty milestone.
message MilestoneRewardItem {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema: {
title: "Milestone Reward Item"
description: "A single reward granted when a loyalty milestone is reached."
}
};
string item_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards."}];
uint32 quantity = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Amount of the reward granted."}];
LoyaltyRewardType type = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)."}];
}

// Milestone identifies the next loyalty milestone a player is progressing toward
// and lists every reward it grants. Kept intentionally minimal (an identifier
// plus the reward list) and extensible; full milestone configuration is served
// by the loyalty read API.
message Milestone {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema: {
title: "Milestone"
description: "The next loyalty milestone a player is progressing toward and the rewards it grants."
}
};
string milestone_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API."}];
repeated MilestoneRewardItem rewards = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "All rewards granted when this milestone is reached."}];
}
}
16 changes: 16 additions & 0 deletions server/ingress/loyalty/v1/enums.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";

package server.ingress.loyalty.v1;

import "protoc-gen-openapiv2/options/annotations.proto";

option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress/loyalty/v1";

enum LoyaltyRewardType {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {description: "The kind of reward granted at a loyalty milestone."};
LOYALTY_REWARD_TYPE_UNSPECIFIED = 0;
LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY = 1;
LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY = 2;
LOYALTY_REWARD_TYPE_LOYALTY_POINTS = 3;
LOYALTY_REWARD_TYPE_SKU_ITEM = 4;
}
Loading
Loading