Questa pagina è disponibile solo in inglese.
GraphQL API
Call the Vulnara GraphQL API with a bearer token and the X-Tenant header, page and filter lists, handle errors, and subscribe to live events.
Everything the Vulnara web app does goes through one GraphQL API. You can call it yourself to automate scans, pull findings into other tools, or build your own reports. The CLI, the GitHub Action and the MCP server all use the same API.
What it is for
- Integrating Vulnara with your own tooling, dashboards and pipelines.
- Doing in bulk what the web app does one item at a time.
- Receiving scan progress and notifications live over a WebSocket.
How it works
There is one endpoint, https://vulnara-gw.rso.dev/graphql. It takes queries and mutations as JSON over HTTP POST, and subscriptions over a WebSocket on the same path. Introspection is on, so any GraphQL client or IDE can load the schema.
Every request carries two headers:
- Authorization:
Bearer <access token>. - X-Tenant: the id of the workspace the request is about.
Vulnara checks your role in that workspace before it runs each field.
What you can set
On every list query, the list argument takes the same fields:
- limit: how many items to return. The default is 10, the maximum is 500.
- skip: how many items to skip, for paging. The default is 0.
- filters: a list of
{ field, ... }filters. UsestringEqualsoridEqualsfor an exact match,categoryEqualswith aScanCategory(SAST,SCA,SECRETSorPIIon repository findings, andDASTon web application findings) for a category, andminormaxfor a range. A list takes onecategoryEqualsfilter per field; a second one is refused withVALIDATION_ERROR. - sort: the field to sort by. The default is
createdAt. - order:
ASCorDESC. The default isDESC. - search: free text to search for.
A list returns total, the number of items that match the filters, and items, the current page.
query Findings($scanResultId: ID!) {
scanFindings(
list: {
limit: 50
skip: 0
filters: [{ field: "scanResultId", idEquals: $scanResultId }]
sort: "severity"
order: DESC
}
) {
total
items { id severity file line }
}
}Sorting findings by severity ranks them Critical, High, Medium, Low, not alphabetically.
Do it
Get an access token
Use one of these:
- A service account, for scripts and CI. Create one (see Service accounts), then exchange its name and token for an access token at the OAuth token endpoint, with the
client_credentialsgrant,client_idset tohl04e6MSMRY60LdpGh5rdMRQjkPxvldAYoqXdzo4,usernameset to the service account name,passwordset to its token, andscope=profile. The endpoint is thetoken-urldefault on the GitHub Action reference. The response carriesaccess_tokenandexpires_in. - Your own sign-in, for trying things out. Run
vulnara loginwith the CLI. It stores your access token in~/.config/vulnara/jwt/access_token.
- A service account, for scripts and CI. Create one (see Service accounts), then exchange its name and token for an access token at the OAuth token endpoint, with the
Find your workspace id
Query myUser. Its
tenantsfield lists the workspaces you belong to, with their ids.graphqlquery { myUser { tenants { id } } }Send a request
shcurl https://vulnara-gw.rso.dev/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $VULNARA_TOKEN" \ -H "X-Tenant: my-workspace" \ -d '{"query":"{ repositories(list: { limit: 5 }) { total items { id repositoryName } } }"}'
Every query, mutation and type, with examples, is on the GraphQL reference.
The X-Tenant header
X-Tenant must name a workspace you belong to. If it names one you do not belong to, the request is refused. If you leave it out, Vulnara uses the first workspace in your list, so always send it when you belong to more than one.
Roles guard every field
Each workspace member holds a role: VIEWER, EDITOR or ADMIN. A higher role includes the lower ones.
- VIEWER reads: repositories, scans, findings, the dashboard and statistics.
- EDITOR also changes things: adds and deletes repositories, networks, web applications and git workspaces, starts and cancels scans, manages schedules, alert rules and triage decisions.
- ADMIN also manages credentials, people and money: git tokens, service accounts, members, invitations, roles, transfers between workspaces, connector agents for networks, plans and payment methods.
A field your role does not allow returns an AUTHORIZATION_ERROR and does nothing. Service accounts hold the VIEWER role. See Teams and roles.
Errors
Errors come back in the standard GraphQL errors array. Each carries a stable code in extensions.code:
- AUTHENTICATION_ERROR: no token, an invalid or expired token, or an
X-Tenantyou do not belong to. - AUTHORIZATION_ERROR: your role does not allow this field.
- NOT_FOUND: the id does not exist in this workspace.
- VALIDATION_ERROR: the input broke a rule.
extensions.fieldslists each field and why, for exampleTOO_SHORT,INVALID_PATTERNorDUPLICATE_ENTRY. - LIMIT_EXCEEDED: you hit a rate limit.
- NEW_TENANT_INVALID: a transfer named the workspace the resource is already in.
- INTERNAL_ERROR: something failed on Vulnara's side.
Some errors carry a more specific code, such as REPOSITORY_LIMIT_EXCEEDED when your plan's repository cap stops you, GIT_TOKEN_NOT_VALID, or, for a web scan, NO_SCANNER_FOR_CATEGORY (web application scanning is not enabled for the workspace), WEB_TARGET_ADDRESS_REFUSED (the address is not publicly reachable) and DAST_SCAN_MINUTES_LIMIT_EXCEEDED, or WEB_TARGET_CREDENTIALS_CONFLICT when a web application's sign-in methods cannot be combined. That one also carries extensions.reason: BASIC_AND_BEARER, AUTHORIZATION_HEADER_TAKEN or TOO_MANY. Handle unknown codes as generic failures. Build on the code, not on the message: messages can be generic and may change.
A query that selects a field the schema does not have fails validation before anything runs. That is what happens to a query still selecting ScanResult.scanner, DockerScanTool.name, ScanFindingMatchGroup.scanners or CorroboratedFinding.scanners, which have been removed. Use the finding's category, the scanner's categories and toolCount instead. See Scan categories.
Rate limits
Some fields are limited per user. Each limit is a number of calls a minute with a burst you can spend at once, and a call over it returns LIMIT_EXCEEDED and does nothing:
- 20 a minute, burst 5: checkGitToken, gitTokenExpiration, testNotificationScope, inviteTenantMember,
requestRSFConsultationandrequestNSFConsultation. - 30 a minute, burst 10: the fields that ask your git provider:
gitEntityProviders,gitEntityRepositoryCount, repositoryBranches, createGitEntity and fetchRepositories.gitEntityProvidersneeds you to be signed in. - 10 a minute, burst 5: retryPayment, and each of the web application sign-in fields: addWebTargetSignInMethod, removeWebTargetSignInMethod, setWebTargetCredentials and clearWebTargetCredentials.
- Starting scans: startRepositoryScan 120 a minute, burst 60; startRepositoryCategoryScan 30 a minute, burst 15, because one call can start several scanners; startNetworkScan 30 a minute, burst 10; startWebScan 10 a minute, burst 3.
- Aggregations: dashboardAnalytics 30 a minute, burst 15; statistics, workspaceOverview and findingCorroboration 60 a minute, burst 20; the finding lists, such as scanFindings and scanFindingMatchGroups, 180 a minute, burst 60.
The limits are sized so that working in the web app never reaches them. A script that does should wait and retry.
Subscriptions
Open a WebSocket to wss://vulnara-gw.rso.dev/graphql using the graphql-ws protocol. Pass the same two values as connection parameters: Authorization with your bearer token, and X-Tenant.
- taskEvents: live progress of scans and imports in the workspace. Each event has a
type(STATE_UPDATEDorTASK_CANCELLED), ataskIdand the task's current state. - notifications: new in-app notifications for the workspace as they are created. See Notifications.
subscription { taskEvents { type taskId } }You only receive events for the workspace you connected with. To follow another workspace, open another connection.
Good to know
- Send JSON: POST requests need
Content-Type: application/json. Requests that look like a plain browser form are refused. - Batching works: you can send several operations as a JSON array in one request.
- Secrets are write-only: git token values and webhook header values are never returned after you set them.
- Some reads are role-dependent: member email addresses are hidden from viewers, except their own.
- Plan limits apply: creating repositories or networks, enabling repositories and starting scans can be refused when your plan's limit is reached. See Plans, usage and billing.
- Token lifetime: service account tokens expire. Read
expires_inand exchange again before it runs out.