Rate Limiting as a Service
VettiGuard Rate Limiting protects backend and mobile operations with atomic check-and-consume decisions. It combines token-bucket traffic control for short bursts with optional fixed-window quotas for longer usage caps.
Use it immediately before the protected work. Your application sends the action and only the scope identifiers required by your policy; VettiGuard evaluates every matching policy, consumes capacity atomically when appropriate, and returns an allow, throttle, or reject decision.
Keep VettiGuard private secrets on a trusted backend. Native mobile apps should call your backend where practical; if you use the native VettiGuard operation directly, use the registered mobile secret and application identity exactly as documented below.
When to use Rate Limiting
Rate Limiting is appropriate when you need to control how frequently an operation can happen.
Common examples include:
- Login, registration, password-reset, and OTP requests.
- Payment, transfer, checkout, and account-change APIs.
- Search, catalog, scraping-sensitive, or enumeration-sensitive endpoints.
- Expensive report or export initiation.
- Public APIs consumed by API keys or authenticated subjects.
- Mobile operations that should be bounded per installation, subject, or action.
For operations where the problem is the number of jobs executing simultaneously, use Concurrency Control. For unhealthy or saturated downstream providers, use Dependency Resilience.
How the decision works
A matching Rate Limiting policy can combine:
| Control | Purpose |
|---|---|
| Token bucket | Allows normal bursts while limiting sustained request rate. |
| Fixed quota | Caps usage over a minute, hour, day, or month. |
| Weighted cost | Lets expensive operations consume more than one token. |
| Adaptive cost | Can increase token cost from a fresh authoritative API Protection assessment. |
| Hierarchical policies | Applies every matching workspace, application, route, subject, device, or custom-scope policy. |
A request must satisfy every applicable live policy. The strictest applied outcome wins.
Policy rollout modes
Policies support a safe rollout model:
| Mode | Behaviour when capacity is exhausted |
|---|---|
observe | Records the recommended restriction but applies allow. Simulation capacity is isolated from live capacity. |
throttle | Returns a throttle decision and bounded retry guidance. |
enforce | Returns a reject decision. |
Start new production policies in Observe. Review genuine traffic before moving a policy into Throttle or Enforce.
The API operation itself can return HTTP 200 even when the service decision is throttle or reject, because the rate-limit check was processed successfully. Always inspect the decision field. An HTTP 429 from the VettiGuard endpoint is different: it means the VettiGuard API operation itself has hit its protective ingress limit.
Configure a policy
In the workspace console, open Protection & trust → Rate Limiting.
A policy defines:
- A protected site or mobile application, or all applications in the workspace.
- An action pattern such as
payment.*orotp.send. - An HTTP method or
*. - A scope.
- Bucket capacity.
- Refill amount and refill interval.
- Default token cost.
- Optional fixed quota and quota window.
- Optional adaptive token cost.
- Observe, Throttle, Enforce, or workspace-default mode.
- The configured degraded behaviour if the distributed state service is temporarily unavailable.
Scope types
| Scope | Identifier sent by the integration | Example use |
|---|---|---|
workspace | None | Global workspace ceiling. |
application | None | Per protected site or mobile app. |
subject | subject_id | Per authenticated customer/account. |
device | device_id | Per installation or privacy-safe device reference. |
route | route | Per backend route. |
custom | scope_key | A customer-defined opaque grouping key. |
VettiGuard retains subject, device, route/custom scope values only as privacy-safe keyed hashes in rate-limit state/history.
Token bucket example
Suppose a payment policy has:
Capacity: 100 tokens
Refill: 10 tokens
Refill interval: 1 second
Default cost: 1 tokenA client can temporarily burst up to the available bucket capacity, while sustained traffic settles around the configured refill rate.
Do not assume every request must cost exactly one token. An expensive operation can deliberately consume more:
profile.read = 1 token
search.execute = 3 tokens
report.generate = 10 tokens
otp.send = 20 tokensServer endpoint
POST https://api.vettiguard.com/v1/rate-limit/check
Authorization: Bearer YOUR_PRIVATE_SECRET
Content-Type: application/jsonThe protected-site compatibility secret may also be sent as the secret JSON field, but new server integrations should prefer a scoped credential or Bearer secret held only on the trusted backend.
Server request
{
"action": "payment.create",
"method": "POST",
"subject_id": "customer-1842",
"device_id": "browser-device",
"route": "/api/payments",
"cost": 1,
"api_protection_assessment_id": "70f05cc7-989e-41f0-9a37-c9884d323a67"
}Request fields
| Field | Required | Description |
|---|---|---|
action | Yes | Stable protected action, for example payment.create. |
method | No | HTTP method used for policy matching. Defaults to POST. |
subject_id | When a subject-scoped policy matches | Opaque application subject/account identifier. |
device_id | When a device-scoped policy matches | Opaque installation/device reference. |
route | When a route-scoped policy matches | URL path only, for example /api/payments; do not send query strings. |
scope_key | When a custom-scoped policy matches | Opaque customer-defined grouping key. |
cost | No | Requested base cost. The configured policy cost remains the minimum. |
api_protection_assessment_id | No | Fresh VettiGuard API Protection assessment used for authoritative adaptive cost. |
Do not send passwords, access tokens, cookies, request bodies, full URLs with secrets, or sensitive personal data as scope identifiers.
Server response
{
"success": true,
"decision_id": "bca30ea6-72d7-42e2-a6d3-2c1b350551c5",
"allowed": true,
"recommended_decision": "allow",
"decision": "allow",
"retry_after_ms": 0,
"retry_after_seconds": 0,
"matched_policy_count": 3,
"remaining": 74,
"adaptive_multiplier": 1,
"reason_codes": [],
"policies": []
}Response fields
| Field | Meaning |
|---|---|
success | VettiGuard processed the rate-limit decision. |
decision_id | Opaque identifier for correlation and workspace analytics. |
allowed | Convenience boolean; true only when the applied decision is allow. |
recommended_decision | What live capacity recommends, including in Observe mode. |
decision | What your application should apply: allow, throttle, or reject. |
retry_after_ms / retry_after_seconds | Bounded wait before retry when capacity is exhausted. |
matched_policy_count | Number of policies that participated in the decision. |
remaining | Lowest remaining token capacity across matching policies when available. |
adaptive_multiplier | Highest authoritative cost multiplier applied to the request. |
reason_codes | Safe machine-readable reasons such as token-bucket-exhausted or fixed-window-quota-exceeded. |
policies | Per-policy decision detail suitable for trusted server diagnostics. |
When retry guidance is present, VettiGuard can also return an HTTP Retry-After header. Use the body fields as the canonical integration contract for the service decision.
Handle the decision
A typical trusted-backend flow is:
Receive application request
↓
Authenticate / establish application context
↓
Call VettiGuard /rate-limit/check
↓
allow → execute the protected operation
throttle → defer or return controlled backoff
reject → do not execute the protected operationDo not perform the protected operation first and call Rate Limiting afterward. The check-and-consume operation is intentionally atomic so concurrent requests cannot all observe the same unconsumed capacity.
cURL example
curl -X POST "https://api.vettiguard.com/v1/rate-limit/check" \
-H "Authorization: Bearer $VETTIGUARD_SECRET" \
-H "Content-Type: application/json" \
-d '{
"action": "payment.create",
"method": "POST",
"subject_id": "customer-1842",
"route": "/api/payments",
"cost": 1
}'PHP SDK
use VettiGuardSdk\VettiGuardVerifier;
$client = VettiGuardVerifier::production($_ENV['VETTIGUARD_SECRET']);
$decision = $client->checkRateLimit([
'action' => 'payment.create',
'method' => 'POST',
'subject_id' => 'customer-1842',
'route' => '/api/payments',
'cost' => 1,
]);
if (($decision['decision'] ?? '') !== 'allow') {
// Apply your application's controlled backoff or rejection response.
}Node.js SDK
const { checkRateLimit } = require('@vettiguard/node');
const decision = await checkRateLimit({
secret: process.env.VETTIGUARD_SECRET,
action: 'payment.create',
method: 'POST',
subjectId: 'customer-1842',
route: '/api/payments',
cost: 1
});
if (decision.decision !== 'allow') {
// Respect retry_after_seconds before retrying.
}Python SDK
from vettiguard import VettiGuardClient
client = VettiGuardClient(secret="vg_secret_replace_me")
decision = client.rate_limit(
action="payment.create",
method="POST",
subject_id="customer-1842",
route="/api/payments",
cost=1,
)
if decision.get("decision") != "allow":
# Respect retry_after_seconds before retrying.
passGo SDK
client, err := vettiguard.New("vg_secret_replace_me")
if err != nil {
return err
}
decision, err := client.RateLimit(ctx, map[string]any{
"action": "payment.create",
"method": "POST",
"subject_id": "customer-1842",
"route": "/api/payments",
"cost": 1,
})
if err != nil {
return err
}
if decision["decision"] != "allow" {
// Apply controlled backoff or reject the protected work.
}Native mobile endpoint
Native applications use:
POST https://api.vettiguard.com/v1/mobile/rate-limit/checkThe request must authenticate with the registered mobile secret and include the application's exact platform and application identifier.
{
"platform": "android",
"app_id": "com.example.app",
"action": "payment.create",
"method": "POST",
"subject_id": "customer-1842",
"device_id": "installation-id",
"route": "/api/payments",
"cost": 1
}The decision semantics are the same as the server endpoint. Never place a general protected-site server secret in a mobile binary.
Hierarchical policy example
You can create several policies that all match the same operation:
Workspace 50,000 requests/minute
Protected application 10,000 requests/minute
payment.create 1,000 requests/minute
Per subject 20 requests/minuteVettiGuard evaluates every matching policy. The request is only fully admitted when all live constraints have capacity.
Fixed quotas
Token buckets control immediate traffic. Fixed quotas are useful for longer ceilings such as:
100 requests/minute
10,000 requests/hour
250,000 requests/day
5,000,000 requests/monthA quota is optional and is evaluated in addition to the token bucket. If either control is exhausted, the policy recommends restriction.
Adaptive token cost
When both the workspace and policy enable adaptive cost, you may supply a fresh api_protection_assessment_id from VettiGuard API Protection.
VettiGuard does not trust a caller-provided risk score. The assessment must:
- Be an authoritative VettiGuard API Protection assessment.
- Belong to the same workspace and protected application.
- Be fresh enough for the adaptive-cost window.
Higher-risk requests can therefore consume capacity faster without allowing the caller to invent its own risk value.
No matching policy
If Rate Limiting is enabled but no policy matches the action/method/application, the check returns an allow decision with no-matching-policy in reason_codes.
Create explicit policies for every operation that you expect VettiGuard to govern.
Error handling
The most important transport/API errors are:
| HTTP | Error | Meaning |
|---|---|---|
400 | missing-input-secret | No server/mobile secret was supplied. |
400 | invalid-input-secret | The supplied secret is not valid. |
400 | invalid-action | The action is missing or malformed. |
400 | missing-route | A route-scoped policy matched but no valid route was provided. |
400 | invalid-subject_id / invalid-device_id / invalid-scope_key | A required opaque scope identifier is invalid. |
403 | rate-limiting-disabled | The workspace has disabled Rate Limiting. |
403 | invalid-app-identity | Native mobile platform/application identity does not match registration. |
429 | rate-limit-exceeded | The VettiGuard API endpoint itself is receiving too many calls; back off. |
503 | rate-limiting-unavailable | The Rate Limiting service is not currently ready. |
503 | safe runtime error | VettiGuard could not complete the operation; follow your configured failure strategy. |
Do not retry tight loops on 429 or 503. Use bounded exponential backoff with jitter and respect Retry-After when supplied.
Privacy and logging guidance
Safe integration practice:
- Keep private VettiGuard secrets in server-side secret storage.
- Use opaque subject/device/custom identifiers.
- Send only a path for
route; exclude query strings and credentials. - Do not use access tokens, session cookies, passwords, email bodies, payment data, or identity documents as scope keys.
- Keep
decision_idandX-Request-Idfor correlation when useful. - Do not log private secrets or raw authentication material.
Rate Limiting vs other traffic controls
| Service | Primary question |
|---|---|
| Rate Limiting | How frequently may this operation happen? |
| Concurrency Control | How many of these operations may execute simultaneously? |
| Dependency Resilience | Should this downstream dependency call happen right now? |
| API Protection | Does this request exhibit abuse, scraping, replay, or resource-risk signals? |
These services can be used together. For example, API Protection can provide authoritative risk evidence that increases Rate Limiting token cost, while Concurrency Control protects expensive in-flight work and Dependency Resilience protects the provider called by that work.
Production rollout checklist
- Create the protected site or mobile application.
- Create one or more Rate Limiting policies for the protected actions.
- Keep the policies in Observe initially.
- Call the atomic check immediately before protected work.
- Verify that action, scope and cost values match the intended policy.
- Monitor
recommended_decision,decision, remaining capacity and retry guidance. - Confirm legitimate peak traffic is not being classified as exhausted capacity.
- Move selected policies to Throttle where controlled backoff is appropriate.
- Move mature policies to Enforce only after traffic baselines are understood.
- Continue monitoring policy decisions after deployment changes or traffic-pattern changes.
For the complete operation schema, use the API reference. For official backend clients, see Server SDK quickstarts.