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 (DRAFTACTIVEENDED, 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); a PROJECT or PLATFORM subject carries no key at all, and 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 idempotencyReplayExpiredparams.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
typeenumPLATFORM | PROJECT | ORGANIZATION | 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 PLATFORM/PROJECT (the scope is the project in the path, or the installation) and 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.

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).

One difference: on this surface mode may additionally be INHERIT (explicitly opt this subject into the nearest enabled ancestor default) or OFF (this subject contributes no local bucket of its own — ancestor ceilings still apply), on top of PLAN and CUSTOM. See Limit Plans, Assignments & Defaults API for what each mode requires and forbids.

A plan key that resolves to no plan is 404 resourceNotFound (params.plan); a retired or unpublished plan is 409 lifecycleConflict (params.reason = limit.binding.planRetired / limit.binding.planNotPublished). AI_TOKEN_BUDGET content is refused with 403 licenseModuleNotEnabled when the project's license does not include the AI Gateway module — checked after authentication.

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) or GENERAL_QUOTA
generalQuotaModeenumTOTAL (default) or PER_IDENTITY — read only when outOfTargetAction is GENERAL_QUOTA
generalQuotaWindowsarrayAt least one window (same shape as a Request Rate plan's windows) — required when outOfTargetAction is GENERAL_QUOTA

content.family must be REQUEST_RATE for an AUDIENCE_RULE subject — there is no rule-based assignment for the AI Token Budget family; AI_TOKEN_BUDGET on an AUDIENCE_RULE subject 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 | INHERIT | OFF
planKeysstring[]Published plan keys, when mode is PLAN
customRuleSpecobjectThe inline rule body, when mode is CUSTOM
audienceRuleobjectThe Audience rule, when subject.type is AUDIENCE_RULE
cacheErrorHandlingType / cacheConnectionTimeoutInSeconds / showRateLimitStatisticsInResponseHeaderenum / integer / booleanEnforcement posture — Request Rate only
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 family was not REQUEST_RATE, 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.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)
licenseModuleNotEnabledAI_TOKEN_BUDGET content was used without the AI Gateway license module
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.
  • AI_TOKEN_BUDGET content additionally requires the AI Gateway license module on every write that touches it (create, update, activate, end, validate); REQUEST_RATE needs no license module.
  • 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