16 Sep 2025
	
		
		06:06 PM
	
	
	
	
	
	
	
	
	
	
	
	
	
	
 - last edited on 
    
	
		
		
		17 Sep 2025
	
		
		07:16 AM
	
	
	
	
	
	
	
	
	
	
	
	
	
	
 by 
				
		 MaciejNeumann
		
			MaciejNeumann
		
		
		
		
		
		
		
		
	
			
		
Our teams are looking to understand how many problem notifications are triggered for a period of 30 days or so for a given application.
Is there a way to grab this information via any API?
16 Sep 2025 07:35 PM
Hi there,
How I would do this: put your application in a management zone. Include all the entities like hosts, services and databases etcetera.
The problem notifications are bound to an alerting profile. IYou can use the problems V2 api to query for it using the problemSelector to filter on the alerting profile and the management zone.
Now you could know how many time an alert went to a distribution channel.
KR.
Michiel
17 Sep 2025 09:41 AM
Yes — indirectly. Dynatrace doesn’t expose a dedicated “notification delivery log,” but at the problem level you can fetch the associated alerting profiles. If we assume alerting profile = notification, then you can simply count problems that have an alerting profile within your 30-day window (optionally scoped to your app/MZ).
I past script to do this:
#!/usr/bin/env python3
import os, sys, csv, time, requests
from urllib.parse import quote
DT_BASE   = os.getenv("DT_BASE", "https://{environmentid}.live.dynatrace.com/api/v2<br />(Managed: https://<cluster>/e/<ENV-ID>/api/v2)</p>
<p>Token: Read problems permission.</p>
<p>Pagination: when using nextPageKey, omit all other query params (except optional fields).</p>17 Sep 2025 09:42 AM
#!/usr/bin/env python3
import os, sys, csv, time, requests
from urllib.parse import quote
DT_BASE   = os.getenv("DT_BASE", "https://DYNATRACE_URL/api/v2")
DT_TOKEN  = os.getenv("DT_TOKEN", "dt0c01.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
# timeframe
FROM      = os.getenv("FROM", "now-30d")
TO        = os.getenv("TO", "now")
# (Optionaly) you can filter by status etc
# empty, if u want all 
PROBLEM_SELECTOR = os.getenv("PROBLEM_SELECTOR", "")
PAGE_SIZE = int(os.getenv("PAGE_SIZE", "500"))
OUT_CSV   = os.getenv("OUT", "problems_with_alerting_profiles.csv")
headers = {"Authorization": f"Api-Token {DT_TOKEN}"}
def list_problems():
    next_key = None
    page = 0
    while True:
        page += 1
        params = {}
        if next_key:
            params["nextPageKey"] = next_key
        else:
            params.update({"from": FROM, "pageSize": PAGE_SIZE})
            if TO:
                params["to"] = TO
            if PROBLEM_SELECTOR:
                params["problemSelector"] = PROBLEM_SELECTOR
        r = requests.get(f"{DT_BASE}/problems", headers=headers, params=params, timeout=30)
        if not r.ok:
            try: print("[ERROR]", r.status_code, r.json())
            except Exception: print("[ERROR]", r.status_code, r.text)
            r.raise_for_status()
        data = r.json()
        if page == 1:
            tc = data.get("totalCount")
            print(f"[INFO] totalCount z API (strona 1): {tc}")
        probs = data.get("problems", [])
        print(f"[INFO] Strona {page}: {len(probs)} problemów")
        for p in probs:
            yield p
        next_key = data.get("nextPageKey")
        if not next_key:
            print("[INFO] Brak nextPageKey — koniec.")
            break
def get_problem_detail(pid: str):
    for attempt in range(3):
        r = requests.get(f"{DT_BASE}/problems/{pid}", headers=headers, timeout=30)
        if r.ok:
            return r.json()
        if r.status_code in (429, 500, 503):
            time.sleep(1.5 * (attempt + 1)); continue
        try: print(f"[WARN] details {pid}:", r.status_code, r.json())
        except Exception: print(f"[WARN] details {pid}:", r.status_code, r.text)
        r.raise_for_status()
    r.raise_for_status()
def main():
    found_any = False
    with open(OUT_CSV, "w", newline="", encoding="utf-8") as f:
        wr = csv.writer(f)
        wr.writerow([
            "problemId","displayId","startTime","endTime","status","severityLevel",
            "impactLevel","managementZones","alertingProfileId","alertingProfileName"
        ])
        for p in list_problems():
            det = get_problem_detail(p.get("problemId"))
            filters = det.get("problemFilters") or []
            if not filters:
                continue  # only with alerting profiles
            found_any = True
            mz = det.get("managementZones") or []
            mz_join = ";".join([m.get("name","") for m in mz])
            for flt in filters:
                wr.writerow([
                    det.get("problemId"), det.get("displayId"),
                    det.get("startTime"), det.get("endTime"),
                    det.get("status"), det.get("severityLevel"),
                    det.get("impactLevel"), mz_join,
                    flt.get("id",""), flt.get("name","")
                ])
    if not found_any:
        print("[INFO] not find problems with alerting profiles in timeframe.")
    else:
        print(f"[OK] Save to: {OUT_CSV}")
if __name__ == "__main__":
    main()17 Sep 2025 09:44 AM
What the script does:
Lists problems for a given time window (from=now-30d, etc.) using Problems API v2 with nextPageKey pagination.
For each problem, it requests details (GET /problems/{problemId}) and inspects problemFilters[] — these are the alerting profiles tied to the problem.
Filters to include only problems with at least one alerting profile (per your requirement).
Writes a CSV with: problemId, displayId, timestamps, status, severity, impactLevel, managementZones, alertingProfileId, alertingProfileName.
How to get “# of notifications over 30 days”:
Easiest: count rows in the CSV (each row = problem × assigned alerting profile).
Per profile: group by alertingProfileId/alertingProfileName.
Scope to a specific application/MZ via PROBLEM_SELECTOR, e.g.
managementZones("My App MZ") (recommended),
or (if needed) affectedEntities("APPLICATION-XXXXXX").
Technical requirements:
Token: Read problems permission.
