GraphQL API
The GraphQL endpoint, authentication, pagination and the operations available.
You will finish this page able to drive Argus from a script: authenticate, list and triage findings, trigger scans, export BOMs and SARIF, and evaluate a gate policy in CI.
Throughout, $ARGUS_URL is your Argus instance URL and argus_live_xxx is an API key from Settings → API keys.
export ARGUS_URL="https://your-argus-instance"
export ARGUS_API_KEY="argus_live_xxx"
Authentication
Argus exposes one GraphQL endpoint:
POST $ARGUS_URL/api/graphql
Authenticate with a bearer token:
Authorization: Bearer argus_live_xxx
The bearer may be an API key or a signed-in session token. Only the Bearer scheme is accepted. There is no x-api-key header and no query-parameter token on GraphQL.
An API key authenticates as the machine principal. It can create projects, trigger scans, triage findings, import BOMs and use AI features. It cannot delete projects or change settings, members, billing or keys. See Team and access.
Everything is scoped to the key's organization. There is no way to reach another organization's data.
A first call
curl -sS -X POST "$ARGUS_URL/api/graphql" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-H "content-type: application/json" \
-d '{"query":"{ GetPostureSummary { totalOpen critical high medium low slaBreaches } }"}'
{"data":{"GetPostureSummary":{"totalOpen":349,"critical":7,"high":146,"medium":121,"low":75,"slaBreaches":142}}}
Conventions
Pagination is 1-based page plus limit. Responses echo total, and usually page and limit.
| Query | Default limit |
|---|---|
listProjects, listVulnerabilities, ListDeps, ListBOMs | 20 |
listUsers, listScanJobs, ListAPIs, ListThirdParties, ListPolicies | 50 |
ListCreditLedger, GetAuditLog, listPullRequests, ListNotifications | 20 (capped at 100) |
AttackPaths | 8 |
Dates are ISO 8601 strings. JSON is a custom scalar for free-form objects.
Removed in 2026-08
ListRules, CreateRule, UpdateRule and DeleteRule were removed from the schema. They
served a seeded rule catalogue whose enabled flag no scan ever read, so a client calling
UpdateRule received a success response while configuring nothing. Detection rules are gated by
SetRuleOverride / DeleteRuleOverride, which the scan pipeline does consult, see
Policies and gates. Calls to the removed fields now fail with a schema
error rather than silently doing nothing.
Vocabularies are plain strings, not GraphQL enums:
| Concept | Values |
|---|---|
| Severity | CRITICAL · HIGH · MEDIUM · LOW · INFO |
| Finding status | Open · Resolved · false_positive · accepted_risk |
| Finding type | SAST · SCA · Secret · IaC · DAST |
| Scan depth | standard · deep_agentic |
| Scan job status | Queued · Running · Completed · Failed |
| Project status | Pending · Scanning · Scanned · Failed |
| Pull request state | open · closed · merged |
Errors come back in the standard GraphQL errors array. These codes are surfaced in extensions.code and are safe to branch on:
| Code | Meaning |
|---|---|
UNAUTHORIZED | No valid credential, or no longer a member of the organization |
FORBIDDEN | Your role is not permitted to perform this action |
INSUFFICIENT_CREDITS | Not enough credits for this AI action |
RULE_SCAN_LIMIT | Monthly rule-scan quota exhausted |
BAD_INPUT | Invalid arguments |
RATE_LIMITED | Slow down. Carries a Retry-After |
Other server errors are masked to a generic message.
Projects
{
listProjects(page: 1, limit: 20, query: "api", status: "Scanned") {
total page limit
projects {
id name repoUrl branch defaultBranch language status lastScan riskScore
vulnerabilities { critical high medium low info total }
}
}
}
{ GetProject(id: "<project-id>") { id name repoUrl status riskScore lastScan } }
Import a connected GitHub repo (free, does not scan):
mutation { CreateProjectFromRepo(fullName: "owner/name") { id name status } }
Create from a Git URL:
mutation {
CreateProject(name: "my-service", type: "api", repoUrl: "https://github.com/owner/repo", branch: "main") {
id name status
}
}
UpdateProject(id, name, branch) renames or re-targets a project. DeleteProject(id) requires Admin and is not available to API keys, a key authenticates as the machine principal, which does not hold project:delete. In the app it lives at Projects → open a project → Settings → Remove project, behind a type-the-name confirmation.
Deleting a project is irreversible and cascading: it removes the project's findings, scan history, BOMs, API inventory and dependency records. Your Git repository is untouched, only Argus's copy of what it found. There is no archive and no undo.
Scans
Trigger:
curl -sS -X POST "$ARGUS_URL/api/graphql" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-H "content-type: application/json" \
-d '{
"query": "mutation Scan($p: ID!, $d: String) { TriggerScan(projectId: $p, scanDepth: $d) { id status scanType } }",
"variables": { "p": "<project-id>", "d": "standard" }
}'
scanDepth is standard (3 credits) or deep_agentic (10 credits). Every scan also consumes one rule-scan slot.
Poll:
{
listScanJobs(projectId: "<project-id>", limit: 10, status: ["Completed", "Running"]) {
id status scanType findings branch commit triggeredBy startedAt finishedAt duration
}
}
A job reports Queued → Running → Completed or Failed. GetProject(id:) reflects the same lifecycle in its status field.
Findings
{
listVulnerabilities(
page: 1
limit: 50
severity: ["CRITICAL", "HIGH"]
status: ["Open"]
type: ["SCA"]
reachable: true
) {
total page limit
criticalCount highCount mediumCount lowCount
vulnerabilities {
id title severity status type
cveId cvss epss kevKnownExploited
priorityScore priorityBand
reachable reachabilityEvidence
package file line ruleId scanAgent projectName
aiConfidence aiReasoning attackScenario
introducedAt author assignee
}
}
}
All filter arguments: projectId, namespaceId, severity[], status[], type[], scanAgent[], query, from_date, to_date, falsePositive, committer, environment, prBranch, reachable, light.
Pass light: true for a slimmer payload when you only need the list, not the AI narrative fields.
Findings are returned in priority order.
{ GetVulnerability(id: "<finding-id>") { id title description remediation severity priorityScore reachable taintChain } }
Triage
mutation { UpdateVulnerabilityStatus(id: "<finding-id>", status: "false_positive") { id status falsePositive } }
mutation { BulkUpdateVulnerabilities(ids: ["<id1>", "<id2>"], status: "accepted_risk") { status message } }
mutation { AssignVulnerability(id: "<finding-id>", userId: "<user-id>") { id assignee } }
Create and assign in a connected tracker:
mutation {
AssignToExternal(issueIds: ["<id1>"], provider: "linear", assigneeId: "<tracker-user-id>") {
status message provider key url
}
}
provider is jira or linear. On success, key and url identify the created ticket, and the URL is written back onto every finding in the selection. On failure, status is error with the provider's real message, and nothing is written.
Valid statuses: Open, Resolved, false_positive, accepted_risk. Setting false_positive also sets the false-positive flag; any other status clears it.
Re-verify with AI
mutation {
ReverifyFindings(ids: ["<id1>", "<id2>"]) {
status message reverified confirmed falsePositives uncertain
}
}
2 credits per call, up to 25 findings.
Remediation
| Operation | What it does | Cost |
|---|---|---|
GenerateRemediationTask(vulnId) | An agent-ready task brief in markdown and JSON | Free |
GenerateFixPatch(vulnId) | A proposed diff with explanation | AI credits |
VerifyRemediation(vulnId) | Re-checks whether the finding is still reported | Free |
TriggerAgenticFix(vulnId) | Clone → fix → verify → open a pull request | 15 credits |
GetAgenticFixJob(id) | Poll a fix job: status, prUrl, diff, verification | Free |
mutation { TriggerAgenticFix(vulnId: "<finding-id>") { id status agent } }
{ GetAgenticFixJob(id: "<job-id>") { status prUrl error diff verification } }
status progresses to one of pr_opened, no_changes, verification_failed, failed.
Posture and reporting
{ GetPostureSummary { totalOpen critical high medium low slaBreaches slaCritical slaHigh } }
{ GetReachabilitySummary { declared reachable unreachable noiseReductionPct } }
{
GetMTTRMetrics {
overallAvgHours totalFixed totalOpen oldestOpenDays
bySeverity { severity avgHours count slaTarget withinSla }
}
}
{
GetSLABreaches(limit: 50) {
total critical high medium
breaches { vulnId title severity projectName createdAt slaDeadline daysOverdue }
}
}
Dashboard queries, GetDashboardKpis, GetDashboardTrend, GetDashboardTopRiskyRepos, GetDashboardSLABreaches, GetDashboardFrameworks, GetShiftLeftStats, all accept days (1-365).
{
AttackPaths(projectId: "<project-id>", limit: 8) {
id score band likelihood impact
entry { id label exposure }
crownJewel { id label kind }
narrative steps inferred
}
}
Attack-path edges are inferred rather than observed, and the response says so with inferred.
Dependencies, BOMs, VEX, SARIF
{
ListDeps(page: 1, limit: 50, has_vulnerabilities: ["true"]) {
total
dependencies { id name version latestVersion ecosystem transitive reachable licenses vulnerabilityCount }
}
}
{ DependencyFindings(dependencyId: "<dep-id>") { id title severity cveId } }
{ ListBOMs(projectId: "<project-id>", bom_type: "SBOM", page: 1, limit: 20) { total boms { id bomSubtype bomType components status createdAt } } }
{ ExportBOM(id: "<bom-id>", format: "cyclonedx-json") { filename mimeType content } }
Formats: cyclonedx-json, cyclonedx-xml, spdx-json, spdx-tv, csv.
{ GenerateVex(projectId: "<project-id>", format: "openvex") { filename mimeType content } }
Formats: openvex, cyclonedx-vex.
{ ExportSarif(projectId: "<project-id>") { filename mimeType content } }
mutation { ImportBOM(projectId: "<project-id>", content: "<document>", format: "cyclonedx", bomSubtype: "SBOM") { id components status } }
mutation { ImportVex(projectId: "<project-id>", content: "<document>") { statements matchedCves suppressed message } }
{ GetBOMDiff(bomId1: "<a>", bomId2: "<b>") { added removed updated unchanged components { name changeType oldVersion newVersion } } }
{ CheckProvenance(package: "sigstore", version: "3.0.0", ecosystem: "npm") { supported hasProvenance signed slsaLevel builderId sourceRepo summary } }
Policies
{ ListGatePolicies { id name description source } }
source is builtin or saved.
{
EvaluatePolicy(projectId: "<project-id>", policyId: "block-kev-reachable") {
policyName pass failCount warnCount evaluated
violations { ruleId ruleName effect findingId severity cveId }
}
}
PreviewPolicy(projectId:, policy: <JSON>) evaluates an unsaved policy. GetPolicyFacts(projectId:) returns the normalized facts a policy sees.
Gating a pipeline:
PASS=$(curl -sS -X POST "$ARGUS_URL/api/graphql" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-H "content-type: application/json" \
-d '{"query":"query($p:ID!,$id:String!){EvaluatePolicy(projectId:$p,policyId:$id){pass failCount}}","variables":{"p":"'"$PROJECT_ID"'","id":"block-kev-reachable"}}' \
| jq -r '.data.EvaluatePolicy.pass')
[ "$PASS" = "true" ] || { echo "Argus gate: blocked"; exit 1; }
Pull requests
{
listPullRequests(page: 1, limit: 20, state: "merged", mergedWithUnresolvedOnly: true) {
total
pullRequests {
repo number title url state authorLogin
reviewEvent reviewJobStatus reviewFindingCount
findingsRaised findingsResolved findingsRemaining
mergedWithUnresolved mergedUnresolvedCount mergedSnapshotAt snapshotMissing
}
}
}
{ GetPullRequest(id: "<pr-id>") { pullRequest { repo number state } findings { id title severity unresolved } } }
mutation { ReviewPullRequest(repo: "owner/name", prNumber: 42) { status message } }
mutation { SyncPullRequests(projectId: "<project-id>", limit: 30) { status message } }
Billing
{ GetBilling { plan planName priceUsd creditBalance monthlyCredits ruleScansUsed ruleScansLimit periodStart periodEnd } }
{ ListPlans { id name priceUsd monthlyCredits ruleScansPerMonth features } }
{ ListCreditLedger(page: 1, limit: 50) { total entries { createdAt reason delta balanceAfter refType refId } } }
Organization and access
{ OrganizationInfo { id name plan members createdAt logoUrl } }
{ getCurrentUser { id email name role } }
{ MyOrganizations { orgId name role plan isActive } }
{ listUsers(page: 1, limit: 50) { id email name role status lastLogin } }
{ ListRoles { id name description assignable isMachine capabilities { id label granted } } }
{ GetOrgSettings { slaCriticalHours slaHighHours slaMediumHours slaLowHours riskWeightCritical riskWeightHigh retentionDays } }
{ GetAuditLog(page: 1, limit: 50) { total entries { createdAt actorEmail action capability outcome } } }
{ ListAPIKeys { id name key createdAt lastUsed status } }
ListAPIKeys returns masked prefixes. The full key is returned exactly once, by GenerateAPIKey(name:).
GetAuditLog requires the Admin role. An API key authenticates as the machine principal and will get FORBIDDEN:
{"errors":[{"message":"Your role (api) is not permitted to perform this action.",
"extensions":{"code":"FORBIDDEN"}}]}
Read it with an Admin session instead. The same applies to ListInvites and ListOrgDomains.
Admin-only mutations include InviteUser, UpdateUserRole, DeleteUser, GenerateAPIKey, DeleteAPIKey, UpdateOrganization, UpdateOrgSettings, UpdateAIConfig, DeleteProject, the policy mutations, the rule-override mutations (SetRuleOverride, DeleteRuleOverride), and the domain mutations.
REST endpoints
Two endpoints return files rather than GraphQL payloads. Both accept Authorization: Bearer or a ?token= query parameter, which is useful for a browser download.
Project security report, HTML
curl -sS "$ARGUS_URL/api/report/<project-id>" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-o report.html
A complete standalone HTML report: project summary, latest scan, findings, dependencies, API endpoints, third parties and BOM data.
Compliance evidence pack, JSON
curl -sS "$ARGUS_URL/api/compliance/<project-id>?framework=NIST-SSDF&download=1" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-o evidence-pack.json
framework accepts CERT-In (default), EO-14028, NIST-SSDF, EU-CRA. Omit download=1 to receive the JSON inline. The same data is available over GraphQL as GenerateCompliancePack(projectId, framework).
A missing project and a project in another organization both return 404, Argus never reveals that a project exists elsewhere.
MCP endpoint
POST $ARGUS_URL/api/mcp
JSON-RPC 2.0 over Streamable HTTP, with the same bearer authentication. Twelve tools; full reference in Integrations.
List them:
curl -sS -X POST "$ARGUS_URL/api/mcp" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Call one:
curl -sS -X POST "$ARGUS_URL/api/mcp" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-H "content-type: application/json" \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "scan_package", "arguments": { "name": "event-stream", "version": "3.3.6", "ecosystem": "npm" } }
}'
Results carry both content[0].text (pretty-printed JSON) and structuredContent. A tool that fails returns isError: true in the result rather than a protocol error.
scan_diff and scan_package are limited to 30 calls per organization per minute.
Discovering the schema
Introspection is available, so any GraphQL client can generate types and give you autocomplete:
curl -sS -X POST "$ARGUS_URL/api/graphql" \
-H "Authorization: Bearer $ARGUS_API_KEY" \
-H "content-type: application/json" \
-d '{"query":"{ __schema { queryType { fields { name } } } }"}'
There are 97 queries and 86 mutations. This page documents the ones you will reach for most.
Next
- Integrations, MCP, CI and trackers
- FAQ and troubleshooting, when a call does not do what you expect