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, andminormaxfor a range. - 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 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, 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, or GIT_TOKEN_NOT_VALID. Handle unknown codes as generic failures.
Rate limits
checkGitToken and gitTokenExpiration are limited to 20 calls a minute per user, with a burst of 5. Over the limit they return LIMIT_EXCEEDED.
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.