Skip to main content

Authentication, Permissions & Errors

Exactly how tokens are accepted, which StratoLens permission each route needs, how your data scope applies, and what every error response means. Start at Troubleshooting if you arrived here from an error message.

Token Requirements

Every request carries an Authorization: Bearer <token> header. The token must be:

  • an Entra ID v2 access token,
  • with audience = the StratoLens client ID (api://<clientId>),
  • with scope (scp) containing external_read and not access_as_user,
  • issued to a client app (azp/appid) on the Trusted clients list,
  • for a user, not app-only.

Azure CLI --resource "api://<clientId>" and PowerShell -ResourceUrl "api://<clientId>" request the app's default scope. The token then carries only what the client is pre-authorized for, which is external_read.

Both scopes means a browser token

A token carrying both access_as_user and external_read is treated as a browser token and rejected with 401. Custom apps must request api://<clientId>/external_read only.

What Happens to Every Request

Checks run in this order. The first one that fails decides the response.

  1. The token is validated. Any problem gives 401.
  2. Is the API on? If not: 403 External API access is disabled by your StratoLens administrator.
  3. Is the client trusted? If not: 403 Client <id> is not a trusted client for this StratoLens installation.
  4. Is it a GET to one of the 73 listed routes? If not: 403 This route is not available through the external API.
  5. If Restrict API access to granted users is on, does the user have the flag? If not: 403 Your StratoLens administrator has not granted you external API access.
  6. Does the user have the route's permission? If not: 403 Access denied
  7. The route runs and validates its own parameters (400/404).

Permissions per Route Area

Permission names are as shown in the StratoLens UI. Each route's reference block repeats its permission.

StratoLens permission required per route area
PermissionRoutes
none (any signed-in user)whoami
View ScansgetAllScans, getScanStatus, getScanWarnings
View ResourcesInventory, entity, role assignment, scan diff, security findings, sizing, orphaned, retirement, quota, access optimization and performance metrics routes, and the public-exposure, privileged-access, access-drift, security-config-drift, infrastructure-change and orphan-waste reports
View CostsCost explorer, anomalies and commitment routes, and the governance-audit, forecast, trend-variance and spend-accountability reports
View Defender AssessmentsEvery defender-* and getDefender* route, and defender-plan-costs
View Policy DataEvery policy-* route
View Advisor RecommendationsEvery getAdvisor* route
View Resource ChangesgetResourceGraphChanges, resource-changes-summary
View Azure Activity LogsgetActivityLogsInTimeWindow, activity-log-summary, activity-log-details
View Audit LogsgetAuditLogs
decided per requestsearchResources (by destination), realized-savings-recommendations (by types), getChangeAttribution (either View Resource Changes or View Azure Activity Logs)

Built-in Roles

The read permissions each built-in role holds. Custom roles may differ; the per-route permission is what counts.

Read permissions held by each built-in role
PermissionViewerOperatorManagerAdministratorCost AnalystResource Viewer
View Scans✓✓✓✓✓✓
View Resources✓✓✓✓✓✓
View Costs✓✓✓✓✓–
View Defender Assessments✓✓✓✓––
View Policy Data✓✓✓✓––
View Advisor Recommendations✓✓✓✓✓–
View Resource Changes✓✓✓✓––
View Azure Activity Logs✓✓✓✓––
View Audit Logs––✓✓––

Data Scope

Every route returns only data inside your role-based scope: the subscriptions and management groups an administrator granted you. A user with full access sees everything. A personal view filter chosen in the browser is ignored for API calls, so a script's output does not change when you change your UI filter.

These are not narrowed by scope:

  • getAllScans and getScanWarnings
  • getAuditLogs
  • defender-cve-detail (a shared CVE catalog)
  • governance-audit auditIntegrity.auditEntryCount and auditByType (returned only to callers with View Audit Logs, otherwise null)
  • Some installation-wide summary counts, noted per route

Out of scope usually means empty, not 403

When a request names a subscription outside your scope, most detail routes return an empty 200 rather than a 403. The reference pages note the exceptions.

Error Format

Every error body is JSON with a single error string:

Error bodyJSON
{ "error": "<message>" }
HTTP status codes and their meaning
StatusMeaning
400A query parameter is missing or invalid. The message names the parameter.
401Always exactly Authentication failed, for any token problem: missing header, expired, wrong audience, wrong scope, app-only, both scopes. Details are only in server logs.
403One of the admin-controlled messages listed under What Happens to Every Request, or Access denied (missing permission), or a route's own permission message.
404An unknown scan in a two-scan comparison, or an unknown entity on a few routes (noted per route).
500Unexpected failure. Also returned for an unknown scanId on most single-scan routes (see Conventions, Scan IDs).

401 never says why

Check the token-requirement list first, in this order: user sign-in (not app-only), external_read only, a trusted client, not expired.

Troubleshooting

Find your symptom, then the one-line fix.

Answer

The client isn't pre-authorized for external_read. An admin completes wizard step 2, then clicks Verify and add.

401 Authentication failed after az login --service-principal or --identity

Answer

That is an app-only token. Sign in as a user. Unattended automation is not supported.

401 from a custom app

Answer

The app requested both access_as_user and external_read, or the token expired. Request external_read only and get a fresh token.

401 on every call, and administrators see the migration banner

Answer

The installation hasn't completed the one-time access-token migration. An admin clicks the banner, runs the Cloud Shell script in the Enable Scoped Access Tokens drawer, clicks Verify access, then signs out and in.

401 with az rest against an http:// address

Answer

az rest sends no token over plain http. Use the token-only approach.

403 "External API access is disabled by your StratoLens administrator."

Answer

The master switch is off. An admin turns on Allow external API access and saves.

403 "Client <id> is not a trusted client for this StratoLens installation."

Answer

The client isn't on the Trusted clients list. An admin adds it with Add client.

403 "This route is not available through the external API."

Answer

The request isn't a GET, or the route isn't one of the 73 documented here. Use a listed route.

403 "Your StratoLens administrator has not granted you external API access."

Answer

Restrict API access to granted users is on and none of your grants carries the flag. An admin enables External API access on your grant or on one of your groups' grants.

403 "Access denied"

Answer

Your role lacks the route's permission. Ask for a role that includes it (see Permissions per Route Area).

403 "You do not have permission to search <destination>." or "...realized savings recommendations."

Answer

These two routes check permission per request. Narrow destination or types to what your role covers, or get the missing permission.

A browser app fails with a CORS error

Answer

The API has no CORS support. Call it from a backend or a script.

400 "time window must not exceed 366 days"

Answer

Split the period into windows of up to 366 days. See Time windows for how to chain them.

400 "<name> must list at most 200 values"

Answer

Split the list across several calls.

404 "Base scan with ID '…' not found."

Answer

That scan ID isn't on this installation. Take IDs from getAllScans.

400 "Base scan '…' must be older than compare scan '…'."

Answer

The pair is reversed or equal. Pass the older scan as baseScanId.

500 on a single-scan route with a plausible scanId

Answer

The scan doesn't exist. Most single-scan routes return 500 rather than 404 for an unknown scan. Take IDs from getAllScans.

Was this page helpful?