APIM-CLI version
1.14.15
API-Management version
7.7.20250530
Bug description
When an API has multiple quota restrictions configured for the same method, especially method: "*", updating one restriction through apim api import can incorrectly modify another restriction.
I reproduced the issue with both APIM CLI 1.14.13 and 1.14.15.
Environment
- APIM CLI:
1.14.15
- Java:
17.0.12
- API Manager:
7.7.20250530
- Quota: System Default Quota
- API state: unpublished
quotaMode: default behavior
Initial quota
The API initially has two independent System Default Quota restrictions:
systemQuota:
system: false
restrictions:
- method: "*"
type: "throttle"
config:
period: "day"
per: "1"
messages: "10000"
- method: "*"
type: "throttlemb"
config:
period: "minute"
per: "1"
mb: "100"
This configuration was exported by APIM CLI from an API where the quotas had initially been configured manually in API Manager.
In API Manager this correctly represents:
10000 messages every 1 day
100 MB every 1 minute
Change
Only the message rate was changed from 10000 to 20000:
systemQuota:
system: false
restrictions:
- method: "*"
type: "throttle"
config:
period: "day"
per: "1"
messages: "20000"
- method: "*"
type: "throttlemb"
config:
period: "minute"
per: "1"
mb: "100"
Then the API was imported again:
apim api import -s <stage> -c api-config.yaml
Expected result
The existing restrictions should remain independent:
20000 messages every 1 day
100 MB every 1 minute
Only the messages value of the throttle restriction should be updated.
Actual result
After the import, the System Default Quota contains two Throttle MB restrictions:
100 MB every 1 minute
100 MB every 1 minute
The throttle restriction is lost.
Debug log
The debug log shows that the desired and actual quotas are correctly detected before the merge:
Desired:
[
QuotaRestriction [method=*, type=throttle,
config={period=day, per=1, messages=20000}],
QuotaRestriction [method=*, type=throttlemb,
config={period=minute, per=1, mb=100}]
]
Actual:
[
QuotaRestriction [method=*, type=throttle,
config={period=day, per=1, messages=10000}],
QuotaRestriction [method=*, type=throttlemb,
config={period=minute, per=1, mb=100}]
]
However, immediately after mergeRestriction(), APIM CLI produces:
Merged Quota:
[
QuotaRestriction [method=*, type=throttlemb,
config={period=minute, per=1, mb=100}],
QuotaRestriction [method=*, type=throttlemb,
config={period=minute, per=1, messages=20000, mb=100}]
]
The incorrect merged quota is then sent to API Manager with the quota PUT request.
This indicates that the problem happens inside APIM CLI before the update is sent to API Manager.
Suspected root cause
In APIQuotaManager.mergeRestriction(), the source code contains this comment:
// It's considered as the same restriction when quota type, method, period & per are equal
However, the actual matching condition only compares the method:
if (desiredRestriction.getMethod().equals(existingRestriction.getMethod()))
quotaApiMethodExists() also appears to determine whether a restriction exists using only method.
Therefore, when multiple restrictions have:
they are all considered to be the same restriction, regardless of:
In the example above, while processing the second desired restriction (throttlemb), the CLI matches it against the first existing restriction (throttle) simply because both use method: "*".
It then changes the existing restriction type from throttle to throttlemb and updates its configuration. This also explains why the debug output contains the invalid mixed configuration:
type=throttlemb
config={period=minute, per=1, messages=20000, mb=100}
Suggested behavior
Restrictions should be matched using a key that uniquely identifies the quota window, consistent with the existing source code comment, for example:
type + method + period + per
With that matching logic, these two restrictions would correctly be treated as different entries:
throttle + * + day + 1
throttlemb + * + minute + 1
The value being limited, messages or mb, could then be updated without affecting the other restriction.
Reproducibility
The issue has been reproduced with:
APIM CLI 1.14.13
APIM CLI 1.14.15
and is consistently reproducible when an API has multiple quota restrictions using the same method, particularly method: "*".
Steps to reproduce
Written in description.
Relevant log output
2026-09-11 12:54:16,419 [APIChangeState] DEBUG: Changed property: systemQuota [
Desired: APIQuota [
id=null,
type=SYSTEM,
restrictions=[
QuotaRestriction [
api=*,
method=*,
type=throttle,
config={period=day, per=1, messages=20000}
],
QuotaRestriction [
api=*,
method=*,
type=throttlemb,
config={period=minute, per=1, mb=100}
]
]
]
vs
Actual: APIQuota [
id=null,
type=SYSTEM,
restrictions=[
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttle,
config={period=day, per=1, messages=10000}
],
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttlemb,
config={period=minute, per=1, mb=100}
]
]
]
]
2026-09-11 12:54:16,954 [APIQuotaManager] INFO : Updating System default quota for API: <API_NAME>
2026-09-11 12:54:16,954 [APIQuotaManager] DEBUG: System default-Restrictions:
Desired: [
QuotaRestriction [
api=*,
method=*,
type=throttle,
config={period=day, per=1, messages=20000}
],
QuotaRestriction [
api=*,
method=*,
type=throttlemb,
config={period=minute, per=1, mb=100}
]
],
Actual: [
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttle,
config={period=day, per=1, messages=10000}
],
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttlemb,
config={period=minute, per=1, mb=100}
]
]
2026-09-11 12:54:16,955 [APIQuotaManager] DEBUG: Current Default Quota:
APIQuota [
id=<SYSTEM_DEFAULT_QUOTA_ID>,
type=API,
restrictions=[
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttle,
config={period=day, per=1, messages=10000}
],
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttlemb,
config={period=minute, per=1, mb=100}
]
]
]
2026-09-11 12:54:16,956 [APIQuotaManager] DEBUG: Merged Quota: [
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttlemb,
config={period=minute, per=1, mb=100}
],
QuotaRestriction [
api=<API_ID>,
method=*,
type=throttlemb,
config={period=minute, per=1, messages=20000, mb=100}
]
]
2026-09-11 12:54:16,973 [RestAPICall] DEBUG:
Http verb: PUT
URI: https://<API_MANAGER_HOST>/api/portal/v1.4/quotas/<SYSTEM_DEFAULT_QUOTA_ID>
2026-09-11 12:54:17,684 [UpdateExistingAPI] INFO :
Successfully updated unpublished API: <API_NAME> <API_VERSION>
APIM-CLI version
1.14.15
API-Management version
7.7.20250530
Bug description
When an API has multiple quota restrictions configured for the same method, especially
method: "*", updating one restriction throughapim api importcan incorrectly modify another restriction.I reproduced the issue with both APIM CLI 1.14.13 and 1.14.15.
Environment
1.14.1517.0.127.7.20250530quotaMode: default behaviorInitial quota
The API initially has two independent System Default Quota restrictions:
This configuration was exported by APIM CLI from an API where the quotas had initially been configured manually in API Manager.
In API Manager this correctly represents:
Change
Only the message rate was changed from
10000to20000:Then the API was imported again:
Expected result
The existing restrictions should remain independent:
Only the
messagesvalue of thethrottlerestriction should be updated.Actual result
After the import, the System Default Quota contains two
Throttle MBrestrictions:The
throttlerestriction is lost.Debug log
The debug log shows that the desired and actual quotas are correctly detected before the merge:
However, immediately after
mergeRestriction(), APIM CLI produces:The incorrect merged quota is then sent to API Manager with the quota
PUTrequest.This indicates that the problem happens inside APIM CLI before the update is sent to API Manager.
Suspected root cause
In
APIQuotaManager.mergeRestriction(), the source code contains this comment:// It's considered as the same restriction when quota type, method, period & per are equalHowever, the actual matching condition only compares the method:
quotaApiMethodExists()also appears to determine whether a restriction exists using onlymethod.Therefore, when multiple restrictions have:
they are all considered to be the same restriction, regardless of:
In the example above, while processing the second desired restriction (
throttlemb), the CLI matches it against the first existing restriction (throttle) simply because both usemethod: "*".It then changes the existing restriction type from
throttletothrottlemband updates its configuration. This also explains why the debug output contains the invalid mixed configuration:Suggested behavior
Restrictions should be matched using a key that uniquely identifies the quota window, consistent with the existing source code comment, for example:
With that matching logic, these two restrictions would correctly be treated as different entries:
The value being limited,
messagesormb, could then be updated without affecting the other restriction.Reproducibility
The issue has been reproduced with:
and is consistently reproducible when an API has multiple quota restrictions using the same
method, particularlymethod: "*".Steps to reproduce
Written in description.
Relevant log output
2026-09-11 12:54:16,419 [APIChangeState] DEBUG: Changed property: systemQuota [ Desired: APIQuota [ id=null, type=SYSTEM, restrictions=[ QuotaRestriction [ api=*, method=*, type=throttle, config={period=day, per=1, messages=20000} ], QuotaRestriction [ api=*, method=*, type=throttlemb, config={period=minute, per=1, mb=100} ] ] ] vs Actual: APIQuota [ id=null, type=SYSTEM, restrictions=[ QuotaRestriction [ api=<API_ID>, method=*, type=throttle, config={period=day, per=1, messages=10000} ], QuotaRestriction [ api=<API_ID>, method=*, type=throttlemb, config={period=minute, per=1, mb=100} ] ] ] ] 2026-09-11 12:54:16,954 [APIQuotaManager] INFO : Updating System default quota for API: <API_NAME> 2026-09-11 12:54:16,954 [APIQuotaManager] DEBUG: System default-Restrictions: Desired: [ QuotaRestriction [ api=*, method=*, type=throttle, config={period=day, per=1, messages=20000} ], QuotaRestriction [ api=*, method=*, type=throttlemb, config={period=minute, per=1, mb=100} ] ], Actual: [ QuotaRestriction [ api=<API_ID>, method=*, type=throttle, config={period=day, per=1, messages=10000} ], QuotaRestriction [ api=<API_ID>, method=*, type=throttlemb, config={period=minute, per=1, mb=100} ] ] 2026-09-11 12:54:16,955 [APIQuotaManager] DEBUG: Current Default Quota: APIQuota [ id=<SYSTEM_DEFAULT_QUOTA_ID>, type=API, restrictions=[ QuotaRestriction [ api=<API_ID>, method=*, type=throttle, config={period=day, per=1, messages=10000} ], QuotaRestriction [ api=<API_ID>, method=*, type=throttlemb, config={period=minute, per=1, mb=100} ] ] ] 2026-09-11 12:54:16,956 [APIQuotaManager] DEBUG: Merged Quota: [ QuotaRestriction [ api=<API_ID>, method=*, type=throttlemb, config={period=minute, per=1, mb=100} ], QuotaRestriction [ api=<API_ID>, method=*, type=throttlemb, config={period=minute, per=1, messages=20000, mb=100} ] ] 2026-09-11 12:54:16,973 [RestAPICall] DEBUG: Http verb: PUT URI: https://<API_MANAGER_HOST>/api/portal/v1.4/quotas/<SYSTEM_DEFAULT_QUOTA_ID> 2026-09-11 12:54:17,684 [UpdateExistingAPI] INFO : Successfully updated unpublished API: <API_NAME> <API_VERSION>