External Issue Linking - API

Prev Next

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-issues permission on that project (or org-admin rights).

  • The Nucleus project_id for 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

/api/projects/{project_id}/issues/bulk

Link tickets to findings

POST

/api/projects/{project_id}/issues/link/bulk

Update ticket details

PUT

/api/projects/{project_id}/issues/bulk

Unlink instances from a ticket

POST

/api/projects/{project_id}/issues/unlink/bulk

Delete tickets

POST

/api/projects/{project_id}/issues/delete/bulk

All five accept an array of items in one call, up to 1000 rows per request.

Getting an external ticket to appear on a Nucleus finding is a two-step process:

  1. 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.

  2. Link the ticket to one or more finding instances using the external_issue_id from 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:

  1. You submit the request. Nucleus responds with 202 Accepted and a  job_id .

  2. You poll  GET /api/projects/{project_id}/jobs/{job_id}  until the job finishes.

  3. 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

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: true  and 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  status  changes the label your users see in Nucleus but does not trigger any automation, close linked instances, or sync back to an upstream system.


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.


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  status  or 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.