1. Before you begin
1.1 What this guide covers
Using the External Issue Linking API to bring tickets from another system - Jira, ServiceNow, or any tool with a ticket URL - into Nucleus and attach them to the findings they cover.
1.2 When to use this API
This API is designed to give you full control over how tickets and findings are connected in Nucleus, independent of what Nucleus supports out of the box. If your ticketing process does not fit cleanly into a built-in connector or grouping option, this is the tool to reach for.
Common examples:
Onboarding with existing ticket history. Import the tickets your teams have already been working in another tool so they show up on the matching findings from day one.
Custom ticketing integrations. Wire up a ticketing system Nucleus does not have a connector for, or layer workflow logic on top of a system it does.
Custom ticket-to-finding grouping. Group finding instances under a single ticket in whatever way your process requires, beyond the grouping options Nucleus provides natively.
If you instead want Nucleus to create tickets upstream, or to keep a ticket in sync with its upstream counterpart (upstream auto-close in Nucleus, comments flow, etc.), use a connector-based ticketing integration.
1.3 What you'll need
A Nucleus API key with access to the target project.
The
create-external-issuespermission on that project (or org-admin rights).The Nucleus
project_idfor the project you're importing into.Ticket data from your source system, at minimum, a URL and a key (e.g. OPS-1234 ) per ticket.
An HTTP client you're comfortable with. All examples use JSON.
1.4 Key terms
External ticket - A ticket that lives in another system. This API brings a reference to it into Nucleus; the ticket itself stays upstream.
API-link ticket - What Nucleus calls a ticket record created through this API. Section 8 covers how they behave differently from other tickets in Nucleus.
2. How it works at a glance
2.1 The five endpoints
The API has five endpoints, grouped by purpose:
Purpose | Method | Endpoint |
|---|---|---|
Register tickets | POST |
|
Link tickets to findings | POST |
|
Update ticket details | PUT |
|
Unlink instances from a ticket | POST |
|
Delete tickets | POST |
|
All five accept an array of items in one call, up to 1000 rows per request.
2.2 The register, link, surface flow
Getting an external ticket to appear on a Nucleus finding is a two-step process:
Register the ticket. You give Nucleus the ticket's URL and key, and Nucleus returns an
external_issue_id. At this point the ticket exists in Nucleus but is not yet visible in the UI.Link the ticket to one or more finding instances using the
external_issue_idfrom step 1. Once linked, the ticket surfaces on Finding Details, All Vulnerabilities, and the Tickets page.
Updates, unlinks, and deletes all reference the external_issue_id from step 1, so hold onto it.
2.3 Async job model: submit, poll, read results
Every endpoint runs asynchronously:
You submit the request. Nucleus responds with 202 Accepted and a job_id .
You poll
GET /api/projects/{project_id}/jobs/{job_id}until the job finishes.The completed job returns a per-row result for every item you submitted, each marked success: true or success: false with an error message.
Row-level failures do not fail the job. As long as the job ran, the poll returns 200 , and your integration can inspect each row individually to see what succeeded.
3. Registering tickets
Registering tells Nucleus "this external ticket exists." It does not attach the ticket to anything yet, but it returns the external_issue_id your integration will need for every step after this one.
3.1 Building the request
Your integration sends a POST to /api/projects/{project_id}/issues/bulk with an array of tickets. Each ticket requires issue_url and issue_key. Everything else is optional but recommended if you have it, since these values are what your users will see in Nucleus.
[
{
"issue_url": "https://jira.example.com/browse/OPS-1234",
"issue_key": "OPS-1234",
"title": "Patch OpenSSL on prod fleet",
"description": "Rolling out CVE-2024-12345 remediation this sprint.",
"priority": "High",
"assignee": "jsmith",
"status": "In Progress"
}
]You can submit up to 1000 tickets in a single request.
3.2 Submitting and getting your external_issue_id
The call returns 202 Accepted with a job_id . Your integration should poll GET /api/projects/{project_id}/jobs/{job_id} until the job completes, then read the per-row results:
{
"success": true,
"external_issue_id": 1003421,
"issue_url": "https://jira.example.com/browse/OPS-1234",
"issue_key": "OPS-1234"
}The external_issue_id is the identifier used for every subsequent link, update, and delete call for this ticket. Persist it in your integration's system of record alongside the source ticket. Re-posting the same issue_url + issue_key will return the same external_issue_id (see below), but relying on that as a lookup mechanism is slower and less reliable than storing it once at registration time.
If you re-submit the same issue_url with the same issue_key , Nucleus returns the existing external_issue_id and does not create a duplicate. If you re-submit the same URL with a different key, that row fails with an error and the existing ticket is left untouched.
3.3 Why the ticket isn't visible yet
A ticket that has been registered but not linked to any finding is invisible in the Nucleus UI. It won't show up on the Tickets page, on Finding Details, or on the All Vulnerabilities page. This is intentional: Nucleus surfaces tickets in the context of the findings they cover, and until you link one, there is no context to surface it in.
4. Linking tickets to findings
Linking attaches a registered ticket to one or more finding instances. This is the step that makes tickets appear in Nucleus.
4.1 Identifying the finding instance (the 4-tuple)
In Nucleus ticketing, a linked finding instance is uniquely identified by four values used together:
Field | What it is |
|---|---|
scan_type | The scanner or source that produced the finding (for example, NESSUS, QUALYS). |
asset_id | The Nucleus asset ID the finding is on. |
finding_number | The finding identifier (for example, a CVE or plugin ID). |
finding_justification_key | The finding justification key. |
The findings search endpoint returns all four fields on every result, so the recommended pattern is to query it, collect it and pass it into the link request.
Endpoint: POST /api/projects/{project_id}/findings/search
4.2 Building and submitting the link request
Your integration sends a POST to /api/projects/{project_id}/issues/link/bulk with an array. Each row combines an external_issue_id from section 3 with a 4-tuple from section 4.1:
[
{
"external_issue_id": 1003421,
"scan_type": "NESSUS",
"asset_id": 12345,
"finding_number": "CVE-2024-12345",
"finding_justification_key": "987654321"
}
]What to Know:
One ticket, many instances. Linking a ticket to 50 instances is 50 rows referencing the same
external_issue_id.Re-linking is safe. A row that is already linked returns
success: trueand changes nothing, so batches can be retried without tracking which rows already succeeded.
The response follows the async pattern from section 3: 202 Accepted with a job_id, then your integration polls for per-row results.
4.3 Verifying the ticket appears in Nucleus
Once the link job completes, the ticket surfaces everywhere a Nucleus generated ticket would appear, such as Vulnerabilities page or on the Tickets page. Ticket-count metrics and filters like has_no_tickets update accordingly.
5. Updating tickets
Once a ticket is registered, use the update endpoint to change its header fields - title, description, priority, assignee, status, or the URL/key themselves. Only fields the caller explicitly sends are changed; anything omitted is left as-is.
5.1 Building and submitting the update
Your integration sends a PUT to /api/projects/{project_id}/issues/bulk with an array. Each row must include the external_issue_id (which identifies the ticket) plus at least one mutable field:
[
{
"external_issue_id": 1003421,
"title": "Patch OpenSSL on prod fleet - escalated",
"priority": "Critical",
"assignee": "asmith",
"status": "In Review"
}
]Mutable fields: title , description , priority , assignee, status, issue_url, issue_key. The external_issue_id itself is immutable.
The response follows the async pattern from section 3: 202 Accepted with a job_id , then your integration polls for per-row results. Re-sending the same payload is a no-op and returns success: true , so retries are safe.
5.2 Common update patterns
Keeping header fields in sync with your source system. When your integration observes a change upstream (assignee reassigned, priority raised, status transitioned), submit an update row with just the changed fields. There is no need to resend fields that haven't changed.
Moving a ticket to a different finding instance. Updates do not touch links. To move a ticket, unlink the wrong instance (section 6) and link the correct one (section 4).
5.3 What this endpoint will not do
It won't modify tickets that weren't created through this API. Rule-driven, connector-driven, and UI-manual tickets return a per-row error. This endpoint only operates on API-link tickets.
Status is opaque text. Updating
statuschanges the label your users see in Nucleus but does not trigger any automation, close linked instances, or sync back to an upstream system.
6. Removing links and tickets
Cleanup has two levels: unlink individual instances from a ticket, or delete the ticket entirely. Both follow the same async pattern as previous sections.
6.1 Unlinking specific instances
Unlinking removes the connection between a ticket and one or more finding instances. The ticket header stays intact and can be re-linked or linked to different instances later.
Your integration sends a POST to /api/projects/{project_id}/issues/unlink/bulk with an array. Each row is the same 4-tuple + external_issue_id shape used for linking:
[
{
"external_issue_id": 1003421,
"scan_type": "NESSUS",
"asset_id": 12345,
"finding_number": "CVE-2024-12345",
"finding_justification_key": "987654321"
}
]Common use cases: moving a ticket to a different instance (unlink the wrong one, then link the correct one via section 4), or narrowing an over-inclusive link batch without discarding the ticket record.
6.2 Deleting tickets
Deleting removes the ticket header and any remaining links atomically. Use this to retire tickets your integration no longer needs.
Your integration sends a POST to /api/projects/{project_id}/issues/delete/bulk with an array of external_issue_id values:
[
{ "external_issue_id": 1003421 },
{ "external_issue_id": 1003422 }
]6.3 What these endpoints will not do
Both endpoints only operate on API-link tickets. Attempts to unlink or delete rule-driven, connector-driven, or UI-manual tickets return a per-row error, and the ticket is left untouched.
7. How API-link tickets behave in Nucleus
API-linked tickets are first-class ticket records - they appear in the UI, count toward metrics, and can be filtered on - but they are intentionally isolated from Nucleus's built-in ticketing automation at this time. This section is what integration owners need to know so their users aren't surprised.
7.1 Where they surface
Once a ticket has at least one link, users will see it in these places:
Vulnerability Details (the finding detail window that opens from any findings grid): on the Tickets tab, alongside any other tickets on that finding. The tab title shows the total ticket count for the finding.
Tickets (the project-level page under the project menu): listed among the project's tickets with its title, key, URL, priority, assignee, and status.
Active Findings and All Findings (project-level findings pages): rows for linked instances reflect the ticket in their ticket-count column, and filters like "has no tickets" respect the link.
Asset Details: the finding rows for that asset show the same ticket presence as the findings pages.
A ticket with no links is registered but hidden - see section 3.3.
7.2 What automation and connectors will not do to them
API-link tickets are deliberately outside the scope of Nucleus's connector-driven ticket lifecycle. This is what keeps your integration authoritative:
Ticketing automation rules do not create, update, or close them. Rules only operate on tickets they created.
Connector sync does not touch them. Jira, ServiceNow, and other connectors will not overwrite the header fields, mutate status, or close the ticket when linked instances are remediated.
Auto-close on remediation does not fire. When every linked instance transitions to mitigated, Nucleus will not close the ticket. Your integration decides when to update
statusor delete the ticket.Webhooks and outbound notifications (bot comments) for ticket lifecycle events do not fire for actions taken through these endpoints.
Users can still see and interact with the ticket in the UI, but the source of truth for its fields is your external system and custom integration.
7.3 What the ticket detail view does and does not show
On the Tickets tab and Tickets page, API-link tickets display the fields your integration submitted: title , description, priority, assignee, status, issue_url , and issue_key. The URL renders as a clickable link out to your source system.
Because there is no connector behind them, the following are not populated:
Comments / activity history from the source system. Nucleus does not poll your ticketing system, so comments made in Jira or ServiceNow will not appear here.
Sync status indicators. There is no "last synced" timestamp because there is no sync.
If your users need to see comments or full history, the intended path is the issue_url link out to the source system.