Jump to
Quick start
Fetch organization-scoped security aggregation in one call. This is the fastest path from an authenticated client to dashboard-ready totals.
const base = process.env.NEXT_PUBLIC_APP_URL ?? "https://api.pacci-cvms.com";
await fetch(`${base}/api/manage-assets`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
"x-customer-id": "<customer_id>",
},
});- Requires an authenticated session (NextAuth cookies are sent when
credentials: "include"is set). - Requires
x-customer-id(orx-pacci-customer-id) on every request: the handler returns 400 if it is missing. Use your organization id for customer portal users; for MSP staff, use the active organization id (same value the UI stores in the customer cookie).
Overview
PACCI CVMS exposes HTTP APIs under the Next.js App Router. For **organization-wide rollups** (deduplicated union of visible unified asset groups), use **GET /api/manage-assets** with an authenticated session and **x-customer-id**. The in-app Security Overview uses **GET /api/dashboard/overview** plus companion routes **GET /api/dashboard/overview/sonar** and **GET /api/dashboard/overview/sla** (session org scope only; no customer header). Per-group breakdowns use **GET /api/security-overview**.
Base URL (production)
https://api.pacci-cvms.com
In local development, use your app origin (e.g. http://localhost:3000) with the same path prefixes.
Authentication
APIs rely on NextAuth session cookies for browser and same-origin clients. Include credentials on every request so the session is sent.
For programmatic access from tools such as Postman, use either a valid session cookie from an authenticated browser session or a Bearer token where your deployment supports it (e.g. JWT from your IdP), consistent with your PACCI auth configuration.
fetch(url, {
credentials: "include",
});Headers & org scope
Most JSON APIs expect Content-Type on writes. How the active organization is chosen depends on the route.
Content-Type: application/jsonfor JSON request bodies (POST/PUT).- Always required for manage-assets:
x-customer-idorx-pacci-customer-idmust be the target organization id. The handler returns 400 if the header is missing. Customer portal users must use their own org id (403 otherwise). - Optional for security-overview: the same headers override org when present. If omitted, the server uses the organization already resolved on the session (e.g. customer portal users, or MSP after selecting a customer in the UI). If no organization id can be resolved, you get 400 with a "missing organization scope" style message.
- Dashboard overview family:
GET /api/dashboard/overview,GET /api/dashboard/overview/sonar, andGET /api/dashboard/overview/slado not readx-customer-id; they usegetCustomerContextonly (MSP must have an active customer selected in-session).
Primary endpoint
Manage Assets (primary endpoint)
This is the primary endpoint powering all security dashboards, executive summaries, and reporting across PACCI CVMS. It returns unified Application Code Security and Offensive Security aggregates for the active customer context.
Endpoint
GET https://api.pacci-cvms.com/api/manage-assets
Application Code Security
Includes contributions from:
- SonarQube / SonarCloud (SAST)
- Snyk (SCA)
- Checkmarx (SAST, SCA, and related AppSec categories)
Offensive Security
Includes:
- DAST scan results (e.g. Checkmarx DAST)
- Manual VAPT / vault-synced findings
Response (authoritative)
Field names use camelCase. Severity blocks roll up open issues in the **union** of all visible unified asset groups (AppSec vs OffSec classification). Checkmarx AppSec counts prefer **live CxOne scan-summary** totals when the integration is configured; they fall back to synced DB findings if live data is unavailable. offensiveFindings lists open offensive items (Secure Vault synced rows and Checkmarx DAST); source is secure_vault or DAST. category is Offensive Security for both.
{
"applicationCodeSecurity": {
"total": 1234,
"critical": 45,
"high": 210,
"medium": 700,
"low": 279
},
"offensiveSecurity": {
"total": 320,
"critical": 12,
"high": 80,
"medium": 150,
"low": 78
},
"offensiveFindings": [
{
"id": "sv-example-id",
"title": "Example finding",
"severity": "High",
"description": null,
"source": "secure_vault",
"category": "Offensive Security",
"createdAt": "2026-01-15T12:00:00.000Z",
"cweMappings": null,
"targetUrl": null,
"riskImpact": null,
"recommendation": null,
"folderId": "folder-uuid",
"folderPath": "/Reports",
"sourceFileName": null,
"syncedAt": "2026-01-15T12:00:00.000Z",
"checkmarxProjectId": null,
"checkmarxProjectName": null
}
]
}Behavior
- Required header:
x-customer-idorx-pacci-customer-idmust match an active customer id. Customer portal users may only use their own organization id (403 otherwise). - Aggregates Sonar/Snyk/Checkmarx AppSec and offensive sources (vault + DAST) within visible unified groups for that organization.
- Requires an org-scoped role with read access (same family as other org security APIs).
- After **Sonar** or **Checkmarx** catalog sync, projects removed in the upstream tool are pruned in PACCI (including
sonar_project/checkmarx_appsecgroup members where applicable), so manage-assets totals stay aligned without manual cleanup.
Example request
curl -X GET https://api.pacci-cvms.com/api/manage-assets \
-H "Content-Type: application/json" \
-b "next-auth.session-token=..." \
-H "x-customer-id: <customer_id>"Related: per-group overview
GET https://api.pacci-cvms.com/api/security-overview
- Returns per unified asset group application vs offensive severity totals (same live Checkmarx / DAST / Sonar pipeline as manage-assets, but **one row per group** — summing groups can double-count if the same asset appears in multiple groups).
x-customer-idis optional; if omitted, org scope comes from the session. If neither resolves to an organization id, the response is 400.- For deduplicated org-wide rollups and the offensive findings list, use
GET /api/manage-assets(requiresx-customer-id, includesoffensiveFindings).
Core routes reference
Read-heavy endpoints most integrations and internal tools call. All require an authenticated session unless noted.
GET /api/manage-assets
Org-wide AppSec + OffSec severity blocks and offensiveFindings. Requires x-customer-id (or x-pacci-customer-id) + credentials: "include".
GET /api/security-overview
JSON groups[] with per-group totals and last_updated. Optional customer header; needs resolvable org scope (session or header).
GET /api/dashboard/overview
Core payload for the in-app Security Overview: KPI-style counters, donuts (provider + severity slices for charts), severity totals (criticalSeverity … lowSeverity), and topAssets. Uses session org scope only — no x-customer-id. SLA breach counts are not included here; load GET /api/dashboard/overview/sla separately.
When mergeSonar is true (full-customer scope), SonarCloud open totals and topAssets are provisional until the client merges GET /api/dashboard/overview/sonar. Group-scoped dashboards omit Sonar merge and compute topAssets from the overview response alone.
{
"scopedToAssetGroups": false,
"assetGroups": 3,
"totalLinkedAssets": 42,
"vulnerabilitiesOpen": 120,
"vulnerabilitiesTotal": 400,
"remediated": 280,
"criticalSeverity": 5,
"highSeverity": 20,
"mediumSeverity": 45,
"lowSeverity": 50,
"lastUpdated": "2026-05-12T12:00:00.000Z",
"mergeSonar": true,
"donuts": {
"providerMode": "full",
"provider": [
{ "key": "vault", "name": "Secure Vault", "value": 12, "fill": "#0891b2" },
{ "key": "snyk", "name": "Snyk", "value": 40, "fill": "#db2777" }
],
"severity": [
{ "key": "critical", "name": "Critical", "value": 5, "fill": "#dc2626" },
{ "key": "high", "name": "High", "value": 20, "fill": "#ea580c" }
]
},
"topAssets": []
}GET /api/dashboard/overview/sonar
SonarCloud rollups (sonarOpen, per-project counts, etc.) plus merged topAssets for full-customer scope. Same auth and org rules as GET /api/dashboard/overview. Typically called right after the overview when mergeSonar is true.
GET /api/dashboard/overview/sla
Returns { "breaches": <number> } — count of open vulnerabilities past SLA in the same merged scope as the Vulnerabilities tab. Same session-only org resolution as the other dashboard routes.
GET /api/application-code-security/summary
Lightweight ACS landing totals (Snyk, Checkmarx, SonarCloud open severities). Cached; optional query ?forceRefresh=true. Session org scope (customerIdOr400).
GET /api/asset-groups
Unified groups (MSP-wide + org-owned) visible to the current user. No customer header; org comes from the session. Use PUT /api/asset-groups with replaceMode to mutate (role-gated).
Asset groups
Unified groups link Secure Vault folders, application code security assets (Sonar, Snyk, Checkmarx AppSec), and Checkmarx DAST environments.
GET /api/asset-groups — Returns groups visible in the current context:
- MSP-wide groups:
customerId = null - Org-owned groups:
customerIdset to the organization id
PUT /api/asset-groups — Replaces or updates groups according to replaceMode (all, vault_folders, dast_environments).
For customer_admin, only org-owned rows are modified; MSP-wide groups in the list are read-only in Manage Assets and are not recreated as org-owned (avoids duplicate groups).
Customers
Customer records and nested user management for MSP context switching and org initialization.
- GET /api/customers — List customers (MSP)
- POST /api/customers — Create customer (role-gated)
- Related:
GET/PATCH /api/customers/[customerId],/api/customers/[customerId]/users
Secure Vault
Secure Vault stores PDF reports and synced findings. File payloads are written to disk on the application host.
Production deployments should set SECURE_VAULT_UPLOAD_DIR to a persistent, writable directory. Serverless filesystems (e.g. default Vercel) are not suitable for durable binary storage unless you use external object storage.
- Vault-oriented reads/writes are exposed under routes such as
GET /api/folders,GET /api/secure-vault/findings, and PDF upload viaPOST /api/folders/files(multipart). Conceptually, GET /api/secure-vault and POST /api/secure-vault/upload map to this surface in product documentation.
Integrations & sync
Validate credentials, then trigger catalog sync so PACCI matches SonarCloud, Snyk, and Checkmarx One. Sync routes require an admin role and an active organization on the session (same rules as Setup).
Validation (pre-flight)
- POST /api/integrations/sonarqube/validate — SonarQube / SonarCloud connectivity and token checks
- POST /api/integrations/snyk/validate — Snyk API configuration
- POST /api/integrations/checkmarx/validate — Checkmarx One realm and client credentials
Product docs may abbreviate Sonar as /integrations/son/validate; the deployed route is sonarqube.
Catalog sync (writes PACCI DB)
- POST /api/integrations/sonarqube/sync — Pull Sonar projects and issues for the active session organization. After a successful run, Sonar projects that no longer exist upstream are pruned in PACCI (synced
Projectrows, findings, andsonar_projectasset-group members). - POST /api/integrations/snyk/sync — Refresh Snyk projects and org-level summaries for the active organization.
- POST /api/integrations/checkmarx/sync — Sync Checkmarx One catalog and findings. AppSec code projects no longer assigned in Cx are pruned (including
checkmarx_appsecgroup members for those PACCI project ids).
MSP users must select a customer before calling sync (session customerId); otherwise the API returns 400 with a "no active organization" message.
Architecture notes
PACCI uses a hybrid multi-tenant model for asset groups, integrations, and scoped APIs.
MSP-wide data
customerId = null- Shared across organizations (e.g. template unified groups, filtered by what each org can see)
Customer-owned data
customerId = <org_id>- Scopes folders, projects, org-owned asset groups, and customer-scoped API results
Rules
- Customer admins can view shared plus owned data where the product allows
- Customer admins can modify only owned data (not MSP-wide templates via full replace in Manage Assets)
- MSP admins retain global management capabilities
Common mistakes
Frequent integration issues and how to avoid them.
- Missing
credentials: "include"— Browser requests omit the session cookie → 401 Unauthorized. - Missing
x-customer-id/x-pacci-customer-id— OnGET /api/manage-assetsyou get 400 Missing x-customer-id header. On other org routes, MSP flows may resolve the wrong scope → empty or incorrect data. - Modifying MSP-wide asset groups as customer_admin — Full replace and some writes are forbidden or no-op for shared rows; use org-owned groups or ask an MSP admin.
- Stale local builds — After backend or route changes, rebuild
.next(e.g.next build/ dev restart) so App Router handlers and middleware match the server you are testing.
Error handling
Errors use JSON bodies with an error string; some responses include a details field for debugging.
{
"error": "Unauthorized"
}- 401 — Missing or invalid session
- 403 — Authenticated but not allowed for this resource
- 400 — Malformed body or parameters
Examples
Minimal patterns for browser and cURL clients.
fetch — security aggregation
await fetch("https://api.pacci-cvms.com/api/manage-assets", {
credentials: "include",
headers: {
"Content-Type": "application/json",
"x-customer-id": "<customer_id>",
},
});fetch — asset groups
await fetch("/api/asset-groups", {
credentials: "include",
});cURL — manage assets
curl -X GET https://api.pacci-cvms.com/api/manage-assets \
-H "Content-Type: application/json" \
-H "x-customer-id: <customer_id>"