# Jira GitHub Development-Link Health Check (`kjjkjjzyayufqza/jira-github-devlink-health`) Actor

Find missing or stale Jira–GitHub development links. Free read-only scan of one Jira Cloud site + one GitHub org. Detects PRs that never linked and PRs still open in Jira after every matching PR closed. Actionable next steps.

- **URL**: https://apify.com/kjjkjjzyayufqza/jira-github-devlink-health.md
- **Developed by:** [kjjkjjzyayufqza](https://apify.com/kjjkjjzyayufqza) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Jira GitHub Development-Link Health Check

This free public Apify Actor finds two silent integration failures without Jira or GitHub write access:

- a GitHub pull request contains a Jira key, but Jira reports no development link;
- every matching GitHub pull request is closed, but Jira still reports an open pull request.

After one-time setup, the product is designed to run without routine human labour. An encrypted Task supplies the scope, a schedule starts each run, the Actor proves source completeness, and returns deterministic next actions. The current Store release is free. Invalid input, failed authentication, partial evidence, and provider failure return controlled results instead of false healthy reports.

Version 0.2 supports Jira Cloud, one GitHub organisation, 1–5 repositories, 1–20 Jira projects, and a 1–90 day lookback. Jira Data Center, GitHub personal accounts, GitLab, Azure DevOps, automatic repair, and manual interpretation are outside this version.

### Quick start in Apify Console

1. Create dedicated read-only Jira and GitHub credentials using the access guidance below.
2. Open the Actor's **Input** page. Each field includes a safe example and explanation. Replace every `your-*`, `acme-*`, and `replace_with_*` value; the examples are not real credentials.
3. Click **Start**. Read `status` first in the **Results** tab, then follow each `nextAction` under `exceptions` or `error`.
4. When the controlled run is correct, save the input as an Apify Task. Confirm Jira email/token and GitHub token remain secret fields.
5. Add a UTC schedule to the Task. Scheduled `ok` and `exceptions` runs need no operator action; controlled failures already say what the customer must correct.

The UI examples are validated in the automated test suite so they cannot drift away from runtime input rules.

### Copyable input example

This is a scoped Atlassian-token example. Replace all values before starting. For a classic Atlassian API token, remove `jiraCloudId`. The optional `repositories` pin is intentionally omitted so the Actor discovers the complete organisation scope automatically.

```json
{
  "jiraBaseUrl": "https://acme.atlassian.net",
  "jiraCloudId": "123e4567-e89b-42d3-a456-426614174000",
  "jiraEmail": "jira-bot@acme.example",
  "jiraApiToken": "replace_with_your_atlassian_token",
  "jiraProjectKeys": ["ENG", "OPS"],
  "githubToken": "github_pat_replace_with_your_token",
  "githubOrganization": "acme-engineering",
  "lookbackDays": 30
}
```

| Field | Required | Example | Meaning |
|---|---:|---|---|
| `jiraBaseUrl` | Yes | `https://acme.atlassian.net` | Exact Jira Cloud tenant root. No path, custom domain, port, query, or credentials. |
| `jiraCloudId` | Scoped token only | UUID | Jira cloud UUID used by Atlassian's scoped-token gateway. Leave empty for a classic token. |
| `jiraEmail` | Yes | `jira-bot@acme.example` | Dedicated Jira account that created the token. Secret; never returned. |
| `jiraApiToken` | Yes | `replace_with_…` | Read-only Atlassian API token. Secret; no UI prefill/default. |
| `jiraProjectKeys` | Yes | `ENG`, `OPS` | 2+ character uppercase alphanumeric allowlist. Referenced keys outside it are reported and never queried. |
| `githubToken` | Yes | `github_pat_…` | Expiring fine-grained read-only token for organisation metadata and every organisation repository. Other GitHub token types are rejected; secret; no UI prefill/default. |
| `githubOrganization` | Yes | `acme-engineering` | Organisation login only, not a URL or personal username. |
| `repositories` | No | `acme-engineering/backend-api` | Leave empty for zero-touch exhaustive discovery. Optionally enter the complete list as a change-control pin; a mismatch is incomplete and uncharged. |
| `lookbackDays` | No | `30` | Recently updated PR window, 1–90 days. Every currently open PR is also read even when older. |

#### Jira access

Use a dedicated account that can Browse Projects for every configured project and can see relevant issue-security levels. Create an Atlassian API token in [Atlassian account settings](https://id.atlassian.com/manage-profile/security/api-tokens). A scoped token needs `read:jira-work` and the Jira cloud UUID; a classic token uses the tenant URL directly. The Actor never needs Jira write or administrator scopes.

Jira may deliberately return the same response for an absent issue and an issue hidden by permissions. The Actor therefore reports `jiraKeyNotFoundOrInaccessible` instead of claiming a distinction the API cannot prove.

#### GitHub access

For this public release, create a fine-grained token whose resource owner is the GitHub organisation. The authenticated user must be an organisation owner so GitHub returns the complete private-repository count. Select **All repositories**, keep the mandatory **Metadata: Read**, add **Pull requests: Read-only**, set an expiry, and grant no Contents, Actions, Workflows, or other write permission. GitHub documents that full organisation details require an owner and that listing organisation repositories uses Metadata read; see [Get an organization](https://docs.github.com/en/rest/orgs/orgs), [List organization repositories](https://docs.github.com/en/rest/repos/repos), and [Keeping API credentials secure](https://docs.github.com/en/rest/authentication/keeping-your-api-credentials-secure).

The Actor requires the organisation's declared public/private count to equal the complete API listing. It safely discovers that listing when `repositories` is empty; an optional supplied list must also match exactly. An empty organisation, a non-owner token that cannot expose the private count, or a token that cannot see even one repository returns `INCOMPLETE_SOURCE_EVIDENCE`; zero or partial evidence is never labelled healthy or charged. Newly added repositories are discovered on the next run without Task maintenance. A GitHub App is the preferred future organisation-wide credential model, but version 0.2 accepts a token for the private beta.

### How to read Results

Open **Results — run history** for Dataset rows or **Latest result — single JSON** for automation. Both follow the same documented schema.

| `status` | Completed check | Meaning |
|---|---:|---|
| `ok` | Yes | Exhaustive scan completed and found no supported exception. This is not a guarantee that every possible integration failure is absent. |
| `exceptions` | Yes | Exhaustive scan completed and found one or more actionable mismatches. Follow each exception's `nextAction`. |
| `incomplete` | No | The Actor refused to classify partial evidence or exceeded a declared scan limit. Follow `error.nextAction`; never interpret this as healthy. |
| `failed` | No | Input, credentials, provider, storage, or another controlled execution path failed. Use `error.code` for automation. |

#### Healthy result

```json
{
  "status": "ok",
  "checkedAt": "2026-07-15T08:30:00.000Z",
  "lookbackStartedAt": "2026-06-15T08:30:00.000Z",
  "exceptions": []
}
```

#### Completed result with an exception

```json
{
  "status": "exceptions",
  "checkedAt": "2026-07-15T08:30:00.000Z",
  "lookbackStartedAt": "2026-06-15T08:30:00.000Z",
  "exceptions": [
    {
      "jiraKey": "ENG-42",
      "type": "missingDevelopmentLink",
      "nextAction": "Reconnect GitHub for Jira to this repository or correct the Jira key in the pull request.",
      "pullRequests": [
        {
          "url": "https://github.com/acme-engineering/backend-api/pull/123",
          "state": "MERGED",
          "updatedAt": "2026-07-14T10:20:30.000Z",
          "mergedAt": "2026-07-14T10:15:00.000Z"
        }
      ]
    }
  ]
}
```

#### Uncharged incomplete result

```json
{
  "status": "incomplete",
  "checkedAt": "2026-07-15T08:30:00.000Z",
  "error": {
    "code": "INCOMPLETE_SOURCE_EVIDENCE",
    "nextAction": "Use an organisation owner’s read-only GitHub credential with visibility of every repository, remove or correct any optional repository pin, then run again.",
    "details": {
      "reason": "repositoryListMismatch",
      "configuredRepositoryCount": 2,
      "visibleRepositoryCount": 1,
      "declaredRepositoryCount": 2
    }
  }
}
```

#### Exception meanings

| Type | What the evidence proves | Typical action |
|---|---|---|
| `missingDevelopmentLink` | Jira reports zero PR links for a visible referenced issue. | Restore connector repository access or correct the Jira key in the PR. |
| `staleOpenDevelopmentLink` | Every matching GitHub PR is closed, while Jira still reports an open PR. | Restore connector access and refresh/backfill the PR event. |
| `jiraKeyRenamed` | Jira resolved the referenced key to a different current key. | Update future source references to the current key. |
| `jiraKeyInaccessible` | Jira explicitly denied this issue lookup. | Grant Browse Projects and applicable issue-security access. |
| `jiraKeyNotFoundOrInaccessible` | Jira's response cannot distinguish absent from hidden. | Verify the key and permissions; do not assume deletion. |
| `jiraKeyOutOfScope` | The PR references a project not in `jiraProjectKeys`. | Add the intended Jira project, or correct/remove the Jira key in the PR. Partial repository selection is not allowed. |

### Completeness, limits, and billing

The Actor safely discovers GitHub's full visible organisation listing and proves it equals the declared public/private count both before and after the PR reads. If the optional repository pin is supplied, that must also match exactly. It then resolves historical Jira keys and queries supported Jira development fields only when that bracketed scope is stable. Jira search rows, PR pages, and overlapping PR snapshots must also be unique and internally consistent. Only after classification succeeds does it persist `OUTPUT` and request the `completed-check` Dataset event.

Fail-closed limits keep cost and memory predictable: five repositories, 90 lookback days, 100 Jira keys, 2,000 PRs per stream, 5,000 PR rows and key associations per run, 10 MiB per successful response, 50 MiB of successful response data across the run, four HTTP attempts, a 20-second full-response timeout, a 30-second maximum wait, and a 4-minute internal deadline under the 5-minute platform timeout. Raw PR text is discarded page by page. A product limit returns `incomplete` and is not a completed charge.

This Store release uses Apify's `FREE` pricing model: completed results have zero Actor charges, while the user's normal Apify platform usage still applies. The code retains a dormant fail-closed pay-per-event boundary for a future, separately announced pricing change. If PPE is ever enabled, its preflight requires exactly one positive-priced event named `completed-check`, rejects the synthetic default-Dataset-item charge, and refuses source API work when no event capacity remains. Private controlled runs measured approximately USD 0.000214–0.000252 in platform cost, but willingness to pay, acquisition cost, payout setup, and unrelated-user retention are not yet proven. This is technical unit-economics evidence, not a profit forecast.

Jira's supported development JQL exposes issue-level aggregate counts, not the official connector's private event store. Consequently, `missingDevelopmentLink` means Jira reports zero pull-request links for the referenced issue. If the same Jira issue already has one correctly linked PR, this Actor cannot prove that a different PR using the same key is individually missing. Version 0.2 fails no broader claim than the API evidence supports.

GitHub and Jira do not provide one transactional cross-service snapshot. A result reflects the bounded API responses observed during that run; a PR or repository changed immediately afterward is evaluated on the next scheduled run. If the same PR changes between overlapping recent/open reads, or the organisation scope changes between the before/after proofs, the run returns incomplete instead of deriving a conclusion from inconsistent snapshots.

Following Jira Cloud's development-link rules, candidate keys must use the canonical uppercase form, for example `ENG-42`; lowercase `eng-42` is intentionally not treated as linked evidence. Underscores and other normal branch separators around a valid key are supported, such as `feature/ENG-42_release`. See Atlassian's [development-work reference](https://support.atlassian.com/jira-software-cloud/docs/reference-issues-in-your-development-work/) and [project-key rules](https://support.atlassian.com/jira-software-cloud/docs/edit-a-projects-details/).

### Privacy and security

Output never contains PR titles, PR bodies, branch names, Jira/GitHub response bodies, Jira email, or tokens. It does contain Jira keys and private PR URLs, so Dataset and `OUTPUT` must remain private. Authenticated redirects are rejected, Jira hosts are allowlisted to first-party Jira Cloud origins, error bodies are discarded, and controlled failures are recursively redacted. Public diagnostic details use a per-code key allowlist; arbitrary input field names, provider prose, and accessor values are never reflected.

Treat any token pasted into chat or another unapproved channel as exposed, regardless of perceived privacy, and rotate it. Read the complete [security policy](./SECURITY.md) and [operations runbook](./OPERATIONS.md) before deployment.

### Local development

Node 24 is the reviewed runtime.

```sh
npm ci --ignore-scripts
npm run verify
npm run test:coverage
```

Install Apify CLI 1.7.1+ to include official schema validation in `npm run verify`. Local input belongs in Apify local storage or environment state, never in this repository. `.gitignore` excludes `storage/`, `.env*`, credentials, logs, and common local artifacts.

The root `github-pr-keys.mjs`, `jira-devlink-check.mjs`, and `configure-jira-auth.sh` files are retained only as disposable research/regression tools. They are not the hosted customer workflow.

Release history is available in the Actor Changelog. Operators with source access can also review `SECURITY.md`, `OPERATIONS.md`, and the private validation records before publication.

# Actor input Schema

## `jiraBaseUrl` (type: `string`):

Your Jira Cloud site URL. Find it in the browser address bar after opening Jira; do not include /jira, /rest, query parameters, or credentials.

## `jiraCloudId` (type: `string`):

Required only for a scoped Atlassian API token. Copy the UUID returned by https://<site>.atlassian.net/\_edge/tenant\_info. Leave this field empty when using a classic Jira API token.

## `jiraEmail` (type: `string`):

Email address belonging to the dedicated Jira account that created the token. This is a secret field and is not included in output.

## `jiraApiToken` (type: `string`):

A read-only Atlassian API token for the account above. Create it in Atlassian account settings. Never paste this token into a repository, log, support ticket, or result.

## `jiraProjectKeys` (type: `array`):

Every Jira project the Actor is allowed to inspect. Enter the 2+ character uppercase alphanumeric project key, such as ENG—not a project name or URL. Referenced keys outside this list are reported as out of scope.

## `githubToken` (type: `string`):

An expiring read-only fine-grained token owned by a GitHub organisation owner. Select all organisation repositories, Metadata read, and Pull requests read-only. This secret is not included in output.

## `githubOrganization` (type: `string`):

The exact organisation login shown in GitHub URLs. Do not enter https://github.com/ or a personal username.

## `repositories` (type: `array`):

Normally leave this empty: the Actor safely discovers and checks every organisation repository on every run. To pin change control, enter the complete list in owner/name form; a mismatch returns incomplete and is not charged. Version 0.2 supports organisations with at most five repositories.

## `lookbackDays` (type: `integer`):

Number of rolling 24-hour periods to scan for recently updated pull requests. Use 30 for the recommended balance of coverage, runtime, and cost.

## Actor input object example

```json
{
  "jiraBaseUrl": "https://acme.atlassian.net",
  "jiraCloudId": "123e4567-e89b-42d3-a456-426614174000",
  "jiraEmail": "jira-bot@acme.example",
  "jiraApiToken": "replace_with_your_atlassian_token",
  "jiraProjectKeys": [
    "ENG",
    "OPS"
  ],
  "githubToken": "github_pat_replace_with_your_token",
  "githubOrganization": "acme-engineering",
  "repositories": [
    "acme-engineering/backend-api",
    "acme-engineering/web-app"
  ],
  "lookbackDays": 14
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset items for this run. Each item follows the documented result schema and includes field descriptions and examples in the table view.

## `latestResult` (type: `string`):

The same result as one JSON object from the OUTPUT key-value-store record; use this link for webhooks and other automation.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "jiraBaseUrl": "https://your-company.atlassian.net",
    "jiraProjectKeys": [
        "ENG"
    ],
    "githubOrganization": "your-github-org"
};

// Run the Actor and wait for it to finish
const run = await client.actor("kjjkjjzyayufqza/jira-github-devlink-health").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "jiraBaseUrl": "https://your-company.atlassian.net",
    "jiraProjectKeys": ["ENG"],
    "githubOrganization": "your-github-org",
}

# Run the Actor and wait for it to finish
run = client.actor("kjjkjjzyayufqza/jira-github-devlink-health").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "jiraBaseUrl": "https://your-company.atlassian.net",
  "jiraProjectKeys": [
    "ENG"
  ],
  "githubOrganization": "your-github-org"
}' |
apify call kjjkjjzyayufqza/jira-github-devlink-health --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kjjkjjzyayufqza/jira-github-devlink-health"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/KE4BkWs9gcAUOBZM0/builds/Yvc1ZYueXKvnHHzLE/openapi.json
