Ana içeriğe geç

Limit Assignments Management API

Overview​

This is the single-assignment counterpart of the Bulk Limit Assignment API: instead of attaching one assignment to a large, filtered population in a run, it creates, reads, updates, activates, ends, and validates one assignment at a time. It documents the Management API surface for the manager's own Limit Assignments REST endpoints (/api/limit-bindings) — the same lifecycle (DRAFT → ACTIVE → ENDED, no delete — ending is the removal), addressed portably instead of by internal id.

Every address on this page is under /apiops/projects/{projectName} and follows the shared Common Contract: the stable error envelope, correlation IDs, and bounded pagination. A caller outside the project's scope always gets 404 resourceNotFound, never 403. Assignments are project-owned (their subjects are consumers, clients, applications, organizations, proxies, or rules of a project), so the admin (platform) project has no subjects of its own and a platform-scoped call is refused with 400 malformedRequest (params.reason = platformScopeNotSupported).

References on this surface are portable — no Mongo id ever crosses the boundary. A subject is addressed by the public key its type is already known by (a consumer's username, an API client's clientId, an application's name, an organization's code, an API proxy's name); an AUDIENCE_RULE subject is identified by the assignment itself. Plans, proxies, groups, and environments are addressed by name the same way. An unknown reference is 404 resourceNotFound naming the field.

Not exposed here: the operator override pin/release (a Manager-screen recovery control that marks a row so a portal-subscription sync will not re-point it) and the covering-API-proxy / referencing-plan lookups (screen conveniences) — a pipeline reads the assignments it needs through the list filters instead.

Endpoints​

MethodPathDescription
GET/limit-assignmentsList the project's assignments, newest first by default
GET/limit-assignments/{bindingKey}One assignment by its key
POST/limit-assignmentsCreate an assignment (201 + Location)
PUT/limit-assignments/{bindingKey}Replace an assignment's editable content
POST/limit-assignments/{bindingKey}/actions/activateMove a DRAFT assignment to ACTIVE
POST/limit-assignments/{bindingKey}/actions/endMove an ACTIVE assignment to ENDED
POST/limit-assignments/actions/validateRun every save-time rule without writing anything

List assignments​

GET /limit-assignments?family&status&subjectType&subjectKey&page&size&sort sorts by assignedAt,desc by default; sort may also name status or family. size is capped at 200 (see Common Contract → Pagination). subjectKey narrows to one subject and requires subjectType alongside it — a subjectKey without a subjectType is 400 malformedRequest (params.field = subjectType, params.reason = subjectKeyRequiresSubjectType). An absent status returns every status, drafts and ended rows included. When present, subjectKey carries the subject's public key in the same per-type convention as Subject below — a consumer's username, an API client's clientId, an application's name, an organization's code, an API proxy's name — with one exception: for subjectType=AUDIENCE_RULE the subject is the assignment itself, so subjectKey is that assignment's own bindingKey and filters directly on it. For PLATFORM/PROJECT, subjectKey is ignored, since neither subject type carries a key of its own.

Update, activate, end​

Update (PUT) replaces the editable content of a DRAFT or ACTIVE assignment — mode, plan keys or custom rule, audienceRule, the enforcement posture, and the validity window. The subject, family, and target are the row's active-scope identity and cannot be changed this way; changing them means ending the assignment and creating a new one. The body's version is optional but, when present, must match the stored one, or the call is refused with 409 versionConflict.

Activate turns a DRAFT assignment live and pushes it to the running environments; it is refused with 409 lifecycleConflict (params.reason = limit.binding.notDraft) on a row that is not DRAFT. End withdraws an ACTIVE assignment and records why; it is refused with 409 lifecycleConflict (params.reason = limit.binding.notActive) on a row that is not ACTIVE, or limit.binding.alreadyEnded on one that already is. Both actions take an optional version in the body, fencing the same conditional write the required status already fences: when the row's status still matches but its version has moved on, the refusal is 409 lifecycleConflict (params.reason = limit.binding.versionConflict) — not the top-level versionConflict key PUT uses.

Both are naturally idempotent through the row's own optimistic version and take no Idempotency-Key.

Idempotency​

POST /limit-assignments requires an Idempotency-Key header (see Common Contract → Idempotency); a missing or blank header is 400 idempotencyKeyRequired. The key is claimed per project and per operation, together with a canonical hash of the request body:

SituationResult
New keyThe assignment is created
Same key, same body, first call already finished409 idempotencyReplayExpired — params.resource names the assignment key the first call created; read it with GET .../{bindingKey} instead
Same key, same body, first call still in flight409 idempotencyInProgress
Same key, a different body409 idempotencyKeyReused

A claim whose underlying call failed is released, so the same key may be retried once the cause is fixed. PUT, the two lifecycle actions, and actions/validate take no Idempotency-Key — the update is fenced by version instead, and the two actions are fenced by both version and the row's current status (see Update, activate, end above).

Request Body​

POST and PUT /limit-assignments/{bindingKey} share one body:

FieldTypeMeaning
subjectobjectThe assignment's owner — see Subject below. Immutable after create
contentobjectThe assignment itself: family, actor scope, target, plan keys or custom rule, posture, validity — see Content below
audienceRuleobjectRequired when subject.type is AUDIENCE_RULE, forbidden otherwise — see Audience rule below
statusenumCreate only. DRAFT (default) or ACTIVE; ENDED is refused with 409 lifecycleConflict (params.reason = limit.binding.statusNotCreatable)
versionintegerUpdate only, as the optimistic-concurrency token — see Update, activate, end above

Subject​

The portable owner reference:

FieldTypeMeaning
typeenumORGANIZATION | CONSUMER | APPLICATION | API_CLIENT | API_PROXY | AUDIENCE_RULE — required
keystringThe subject's public key in the type's own convention: a consumer's username, an API client's clientId, an application's name, an organization's code, an API proxy's name. Absent for AUDIENCE_RULE (its key is minted by the server and echoed back as the assignment's own bindingKey on read)

