PagerDuty Collector
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.
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.
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.
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.
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.
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 →Example Collected Data
This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:
{
"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 GitHubPagerDuty 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:
- Catalog meta annotation — reads
pagerduty/service-idfrom the component's lunar catalog meta. Set vialunar catalog component --meta pagerduty/service-id <id>, typically invoked by a company-specific cataloger that knows which components map to which PagerDuty services. - Explicit
service_idinput — set inlunar-config.ymlfor static org-wide configurations, or when importing the collector multiple times with differenton:scopes (e.g. one import per domain, each with its own service ID). - The checked-out
catalog-info.yaml(opt-in) — withbackstage_discovery: "true", the standard PagerDuty annotation (backstage_annotations, defaultpagerduty.com/service-id,pagerduty/service-id) in the repo's own file. It needs no token: theoncallcode hook clones automatically, andoncall-cronsetsclone-code: true. - The live catalog — when nothing above maps the component, the
from-backstage-collectorsub-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, includingaws_assume_role_arns); the pagerduty collector needs no Backstage secret. - None found — the collector writes
.oncall.service_lookupwith the places it looked, so theoncallpolicy'sservice-mappedcheck 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-collectorsub-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 tobackstage_wait_seconds(1800 by default), backing off up to 60s between attempts, then fails the run. Theoncallchecks wait for it. oncallwrites.oncall.service_lookupfor a component it can't map, andfrom-backstage-collectorthen writes.oncall.serviceif the live catalog maps it. The check reads.oncall.servicefirst, so it passes.oncall-cronchecks 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'sspec.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'ssearch_parent_dirs) is alwaysfrom-backstage-collector's to collect:oncallreads 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.
Common Use Cases
Explore guardrails that use data from PagerDuty Collector.
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.