The Stigg Sidecar is a tiny service that runs alongside your main application, acting as a proxy between the host and the Stigg API. The service provides low latency entitlement checks, handles caching, and subscribes to real-time entitlement and usage data updates. The benefits of deploying the Sidecar service:
  • Less CPU consumption and memory footprint for the host application when compared to embedding the SDK directly.
  • Language neutral API defined in Protocol Buffers accessible over gRPC.
  • Support for in-memory cache or external cache (like Redis) for entitlements and usage data.
  • Scaled together with the main application, or independently if deployed as a standalone service.

Overview

The sidecar service can be deployed together with the host application/service by utilizing the sidecar pattern - meaning each application has a sidecar container next to it which it can send requests to.
Alternatively it can be deployed as a standalone service which can be accessed remotely over an exposed port.
The service can be scaled horizontally to support a higher volume of requests, but keep in mind that if in-memory cache is used, there will be a higher ratio of cache misses. In case of a cache miss, the service will fetch the data over the network directly from API, and update the cache to serve future requests. The sidecar is not intended to be exposed to the internet or to be accessed from a browser.
Sidecar running with in-memory cache

Sidecar running with in-memory cache

Sidecar service can be deployed in the same network namespace (or same K8 Pod), as the main application.
Once entitlement data is fetched from the Stigg API, it is persisted in the local or external cache.
Local cache invalidation is handled by the Sidecar service. If using an external cache, an instance of the Persistent Cache Service must be deployed as well to handle cache updates, as illustrated below:
Sidecar running with an external (Redis) cache

Sidecar running with an external (Redis) cache

Running the service

Prerequisites

  • Docker
  • Redis instance, if a persistent cache is in use

Usage

Run the service, replacing <tag> with the latest version in the ECR Public Gallery:
docker run -it -p 80:80 \ 
  -e SERVER_API_KEY="<SERVER_API_KEY>" \
  public.ecr.aws/stigg/sidecar:<tag>

Sidecar SDK

To interface with the Sidecar service API, please refer to the Sidecar SDK documentation:

Sidecar SDK

Since Sidecar SDK version 3.0.0, the TLS connection method has been deprecated in favor of non-TLS. Please ensure you are running a compatible Sidecar image version 2.494.0 or later. For backwards compatibility details, check the changelog. TLS self-signed certificates will expire on January 26, 2026, so upgrading the Sidecar image is strongly recommended.

Available options

Execution of the Sidecar service can be customized using the following environment variables:
SERVER_API_KEY
string
required
The server API key of the environment.
API_URL
string
default:"https://api.stigg.io"
The URL of the Stigg API.
EDGE_ENABLED
boolean
default:"true"
Whether entitlements will be accessed from Edge.
EDGE_API_URL
string
default:"https://edge.api.stigg.io"
The Edge URL from which entitlements will be accessed.
WS_ENABLED
boolean
default:"true"
Whether to listen for updates using WebSockets.
WS_URL
string
default:"wss://api.stigg.io"
The WebSocket API URL.
REDIS_ENVIRONMENT_PREFIX
string
Identifier of the environment, used to prefix the keys in Redis. If provided, Redis will be used as the cache layer.
REDIS_HOST
string
default:"localhost"
Redis host.
REDIS_PORT
number
default:"6379"
Redis port.
REDIS_DB
number
default:"0"
Redis DB identifier.
REDIS_USERNAME
string
Redis username.
REDIS_PASSWORD
string
Redis password.
REDIS_TLS
boolean
default:"0"
Redis use TLS encryption. (Default=0)
REDIS_KEYS_TTL_IN_SECS
number
default:"604800 (7 days)"
Time period for Redis to keep the data before eviction in seconds.
ENTITLEMENTS_FALLBACK
string
GRPC_PORT
number
default:"80"
Service port (HTTP/2)
PORT
number
default:"8443"
Deprecated TLS service port (HTTP/2 TLS)
CACHE_MAX_SIZE_BYTES
number
default:"50% of total available memory size"
Size of the in-memory cache.
HEALTH_ENDPOINT_URL
string
default:"livez"
Health endpoint URL.
READY_ENDPOINT_URL
string
default:"readyz"
Ready endpoint URL.
METRICS_PORT
number
default:"8080"
The port of the health and metrics endpoints.
LOG_LEVEL
string
default:"warn"
Log level, can be one of: error, warn, info, debug.
OFFLINE
boolean
default:"false"
Enables offline mode for local development.

Error handling

When the Sidecar encounters startup errors, such as an invalid API key or network errors to the Stigg API, it continues to run and serves entitlements from the:
  1. Persistent cache (if available)
  2. Global fallback strategy

Service monitoring

Health