A key that resolves to no subject is 404 resourceNotFound (params.subjectType, params.subject). Which actorScope values a subject type may carry, and which of ORGANIZATION/CONSUMER/APPLICATION/API_CLIENT may hold two live assignments that differ only by actorScope, is the same matrix the manager surface documents — see Limit Plans.

PROJECT and PLATFORM are no longer assignable: project and platform limits were removed. POST /limit-assignments and POST /limit-assignments/actions/validate with subject.type PROJECT or PLATFORM are refused with 400 malformedRequest (params.field = subject.type, params.reason = scopeSubjectRemoved, params.project = the project name). The enum values stay in the schema only because ended rows in the history can still carry them. Update, activate and end of a stored PROJECT/PLATFORM row are unchanged (system administrator only); an ended PROJECT/PLATFORM assignment is read back without a subject key. Assign the plan to a consumer, API client, application, organization or API proxy instead.

Content​

The same shape the Bulk Limit Assignment API carries as its run content — see Content and Target there for the full field-by-field reference (family, actorScope, target, planKeys, customRuleSpec, cacheErrorHandlingType, cacheConnectionTimeoutInSeconds, showRateLimitStatisticsInResponseHeader, the three customMessage* fields, validFrom/validUntil). It also carries the Counted As choice, assignmentMode and inheritTo, described next.

One difference: on this surface mode may additionally be OFF (this subject contributes no local bucket of its own — ancestor ceilings still apply), on top of PLAN and CUSTOM. INHERIT is retired and refused on create and update; a row that carried it was rewritten to OFF at upgrade. See Limit Plans, Assignments & Defaults API for what each mode requires and forbids. The manager screens apply plans in PLAN mode.

Counted As — assignmentMode and inheritTo. For an ORGANIZATION, CONSUMER or APPLICATION subject with a plan or a custom rule, assignmentMode is SELF (the default when absent: one counter for the subject and everything under it) or DESCENDANTS (every subject of the kind named by inheritTo beneath this one that has no assignment of its own gets the plan with its own counter; the subject itself is not counted — except an organization with inheritTo = ORGANIZATION, whose direct members are also counted together in one counter). inheritTo is API_CLIENT (the default), CONSUMER (includes applications) or ORGANIZATION for an organization, and only API_CLIENT for a consumer or an application. An update that sends assignmentMode changes it in place; one that leaves it out keeps the stored value. A pair that the subject type cannot carry is 400 malformedRequest (params.reason = limit.binding.assignmentModeSubject or limit.binding.inheritToInvalid).

A plan key that resolves to no plan is 404 resourceNotFound (params.plan); a plan that is not deployed, or a legacy retired plan, is 409 lifecycleConflict (params.reason = limit.binding.planNotPublished / limit.binding.planRetired). AI_TOKEN_BUDGET content needs no license module: the API layer applies no module gate, so both families behave identically on every installation.

Two assignments of one subject that are counted the same way must not limit the same API, endpoint or AI proxy. A plan has no general limit, so the general overlap (limit.binding.planOverlapGeneral) arises only when both assignments carry their own inline custom limit on all traffic. Create, an update that changes the plans or the counting mode, and activate are refused with 409 lifecycleConflict (params.reason = limit.binding.planOverlap or limit.binding.planOverlapGeneral) when they would.

