Skip to main content

Management API

Observed Runtime policy extensions (SDK 0.30.0)​

The SDK explicitly models additional fields observed in live profile responses on 2026-09-08. They are API extensions, not a claimed update to the published Management OpenAPI contract or its coverage denominator.

Policy location (under model-configuration)Typed extension
data-protection.database-security[]Optional severity string
data-protection.source-code-detectionOptional object with optional action and severity strings
app-protection.malicious-code-protectionOptional severity string
app-protectionOptional url-detected-severity string
model-protection[]Optional severity and severity-by-confidence object
model-protection[].toxic-category-list[]Optional severity-by-confidence object on each category
model-protection[].topic-list[].topic[]Optional severity string on each topic reference
agent-protection[]Optional severity string

SeverityByConfidenceSchema types the observed optional high and moderate string properties. SourceCodeDetectionSchema types source-code settings. Both schemas and their inferred types are exported from the SDK root. ToxicCategorySchema and its inferred ToxicCategory type also expose category-level confidence severities. Existing models expose the other fields directly. Unknown additive fields still pass through; severity strings are not restricted to an unverified enum. Incorrect types in these explicitly modeled fields now fail validation rather than passing through as unknown values.

Older profiles can omit these fields. Parsing does not fill in defaults, remove server-added fields, or declare different policies equivalent. In particular, explicitly configured severity values must survive read-modify-write operations. A schema update alone does not fix the CLI restore verifier's comparison of a source policy with a destination policy containing additional server-generated settings.

import { PolicySchema } from '@cdot65/prisma-airs-sdk';

const parsed = PolicySchema.parse({
'ai-security-profiles': [{
'model-configuration': {
'model-protection': [{
name: 'toxic-content',
'severity-by-confidence': { high: 'medium', moderate: 'low' },
}],
},
}],
});
const severity: string | undefined = parsed['ai-security-profiles']?.[0]
?.['model-configuration']?.['model-protection']?.[0]
?.['severity-by-confidence']?.high;
console.log(severity); // medium

Runnable, credential-free example: docs-site/examples/runtime-policy-extensions.ts. Regression tests include the observed shape, omission preservation, future values, malformed types, and OAuth-backed mocked transport read/write preservation.

Nested toxicity follow-up — 2026-09-09​

Live read-only OAuth retrieval validated 19 source profiles (40 nested toxicity categories) and eight destination profiles (eight categories), preserving every returned policy and both credential files. The SDK does not inject the observed nested defaults.

{"role":"source","profiles":19,"nestedCategories":40,"validated":true}
{"role":"destination","profiles":8,"nestedCategories":8,"validated":true}

The CLI separately verified five synthetic live policy round trips, including omitted category severities and explicit overrides; all five test profiles were removed. The real migration remains incomplete: the read-only recovery preview verifies eight existing profiles and plans 11 creations. This is not evidence that those 11 writes have succeeded.

The updated full SDK suite passed 11,666 tests, including 28 extension tests. Coverage: 99.72% statements/lines, 100% functions and 96.79% branches. The category model retains unknown fields, rejects malformed confidence values and preserves omission and explicit values through the mocked OAuth read/write workflow.

Earlier validation evidence — 2026-09-08​

Read-only live OAuth retrieval with the rebuilt SDK validated all 19 source profiles and the one existing destination profile, including the severity-bearing profile from the stopped CLI restore. Re-parsing preserved every returned policy; credential files were byte-for-byte unchanged. No profiles, topics or tenant selections were modified.

Actual aggregate validation output (tenant identities omitted):

{"role":"source","profiles":19,"validated":true,"configurationUnchanged":true}
{"role":"destination","profiles":1,"validated":true,"configurationUnchanged":true}

All 11,658 tests passed, including the frozen OpenAPI contracts and 20 new extension tests. Overall coverage: 99.72% statements/lines, 100% functions, 96.8% branches; the changed policy-model file has 100% coverage. TypeScript checks also compile the explicit field types and runnable documentation example. This validates SDK modeling, not server-default equivalence, detection behavior or completion of the CLI migration.

Topic-reference follow-up — 2026-09-09​

The full migration follow-up on 2026-09-09 also observed severity: "medium" on topic-guardrails detectors, blocked-topic references and blocked contextual-grounding detectors. TopicObjectSchema now types optional per-topic severity; the existing detector schema already types the other two fields. Parsing preserves omission and explicit values; the CLI independently checks which server additions are acceptable during restore.