The service exposes two endpoints accessible via HTTP: GET /livez Returns 200 if the service is alive. Healthy response:
{ "status": "UP" }
GET /readyz Returns 200 if the service is ready. Healthy response:
{ "status": "UP" }

Metrics

The Sidecar exposes a GET /metrics endpoint that returns service metrics in Prometheus format. This endpoint includes both system-level and Sidecar-specific metrics. These metrics are helpful for monitoring the health and performance of your Sidecar service.
sidecar_initialization_errors_total
number
Total number of SDK initialization errors (e.g., due to misconfiguration or runtime issues).
sidecar_invalid_api_key_errors_total
number
Total number of invalid API key errors encountered during SDK operation.
sidecar_network_request_errors_total
number
Total number of network request failures between the SDK and the Stigg backend.
sidecar_redis_client_errors_total
number
Total number of Redis client errors.
sidecar_cache_hits_total
number
Total number of times data was successfully retrieved from the Sidecar cache.
sidecar_cache_misses_total
number
Total number of times data was not found in the Sidecar cache.
These metrics can be scraped and visualized in any Prometheus-compatible observability stack like Grafana.

Persistent caching

In order for the cached data to survive service restarted, or shared across multiple instances of the Sidecar service, you can use Redis as the cache layer by providing the REDIS_* environment variables, replacing <tag> with the latest version in the ECR Public Gallery:
docker run -it -p 80:80 \
    -e SERVER_API_KEY="<SERVER_API_KEY>" \
    -e REDIS_ENVIRONMENT_PREFIX="production" \
    -e REDIS_HOST="localhost" \
    public.ecr.aws/stigg/sidecar:<tag>
To keep the cache up-to-date, you will also need to run a persistent cache service in a separate process.

Global fallback strategy

Global fallback strategy can be set by providing the Sidecar service with the ENTITLEMENTS_FALLBACK environment variable. It expects a value in a JSON object in a string format. For example, the following global fallback configuration JSON structure:
{
  'feature-01-templates': {
    hasAccess: true,
    usageLimit: 1000,
  },
  'feature-02-campaigns': {
    hasAccess: true,
    isUnlimited: true
  }
}
Can be formatted using JSON.stringify and then set as value of ENTITLEMENTS_FALLBACK when running the container, replacing <tag> with the latest version in the ECR Public Gallery:
docker run -it -p 80:80 \ 
  -e SERVER_API_KEY="<SERVER_API_KEY>" \
  -e ENTITLEMENTS_FALLBACK='{"feature-01-templates":{"hasAccess":true,"usageLimit":1000},"feature-02-campaigns":{"hasAccess":true,"isUnlimited":true}}' \
  public.ecr.aws/stigg/sidecar:<tag>

Offline mode

During local development or testing, you might want to avoid making network requests to the Stigg API.
To do this, you can run the Sidecar service in offline mode by enabling the offline option. When enabled, API key validation will always succeed, regardless of the key provided, replacing <tag> with the latest version in the ECR Public Gallery:
docker run -it -p 80:80 \
    -e SERVER_API_KEY="localhost" \
    -e OFFLINE=TRUE \
    public.ecr.aws/stigg/sidecar:<tag>
In offline mode, the Sidecar respects the global fallback strategy, and entitlement evaluations are limited to the values defined as fallback entitlements. All other Sidecar service methods will effectively become no-ops. For example, replacing <tag> with the latest version in the ECR Public Gallery:
docker run -it -p 80:80 \ 
  -e SERVER_API_KEY="<SERVER_API_KEY>" \
  -e OFFLINE=TRUE \
  -e ENTITLEMENTS_FALLBACK='{"feature-01-templates":{"hasAccess":true,"usageLimit":1000},"feature-02-campaigns":{"hasAccess":true,"isUnlimited":true}}' \
  public.ecr.aws/stigg/sidecar:<tag>

Service schema

syntax = "proto3";

package stigg.sidecar.v1;

import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";

enum AccessDeniedReason {
  ACCESS_DENIED_REASON_UNSPECIFIED = 0;
  ACCESS_DENIED_REASON_UNKNOWN = 1;
  ACCESS_DENIED_REASON_CUSTOMER_IS_ARCHIVED = 2;
  ACCESS_DENIED_REASON_CUSTOMER_NOT_FOUND = 3;
  ACCESS_DENIED_REASON_CUSTOMER_RESOURCE_NOT_FOUND = 4;
  ACCESS_DENIED_REASON_FEATURE_NOT_FOUND = 5;
  ACCESS_DENIED_REASON_NO_ACTIVE_SUBSCRIPTION = 6;
  ACCESS_DENIED_REASON_NO_FEATURE_ENTITLEMENT_IN_SUBSCRIPTION = 7;
  ACCESS_DENIED_REASON_REQUESTED_USAGE_EXCEEDING_LIMIT = 8;
  ACCESS_DENIED_REASON_BUDGET_EXCEEDED = 9;
  ACCESS_DENIED_REASON_REQUESTED_VALUES_MISMATCH = 10;
  ACCESS_DENIED_REASON_FEATURE_TYPE_MISMATCH = 11;
  ACCESS_DENIED_REASON_INSUFFICIENT_CREDITS = 12;
  ACCESS_DENIED_REASON_REVOKED = 13;
}

