Hamburger Cross Icon
PagerDuty Collector - Lunar Collector

PagerDuty Collector

↓ Collector Beta Incident Management

Query the PagerDuty API to collect on-call schedule, escalation policy, and current responder data. Normalizes results into the .oncall category for tool-agnostic policy evaluation. Can find each component's service in Backstage, including one declared on its System or Domain.

Add pagerduty to your lunar-config.yml:
uses: github://earthly/lunar-lib/collectors/pagerduty@v1.0.5

What This Integration Collects

This integration includes 3 collectors that gather metadata from your systems.

Collector code

oncall

Queries the PagerDuty REST API for the component's service on a code hook — it runs when commits are pushed (PRs and the default branch). Resolves the service ID in order: the component's pagerduty/service-id meta annotation (set by a cataloger via lunar catalog component --meta pagerduty/service-id <id>), then the explicit service_id input, then — with backstage_discovery: "true" — the component's own catalog-info.yaml from the checkout. Records where the ID came from at .oncall.service.discovered_via. Fetches service details, on-call schedule participants, and escalation policy. Writes normalized data to .oncall.service, .oncall.schedule, .oncall.escalation, and .oncall.summary, and the raw API responses under .oncall.native.pagerduty. When none of those sources maps the component, writes .oncall.service_lookup with the places it looked instead, and the from-backstage-collector sub-collector, if included, looks in the live catalog.

pagerduty oncall escalation incident management on-call schedule service mapping
Book a demo
Collector cron

oncall-cron

Same PagerDuty query as oncall, but on a daily cron instead of on push — it re-collects on a schedule so .oncall stays fresh as on-call schedules and escalation policies rotate between commits (they change on their own clock, independent of code). Resolves the service ID identically, then also reads the backstage collector's live lookup from the default branch's Component JSON when there is one. Writes the same normalized .oncall.service, .oncall.schedule, .oncall.escalation, and .oncall.summary (raw responses under .oncall.native.pagerduty). clone-code gives it the checkout so backstage_discovery: "true" can read catalog-info.yaml. Include oncall for on-push evaluation, oncall-cron for always-fresh scheduled refresh, or both.

pagerduty oncall escalation incident management on-call schedule cron scheduled
Book a demo
Collector after-json

from-backstage-collector

Finds the component's PagerDuty service in what the backstage collector read from the live catalog: the component's own entity, else its System, else that System's Domain. Covers the components oncall can't map from meta, service_id or, with backstage_discovery: "true", the catalog file, and leaves those to oncall. Runs once the backstage collector's lookup (.catalog.native.backstage.refs.entity) lands in the Component JSON, retrying the read for up to backstage_wait_seconds while it catches up, then queries PagerDuty and writes .oncall like oncall. Only the backstage collector calls Backstage; it needs backstage_url set.

pagerduty oncall backstage service mapping service-id after-json inherited annotations
Book a demo

How Collectors Fit into Lunar

Lunar watches your code and CI/CD systems to collect SDLC data from config files, test results, IaC, deployment configurations, security scans, and more.

Collectors are the automatic data-gathering layer. They extract structured metadata from your repositories and pipelines, feeding it into Lunar's centralized database where guardrails evaluate it to enforce your engineering standards.

Learn How Lunar Works →
1
↓ Collectors Gather Data This Integration
Triggered by code changes or CI pipelines, collectors extract metadata from config files, tool outputs, test results, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
✓ Guardrails Enforce Standards
Real-time feedback in PRs and AI workflows

Example Collected Data

This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:

{ } component.json Component JSON
{
  "oncall": {
    "source": {
      "tool": "pagerduty",
      "integration": "api"
    },
    "service": {
      "id": "PXXXXXX",
      "name": "Payment API",
      "status": "active",
      "discovered_via": "system:default/payment-platform"
    },
    "schedule": {
      "exists": true,
      "participants": 4,
      "rotation": "weekly"
    },
    "escalation": {
      "exists": true,
      "levels": 3,
      "policy_name": "Payment API Escalation"
    },
    "summary": {
      "has_oncall": true,
      "has_escalation": true,
      "min_participants": 4
    },
    "native": {
      "pagerduty": { "...full PagerDuty API response..." }
    }
  }
}

Configuration

Configure this collector in your lunar-config.yml.

Inputs

