User Overrides API
The User Overrides API provides a RESTful interface for managing tenant-specific limit overrides at runtime without requiring manual edits to the runtime configuration file or service restarts.
Context
Cortex is a multi-tenant system that applies resource limits to each tenant to prevent any single tenant from using too many resources. These limits can be configured globally via the limits section in the main configuration file, or per-tenant via the runtime_config file.
Traditionally, updating per-tenant limits required:
- Manually editing the runtime configuration file
- Waiting for Cortex to reload the configuration (based on
reload-period) - Direct access to the runtime configuration storage
The User Overrides API simplifies this process by providing HTTP endpoints that allow authorized users or systems to:
- View current tenant overrides
- Set or update specific limit overrides
- Delete all overrides for a tenant
Architecture
The overrides module runs as a service within Cortex and provides three main capabilities:
- API Endpoints: RESTful HTTP endpoints for managing overrides
- Validation: Enforces allowed limits and hard limits from runtime configuration
- Merge Behavior: Preserves existing overrides when updating specific limits
Configuration
Enabling the Overrides Module
The overrides module must be explicitly enabled in Cortex. It is not included in the all target by default.
# Run only the overrides module
cortex -target=overrides -runtime-config.file=runtime.yaml
# Include overrides with other modules
cortex -target=overrides,query-frontend,querier -runtime-config.file=runtime.yaml
Runtime Configuration File
The runtime configuration file controls which limits can be modified via the API and sets upper bounds (hard limits) for tenant overrides.
Basic Configuration
# file: runtime.yaml
# Current tenant overrides
overrides:
tenant1:
ingestion_rate: 50000
max_global_series_per_user: 1000000
# Limits that can be modified via the API
api_allowed_limits:
- ingestion_rate
- ingestion_burst_size
- max_global_series_per_user
- max_global_series_per_metric
- ruler_max_rules_per_rule_group
- ruler_max_rule_groups_per_tenant
Configuration with Hard Limits
Hard limits prevent tenants from setting overrides above a specified maximum value:
# file: runtime.yaml
# Current tenant overrides
overrides:
tenant1:
ingestion_rate: 50000
max_global_series_per_user: 500000
# Allowed limits that can be modified via API
api_allowed_limits:
- ingestion_rate
- ingestion_burst_size
- max_global_series_per_user
- max_global_series_per_metric
- ruler_max_rules_per_rule_group
- ruler_max_rule_groups_per_tenant
# Hard limits (maximum values) per tenant
hard_overrides:
tenant1:
ingestion_rate: 100000
max_global_series_per_user: 2000000
tenant2:
ingestion_rate: 200000
max_global_series_per_user: 5000000
Storage Backend
The overrides module uses the same storage backend as the runtime config. Configure it using the runtime-config section:
runtime_config:
period: 10s
file: runtime.yaml
# For S3 backend
backend: s3
s3:
bucket_name: cortex-runtime-config
endpoint: s3.amazonaws.com
access_key_id: ${AWS_ACCESS_KEY_ID}
secret_access_key: ${AWS_SECRET_ACCESS_KEY}
# For GCS backend
# backend: gcs
# gcs:
# bucket_name: cortex-runtime-config
# For filesystem backend (default)
# backend: filesystem
# filesystem:
# dir: /etc/cortex
API Reference
All endpoints require authentication using the X-Scope-OrgID header with the tenant ID.
Get User Overrides
GET /api/v1/user-overrides
X-Scope-OrgID: tenant1
Returns the current overrides for the authenticated tenant in JSON format.
Response (200 OK):
{
"ingestion_rate": 50000,
"max_global_series_per_user": 500000,
"ruler_max_rules_per_rule_group": 100
}
Response (404 Not Found): If the tenant has no overrides configured, an empty object is returned:
{}
Set User Overrides
POST /api/v1/user-overrides
X-Scope-OrgID: tenant1
Content-Type: application/json
{
"ingestion_rate": 75000
}
Sets or updates specific overrides for the authenticated tenant. This operation merges with existing overrides rather than replacing them entirely.
Merge Behavior Example:
Current state:
{
"ingestion_rate": 50000,
"max_global_series_per_user": 500000,
"ruler_max_rules_per_rule_group": 100
}
Request:
{
"ingestion_rate": 75000
}
Result:
{
"ingestion_rate": 75000,
"max_global_series_per_user": 500000,
"ruler_max_rules_per_rule_group": 100
}
Response (200 OK): Returns success with no body.
Response (400 Bad Request):
- Invalid limit names (not in
api_allowed_limits) - Values exceeding hard limits
- Invalid JSON format
Delete User Overrides
DELETE /api/v1/user-overrides
X-Scope-OrgID: tenant1
Removes all overrides for the authenticated tenant. The tenant will revert to using global default values.
Response (200 OK): Returns success with no body.
API Allowed Limits
The api_allowed_limits configuration in the runtime config file controls which limits can be modified via the API. This provides an additional security layer to prevent unauthorized modification of critical limits.
Configuring Allowed Limits
api_allowed_limits:
- ingestion_rate
- ingestion_burst_size
- max_global_series_per_user
- max_global_series_per_metric
- ruler_max_rules_per_rule_group
- ruler_max_rule_groups_per_tenant
Validation Behavior
When a POST request is made:
- Allowed limits check: Each limit in the request is validated against
api_allowed_limits - Rejection: If any limit is not in the allowed list, the entire request is rejected with a 400 error
Example - Rejected Request:
Runtime config:
api_allowed_limits:
- ingestion_rate
- max_global_series_per_user
Request:
{
"ingestion_rate": 50000,
"max_series_per_query": 100000
}
Response (400 Bad Request):
the following limits cannot be modified via the overrides API: max_series_per_query
Available Limit Names
Limit names correspond to fields in the limits_config section. Common examples include:
ingestion_rateingestion_burst_sizemax_global_series_per_usermax_global_series_per_metricmax_local_series_per_usermax_local_series_per_metricmax_series_per_querymax_samples_per_queryruler_max_rules_per_rule_groupruler_max_rule_groups_per_tenantmax_label_names_per_seriesmax_label_name_lengthmax_label_value_length
Hard Limits
Hard limits provide per-tenant upper bounds for override values. They prevent tenants from setting limits that exceed their allocated capacity.
Hard limits are inclusive - if a hard limit is set to 100000, then values up to and including 100000 are allowed, but 100001 and above will be rejected.
Configuring Hard Limits
Hard limits are specified per tenant in the hard_overrides section:
hard_overrides:
tenant1:
ingestion_rate: 100000
max_global_series_per_user: 2000000
tenant2:
ingestion_rate: 500000
max_global_series_per_user: 10000000
Validation Behavior
When a POST request is made:
- Hard limit lookup: System checks if the tenant has hard limits configured
- Value comparison: Each requested override value is compared against its hard limit (inclusive)
- Rejection: If any value exceeds its hard limit, the entire request is rejected
Example - Allowed Request (at hard limit):
Runtime config:
hard_overrides:
tenant1:
ingestion_rate: 100000
max_global_series_per_user: 2000000
Request:
{
"ingestion_rate": 100000
}
Response (200 OK): The request succeeds because 100000 equals the hard limit (inclusive).
Example - Rejected Request (exceeds hard limit):
Runtime config:
hard_overrides:
tenant1:
ingestion_rate: 100000
max_global_series_per_user: 2000000
Request:
{
"ingestion_rate": 100001
}
Response (400 Bad Request):
limit ingestion_rate exceeds hard limit: 100001 > 100000
Hard Limits vs. Default Limits
| Configuration | Purpose | Scope |
|---|---|---|
Default limits (limits_config) | Global defaults for all tenants | All tenants |
Overrides (overrides) | Per-tenant custom limits | Specific tenants |
Hard limits (hard_overrides) | Maximum allowed override values (inclusive) | Specific tenants |
Hierarchy:
Default Limits (global)
↓
Tenant Overrides (per-tenant, must be ≤ hard limits)
↓
Hard Limits (per-tenant maximum, inclusive)
Usage Examples
Example 1: Initial Override Setup
Set initial overrides for a new tenant:
curl -X POST http://cortex:8080/api/v1/user-overrides \
-H "X-Scope-OrgID: tenant1" \
-H "Content-Type: application/json" \
-d '{
"ingestion_rate": 50000,
"max_global_series_per_user": 1000000,
"ruler_max_rules_per_rule_group": 50
}'
Example 2: Update Specific Limit
Update only the ingestion rate while preserving other overrides:
curl -X POST http://cortex:8080/api/v1/user-overrides \
-H "X-Scope-OrgID: tenant1" \
-H "Content-Type: application/json" \
-d '{
"ingestion_rate": 75000
}'
Result: ingestion_rate updated to 75000, other limits remain unchanged.
Example 3: View Current Overrides
curl -X GET http://cortex:8080/api/v1/user-overrides \
-H "X-Scope-OrgID: tenant1"
Response:
{
"ingestion_rate": 75000,
"max_global_series_per_user": 1000000,
"ruler_max_rules_per_rule_group": 50
}
Example 4: Remove All Overrides
curl -X DELETE http://cortex:8080/api/v1/user-overrides \
-H "X-Scope-OrgID: tenant1"
Result: Tenant reverts to global default limits.
Example 5: Handling Validation Errors
Attempt to set a disallowed limit:
curl -X POST http://cortex:8080/api/v1/user-overrides \
-H "X-Scope-OrgID: tenant1" \
-H "Content-Type: application/json" \
-d '{
"ingestion_rate": 50000,
"some_invalid_limit": 100
}'
Response (400):
the following limits cannot be modified via the overrides API: some_invalid_limit
Attempt to exceed hard limit:
curl -X POST http://cortex:8080/api/v1/user-overrides \
-H "X-Scope-OrgID: tenant1" \
-H "Content-Type: application/json" \
-d '{
"ingestion_rate": 100001
}'
Response (400):
limit ingestion_rate exceeds hard limit: 100001 > 100000
Note: If the hard limit is 100000, then 100000 itself would be allowed (hard limits are inclusive), but 100001 exceeds it.
Operational Considerations
Security
- Authentication: All endpoints require valid tenant authentication via
X-Scope-OrgIDheader - Authorization: Tenants can only manage their own overrides
- Allowed limits: Use
api_allowed_limitsto restrict which limits can be modified - Hard limits: Use
hard_overridesto enforce maximum values per tenant
High Availability
- The overrides module can run on multiple instances for high availability
- All instances read/write to the same runtime configuration storage backend
- Changes are eventually consistent based on
runtime-config.period
Best Practices
- Use hard limits: Always configure
hard_overridesto prevent runaway resource usage - Restrict allowed limits: Only expose limits via
api_allowed_limitsthat are safe for self-service - Monitor changes: Track when overrides are modified and by whom
- Version control: Keep runtime configuration in version control for audit trail
- Gradual rollout: Test override changes in non-production environments first
Troubleshooting
“user not found” error when getting overrides
This means the tenant has no overrides configured. This is normal for new tenants. An empty JSON object {} is returned.
Changes not taking effect immediately
The runtime config is reloaded periodically based on runtime-config.period (default: 10s). Changes may take up to this duration to be applied.
“failed to validate hard limits” error
This indicates an issue reading or parsing the runtime configuration file. Check:
- Runtime config file is accessible
- YAML syntax is valid
- Storage backend is properly configured