enum FeatureType {
  FEATURE_TYPE_UNSPECIFIED = 0;
  FEATURE_TYPE_BOOLEAN = 1;
  FEATURE_TYPE_NUMBER = 2;
  FEATURE_TYPE_ENUM = 3;
}

enum MeterType {
  METER_TYPE_UNSPECIFIED = 0;
  METER_TYPE_NONE = 1;
  METER_TYPE_FLUCTUATING = 2;
  METER_TYPE_INCREMENTAL = 3;
}

enum EntitlementResetPeriod {
  ENTITLEMENT_RESET_PERIOD_UNSPECIFIED = 0;
  ENTITLEMENT_RESET_PERIOD_DAY = 1;
  ENTITLEMENT_RESET_PERIOD_HOUR = 2;
  ENTITLEMENT_RESET_PERIOD_MONTH = 3;
  ENTITLEMENT_RESET_PERIOD_WEEK = 4;
  ENTITLEMENT_RESET_PERIOD_YEAR = 5;
}

enum UsageUpdateBehavior {
  USAGE_UPDATE_BEHAVIOR_UNSPECIFIED = 0;
  USAGE_UPDATE_BEHAVIOR_DELTA = 1;
  USAGE_UPDATE_BEHAVIOR_SET = 2;
}

message EntitlementFeature {
  string id = 1;
  FeatureType feature_type = 2;
  optional string units = 3;
  optional string units_plural = 4;
  MeterType meter_type = 5;
  bool is_metered = 6;
}

message BooleanEntitlement {
  optional EntitlementFeature feature = 4;
}

message NumericEntitlement {
  optional EntitlementFeature feature = 4;
  optional int32 value = 5;
  bool is_unlimited = 6;
}

message MeteredEntitlement {
  optional EntitlementFeature feature = 4;
  optional double usage_limit = 5;
  bool is_unlimited = 6;
  double current_usage = 7;
  optional EntitlementResetPeriod reset_period = 9;
  optional google.protobuf.Timestamp next_reset_date = 10 [deprecated = true];
  optional google.protobuf.Timestamp usage_period_anchor = 11;
  optional google.protobuf.Timestamp usage_period_start = 12;
  optional google.protobuf.Timestamp usage_period_end = 13;
}

message EnumEntitlement {
  optional EntitlementFeature feature = 4;
  repeated string enum_values = 5;
}

message Entitlement {
  oneof entitlement {
    BooleanEntitlement boolean = 1;
    NumericEntitlement numeric = 2;
    MeteredEntitlement metered = 3;
    EnumEntitlement enum = 4;
  }
}

message GetEntitlementsRequest {
  string customer_id = 1;
  optional string resource_id = 2;
}

message GetEntitlementsResponse {
  repeated Entitlement entitlements = 1;
}

message GetEntitlementFallback {
  bool has_access = 1;
  optional int32 value = 2;
  optional bool is_unlimited = 3;
  optional double usage_limit = 4;
  repeated string enum_values = 5;
}

message GetEntitlementOptions {
  optional GetEntitlementFallback fallback = 1;
  optional double requested_usage = 2;
  repeated string requested_values = 3;
}

message GetEntitlementRequest {
  string customer_id = 1;
  string feature_id = 2;
  optional string resource_id = 3;
  optional GetEntitlementOptions options = 4;
}

message GetEntitlementResponse {
  bool has_access = 1;
  bool is_fallback = 2;
  optional AccessDeniedReason access_denied_reason = 3;
  oneof entitlement {
    BooleanEntitlement boolean = 4;
    NumericEntitlement numeric = 5;
    MeteredEntitlement metered = 6;
    EnumEntitlement enum = 7;
  }
}

message BooleanEntitlementFallback {
  bool has_access = 1;
}

message BooleanEntitlementOptions {
  optional BooleanEntitlementFallback fallback = 1;
}

message GetBooleanEntitlementRequest {
  string customer_id = 1;
  string feature_id = 2;
  optional string resource_id = 3;
  optional BooleanEntitlementOptions options = 4;
}

message GetBooleanEntitlementResponse {
  bool has_access = 1;
  bool is_fallback = 2;
  optional AccessDeniedReason access_denied_reason = 3;
  BooleanEntitlement entitlement = 4;
}

message NumericEntitlementFallback {
  bool has_access = 1;
  optional int32 value = 2;
  optional bool is_unlimited = 3;
}

