Summary
airs runtime customer-apps consumption returns zero-valued token_stats for apps whose scan-payload app_name differs from the SCM application name registered in customer_apps. This happens with integrations (e.g. LiteLLM's panw_prisma_airs guardrail) that send a metadata.app_name value that overrides what's registered in SCM.
Root cause: two identifier spaces
The dashboard endpoint and the customer-apps endpoint use different (id, name) tuples for the same logical application:
| Source |
id |
name |
Populated by |
GET /aisec/v1/mgmt/customerapp/tsg/<TSG> (what the CLI lists) |
customer_appId |
app_name |
SCM application registration |
GET /aisec/v1/mgmt/dashboard/v2/apps/application (where token_stats live) |
dashboard id |
dashboard name |
Whatever app_name the scan payload actually sent |
The dashboard endpoint requires both appid AND appname to match a bucket it knows about. Empirically (verified live 2026-05-29): any mismatched (appid, appname) tuple returns a 200 with an all-null body, not a 4xx, so the CLI's per-app loop silently produces zeros instead of erroring.
Today's getCustomerAppConsumption(appName) in src/airs/management.ts does:
- List
customer_apps
- Find entry where
a.app_name === appName
- Use that entry's
customer_appId as appid and its app_name as appname when calling dashboard.application(...)
When the dashboard's tuple is (85ddfdbb-..., "LiteLLM") (scan-payload name) but customer_apps only has (some-other-uuid, "airs-app-litellm") (SCM application name), the dashboard call hits the wrong bucket and returns nulls.
Evidence
Sample response from dashboard/v2/apps/application for a real LiteLLM-fronted app where this is happening (UUIDs masked, structure preserved):
{
"cloud": "gcp",
"id": "<dashboard-uuid-A>",
"name": "LiteLLM",
"profiles": ["ep-airs-api-litellm"],
"source": "api",
"session_stats": {
"most_recent_session_time": "2026-05-29T16:39:36Z",
"total": 14431,
"violating": 8372
},
"token_stats": {
"average_daily_tokens": 155.504,
"average_daily_tokens_scale": "K",
"monthly_total_tokens": 2.985758,
"monthly_total_tokens_scale": "M"
}
}
That's the bucket the dashboard UI displays correctly. But the customer-apps list for the same tenant has:
app_name: "airs-app-litellm" (the SCM application name)
customer_appId: <customer-apps-uuid-B> (a UUID different from <dashboard-uuid-A>)
So airs runtime customer-apps consumption airs-app-litellm calls the dashboard with the wrong tuple (<customer-apps-uuid-B>, "airs-app-litellm") and returns zeros. And there is no way for the user to surface the ("LiteLLM", <dashboard-uuid-A>) bucket through the current CLI.
Reproduction
Any tenant where an integration sends a metadata.app_name (or equivalent) in the scan request that differs from the SCM application name. The LiteLLM panw_prisma_airs guardrail is one example: it sets app_name from its own app_name config option, prefixed with "LiteLLM-" by default. Customers who register a friendlier SCM application name (airs-app-litellm, prod-genai, etc.) hit this immediately.
Proposed fix
Add two override flags to runtime customer-apps consumption that bypass the customer_apps lookup:
airs runtime customer-apps consumption --app-id <uuid> --app-name <literal-name>
When both are provided, skip the customer_apps.list() lookup and pass them directly to dashboard.application() / dashboard.applicationViolationBreakdown(). The user obtains the values from the SCM dashboard UI (AI Security > Runtime > API Applications > pick the app > details panel), where the dashboard-side (id, name) tuple is what's displayed.
Both flags should be required together (passing one without the other should error with guidance), because the dashboard endpoint requires both.
Behavior matrix
| Invocation |
Behavior |
consumption (no args) |
Unchanged: loop customer_apps, query dashboard for each (still useful when names match) |
consumption <appName> |
Unchanged: look up customer_apps, call dashboard |
consumption --app-id <uuid> --app-name <name> |
New: skip lookup, call dashboard directly |
consumption <appName> --app-id <uuid> |
Error: mutually exclusive with positional |
consumption --app-id <uuid> (no name) |
Error: both --app-id and --app-name required together |
consumption --app-name <name> (no id) |
Error: same |
Additional improvement: warn on all-zero all-apps mode
When the no-args "all apps" mode produces a result where every app's token_stats.monthly_total_tokens is zero/null, emit a diagnostic note pointing to this issue. Distinguishing "no apps known" from "apps known but the dashboard doesn't have data under those names" saves customers from wondering why they see zero.
Suggested message:
All apps returned zero token consumption. This can happen when the app_name your integration sends in scan requests does not match the SCM application name. Try airs runtime customer-apps consumption --app-id <uuid> --app-name <scan-payload-name> using the values shown in the SCM dashboard UI.
Out of scope (future)
It would be cleaner to enumerate apps from the dashboard's own backing store rather than from customer_apps. The SCM UI does this somewhere, but the endpoint isn't documented. Probes against likely paths under /dashboard/v2/apps/ return HTTP 403 (which per prior findings means "wrong path" for this surface, not "no permission"). If anyone has visibility into the UI's network calls, locating that endpoint would let a future enhancement auto-list dashboard apps and remove the need for users to look up (id, name) in the UI. Not blocking for this fix.
Related
Summary
airs runtime customer-apps consumptionreturns zero-valuedtoken_statsfor apps whose scan-payloadapp_namediffers from the SCM application name registered incustomer_apps. This happens with integrations (e.g. LiteLLM'spanw_prisma_airsguardrail) that send ametadata.app_namevalue that overrides what's registered in SCM.Root cause: two identifier spaces
The dashboard endpoint and the customer-apps endpoint use different (id, name) tuples for the same logical application:
idnameGET /aisec/v1/mgmt/customerapp/tsg/<TSG>(what the CLI lists)customer_appIdapp_nameGET /aisec/v1/mgmt/dashboard/v2/apps/application(where token_stats live)idnameapp_namethe scan payload actually sentThe dashboard endpoint requires both
appidANDappnameto match a bucket it knows about. Empirically (verified live 2026-05-29): any mismatched (appid,appname) tuple returns a 200 with an all-null body, not a 4xx, so the CLI's per-app loop silently produces zeros instead of erroring.Today's
getCustomerAppConsumption(appName)insrc/airs/management.tsdoes:customer_appsa.app_name === appNamecustomer_appIdasappidand itsapp_nameasappnamewhen callingdashboard.application(...)When the dashboard's tuple is
(85ddfdbb-..., "LiteLLM")(scan-payload name) butcustomer_appsonly has(some-other-uuid, "airs-app-litellm")(SCM application name), the dashboard call hits the wrong bucket and returns nulls.Evidence
Sample response from
dashboard/v2/apps/applicationfor a real LiteLLM-fronted app where this is happening (UUIDs masked, structure preserved):{ "cloud": "gcp", "id": "<dashboard-uuid-A>", "name": "LiteLLM", "profiles": ["ep-airs-api-litellm"], "source": "api", "session_stats": { "most_recent_session_time": "2026-05-29T16:39:36Z", "total": 14431, "violating": 8372 }, "token_stats": { "average_daily_tokens": 155.504, "average_daily_tokens_scale": "K", "monthly_total_tokens": 2.985758, "monthly_total_tokens_scale": "M" } }That's the bucket the dashboard UI displays correctly. But the customer-apps list for the same tenant has:
app_name: "airs-app-litellm"(the SCM application name)customer_appId: <customer-apps-uuid-B>(a UUID different from<dashboard-uuid-A>)So
airs runtime customer-apps consumption airs-app-litellmcalls the dashboard with the wrong tuple(<customer-apps-uuid-B>, "airs-app-litellm")and returns zeros. And there is no way for the user to surface the("LiteLLM", <dashboard-uuid-A>)bucket through the current CLI.Reproduction
Any tenant where an integration sends a
metadata.app_name(or equivalent) in the scan request that differs from the SCM application name. The LiteLLMpanw_prisma_airsguardrail is one example: it setsapp_namefrom its ownapp_nameconfig option, prefixed with"LiteLLM-"by default. Customers who register a friendlier SCM application name (airs-app-litellm,prod-genai, etc.) hit this immediately.Proposed fix
Add two override flags to
runtime customer-apps consumptionthat bypass thecustomer_appslookup:When both are provided, skip the
customer_apps.list()lookup and pass them directly todashboard.application()/dashboard.applicationViolationBreakdown(). The user obtains the values from the SCM dashboard UI (AI Security > Runtime > API Applications > pick the app > details panel), where the dashboard-side(id, name)tuple is what's displayed.Both flags should be required together (passing one without the other should error with guidance), because the dashboard endpoint requires both.
Behavior matrix
consumption(no args)customer_apps, query dashboard for each (still useful when names match)consumption <appName>customer_apps, call dashboardconsumption --app-id <uuid> --app-name <name>consumption <appName> --app-id <uuid>consumption --app-id <uuid>(no name)--app-idand--app-namerequired togetherconsumption --app-name <name>(no id)Additional improvement: warn on all-zero all-apps mode
When the no-args "all apps" mode produces a result where every app's
token_stats.monthly_total_tokensis zero/null, emit a diagnostic note pointing to this issue. Distinguishing "no apps known" from "apps known but the dashboard doesn't have data under those names" saves customers from wondering why they see zero.Suggested message:
Out of scope (future)
It would be cleaner to enumerate apps from the dashboard's own backing store rather than from
customer_apps. The SCM UI does this somewhere, but the endpoint isn't documented. Probes against likely paths under/dashboard/v2/apps/return HTTP 403 (which per prior findings means "wrong path" for this surface, not "no permission"). If anyone has visibility into the UI's network calls, locating that endpoint would let a future enhancement auto-list dashboard apps and remove the need for users to look up(id, name)in the UI. Not blocking for this fix.Related
processed/runtime-security/scm-app-consumption-mgmt-api-howto.mdinairs-dataset(internal, but happy to share verbatim curl examples on request)