Management API
CRUD operations for AIRS configuration via OAuth2 client credentials. Covers security profiles, custom topics, API keys, customer apps, DLP profiles, deployment profiles, scan logs, and OAuth token management.
How it works
If the Scan API is the data plane that inspects traffic, the Management API is the control plane that decides what gets inspected and how. It's the programmatic equivalent of the AIRS configuration screens in Strata Cloud Manager — everything you'd otherwise click through, exposed as typed CRUD calls.
The thing you'll touch most is the security profile: a named ruleset that turns detectors on or off and maps each hit to allow or block. The Scan API references these profiles by name; this API is where you create, tune, and version them. Around profiles sit the supporting objects:
| Resource | What it's for |
|---|---|
client.profiles | Security profiles — the rulesets scans run against. The core resource. |
client.topics | Custom detection topics (your own patterns) referenced inside profiles. |
client.apiKeys | AIRS scan API keys for your tenant — create, rotate, revoke. |
client.customerApps | Customer application registrations. |
client.dashboard | Per-app token consumption + violation breakdown (SCM detail panel). |
client.dlpProfiles | Data-loss-prevention data profiles (list). |
client.deploymentProfiles | Deployment profiles for the tenant (list). |
client.scanLogs | Query historical scan activity by time range. |
client.oauth | Mint / invalidate OAuth tokens for client-credential flows. |
Two ideas to keep in mind:
- One client, many sub-clients. You construct a single
ManagementClient; each resource hangs off it as a property (client.profiles,client.topics, …). Auth is shared and managed for you. - Everything is tenant-scoped. All operations run against the Tenant Service Group (TSG) you authenticate with — there's no cross-tenant access.
Create a profile (and any custom topics it needs) here → reference that profile by name from a scan → later, query scanLogs to see what it caught and refine the profile.
Authentication
The Management API uses OAuth2 client_credentials flow, separate from the scan API's API key auth. Three values are required:
| Env Var | Required | Description |
|---|---|---|
PANW_MGMT_CLIENT_ID | Yes | OAuth2 client ID from SCM |
PANW_MGMT_CLIENT_SECRET | Yes | OAuth2 client secret |
PANW_MGMT_TSG_ID | Yes | Tenant Service Group ID |
PANW_MGMT_ENDPOINT | No | API base URL (default: https://api.sase.paloaltonetworks.com/aisec) |
PANW_MGMT_TOKEN_ENDPOINT | No | Token URL (default: https://auth.apps.paloaltonetworks.com/oauth2/access_token) |
Setup
# Copy the example env file and fill in your credentials
cp .env.example .env
Or export directly:
export PANW_MGMT_CLIENT_ID=your-client-id
export PANW_MGMT_CLIENT_SECRET=your-client-secret
export PANW_MGMT_TSG_ID=1234567890
Regional Endpoints
Override PANW_MGMT_ENDPOINT for non-US deployments:
# EU
export PANW_MGMT_ENDPOINT=https://api.eu.sase.paloaltonetworks.com/aisec
# UK
export PANW_MGMT_ENDPOINT=https://api.uk.sase.paloaltonetworks.com/aisec
# FedRAMP
export PANW_MGMT_ENDPOINT=https://api.gov.sase.paloaltonetworks.com/aisec
Client Initialization
import { ManagementClient } from '@cdot65/prisma-airs-sdk';
// From env vars (recommended)
const client = new ManagementClient();
// Explicit
const client = new ManagementClient({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
tsgId: '1234567890',
});
// EU endpoint
const client = new ManagementClient({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
tsgId: '1234567890',
apiEndpoint: 'https://api.eu.sase.paloaltonetworks.com/aisec',
});
Token fetch, caching, and refresh are handled automatically. If a request gets a 401 or 403, the client refreshes the token and retries once.
Token Lifecycle
The SDK provides fine-grained control over OAuth token state via OAuthClient:
import { OAuthClient } from '@cdot65/prisma-airs-sdk';
const oauth = new OAuthClient({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
tsgId: '1234567890',
tokenBufferMs: 60_000, // refresh 60s before expiry (default: 30s)
onTokenRefresh: (info) => {
console.log(`Token refreshed, expires in ${info.expiresInMs}ms`);
},
});
// Check token state without triggering a refresh
const info = oauth.getTokenInfo();
// { hasToken, isValid, isExpired, isExpiringSoon, expiresInMs, expiresAt }
// Individual checks
oauth.isTokenExpired(); // true if past expiry time
oauth.isTokenExpiringSoon(); // true if within buffer window
oauth.isTokenExpiringSoon(120_000); // custom buffer override (2 min)
The ManagementClient handles this internally — you only need OAuthClient directly for advanced monitoring or custom auth workflows.
Security Profiles
Full CRUD on AI security profile configurations.
Create
const profile = await client.profiles.create({
profile_name: 'my-profile',
active: true,
policy: {
'ai-security-profiles': [
{
'model-type': 'default',
'model-configuration': {
'app-protection': {
'default-url-category': { member: null },
'url-detected-action': '',
},
'data-protection': {
'data-leak-detection': { action: '', member: null },
'database-security': null,
},
latency: {
'inline-timeout-action': 'block',
'max-inline-latency': 5,
},
'mask-data-in-storage': false,
'model-protection': [],
'agent-protection': [],
},
},
],
'dlp-data-profiles': [],
},
});
console.log(profile.profile_id);
Get
// Get by UUID
const profile = await client.profiles.get('profile-uuid');
// Get by name (returns highest revision if multiple exist)
const profile = await client.profiles.getByName('my-profile');
Both methods throw AISecSDKException if no matching profile is found.
List
// All profiles for the TSG
const { ai_profiles } = await client.profiles.list();
// Paginated
const page = await client.profiles.list({ offset: 0, limit: 10 });
console.log(page.next_offset); // undefined if no more pages
Update
const updated = await client.profiles.update(profile.profile_id, {
profile_name: 'my-profile-v2',
active: true,
policy: {
'ai-security-profiles': [
{
'model-type': 'default',
'model-configuration': {
'app-protection': {
'default-url-category': { member: null },
'url-detected-action': '',
},
'data-protection': {
'data-leak-detection': { action: '', member: null },
'database-security': null,
},
latency: {
'inline-timeout-action': 'allow',
'max-inline-latency': 10,
},
'mask-data-in-storage': false,
'model-protection': [],
'agent-protection': [],
},
},
],
'dlp-data-profiles': [],
},
});
Delete
const result = await client.profiles.delete(profile.profile_id);
If the profile is in use by a policy, the API returns a 409 conflict with the referencing policies.
Force Delete
// Force delete removes the profile even if referenced by a policy
// updatedBy is required for profiles
const result = await client.profiles.forceDelete(profile.profile_id, 'user@example.com');
Custom Topics
CRUD for custom detection topics used in security profiles.
Create
const topic = await client.topics.create({
topic_name: 'credit-card-numbers',
active: true,
description: 'Detects credit card numbers',
examples: ['4111-1111-1111-1111', '5500 0000 0000 0004', 'My card number is 4242424242424242'],
});
List
const { custom_topics } = await client.topics.list();
const page = await client.topics.list({ offset: 0, limit: 10 });
Update
const updated = await client.topics.update(topic.topic_id, {
topic_name: 'credit-card-numbers',
description: 'Updated description',
examples: ['4111-1111-1111-1111', 'CVV: 123'],
});
Delete
// Standard delete (fails with 409 if referenced by a profile)
const result = await client.topics.delete(topic.topic_id);
// Force delete (removes even if referenced)
// updatedBy is optional for topics
const result = await client.topics.forceDelete(topic.topic_id);
// or with updatedBy
const resultWithUser = await client.topics.forceDelete(topic.topic_id, 'user@example.com');
API Keys
Manage AIRS API keys for your TSG.
Create
const apiKey = await client.apiKeys.create({
auth_code: 'my-auth-code',
cust_app: 'my-app',
revoked: false,
created_by: 'user@example.com',
api_key_name: 'production-key',
rotation_time_interval: 90,
rotation_time_unit: 'days',
});
console.log(apiKey.api_key_id);
List
const { api_keys } = await client.apiKeys.list();
// Paginated
const page = await client.apiKeys.list({ offset: 0, limit: 10 });
Delete
const result = await client.apiKeys.delete('my-key-name', 'user@example.com');
Regenerate
const newKey = await client.apiKeys.regenerate('api-key-uuid', {
rotation_time_interval: 30,
rotation_time_unit: 'days',
});
Customer Apps
Manage customer applications for your TSG.
Get
const app = await client.customerApps.get('my-app');
List
const { customer_apps } = await client.customerApps.list();
// Paginated
const page = await client.customerApps.list({ offset: 0, limit: 10 });
Update
const updated = await client.customerApps.update('customer-app-uuid', {
app_name: 'updated-app',
cloud_provider: 'aws',
environment: 'production',
});
Delete
const result = await client.customerApps.delete('my-app', 'user@example.com');
Dashboard
Per-application token consumption and per-detector violation counts — the same data SCM renders in the AI Security > Runtime > API Applications detail panel. Pair with customerApps.list() to iterate every app in the tenant for chargeback or risk reporting.
Both methods require appId and a non-empty appName. Sending an empty appname returns HTTP 400; omitting it entirely returns an all-null body — the SDK requires both to keep those failure modes off the happy path. timeInterval is 7 | 30 | 60 (default 30), timeUnit is 'days' only (default 'days'); other values return HTTP 400.
Application overview
dashboard.application() returns token stats, session stats, attached profiles, and cloud/source metadata for one app.
const overview = await client.dashboard.application({
appId: 'd8dc4033-593b-45e7-9633-e0dfc130cc82',
appName: 'chatbot',
});
const { average_daily_tokens, average_daily_tokens_scale, monthly_total_tokens, monthly_total_tokens_scale } =
overview.token_stats ?? {};
// each numeric value is paired with a scale qualifier — 'K' (thousands) or 'M' (millions) —
// both are needed to reconstruct the SCM panel's display value
Narrow the window:
const lastWeek = await client.dashboard.application({
appId: 'd8dc4033-593b-45e7-9633-e0dfc130cc82',
appName: 'chatbot',
timeInterval: 7,
timeUnit: 'days',
});
Violation breakdown
dashboard.applicationViolationBreakdown() returns one entry per detector in detection_type_violation_breakdown[], each with critical/high/medium/low/total severity counts, plus the rolled-up total_violating.
const breakdown = await client.dashboard.applicationViolationBreakdown({
appId: 'd8dc4033-593b-45e7-9633-e0dfc130cc82',
appName: 'chatbot',
});
for (const entry of breakdown.detection_type_violation_breakdown ?? []) {
if ((entry.violation_breakdown?.total ?? 0) === 0) continue;
console.log(entry.detection_type, entry.violation_breakdown);
}
// breakdown.total_violating // rolled-up count across all detectors
Detector codes observed live (10 as of 2026-05-28): agent_security, contextual_grounding, dbs (database security), dlp, malicious_code, pi (prompt injection), source_code, tc (toxic content), topic_guardrails, uf (URL filtering). Schemas use .passthrough(), so new detectors parse cleanly without an SDK bump.
Per-app chargeback pattern
Combine customerApps.list() with dashboard.application() to attribute token spend across every app in the tenant:
const { customer_apps = [] } = await client.customerApps.list({ offset: 0, limit: 100 });
const scale = (n?: number | null, s?: string | null) =>
(n ?? 0) * (s === 'M' ? 1_000_000 : s === 'K' ? 1_000 : 1);
for (const app of customer_apps) {
if (!app.customer_appId) continue;
const overview = await client.dashboard.application({
appId: app.customer_appId,
appName: app.app_name,
});
const tokens = scale(
overview.token_stats?.monthly_total_tokens,
overview.token_stats?.monthly_total_tokens_scale,
);
console.log(`${app.app_name}: ${tokens.toLocaleString()} tokens this month`);
}
See examples/mgmt-dashboard.ts for a runnable version that also surfaces firing detectors per app.
DLP Profiles
List DLP data profiles configured for the TSG.
const { dlp_profiles } = await client.dlpProfiles.list();
Deployment Profiles
List deployment profiles for the TSG.
// All deployment profiles
const { deployment_profiles } = await client.deploymentProfiles.list();
// Include unactivated profiles
const all = await client.deploymentProfiles.list({ unactivated: true });
Scan Logs
Query scan activity logs by time range.
const results = await client.scanLogs.query({
time_interval: 24,
time_unit: 'hour',
pageNumber: 1,
pageSize: 50,
filter: 'all', // 'all', 'benign', or 'threat'
});
console.log(results.total_pages);
console.log(results.scan_results);
// Continue pagination with page_token
const nextPage = await client.scanLogs.query({
time_interval: 24,
time_unit: 'hour',
pageNumber: 2,
pageSize: 50,
filter: 'all',
page_token: results.page_token,
});
OAuth Token Management
Manage OAuth tokens for client credential flows.
Get Access Token
const token = await client.oauth.getAccessToken({
body: { client_id: 'cid', customer_app: 'my-app' },
tokenTtlInterval: 24,
tokenTtlUnit: 'hours',
});
console.log(token.access_token);
Invalidate Token
await client.oauth.invalidateToken('token-value', {
client_id: 'cid',
customer_app: 'my-app',
});
Error Handling
import { AISecSDKException, ErrorType } from '@cdot65/prisma-airs-sdk';
try {
await client.profiles.list();
} catch (err) {
if (err instanceof AISecSDKException) {
switch (err.errorType) {
case ErrorType.OAUTH_ERROR:
console.error('Auth failed:', err.message);
break;
case ErrorType.CLIENT_SIDE_ERROR:
console.error('Bad request:', err.message);
break;
case ErrorType.SERVER_SIDE_ERROR:
console.error('Server error:', err.message);
break;
case ErrorType.MISSING_VARIABLE:
console.error('Missing config:', err.message);
break;
}
}
}
Get the most out of it
Build a single ManagementClient and share it across your app. It owns the OAuth token cache — every sub-client (profiles, topics, …) shares the same token, so you authenticate once and reuse it everywhere. Constructing a fresh client per call throws that cache away.
A standard delete on a profile or topic that's still referenced by a policy fails with a 409 conflict (the response lists the referencing policies). That's a safety net, not a bug. Either detach the references first, or call forceDelete to remove it anyway. Note the asymmetry:
profiles.forceDelete(id, updatedBy)—updatedByis required.topics.forceDelete(id, updatedBy?)—updatedByis optional.
list() returns up to 100 items by default. Pass { offset, limit } and follow next_offset (or page_token for scan logs) until it comes back undefined to walk the full set. Don't assume the first page is everything.
Look profiles up by name when you can. profiles.getByName('my-profile') saves you a list-then-filter, and returns the highest revision if several exist. Both get and getByName throw AISecSDKException when nothing matches — handle that rather than expecting null.
Rotate keys, don't recreate them. apiKeys.regenerate(id, …) issues a new secret while preserving the key's identity and rotation policy — cleaner than deleting and re-adding.
Tune profiles from real traffic. Use scanLogs.query({ time_interval, time_unit, filter: 'threat' }) to see what a profile actually caught (or missed), then adjust detectors and custom topics. Close the loop instead of guessing.
Set the right region. All control-plane calls share one OAuth identity but must target the matching regional endpoint — override PANW_MGMT_ENDPOINT (see Regional Endpoints) for EU/UK/FedRAMP tenants.
Retries are automatic. Like the scan client, management requests retry on transient 5xx errors with backoff, and refresh-and-retry once on 401/403. Tune attempts with numRetries (0–5). See the OAuth lifecycle for the token side of this.
Full reference
Every sub-client method on ManagementClient — with input and output examples — is in the Full API reference.
Running the Examples
# Copy env file and fill in credentials
cp .env.example .env
# Run examples (require credentials)
npm run example:mgmt-auth
npm run example:mgmt-profiles
npm run example:mgmt-topics
# Self-contained validation (no credentials needed — uses mock servers)
npm run example:profiles-get # get() and getByName() methods
npm run example:profiles-crud # full CRUD lifecycle (create/list/get/update/delete/force-delete)