Used to store protos and auto-generated clients for public- and external-facing APIs.
/proto/servercontains the protobuf definitions for the server-side shop APIs communicating with the game backend./gencontains the auto-generated API clients and swagger files used to validate the protos (each binary using these protos should generate their own clients).
-
server/shop/catalog/v1/- Catalog-related protobuf definitionsenums.proto- Enumeration definitions (CatalogItemType, ChainLinkStatus)service.proto- Catalog gRPC service definitions and request/response messages
-
server/shop/purchase/v1/- Purchase/transaction management protobuf definitionsenums.proto- Enumeration definitions (Platform, PurchaseSource, PurchaseRegistrationStatus, CancellationStatus)service.proto- Purchase gRPC service definitions and request/response messages
-
server/shop/user/v1/- User/player management protobuf definitionsservice.proto- User gRPC service definitions and request/response messages
- Field Names: All protobuf field names closely match the JSON tags from the original alpha API schema.
- Polymorphism:
CatalogItemuses protobuf'soneoffeature to represent multiple types of items. - Big Integers: Go's
big.Intfields are represented asstringfields in protobuf for maximum precision and compatibility. - Timestamps: Go's
time.Timeis mapped togoogle.protobuf.Timestamp.
- Go – for protobuf/OpenAPI code generation (see
install-tool.sh). - Node.js – for Swagger merge, Redoc, and OpenAPI client generation.
- Java 11+ – required only when generating API clients (TypeScript, Java, Python, C#). The client generator is Java-based.
Generate all code (Go, OpenAPI specs, docs) and API clients for all supported languages:
make gen
# or:
./gen.sh
./gen.sh allGenerate clients for a single language:
./gen.sh typescript # TypeScript (Fetch API)
./gen.sh java # Java (native HttpClient)
./gen.sh python # Python (urllib3)
./gen.sh csharp # C# (HttpClient, netstandard2.0)Clients are framework-agnostic. Each language is generated for both egress (shop APIs) and ingress (studio APIs) from their merged OpenAPI specs.
Lint protobuf files:
make lintFormat protobuf files:
make formatGeneration produces:
- Go structs and gRPC client/server code
- gRPC-Gateway HTTP/JSON bindings
- OpenAPI v2 (swagger) documentation
- API clients for TypeScript, Java, Python, and C# for both egress and ingress (when requested via
./gen.sh [language])
Generated files are in the gen/ directory and are not committed. Each consuming project should generate its own clients as needed.
Running make gen or ./gen.sh all creates:
gen/
├── proto/ # Generated Go code
│ └── server/...
├── openapiv2/ # Per-service OpenAPI/Swagger specs
│ └── server/...
├── clients-egress/ # Egress API clients (shop: catalog, user, purchase)
│ ├── typescript/
│ ├── java/
│ ├── python/ # package: stash_api
│ └── csharp/
└── clients-ingress/ # Ingress API clients (studio)
├── typescript/
├── java/
├── python/ # package: stash_api_ingress
└── csharp/
Merged Swagger and Redoc HTML are written to docs/gen/.
- Service files (
service.swagger.json) are rich with API endpoints and full documentation - Non-service files (like
common.swagger.json,enums.swagger.json) are minimal because they only contain type definitions, not REST endpoints - This is normal behavior - only protobuf
servicedefinitions generate REST APIs
The gRPC services are automatically exposed as REST endpoints via grpc-gateway:
- GET
/api/v1/catalog- Retrieve the product catalog
-
POST
/api/v1/purchase/register- Register purchase intent{ "user_id": "string (required)", "transaction_id": "string (required)", "platform": "Platform enum (optional: 0, 1, 2, 3)", "browser": "string (optional, e.g., 'Chrome/91.0', 'any')", "device_id": "string (optional, for fraud prevention)", "source": "PurchaseSource enum (optional: 0, 1, 2)", "registrations": [ { "product_id": "string (SKU, required if guid not present)", "guid": "string (UUID, required if product_id not present)", "price_id": "string (required if price not present, typically same as product_id SKU)", "price.currency": "string (ISO-4217, e.g., 'USD')", "price.amount": "uint32 (required if price_id not present, integer, e.g., 999 for $9.99)", "quantity": "uint32 (required, > 0)" } ] } -
POST
/api/v1/purchase/cancel- Cancel pending purchase{ "user_id": "string (required)", "transaction_id": "string (required)" } Response: { "status": "CANCELLED | TRANSACTION_NOT_FOUND | ALREADY_COMPLETED", "message": "string (optional)", "transaction_id": "string" } -
POST
/api/v1/purchase/confirm- Confirm and complete purchase{ "user_id": "string (required)", "transaction_id": "string (required)", "extra_in_game_currency": "uint32 (optional)", "extra_loyalty_points": "uint32 (optional)", "extra_loyalty_credits": "uint32 (optional)" } Response: { "results": [ { "product_id": "string (SKU, required if guid not present)", "guid": "string (UUID, required if product_id not present)", } ], "transaction_id": "string" }
- POST
/api/v1/user- Get or create a player{ "player_id": "string (required, min_len=1)" }
- Recommended: Always emit enum values as integers for wire-safety when adding new enum values
- In JSON: Parsers accept both enum names (strings) and integer values, but integers are safer for schema evolution
- Example:
"platform": 1(integer) is preferred over"platform": "PLATFORM_IOS"(string)
- Default behavior: Protobuf JSON converts field names to lowerCamelCase (e.g.,
product_id→productId) - Parser requirement: Must accept both lowerCamelCase and original proto field names
- Implementation options: Can be configured to use original proto field names instead
- More details: See JSON Options documentation
The protobuf definitions include comprehensive validation:
- String fields: Required fields have
min_len=1validation - Currency codes: Exactly 3 characters (e.g., "USD", "EUR")
- Price amounts: Must match decimal number pattern
- Unsigned integers: Player level, in-game currency, and purchase limits use
uint32 - Dual identifiers: Catalog items have both
guid(optional UUID v4) andproduct_id(required SKU) - UUID validation: GUID fields use
validate.rules.string.uuid = trueconstraint - Consolidated structure: Response messages inline all sub-types to prevent namespace conflicts
The purchase system follows a 3-step transaction model:
-
Register (
/api/v1/purchase/register) - Create purchase intent- Validates product availability and pricing
- Returns offer statuses for each item
- Creates transaction context
-
Cancel (
/api/v1/purchase/cancel) - Cancel pending purchase (optional)- Cancels the transaction if payment fails or user backs out
- Frees up reserved inventory
-
Confirm (
/api/v1/purchase/confirm) - Complete the purchase- Processes payment and delivers items
- Supports optional bonus rewards (in-game currency, loyalty points/credits)
- Returns final purchase results
- gRPC-Gateway compatibility: Services include HTTP annotations for REST API generation.
- Validation rules: Messages include validation constraints for data integrity.
- Unsigned integers: Fields that can't be negative use
uint32instead ofint32. - Dual identifiers: Catalog items support both
guid(optional UUID) andproduct_id(required SKU). - Consolidated messages: All response sub-messages are inlined to avoid namespace conflicts.
- Optional price: When price amount or ID is not specified, the item is treated as free.