Audience rule​

Required when subject.type is AUDIENCE_RULE, forbidden for every other subject type (400 malformedRequest, params.reason = limit.binding.audienceRuleShape). Unlike target, this is content — it can be changed by PUT:

FieldTypeMeaning
identitySourceenumAUTH_RESOLVED (default) or VARIABLE
variableIdstringThe global Variable's id — required when identitySource is VARIABLE
operatorenumA comparison operator (EQ, CONTAINS, STARTS_WITH, IN, …) — required
valuestringRequired
outOfTargetActionenumBLOCK (default; shown as "Rejected (403)"), GENERAL_QUOTA, or NONE — NONE leaves identities outside the audience untouched: neither blocked nor counted. Omitting the field still means BLOCK, so an existing client keeps its behaviour. This setting belongs to the target, not to one assignment (Request Rate only): when another active rule assignment already sits on the same target, the value sent is replaced by the one that target carries. Change it for the whole target with PUT /api/limit-bindings/targets/out-of-target on the manager's own REST surface.
generalQuotaModeenumTOTAL (default) or PER_IDENTITY — read only when outOfTargetAction is GENERAL_QUOTA
generalQuotaWindowsarrayREQUEST_RATE only. At least one window (same shape as a Request Rate plan's windows) — required when outOfTargetAction is GENERAL_QUOTA, ignored for BLOCK and NONE
generalQuotaBudgetobjectAI_TOKEN_BUDGET only. The AI token budget (same shape as a plan's aiBudget) with at least one positive token, USD or request-count ceiling — required when outOfTargetAction is GENERAL_QUOTA
additionalClausesarrayFurther conditions combined with OR with operator/value (each with its own identitySource, variableId, operator, value)
identityRegexListstring[]Regular expressions combined with OR with every other condition; the first capture group is the counted identity

An AUDIENCE_RULE subject works with both families. The general quota must match content.family: generalQuotaWindows for REQUEST_RATE, generalQuotaBudget for AI_TOKEN_BUDGET. The other one is 400 malformedRequest (params.reason = limit.binding.audienceRuleFamily).

Response​

Assignment — 200 / 201​

The shape every read and write endpoint on this page returns:

FieldTypeMeaning
bindingKeystringOpaque public id — also the subject key echoed for an AUDIENCE_RULE subject
subjectobjectThe portable Subject reference
familyenumREQUEST_RATE or AI_TOKEN_BUDGET
actorScopeenumANY | AUTHENTICATED | ANONYMOUS
targetobjectThe Target this assignment narrows to
modeenumPLAN | CUSTOM | OFF
planKeysstring[]Plan keys, when mode is PLAN
assignmentMode / inheritToenum / enumThe Counted As choice — see Content
customRuleSpecobjectThe inline rule body, when mode is CUSTOM
audienceRuleobjectThe Audience rule, when subject.type is AUDIENCE_RULE
cacheErrorHandlingType / cacheConnectionTimeoutInSecondsenum / integerEnforcement posture — Request Rate only
showRateLimitStatisticsInResponseHeaderbooleanAdds the remaining-limit headers to the response. Applies to Request Rate and AI Token Budget assignments; an AI plan that leaves its own setting unset uses this value
customMessageTemplate / customMessageContentType / customMessageHttpStatusCodestring / string / integerThe custom limit-exceeded message, when set — Request Rate only
sourceenumHow this row came to exist: MANUAL | CREATION_DEFAULT | BULK | MIGRATION | IMPORT | PORTAL_SUBSCRIPTION
statusenumDRAFT | ACTIVE | ENDED
validFrom / validUntiltimestampOptional validity window
assignedAt / assignedBytimestamp / stringWhen the row was created, and by whom
endedAt / endedBy / endedReasontimestamp / string / stringPresent once status is ENDED
operatorOverridebooleanWhether an operator has pinned this row against a portal-subscription resync
versionintegerThe optimistic-concurrency token for PUT and the two lifecycle actions
bulkRunIdstringPresent when source is BULK — the run that wrote this row
replacedBindingKeystringPresent when this row replaced an earlier one under a bulk run's Replace conflict policy

List envelope — 200​

GET /limit-assignments returns the shared paged envelope — see Common Contract → Pagination:

{
"status": "SUCCESS",
"items": [],
"page": 0,
"size": 20,
"totalElements": 42,
"totalPages": 3,
"sort": ["assignedAt,desc"]
}

Validate — 200​

POST /limit-assignments/actions/validate returns 200 with an empty body when the assignment would be accepted, and 400 malformedRequest with the reason otherwise — nothing is written either way.

Errors​

Every error follows the Common Contract's error envelope — branch on errorKey, never on message.

Error keyWhen
malformedRequestThe body could not be understood, a reference field was left blank (params.field names it), subjectKey was sent without subjectType on the list, an httpMethods list was sent on a target type that does not support one (params.reason = limit.binding.methodAxisNotAllowed), audienceRule was missing/present on the wrong subject type or its general quota did not match the family, a PROJECT or PLATFORM subject was sent on create or validate (params.field = subject.type, params.reason = scopeSubjectRemoved), or any other save-time shape rule failed — params.reason names the service's own key
resourceNotFoundAn unknown assignment key, or a subject/content reference (plan, apiProxy, apiProxyGroup, environment, or subjectType + subject) that resolves to nothing
versionConflictPUT's body version did not match the stored one — params.entity, params.expected, params.current
lifecycleConflictparams.reason names the service's own cause: limit.binding.scopeAlreadyBound (this subject/family/target/actorScope already has a live assignment), limit.binding.conflict (an unrelated unique-index collision), limit.binding.notDraft (activate on a row that is not DRAFT), limit.binding.notActive (end on a row that is not ACTIVE), limit.binding.alreadyEnded, limit.binding.endedIsFinal (update on an ENDED row), limit.binding.planRetired, limit.binding.planNotPublished, limit.binding.planOverlap / limit.binding.planOverlapGeneral, limit.binding.keyAlreadyExists, limit.binding.keyImmutable, limit.binding.scopeImmutable (update tried to move subject/family/target), limit.binding.counterIdentityImmutable, limit.binding.versionConflict (activate/end sent a version the row has moved past, while its status still matched), or limit.binding.statusNotCreatable (status: ENDED on create)
idempotencyKeyRequiredPOST /limit-assignments was missing its Idempotency-Key header
idempotencyKeyReusedThe same key was sent with a different request body
idempotencyReplayExpiredThe same key and body already completed — params.resource names the assignment it created
idempotencyInProgressThe same key and body's first call is still running
permissionDeniedAuthenticated, but the caller lacks Identity/AI Budgets Manage (writes) or View (reads)
unsupportedSortFieldsort named a field other than assignedAt, status, or family

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) require it at Manage. The AI Budgets permission is also accepted in place of Identity.
  • No license module is checked on this surface: AI_TOKEN_BUDGET content is gated by permission exactly like REQUEST_RATE.
  • The admin (platform) project has no subjects and is refused with 400 malformedRequest (params.reason = platformScopeNotSupported) — assignments are always project-scoped on this surface.
  • A project outside the caller's authorized scope is always 404 resourceNotFound, never 403 — see Common Contract → Telling 402, 403 and 404 apart.

Example​

Create a draft assignment, validate a change before saving it, activate the draft, then end it:

# 1. Create — DRAFT by default; requires Idempotency-Key
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-assignments" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c2a9e10-4b3d-4f8a-9e21-6a0d3f5b7c88" \
-d '{
"subject": { "type": "API_CLIENT", "key": "acme-mobile-app" },
"content": {
"family": "REQUEST_RATE",
"actorScope": "AUTHENTICATED",
"mode": "PLAN",
"planKeys": ["standard-100rps"],
"target": { "type": "ALL" }
}
}'
# 201 Created, Location: /apiops/projects/finance/limit-assignments/lpb_9f2a1c

# 2. Validate a content change without writing anything
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-assignments/actions/validate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "API_CLIENT", "key": "acme-mobile-app" },
"content": {
"family": "REQUEST_RATE",
"actorScope": "AUTHENTICATED",
"mode": "PLAN",
"planKeys": ["standard-500rps"],
"target": { "type": "ALL" }
}
}'

# 3. Activate the draft
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-assignments/lpb_9f2a1c/actions/activate" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "version": 0 }'

# 4. End it later
curl -X POST "https://apinizer.example.com/apiops/projects/finance/limit-assignments/lpb_9f2a1c/actions/end" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "version": 1, "reason": "Client decommissioned" }'
  • Bulk Limit Assignment API - The population-scale counterpart of this same engine, sharing its Content and Target shapes
  • Limit Plans, Assignments & Defaults API - The manager's own REST surface behind Limit Plans, including its single-assignment endpoints (/api/limit-bindings) this page mirrors portably
  • Limit Plans - The manager UI screen 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