message NumericEntitlementOptions {
  optional NumericEntitlementFallback fallback = 1;
}

message GetNumericEntitlementRequest {
  string customer_id = 1;
  string feature_id = 2;
  optional string resource_id = 3;
  optional NumericEntitlementOptions options = 4;
}

message GetNumericEntitlementResponse {
  bool has_access = 1;
  bool is_fallback = 2;
  optional AccessDeniedReason access_denied_reason = 3;
  NumericEntitlement entitlement = 4;
}

message MeteredEntitlementFallback {
  bool has_access = 1;
  optional double usage_limit = 2;
  optional bool is_unlimited = 3;
}

message MeteredEntitlementOptions {
  optional double requested_usage = 1;
  optional MeteredEntitlementFallback fallback = 2;
}

message GetMeteredEntitlementRequest {
  string customer_id = 1;
  string feature_id = 2;
  optional string resource_id = 3;
  optional MeteredEntitlementOptions options = 4;
}

message GetMeteredEntitlementResponse {
  bool has_access = 1;
  bool is_fallback = 2;
  optional AccessDeniedReason access_denied_reason = 3;
  double requested_usage = 4;
  MeteredEntitlement entitlement = 5;
}

message EnumEntitlementFallback {
  bool has_access = 1;
  repeated string enum_values = 2;
}

message EnumEntitlementOptions {
  repeated string requested_values = 1;
  optional EnumEntitlementFallback fallback = 2;
}

message GetEnumEntitlementRequest {
  string customer_id = 1;
  string feature_id = 2;
  optional string resource_id = 3;
  optional EnumEntitlementOptions options = 4;
}

message GetEnumEntitlementResponse {
  bool has_access = 1;
  bool is_fallback = 2;
  optional AccessDeniedReason access_denied_reason = 3;
  repeated string requested_values = 4;
  EnumEntitlement entitlement = 5;
}

message RedisOptions {
  string environment_prefix = 1;
  optional string host = 2;
  optional int32 port = 3;
  optional int32 db = 4;
  optional string username = 5;
  optional string password = 6;
  optional int32 ttl = 7;
}

message EntitlementFallback {
  oneof fallback {
    BooleanEntitlementFallback boolean = 1;
    NumericEntitlementFallback numeric = 2;
    MeteredEntitlementFallback metered = 3;
    EnumEntitlementFallback enum = 4;
  }
}

message ApiConfig {
  string api_key = 1;
  optional string api_url = 2;
  optional bool edge_enabled = 3;
  optional string edge_api_url = 4;
}

message LocalSidecarConfig {
  optional bool ws_enabled = 5;
  optional string ws_url = 6;
  optional RedisOptions redis = 7;
  map<string, EntitlementFallback> entitlements_fallback = 8;
  optional int64 cache_max_size_bytes = 9;
}

message ReportUsageRequest {
  string customer_id = 1;
  optional string resource_id = 2;
  string feature_id = 3;
  double value = 4;
  optional UsageUpdateBehavior update_behavior = 5;
}

message EventDimensionValue {
  oneof value {
    string string_value = 1;
    double number_value = 2;
    bool boolean_value = 3;
  }
}

message Event {
  string event_name = 1;
  string customer_id = 2;
  string idempotency_key = 3;
  optional string resource_id = 4;
  map<string, EventDimensionValue> dimensions = 5;
  optional google.protobuf.Timestamp timestamp = 6;
}

message ReportUsageResponse {
  string measurement_id = 1;
}

message ReportEventsRequest {
  repeated Event events = 1;
}

message ReloadEntitlementsRequest {
  string customer_id = 1;
  optional string resource_id = 2;
}

message ReloadEntitlementsResponse {
  bool entitled_entity_exists = 1;
}

service SidecarService {
  rpc GetEntitlements(GetEntitlementsRequest) returns (GetEntitlementsResponse);
  rpc GetEntitlement(GetEntitlementRequest) returns (GetEntitlementResponse);
  rpc GetBooleanEntitlement(GetBooleanEntitlementRequest) returns (GetBooleanEntitlementResponse);
  rpc GetNumericEntitlement(GetNumericEntitlementRequest) returns (GetNumericEntitlementResponse);
  rpc GetMeteredEntitlement(GetMeteredEntitlementRequest) returns (GetMeteredEntitlementResponse);
  rpc GetEnumEntitlement(GetEnumEntitlementRequest) returns (GetEnumEntitlementResponse);
  rpc ReportUsage(ReportUsageRequest) returns (ReportUsageResponse);
  rpc ReportEvents(ReportEventsRequest) returns (google.protobuf.Empty);
  rpc ReloadEntitlements(ReloadEntitlementsRequest) returns (ReloadEntitlementsResponse);
}