Input Required Default Description
service_id Required — PagerDuty service ID to query (e.g. PXXXXXX). Optional if the component has a `pagerduty/service-id` meta annotation set by a cataloger.
pagerduty_base_url Optional https://api.pagerduty.com PagerDuty API base URL
backstage_discovery Optional false When "true", and component meta and the service_id input don't give a service ID, read it from the annotations in the component's checked-out catalog-info.yaml. The live catalog, including an ID inherited from the component's System or Domain, is the `from-backstage-collector` sub-collector's job and needs no setting here.
backstage_annotations Optional pagerduty.com/service-id,pagerduty/service-id Comma-separated annotation keys to read the service ID from, tried in order (first non-empty value wins). Defaults to both conventional spellings — PagerDuty's Backstage integration guide uses `pagerduty.com/service-id`; Lunar's meta uses `pagerduty/service-id`. Used for the checked-out catalog-info.yaml and the live catalog alike.
backstage_catalog_paths Optional catalog-info.yaml,catalog-info.yml Comma-separated catalog-info file paths to try in the checked-out repo (first match wins). Only used when backstage_discovery is "true".
backstage_wait_seconds Optional 1800 How long the `from-backstage-collector` sub-collector keeps retrying the read of the backstage collector's lookup before it fails the run. The Component JSON it reads can lag the write that triggered it by several minutes.

Secrets

This collector requires the following secrets to be configured in Lunar:

Secret Description
PAGERDUTY_API_KEY PagerDuty REST API key (read-only token with service and oncall scopes)

Documentation

View on GitHub

PagerDuty Collector

Collect on-call schedule and escalation data from the PagerDuty API.

Overview

This collector queries the PagerDuty REST API to gather on-call schedule, escalation policy, and service data, writing normalized results to the .oncall category in a tool-agnostic format so the same oncall policy works for PagerDuty, OpsGenie, or any other provider. It runs on push (oncall), on a daily cron (oncall-cron), or once the backstage collector's live catalog lookup lands (from-backstage-collector); see Collectors below. The service ID comes from the component's pagerduty/service-id meta annotation, an explicit service_id input, or a Backstage annotation: in the checked-out catalog-info.yaml, or in the live catalog, including one inherited from the component's System or Domain.

Collected Data

This collector writes to the following Component JSON paths:

Path Type Description
.oncall.source object Tool and integration metadata
.oncall.service object PagerDuty service ID, name, status, and discovered_via: where the ID came from (see Service ID discovery)
.oncall.service_lookup object Written when a sub-collector found no service ID: searched lists the places it looked, and errors any Backstage lookup that didn't complete. The oncall policy's service-mapped check fails on it, or skips when it has errors, unless another sub-collector wrote .oncall.service
.oncall.schedule object On-call schedule: exists flag, participant count, rotation type
.oncall.escalation object Escalation policy: exists flag, level count, policy name
.oncall.summary object Summary flags for quick policy evaluation
.oncall.native.pagerduty object Raw PagerDuty API responses

Collectors

This integration provides the following collectors (use include to select a subset). All three write the same .oncall data; they differ in when they run and where they look for the service ID.

Collector Description
oncall Code hook — queries PagerDuty on pushes to PRs and the default branch, so the on-call guardrail is evaluated as part of a commit/PR check
oncall-cron Cron hook — queries PagerDuty daily (04:00 UTC, staggered off the 02:00/03:00 scheduled jobs) and refreshes .oncall so the data stays current as schedules rotate, independent of code changes
from-backstage-collector After-json hook — reads the service ID from the backstage collector's live catalog lookup once it lands in the Component JSON, for components oncall can't map, then queries PagerDuty

Including both oncall and oncall-cron is supported and the normalized .oncall data stays correct — but be aware the raw arrays under .oncall.native.pagerduty will carry duplicate entries, because Lunar concatenates arrays written to the same path by different collectors. Guardrail results are unaffected: the oncall policy reads only the normalized scalars. On push, oncall collects components mapped by meta, service_id or the checked-out file, and from-backstage-collector collects the rest.

Installation

Add to your lunar-config.yml (use include to pick a trigger variant):

collectors:
  - uses: github://earthly/lunar-lib/collectors/pagerduty@v1.0.0
    include: [oncall]          # code hook (PRs + default branch); use [oncall-cron] for the daily-cron variant
    on: ["domain:your-domain"]
    # with:
    #   service_id: "PXXXXXX"  # Optional — falls back to catalog meta annotation

Secrets:

  • PAGERDUTY_API_KEY — PagerDuty REST API key (read-only, with service and oncall scopes). Required.

Service ID discovery