The updated full SDK suite passed 11,667 tests, including 29 policy extension tests. Coverage remains 99.72% statements/lines, 100% functions and 96.79% branches.

Token-scoped inventories and submission validation​

profiles.listForToken(), topics.listForToken(), apiKeys.listForToken(), and customerApps.listForToken() use the documented token-scoped collection routes. Existing TSG-scoped list() methods retain their behavior for CLI compatibility.

Profile creation requires policy at submission. Revisions are server-assigned when omitted; some profile updates create a new revision with a different ID, so use the returned ID. Minimal custom topics may omit descriptions/examples, and omitted response examples normalize to an empty array. topics.forceDeleteWithAudit(topicId, updatedBy) serializes the audit actor on the force-delete route. Only delete a topic after checking its profile dependencies.

These behaviors were verified through owned live workflows; see the conformance report.

CRUD operations for AIRS configuration via OAuth2 client credentials. Covers security profiles, custom topics, API keys, customer apps, dashboard telemetry, DLP resources, 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:

ResourceWhat it's for
client.profilesSecurity profiles — the rulesets scans run against. The core resource.
client.topicsCustom detection topics (your own patterns) referenced inside profiles.
client.apiKeysAIRS scan API keys for your tenant — create, rotate, revoke.
client.customerAppsCustomer application registrations.
client.dashboardDashboard app buckets, token consumption, and violation breakdown.
client.dlpProfilesManagement-plane DLP profile references (list only).
client.dlpDLP API namespace for filtering profiles, data patterns, profiles, and dictionaries.
client.deploymentProfilesDeployment profiles for the tenant (list).
client.scanLogsDeprecated, broken historical retrieval; use dashboard session methods.
client.oauthMint / 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.
Typical workflow

Create a profile (and any custom topics it needs) here → reference that profile by name from a scan → review SCM dashboard sessions and detector evidence to refine the profile. The legacy scan-log query is broken and under refactor.

Authentication​

The Management API uses OAuth2 client_credentials flow, separate from the scan API's API key auth. Three values are required:

Env VarRequiredDescription
PANW_MGMT_CLIENT_IDYesOAuth2 client ID from SCM
PANW_MGMT_CLIENT_SECRETYesOAuth2 client secret
PANW_MGMT_TSG_IDYesTenant Service Group ID
PANW_MGMT_ENDPOINTNoAPI base URL (default: https://api.apps.paloaltonetworks.com/aisec)
PANW_MGMT_TOKEN_ENDPOINTNoToken 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',
});

// DLP endpoints use the same OAuth credentials but a separate base URL.
const dlpClient = new ManagementClient({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
tsgId: '1234567890',
dlpEndpoint: 'https://api.dlp.paloaltonetworks.com',
});

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 with its own private OAuthClient — it does not accept tokenBufferMs / onTokenRefresh and does not expose the instance. Construct a standalone OAuthClient only when you need a bearer token for your own HTTP calls or a custom auth workflow; see the OAuth lifecycle guide.

Security Profiles​

Revision-aware pagination

profiles.list() returns one API page. Use profiles.listAll() when you need the complete revision history, or profiles.list({ latest: true }) to request only the latest profile revision from the service. get() and getByName() walk pages automatically (up to the all-page helpers' default 10,000-record cap), so their result is not limited to the first 100 records.

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); // 0 (or absent) on the last page

// Flatten every page. latest is sent to the service on every request.
const latestProfiles = await client.profiles.listAll({ latest: true, limit: 100 });

Update​

profile_id is optional on the response type (hence the !); the API always populates it.

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​

Latest topic revisions

The topics API has no server-side latest filter. topics.list({ latestOnly: true }) walks all pages, groups by topic_name, and returns the highest revision. Use topics.listAll() for the complete history. get() resolves an exact revision UUID and getByName() returns the highest revision.

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 });

// Complete revision history across every page
const allRevisions = await client.topics.listAll({ limit: 200 });

// Highest revision per topic name (then apply offset/limit to the grouped result)
const { custom_topics: latestTopics } = await client.topics.list({ latestOnly: true });

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)
// The acting user is required by the API.
const forced = 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 });

// Flat inventory across every page
const allKeys = await client.apiKeys.listAll({ limit: 100 });

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 });

// Flat inventory across every page
const allApps = await client.customerApps.listAll({ limit: 100 });

listAll() follows next_offset and returns a flat array. As with every all-page helper, max defaults to 10,000; use a smaller value to bound an inventory read or max: 0 to opt out.

Update​

const updated = await client.customerApps.update('customer-app-uuid', {
tsg_id: '1234567890', // required by the CustomerApp body
app_name: 'updated-app',
cloud_provider: 'aws',
environment: 'production',
});

Delete​

const result = await client.customerApps.delete('my-app', 'user@example.com');

Dashboard​

For the SDK 0.26.0 alternate-host methods, rankings, session inventory, transaction details, and explicit scan-content retrieval, see SCM Dashboard Extensions.

Per-application token consumption and per-detector violation counts — the same data SCM renders in the AI Security > Runtime > API Applications panel.

Use dashboard.applicationsOverview() to enumerate dashboard buckets. This is the canonical source for reporting because AIRS dashboard data is bucketed by the literal metadata.app_name value sent in scan payloads. A registered customer app can therefore appear as multiple dashboard buckets when the same app ID sends different scan metadata names.

The drill-down methods, application() and applicationViolationBreakdown(), 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. For drill-down calls, timeInterval is 7 | 30 | 60 (default 30), timeUnit is 'days' only (default 'days'); other values return HTTP 400.

Applications overview​

dashboard.applicationsOverview() returns the dashboard bucket inventory plus pagination metadata. Each item's id is the registered customer app UUID, and name is the scan-payload app name to pass into drill-down calls.

const apps = await client.dashboard.applicationsOverview({
timeInterval: 30,
timeUnit: 'days',
limit: 25,
offset: 0,
});

for (const app of apps.items ?? []) {
if (!app.id || !app.name) continue;
console.log(app.name, app.id);
}

This endpoint accepts timeUnit: 'days' with timeInterval: 7 | 30 | 60, plus the singular windows timeUnit: 'day' / timeInterval: 1 and timeUnit: 'hour' / timeInterval: 1.

Alternate SCM dashboard host​

The local SDK also supports the undocumented SCM dashboard host without rerouting other management resources. The constructor-only dashboardEndpoint overrides the base URL for client.dashboard only; its default remains the management endpoint. Dashboard requests share the existing OAuth client and include the configured tenant's x-tsg-id header.

import { ManagementClient, DashboardApplicationsOverviewSchema } from '@cdot65/prisma-airs-sdk';

const scm = new ManagementClient({
dashboardEndpoint: 'https://api.apps.paloaltonetworks.com/aisec',
}); // reads PANW_MGMT_CLIENT_ID, PANW_MGMT_CLIENT_SECRET, PANW_MGMT_TSG_ID

const query = { timeInterval: 1, timeUnit: 'day', limit: 25, offset: 0 } as const;
const raw: unknown = await scm.dashboard.applicationsOverviewRaw(query);
const inspected = DashboardApplicationsOverviewSchema.parse(raw);

// Prefer this typed call once the deployment's response has been verified:
const daily = await scm.dashboard.applicationsOverview(query);

applicationsOverviewRaw() preserves unstructured JSON, returns undefined for an empty HTTP body, and omits response bodies from SDK debug logging. HTTP errors and invalid JSON still fail. The typed overview requires an items array: a missing body/array is not silently treated as an empty inventory. Unknown fields are preserved by the Zod response model.

On 2026-09-07 a fresh service-account client_credentials token succeeded against this host, followed by the raw read, lossless Zod validation, typed read, and four offset-pagination reads. No browser cookies, pasted bearer token, Origin, Referer, or browser-only headers were needed. This verifies application activity summaries, not detailed scan-log retrieval.

Use sessions_total and sessions_violated for the API's per-application counters. Observed time-series buckets can contain total: 0 alongside nonzero violated; do not sum those buckets to reconstruct totals or assume violated <= total within a bucket. Preserve timestamp strings (including fractional seconds) and treat (id, name) as the bucket identity, not id alone.

Runnable example: npx tsx --env-file=.env docs-site/examples/mgmt-dashboard-overview.ts after building this local SDK. For the read-only credential-file E2E, run npx tsx scripts/e2e-dashboard-overview.ts. It prints validation metadata/counts, not credentials or complete response bodies. This feature has not been published yet.

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 dashboard.applicationsOverview() with dashboard.application() to attribute token spend across every dashboard bucket in the tenant:

const apps = await client.dashboard.applicationsOverview({ 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 apps.items ?? []) {
if (!app.id || !app.name) continue;
const overview = await client.dashboard.application({
appId: app.id,
appName: app.name,
});
const tokens = scale(
overview.token_stats?.monthly_total_tokens,
overview.token_stats?.monthly_total_tokens_scale,
);
console.log(`${app.name}: ${tokens.toLocaleString()} tokens this month`);
}

See docs-site/examples/mgmt-dashboard.ts for a runnable dashboard walkthrough. It can be run directly with npx tsx --env-file=.env docs-site/examples/mgmt-dashboard.ts.

DLP Profiles​

List management-plane DLP data profile references configured for the TSG. For CRUD over the DLP service itself, use client.dlp.

const { dlp_profiles } = await client.dlpProfiles.list();

DLP API Namespace​

client.dlp uses the DLP API base URL (https://api.dlp.paloaltonetworks.com by default) with the same OAuth credentials as the Management API. Override the DLP host with the constructor-only dlpEndpoint option.

SubclientOperationsGuide
client.dlp.dataFilteringProfileslist, get, full replaceData Filtering Profiles
client.dlp.dataPatternslist, create, get, replace, patch, deleteData Patterns
client.dlp.dataProfileslist, create, get, replace, patchData Profiles
client.dlp.dictionarieslist, multipart create, get, multipart replace, patch, deleteDictionaries

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​

:::danger Broken legacy retrieval — under refactor

ScanLogsClient / client.scanLogs.query() must currently be considered broken for historical retrieval. Live validation observed HTTP 200 responses with no body, as well as HTTP 400 for some query windows. An empty SDK result is not evidence that no activity occurred.

Use the SCM dashboard session workflow instead: sessionsOverview() → session() → sessionTransaction(), with explicit scanContent() only when needed. These are different data models, not a transparent replacement for the old scan-log payload. The legacy class remains for compatibility while its integration is refactored.

:::

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_result_for_dashboard?.scan_result_entries);

// 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​

Reuse one client

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.

Delete is referential — expect 409s

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) — updatedBy is required.
  • topics.forceDelete(id, updatedBy?) — the API requires updatedBy; omission now fails locally. The optional TypeScript argument is retained for existing CLI wrappers. New code can use forceDeleteWithAudit(id, updatedBy) for a required parameter. Both use /topic/{id}/force, not the obsolete /topic/force/{id} path.
List endpoints paginate

list() returns up to 100 items by default. Pass { offset, limit } and follow next_offset until it comes back 0 or absent (for scan logs, follow page_token) to walk the full set — or call the resource's listAll(), which does exactly that. 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 under the same key ID (you pass the rotation schedule for the new key) — cleaner than deleting and re-adding.

Tune profiles from real traffic. Use SCM dashboard sessions and detector breakdowns to investigate observed traffic, then review detectors and custom topics. The legacy scanLogs.query() path is broken and must not be used to infer a lack of threats.

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 500/502/503/504 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)
npx tsx --env-file=.env docs-site/examples/mgmt-auth.ts
npx tsx --env-file=.env docs-site/examples/mgmt-profiles.ts
npx tsx --env-file=.env docs-site/examples/mgmt-topics.ts
npx tsx --env-file=.env docs-site/examples/mgmt-dashboard.ts
npx tsx --env-file=.env docs-site/examples/mgmt-dlp-data-filtering-profiles.ts
npx tsx --env-file=.env docs-site/examples/mgmt-dlp-data-patterns.ts
npx tsx --env-file=.env docs-site/examples/mgmt-dlp-data-profiles.ts
npx tsx --env-file=.env docs-site/examples/mgmt-dlp-dictionaries.ts

# Self-contained validation (no credentials or .env needed — uses local mock servers)
npx tsx docs-site/examples/profiles-get-validation.ts # get() and getByName() methods
npx tsx docs-site/examples/profiles-crud-validation.ts # full CRUD lifecycle (create/list/get/update/delete/force-delete)

Run everything from the repository root. --env-file needs Node 20.6+ and an existing .env; see Runnable Examples for captured output.