Unable to save changes because the credential limit has been reached.
What the limit actually is
A lot of answers online say every App Registration is limited to two client secrets. That's not
what Microsoft documents. The two-secret cap depends on the app's supported account types,
stored in the manifest as signInAudience. From Microsoft's
validation differences by supported account type:
| Supported account types | signInAudience |
Client secrets per app |
|---|---|---|
| This organizational directory only | AzureADMyOrg |
No fixed limit* |
| Any organizational directory | AzureADMultipleOrgs |
No fixed limit* |
| Any organizational directory and personal Microsoft accounts | AzureADandPersonalMicrosoftAccount |
Maximum of two |
| Personal Microsoft accounts only | PersonalMicrosoftAccount |
Maximum of two |
* Every app shares an aggregate cap of 1,200 entries across the collections in its manifest (credentials, redirect URIs, app roles, and so on). Hitting that cap produces a different error: “The size of the manifest has exceeded its limit.”
So if an organization-only app can't save a new secret, the two-secret rule isn't the reason. If the app accepts personal accounts, it is — and expired secrets count toward it exactly like active ones, because Entra never removes a credential on its own when it expires.
Check which case you're in
In the Microsoft Entra admin center,
open App registrations → your app. The Overview page shows Supported account types, and the
Manifest shows the same value as signInAudience. Then open Certificates & secrets
→ Client secrets to see what's taking up the slots.
From the command line, the Azure CLI shows both at once:
az ad app show --id <application-client-id> \
--query "{audience: signInAudience, secrets: passwordCredentials[].{name: displayName, keyId: keyId, expires: endDateTime}}"
Or with Microsoft Graph PowerShell:
$app = Get-MgApplication -Filter "appId eq '<application-client-id>'" `
-Property id,displayName,signInAudience,passwordCredentials
$app.SignInAudience
$app.PasswordCredentials | Select-Object DisplayName, KeyId, EndDateTime
How to fix it
1. Delete the expired secret
If one of the two secrets has already expired, it isn't authenticating anything. Delete it from Certificates & secrets, or from the command line:
# Azure CLI: --id takes the Application (client) ID
az ad app credential delete --id <application-client-id> --key-id <secret-key-id>
# Graph PowerShell: -ApplicationId takes the Object ID ($app.Id), not the client ID
Remove-MgApplicationPassword -ApplicationId $app.Id -KeyId <secret-key-id>
2. If neither has expired, find the one nothing uses
Both slots can be full of valid secrets, typically after a rotation where the old one was never cleaned up. Don't guess. Open the app's sign-in activity in Entra ID → Monitoring & health → Sign-in logs → Service principal sign-ins: each entry records the key ID of the credential it used. A secret whose key ID hasn't appeared there recently is the one to delete.
3. Add the new secret and update its consumers
Create the new secret, copy its Value immediately, and update every place the app authenticates from. If you're here because of an outage, the AADSTS7000222 guide lists where the old value usually lives for Azure CLI, GitHub Actions, Terraform, Azure DevOps, and App Service.
Rotating safely with only two slots
With a two-secret cap, a rotation without downtime only works if one slot is free when you start. The sequence:
- Delete whichever secret nothing is using, so one slot is free.
- Add the new secret in that slot.
- Move every consumer to the new value.
- Confirm in the sign-in logs that requests now use the new key ID.
- Delete the old secret, which leaves the slot free for next time.
The usual way into this error is skipping step 5. Both slots stay full, the next rotation is due months later, and it starts with “credential limit reached” — often while the current secret is already expiring.
Should you change the supported account types?
Only if the app genuinely doesn't need personal Microsoft accounts. Switching it to organizational
accounts only removes the two-secret cap, but it also stops personal accounts from signing in, and
Microsoft notes that changing signInAudience may require changing other properties first.
That's an app design decision, not a fix for a full credentials list. Cleaning up the stale secret is
almost always the right answer.
Keep it from happening again
Expired credentials sit on an App Registration until someone deletes them, and on a two-slot app each one blocks the next rotation. The fix is unglamorous: know when each secret expires, rotate ahead of time, and delete the old one once nothing uses it.
Token Watch is read-only, so it won't delete anything for you. What it gives you is every secret and certificate across the tenant on one screen, with its expiry date, and an alert before each one expires. Stale credentials are easy to spot there and clear out before rotation day.
FAQ
AzureADandPersonalMicrosoftAccount or PersonalMicrosoftAccount) can have at most two. Organization-only apps have no fixed per-app limit, only the 1,200-entry cap shared by all collections in the app manifest.
Token Watch