The collector resolves the PagerDuty service ID in this order:

  1. Catalog meta annotation — reads pagerduty/service-id from the component's lunar catalog meta. Set via lunar catalog component --meta pagerduty/service-id <id>, typically invoked by a company-specific cataloger that knows which components map to which PagerDuty services.
  2. Explicit service_id input — set in lunar-config.yml for static org-wide configurations, or when importing the collector multiple times with different on: scopes (e.g. one import per domain, each with its own service ID).
  3. The checked-out catalog-info.yaml (opt-in) — with backstage_discovery: "true", the standard PagerDuty annotation (backstage_annotations, default pagerduty.com/service-id,pagerduty/service-id) in the repo's own file. It needs no token: the oncall code hook clones automatically, and oncall-cron sets clone-code: true.
  4. The live catalog — when nothing above maps the component, the from-backstage-collector sub-collector reads what the backstage collector found in the live catalog, so a service declared once on a System or Domain reaches every Component under it. It takes the Component's own annotation first, then its System's, then that System's Domain's. Only the backstage collector calls Backstage, with its own auth (bearer or SigV4, including aws_assume_role_arns); the pagerduty collector needs no Backstage secret.
  5. None found — the collector writes .oncall.service_lookup with the places it looked, so the oncall policy's service-mapped check can fail with that list. A Backstage lookup that failed (an outage, a rejected credential, a response that couldn't be read) goes in .oncall.service_lookup.errors, and the check skips instead: an outage isn't reported as a missing mapping.

.oncall.service.discovered_via records the source of the ID: meta:pagerduty/service-id, input:service_id, the Backstage entity it was read from (e.g. system:default/payment-platform), or file:catalog-info.yaml. When an inherited ID turns out to be wrong, it tells you which entity to fix. It is recorded even when PagerDuty rejects the ID.

Live Backstage lookup

The live lookup needs the backstage collector on the same components with backstage_url set, and from-backstage-collector in the pagerduty collector's include. Together with backstage_discovery: "true", a component mapped in its own catalog-info.yaml is collected on push by oncall, and one that inherits its service from a System or Domain is collected by from-backstage-collector:

collectors:
  - uses: github://earthly/lunar-lib/collectors/backstage@v1.0.0
    on: ["domain:your-domain"]
    include: [catalog-info]
    with:
      backstage_url: "https://backstage.example.com"
  - uses: github://earthly/lunar-lib/collectors/pagerduty@v1.0.0
    on: ["domain:your-domain"]
    include: [oncall, from-backstage-collector]
    with:
      backstage_discovery: "true"   # optional: read catalog-info.yaml first
  • The from-backstage-collector sub-collector runs once the backstage collector's lookup (.catalog.native.backstage.refs.entity) is in the Component JSON. The JSON it reads can lag that write by several minutes, so it retries the read for up to backstage_wait_seconds (1800 by default), backing off up to 60s between attempts, then fails the run. The oncall checks wait for it.
  • oncall writes .oncall.service_lookup for a component it can't map, and from-backstage-collector then writes .oncall.service if the live catalog maps it. The check reads .oncall.service first, so it passes. oncall-cron checks the same places, then the backstage collector's lookup in the default branch's Component JSON.
  • The System and Domain are the ones the catalog file declares (spec.system, and that System's spec.domain), which the backstage collector also checks for referential integrity. A Component missing from the catalog still inherits from the System its file declares.
  • A monorepo component whose entity is in a shared parent catalog-info.yaml (the backstage collector's search_parent_dirs) is always from-backstage-collector's to collect: oncall reads only the component's own directory.

Inputs

Input Default Description
service_id (empty — falls back to catalog meta) PagerDuty service ID (e.g. PXXXXXX). Optional if pagerduty/service-id meta annotation is set.
pagerduty_base_url https://api.pagerduty.com PagerDuty API base URL
backstage_discovery "false" When "true", read the service ID from the checked-out catalog-info.yaml if meta and service_id don't give one. The live catalog needs no setting: it's the from-backstage-collector sub-collector.
backstage_annotations pagerduty.com/service-id,pagerduty/service-id Comma-separated annotation keys to read the service ID from, tried in order, in the checked-out file and the live catalog alike.
backstage_catalog_paths catalog-info.yaml,catalog-info.yml Comma-separated catalog-info file paths to try in the checked-out repo (first match wins). Only used when backstage_discovery is "true".
backstage_wait_seconds 1800 How long the from-backstage-collector sub-collector retries reading the backstage collector's lookup before failing the run.

Open Source

This collector is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.

View Repository

Ready to Automate Your Standards?

See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 300+ built-in guardrails.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Auto-ID My Guardrails
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo