Limit Plans Management API
Overview
A limit plan is a reusable catalog entry that is applied to identities. A plan holds its own rule body and an active flag; it keeps no revisions and no drafts. A saved rule body applies at once: when the plan is active, every assignment that references it is re-pushed to the gateways. A plan is either active (the gateways enforce it) or inactive (it limits nothing, while its rule body, assignments and counters are kept); the active field of the plan response says which. This page documents the Management API surface for that catalog, the portable counterpart of the manager's own Limit Plans REST endpoints. For the concepts behind it — what each family carries and how plans are assigned — see Limit Plans.
Earlier versions kept a revision ladder under each plan (draft, publish, deploy). Plans no longer keep revisions: the read endpoints GET .../revisions, GET .../revisions/{revision}, GET .../draft and GET .../published are removed and answer 404. The revision-era write endpoints remain as deprecated aliases — see Deprecated aliases.
Every address on this page is under /apiops/projects/{projectName} and follows the shared Common Contract: the stable error envelope, correlation IDs, bounded pagination, and Idempotency-Key on plan creation. A caller outside the project's scope always gets 404 resourceNotFound, never 403.
References are portable — no Mongo id ever crosses the boundary. A plan is always addressed by its planKey, minted by the server and returned in the response.
Platform scope (projectName=admin)
projectName names the plan's owning scope. The special admin project — reachable only by a system administrator — addresses the installation-wide platform catalog instead of one project's own: the plans every project can bind to but only the platform can edit.
Reads and writes by planKey (GET/PUT/DELETE .../{planKey}, rule-spec, activate, deactivate) are scoped strictly to the calling project — a project-scoped call can never resolve a platform-only planKey this way, even though GET /limit-plans?includePlatform=true surfaces it in a list. Reaching a platform plan by key requires calling through the admin project.
GET /limit-plans?includePlatform=true adds the platform's plans to a project's own listing; they come back with platformScoped: true, read-only from that project.
Profiles owned by a subscription plan
A portal subscription plan that carries a rate limit or a quota owns a generated REQUEST_RATE profile, republished by the platform whenever that plan's numbers change. Such a plan is omitted from the listing by default and every write on it is refused — see managedBySubscriptionPlan under Errors. Pass includeManaged=true on GET /limit-plans to list them; each one comes back with managedBySubscriptionPlanId and managedBySubscriptionPlanName populated, and the limits are changed through the owning subscription plan rather than here.
Endpoints
Plans
| Method | Path | Description |
|---|---|---|
GET | /limit-plans | List the scope's plans, sortable by name, family, or active |
POST | /limit-plans | Create a plan with its rule body, active (201 + Location) |
GET | /limit-plans/{planKey} | One plan: its metadata, its active flag and its rule body |
PUT | /limit-plans/{planKey} | Update a plan's name, description, or labels (not its rule body) |
DELETE | /limit-plans/{planKey} | Delete a plan that no assignment and no creation default references (204) |
Rule body and status
| Method | Path | Description |
|---|---|---|
PUT | /limit-plans/{planKey}/rule-spec | Replace the plan's rule body; it applies at once |
POST | /limit-plans/{planKey}/actions/activate | The gateways enforce the plan again for every assignment that references it |
POST | /limit-plans/{planKey}/actions/deactivate | Stop enforcing the plan on every gateway; the rule body, assignments and counters are kept |
Deprecated aliases
These endpoints are kept so that pipelines written for the revision model keep working. New integrations should use the endpoints above.
| Method | Path | Behavior now |
|---|---|---|
POST | /limit-plans/{planKey}/draft-revisions | Saves the rule body like PUT .../rule-spec (it applies at once) and answers 200 with the plan — no longer 201 |
PUT | /limit-plans/{planKey}/draft-revisions/{revision} | Saves the rule body like PUT .../rule-spec; the revision number is ignored |
POST | /limit-plans/{planKey}/revisions/{revision}/actions/publish | Answers the plan as it is; the revision number is ignored |
POST | /limit-plans/{planKey}/actions/deploy | Alias of actions/activate |
POST | /limit-plans/{planKey}/actions/undeploy | Alias of actions/deactivate |
POST | /limit-plans/{planKey}/actions/retire | Removed. Always 400 malformedRequest (params.reason = limit.plan.retireRemoved); deactivate the plan instead |
List plans
GET /limit-plans?family&includePlatform&includeManaged&page&size&sort sorts by name,asc by default, tie-broken by planKey; sort may also name family or active (lifecycle is still accepted as a deprecated alias of active). A sort naming any other field is 400 unsupportedSortField. family narrows the listing to one family; includePlatform=true is described under Platform scope above, and includeManaged=true under Profiles owned by a subscription plan. The rows of the listing do not carry ruleSpec; read one plan to get its rule body.
Save the rule body
PUT /limit-plans/{planKey}/rule-spec replaces the plan's limits. When the plan is active, every assignment that references it is re-pushed at once; when it is inactive, the body is stored and takes effect with the next activate. A body equal to the stored one changes nothing but the plan's last-modified stamp. The response is the plan, its rule body included. See Rule Spec for the body.
Activate
POST /limit-plans/{planKey}/actions/activate makes the gateways enforce the plan again for every assignment that references it, and re-pushes those assignments. An active plan is left as it is. The response is the plan, with active: true.
Deactivate
POST /limit-plans/{planKey}/actions/deactivate stops enforcing the plan on every gateway for all of its assignments. The rule body, the assignments and the counters are kept; counters are not reset, so a later activate continues from the current usage. An inactive plan is left as it is. The response is the plan, with active: false.
Delete an unused plan
DELETE /limit-plans/{planKey} removes a plan in any state, active or inactive, as long as nothing references it. A plan that a limit assignment of any status references (an ended assignment included, since it keeps naming the plan as history), or that a creation default references, is refused with 409 lifecycleConflict (params.reason = limit.plan.inUseByBinding). The manager screen's delete ends the assignments and removes the plan from the creation defaults first; this endpoint does not.
Idempotency
POST /limit-plans requires an Idempotency-Key header (see Common Contract → Idempotency); a missing or blank header is 400 idempotencyKeyRequired. The key is claimed per scope and per operation, together with a canonical hash of the request body:
| Situation | Result |
|---|---|
| New key | The plan is created |
| Same key, same body, first call already finished | 409 idempotencyReplayExpired — params.resource names the plan key the first call created; read it with GET .../{planKey} instead |
| Same key, same body, first call still in flight | 409 idempotencyInProgress |
| Same key, a different body | 409 idempotencyKeyReused |
A claim whose underlying call failed is released, so the same key may be retried once the cause is fixed. A create at platform scope (through the admin project) claims its key under that same scope, separately from any project's own claims.
Every other write on this page — saving the rule body, activating, deactivating, deleting — is naturally idempotent (the same body or status twice changes nothing) and takes no Idempotency-Key.
Request Body
Create a plan — POST /limit-plans
| Field | Type | Required | Meaning |
|---|---|---|---|
family | enum | Yes | REQUEST_RATE or AI_TOKEN_BUDGET — immutable after creation |
name | string | Yes | Editable later with PUT |
nameTr | string | No | Turkish display name; left blank or omitted, the plan's name is copied into it |
description | string | No | |
labels | string[] | No | Free-form tags |
initialRuleSpec | object | Yes in practice | The plan's rule body — see Rule Spec below |
initialRuleSpec is nullable on the wire, but a plan is always created with its rule body, so a create that omits it is refused with 400 malformedRequest (params.reason = limit.plan.ruleSpecRequired).
The plan is created active, with version: 1. It limits nobody until it is assigned to an identity.
The name is unique per scope and family, not globally: creating a plan whose family + name combination already exists in the target scope is 409 lifecycleConflict (params.reason = limit.plan.nameAlreadyExists). The planKey is minted from the name and checked for a collision before the plan is saved, so its own conflict (params.reason = limit.plan.keyAlreadyExists) is only reachable under an actual race between two concurrent creates for the same name.
Update a plan's metadata — PUT /limit-plans/{planKey}
Only the plan's descriptive half is editable this way — its family, planKey, and owning scope are immutable, and its rule body is saved with PUT .../rule-spec, not here.
| Field | Type | Required | Meaning |
|---|---|---|---|
name | string | Yes | |
nameTr | string | No | Turkish display name; left blank or omitted, the plan's name is copied into it |
description | string | No | |
labels | string[] | No | Replaces the stored list |
version | integer | Yes in practice | The version last read from the plan |
version is nullable on the wire but required in practice: a plan's version starts at 1 on creation and only increases, while an omitted version defaults to 0 on the wire — so omitting it is always refused as 409 lifecycleConflict (params.reason = limit.plan.staleVersion). Sending an explicit version that no longer matches the stored plan is 409 versionConflict instead (see Common Contract → Optimistic concurrency). Always send the version your last read of the plan returned.
The manager screen no longer collects a separate Turkish name and always sends nameTr equal to name, on both create and update; a caller integrating directly against this API may still set a distinct nameTr.
Rule Spec
PUT /limit-plans/{planKey}/rule-spec (and the deprecated draft-revisions aliases) take one body:
| Field | Type | Required | Meaning |
|---|---|---|---|
ruleSpec | object | Yes | The plan's rule body — exactly one of the two arms below, matching the plan's family |
version | integer | No | The plan version you last read. When sent and the plan has changed since, the save is refused with 409 lifecycleConflict (params.reason = limit.plan.staleVersion); when omitted, no check is made |
changeReason | string | No | Accepted and ignored; plans keep no revision history |
The ruleSpec of POST /limit-plans (initialRuleSpec) has the same shape.
A ruleSpec that is missing, that carries both arms, or that carries the arm for the wrong family, is 400 malformedRequest — missing is params.reason = limit.plan.ruleSpecRequired, the wrong/extra arm is params.reason = limit.plan.ruleSpecFamilyMismatch.
Plan settings and limits
A plan has no general limit: every limit names its own target and sits in ruleSpec.targetedRules. The family arm at the top of the ruleSpec (requestRate for a REQUEST_RATE plan, aiBudget for an AI_TOKEN_BUDGET plan) is required, but it carries only the plan's settings. A ceiling on it — a request-rate window, or a token, USD or request-count ceiling on the budget, including per-model and per-provider budgets — is refused with 400 malformedRequest (params.field names it, for example ruleSpec.requestRate.windows or initialRuleSpec.aiBudget).
Request Rate arm — ruleSpec.requestRate
| Field | Type | Required | Meaning |
|---|---|---|---|
overflowAction | enum | No | BLOCK (default when absent) refuses the request once a window is exhausted; COUNT_ONLY lets it through and keeps counting |
cacheErrorHandlingType | enum | No | FAIL rejects the request when the counter service cannot be reached; CONTINUE lets it through uncounted |
cacheConnectionTimeoutInSeconds | integer | No | Timeout to the counter service, in seconds |
showRateLimitStatisticsInResponseHeader | boolean | No | Adds the remaining-limit headers to the response |
customMessageTemplate / customMessageContentType / customMessageHttpStatusCode | string / string / integer | No | Replaces the standard message when a limit is exceeded |
Each element of ruleSpec.targetedRules of a REQUEST_RATE plan is one limit: its target and a requestRate with the windows of that limit.
| Field | Type | Required | Meaning |
|---|---|---|---|
windows | array | Yes, non-empty | One ceiling per time window — see below |
Each entry of windows:
| Field | Type | Required | Meaning |
|---|---|---|---|
permittedMessageCount | integer | Yes, >= 1 | The ceiling for this window |
interval | enum | Yes | ONE_SECOND | ONE_MINUTE | ONE_HOUR | ONE_DAY | ONE_MONTH |
periodLength | integer | No, >= 1 | How many of interval make up one window (e.g. interval=ONE_HOUR, periodLength=6 is a 6-hour window) |
windowType | enum | No | FIXED or SLIDING |
countingMode | enum | No | ALL_REQUESTS counts a request even when it is the one that gets blocked; ACCEPTED_ONLY counts only requests that reached the backend |
No default is filled in on unset optional fields here — they are read back exactly as sent, absent members included. Two windows over the same interval and periodLength are refused.
Targets of a limit — target
A limit names its target by name, never by an installation id.
| Field | Type | Meaning |
|---|---|---|
type | enum | REQUEST_RATE plans: API_PROXY, API_PROXY_ENV, API_PROXY_GROUP, API_PROXY_GROUP_ENV, API_ENDPOINT, API_ENDPOINT_ENV. AI_TOKEN_BUDGET plans: API_PROXY, API_PROXY_ENV (an AI proxy), AI_MODEL, AI_PROVIDER. Any other type is 400 malformedRequest (params.reason = limit.plan.targetInvalid) |
apiProxyName | string | API_PROXY, API_PROXY_ENV, and the endpoint types |
apiProxyGroupName | string | API_PROXY_GROUP, API_PROXY_GROUP_ENV |
environmentName | string | The _ENV types |
endpointHttpMethod / endpointPath | string | The endpoint types: the endpoint's HTTP method and path, as the API proxy defines them |
llmProviderName | string | AI_PROVIDER: the LLM provider. AI_MODEL: the provider that owns the model |
targetKey | string | AI_MODEL: the model identifier, as listed by the provider |
projectName | string | admin project only: the project of the API proxy, group or endpoint |
httpMethods | string[] | API_PROXY and API_PROXY_ENV only; empty or absent means every method |
A model limit names one model of one LLM provider (llmProviderName plus targetKey) and counts only the traffic that provider's model served; the same model identifier on another provider is not counted. The provider must be one the plan's project can use and must list the model, otherwise the request is 400 malformedRequest (params.reason = limit.plan.targetInvalid). Two limits of one plan with the same target are 400 malformedRequest (params.reason = limit.plan.targetDuplicate).
Other ruleSpec fields
Next to the family arm, the ruleSpec body also carries:
| Field | Type | Meaning |
|---|---|---|
targetedRules | array | The plan's limits. Each element repeats the family arm for one API proxy, API proxy group, endpoint, AI proxy, model or provider, and carries its target by name (API proxy, proxy group, endpoint as proxy + HTTP method + path, environment, LLM provider, and for a model the provider plus the model). An unknown name is 400 malformedRequest (params.reason = limit.plan.targetNotFound, params.field names the limit). In the admin project a target also names its projectName. A targeted AI Token Budget limit must set at least one ceiling (params.reason = limit.plan.noLimits). |
validFrom / validUntil | timestamp | The plan-level validity window; absent on a side means open on that side |
condition | object | The plan's request condition: the plan counts and blocks only the requests that meet it; absent means every request |
errorMessageList | array | The plan's error message customizations for the errors its limits raise; absent means the default messages |
ownBudgetThresholds | boolean | AI Token Budget targeted limit only: true when the limit's alert thresholds and hard cap are its own; absent takes the general budget's values |
A rule body save that adds a limit another plan of an identity holding this plan already sets (the same API, endpoint or AI proxy, counted the same way) is 409 lifecycleConflict (params.reason = limit.plan.targetOverlap). An overlap the previous body already had does not block.
AI Token Budget arm — ruleSpec.aiBudget
The budget object has these fields. On the top-level arm only the plan's settings apply (softCapPercents, hardCapPercent, overflowAction, cheaperModelId, the counter-store and custom message settings); the ceilings below are set on the budget of each targeted limit.
Budget Object Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| enabled | boolean | No | false | Whether the budget is enabled |
| tokensPerHour | integer | No | - | Hourly token cap. null = no limit; if set, must be >= 1 |
| tokensPerMinute | integer | No | - | Per-minute token cap. null = no limit; if set, must be >= 1 |
| tokensPerDay | integer | No | - | Daily token cap. null = no limit; if set, must be >= 1 |
| tokensPerMonth | integer | No | - | Monthly token cap. null = no limit; if set, must be >= 1 |
| monthlyBudgetUsd | number | No | - | Monthly USD cost cap. null = no cost limit; if set, must be > 0 |
| usdPerMinute | number | No | - | Per-minute USD cost cap. null = no limit; if set, must be > 0. Not available in modelBudgets / providerBudgets |
| usdPerHour | number | No | - | Hourly USD cost cap. null = no limit; if set, must be > 0 |
| usdPerDay | number | No | - | Daily USD cost cap. null = no limit; if set, must be > 0 |
| showRateLimitStatisticsInResponseHeader | boolean | No | null | Whether the response carries the remaining-limit headers. null = the assignment's setting applies; true / false = the plan decides. The headers come from the Limit Plan tier with the smallest share left, among the token and call-count tiers; USD tiers are never used |
| requestsPerMinute | integer | No | - | Per-minute LLM call count cap. null = no limit; if set, must be >= 1. Not available in modelBudgets / providerBudgets |
| requestsPerHour | integer | No | - | Hourly LLM call count cap. Same rules as requestsPerMinute |
| requestsPerDay | integer | No | - | Daily LLM call count cap. Same rules as requestsPerMinute |
| requestsPerMonth | integer | No | - | Monthly LLM call count cap. Same rules as requestsPerMinute |
| softCapPercents | array[integer] | No | [] | Soft-cap warning thresholds. Each element in range [0, 100]. When omitted, [] is stored; a GET returns [] explicitly when no thresholds are set |
| hardCapPercent | integer | No | 100 | Hard-cap percent, range [1, 100] |
| overflowAction | string | No | BLOCK | Action on overflow: BLOCK, FAILOVER, ALERT_ONLY or CHEAPER_MODEL |
| cheaperModelId | string | No | - | Cheaper fallback model id (required behaviour when overflowAction=CHEAPER_MODEL) |
| modelBudgets | array | No | keep | Per-model budget overrides (max 50). See list-semantics note below |
| providerBudgets | array | No | keep | Per-provider budget overrides (max 50). See list-semantics note below |
Model / Provider Budget Entry Fields
| Field | Type | Required | Description |
|---|---|---|---|
| modelId | string | Yes (model entry) | Model id the override applies to |
| providerRef | string | Yes (provider entry) | LLM provider the override applies to (the provider reference, not its name) |
| tokensPerHour / tokensPerDay / tokensPerMonth | integer | No | Token caps for this model/provider. If set, must be >= 1 |
| monthlyBudgetUsd / usdPerHour / usdPerDay | number | No | USD cost caps for this model/provider. If set, must be > 0 |
| overflowAction | string | No | Per-entry overflow action (defaults to BLOCK) |
- A request-count ceiling counts one call to the LLM provider for each request and applies on the targeted limits of an AI proxy, model or provider. The Token Rate Limit policy does not accept request-count ceilings.
- An empty array
[]inmodelBudgets/providerBudgetsclears the overrides;nullor an absent field keeps the stored ones. - A USD limit meters only models that have an input price in the LLM provider. A model without a price counts as 0, so the limit does not meter it and the save is not refused. Set the missing price, or use a token-based limit instead.
Response
Plan — 200/201
The shape returned by every plan endpoint that answers with a body; DELETE answers 204 with no body.
{
"planKey": "standard-100rps",
"family": "REQUEST_RATE",
"name": "Standard 100 rps",
"nameTr": "Standard 100 rps",
"description": "Default tier for partner API clients",
"labels": ["partner", "tier-1"],
"active": true,
"deployed": true,
"platformScoped": false,
"ruleSpec": {
"requestRate": { "overflowAction": "BLOCK" },
"targetedRules": [
{
"target": { "type": "API_PROXY", "apiProxyName": "payments-api" },
"requestRate": {
"windows": [
{ "permittedMessageCount": 100, "interval": "ONE_SECOND" }
]
}
}
]
},
"managedBySubscriptionPlanId": null,
"managedBySubscriptionPlanName": null,
"version": 3
}
| Field | Type | Meaning |
|---|---|---|
planKey | string | Opaque public key, minted at creation |
family | enum | REQUEST_RATE or AI_TOKEN_BUDGET |
name / nameTr / description / labels | string / string / string / string[] | Descriptive metadata |
active | boolean | true when the gateways enforce the plan; moved by activate and deactivate |
deployed | boolean | Deprecated alias of active, kept for callers written before deploy became activate |
platformScoped | boolean | true when this plan lives in the platform catalog rather than a project's own |
ruleSpec | object | The plan's rule body, targets named — see Rule Spec. Returned by GET .../{planKey}, PUT .../rule-spec, activate, deactivate and the deprecated aliases; not returned by POST /limit-plans, PUT .../{planKey} or the list rows |
managedBySubscriptionPlanId | string | The portal subscription plan that owns this profile, or null for an ordinary catalog entry. Every write on an owned profile is refused — see Profiles owned by a subscription plan |
managedBySubscriptionPlanName | string | The owning subscription plan's display name, or null |
version | integer | The optimistic-concurrency token for the version field of PUT .../{planKey} and PUT .../rule-spec; every save and every status change increases it |
The lifecycle, currentPublishedRevision, retiredAt and retiredBy fields of earlier versions are no longer returned.
List envelope — 200
GET /limit-plans returns the shared paged envelope (see Common Contract → Pagination):
{
"status": "SUCCESS",
"items": [],
"page": 0,
"size": 20,
"totalElements": 12,
"totalPages": 1,
"sort": ["name,asc"]
}
Errors
Every error follows the Common Contract's error envelope — branch on errorKey, never on message.
| Error key | When |
|---|---|
malformedRequest | The body could not be understood or was left empty (params.reason = bodyRequired), the plan is missing its family (params.reason = limit.plan.familyRequired) or name (params.reason = limit.plan.nameRequired), a rule spec is missing (params.reason = limit.plan.ruleSpecRequired) or carries the wrong/extra arm for the plan's family (params.reason = limit.plan.ruleSpecFamilyMismatch), a targeted limit names an unknown target (params.reason = limit.plan.targetNotFound), a token or request-count limit is below 1 or a USD limit is 0 or less (params.reason = limit.plan.ceilingNotPositive), a top-level ceiling is sent where only a targeted limit may carry one (params.field names it), a targeted limit has a target its family cannot use or names the same target twice (params.reason = limit.plan.targetInvalid / limit.plan.targetDuplicate), a targeted AI Token Budget limit sets no ceiling (params.reason = limit.plan.noLimits), or the removed retire action was called (params.reason = limit.plan.retireRemoved) |
resourceNotFound | An unknown planKey (params.reason = limit.plan.notFound) — including a project-scoped call naming a platform-only planKey, see Platform scope. The removed revision read endpoints (.../revisions, .../revisions/{revision}, .../draft, .../published) also answer 404 |
versionConflict | PUT .../{planKey}'s version field no longer matches the stored plan |
lifecycleConflict | params.reason names the service cause: limit.plan.staleVersion (a stale version on PUT .../rule-spec, or see the version note under Update a plan's metadata above), limit.plan.targetOverlap (a limit that another plan of the same identities already sets), limit.plan.inUseByBinding (deleting a plan that an assignment or a creation default references), limit.plan.nameAlreadyExists / limit.plan.keyAlreadyExists (duplicate within the scope), or limit.plan.keyImmutable / limit.plan.familyImmutable / limit.plan.projectImmutable (an immutable field was sent on update) |
idempotencyKeyRequired | POST /limit-plans was missing its Idempotency-Key header |
idempotencyKeyReused | The same key was sent with a different request body |
idempotencyReplayExpired | The same key and body already completed — params.resource names the plan it created |
idempotencyInProgress | The same key and body's first call is still running |
permissionDenied | Authenticated, but the caller lacks Identity/AI Budgets Manage (writes) or View (reads) |
unsupportedSortField | sort on GET /limit-plans named a field other than name, family, active, or the deprecated lifecycle |
malformedRequest with params.reason = limit.plan.managedBySubscriptionPlan | A write (PUT .../{planKey}, DELETE .../{planKey}, PUT .../rule-spec, activate, deactivate, or a deprecated alias that saves or changes status) targeted a profile a portal subscription plan owns. Change the owning plan's rate limit and quota instead — see Profiles owned by a subscription plan |
See Common Contract → Error keys for the complete closed set and the status-code table.
Permissions & Licensing
- Reads (
GET) require the Identity permission at View; writes (POST/PUT/DELETE) require it at Manage. The AI Budgets permission is also accepted in place of Identity. - No license module is checked on this surface: an
AI_TOKEN_BUDGETplan is gated by permission exactly like aREQUEST_RATEplan. - Writing a platform-scope plan (any endpoint through the
adminproject) is restricted to a system administrator, the same rule the rest of the manager's admin-scoped endpoints follow. - A project outside the caller's authorized scope is always
404 resourceNotFound, never403— see Common Contract → Telling 402, 403 and 404 apart.
Example
Create a plan, raise its ceiling, then deactivate and activate it again:
# 1. Create — requires Idempotency-Key; the plan is created active with its rule body
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-plans" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1a9c3e-2d4b-4a7c-9e1f-8b3a1d5e7c40" \
-d '{
"family": "REQUEST_RATE",
"name": "Standard 100 rps",
"description": "Default tier for partner API clients",
"labels": ["partner", "tier-1"],
"initialRuleSpec": {
"requestRate": { "overflowAction": "BLOCK" },
"targetedRules": [
{
"target": { "type": "API_PROXY", "apiProxyName": "payments-api" },
"requestRate": {
"windows": [
{ "permittedMessageCount": 100, "interval": "ONE_SECOND" }
]
}
}
]
}
}'
# 201 Created, Location: /apiops/projects/finance/limit-plans/standard-100rps
# 2. Raise the ceiling — applies at once to every assignment of the active plan
curl -X PUT "https://apinizer.example.com/apiops/projects/finance/limit-plans/standard-100rps/rule-spec" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"ruleSpec": {
"requestRate": { "overflowAction": "BLOCK" },
"targetedRules": [
{
"target": { "type": "API_PROXY", "apiProxyName": "payments-api" },
"requestRate": {
"windows": [
{ "permittedMessageCount": 150, "interval": "ONE_SECOND" }
]
}
}
]
}
}'
# 200 OK — the plan, "version": 2
# 3. Stop enforcing the plan; its rule body, assignments and counters are kept
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-plans/standard-100rps/actions/deactivate" \
-H "Authorization: Bearer $TOKEN"
# 200 OK — the plan, "active": false
# 4. Enforce it again
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-plans/standard-100rps/actions/activate" \
-H "Authorization: Bearer $TOKEN"
# 200 OK — the plan, "active": true
Related Documentation
- Limit Plans, Assignments & Defaults API - The manager's own REST surface behind Limit Plans, including plan CRUD, the rule body save, activate and deactivate
- Bulk Limit Assignment API - Attach a plan to a large, filtered population of subjects in one run
- Limit Plans - The manager UI screens and concepts this surface backs
- Common Contract - The shared error envelope, idempotency, pagination, and status-code rules every endpoint on this page follows
- Error Handling - The envelope used by the manager's older endpoints