Process Radar API Documentation

API Version: 1.0.0
Base URL: https://api.dkyra.com/api/v1
Authentication: API Key or JWT Token
Last Updated: 2026-07-03

Overview

Process Radar analyzes ITSM ticket workflows to automatically identify anomalies, bottlenecks, and optimization opportunities across teams. The API provides programmatic access to analysis results, prioritized situations, health metrics, and delegation workflows.

Key Concepts

ConceptDescription
FindingAn automatically detected anomaly in your ITSM workflows, for example a team with unusually long processing times, a routing loop between departments, or a seasonal capacity problem
Affected DimensionsThe business context of a finding: which team, category, location, or combination is affected
TierSeverity classification: critical, notable, observed
TrendDevelopment over time: improving, worsening, stable, new
ConfidenceRobustness assessment (low, medium, high) based on data volume and statistical significance
SituationA group of related findings that together describe a business-relevant problem requiring action, such as "Level-1 Support is overwhelmed" instead of 12 separate symptoms
Health ScoreAn A-through-F grade summarizing overall process health, combining throughput, bottleneck risk, and trend indicators
DelegationAssignment of responsibility with feedback loop via magic-link email. Recipients need no login
NarrativeAI-generated explanation of a situation in plain language with recommended actions
StatementHuman-readable description of the finding, available in German and English

Authentication

All API requests require authentication via one of:

API Key (Header)

X-API-Key: your-api-key

JWT Bearer Token

Authorization: Bearer your-jwt-token

Obtaining Credentials

API Key: Create via Settings → API Keys in the Process Radar UI.

JWT Token: Use the login endpoint:

POST /api/v1/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "your-password"
}

Response:

{
  "access_token": "eyJhbGci...",
  "token_type": "bearer",
  "expires_in": 300
}

Access tokens are short-lived (expires_in is in seconds; 5 minutes). Use POST /api/v1/auth/refresh with the refresh token to obtain a new one before expiry. For long-running integrations, prefer API-Key authentication.


Quick Start

Step 1: Upload data

curl -X POST https://api.dkyra.com/api/v1/datasets/upload \
  -H "X-API-Key: YOUR_KEY" \
  -F "[email protected]" \
  -F "[email protected]" \
  -F "name=Q1 Analysis"

Step 2: Start analysis

curl -X POST https://api.dkyra.com/api/v1/datasets/{dataset_id}/runs \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Q1 Deep Dive"}'

Step 3: Get results

curl https://api.dkyra.com/api/v1/executive-summary \
  -H "X-API-Key: YOUR_KEY"

Core Endpoints

Health Check

GET /health

Returns API status. No authentication required.

Response:

{
  "status": "ok"
}

Datasets & Upload

#### List Datasets

GET /api/v1/datasets

Returns all datasets for the current tenant, including run counts.

Response:

[
  {
    "dataset_id": "uuid",
    "name": "Q1 2026 Analysis",
    "status": "uploaded",
    "created_at": "2026-02-09T10:00:00Z",
    "run_count": 3,
    "ticket_count": 1500,
    "event_count": 45000
  }
]

#### Upload CSV Files

POST /api/v1/datasets/upload

Creates a dataset and uploads CSV files in one step. Uses multipart/form-data.

Form Fields:

NameTypeDescription
tickets_filefileTickets.csv (semicolon-separated)
history_filefileTicketHistory.csv (semicolon-separated)
namestringDataset name (default: "Neue Analyse")

Response (200):

{
  "dataset_id": "uuid",
  "status": "uploaded",
  "validation": {
    "tickets_count": 1500,
    "events_count": 45000,
    "warnings": []
  }
}

#### Delete Dataset

DELETE /api/v1/datasets/{dataset_id}?confirm=true

Deletes a dataset and ALL associated data. Requires confirm=true as safety guard.

StatusMeaning
204Successfully deleted
400Missing confirm=true parameter
404Dataset not found
409Cannot delete: active run in progress

Analysis Runs

#### Start Analysis Run

POST /api/v1/datasets/{dataset_id}/runs

Starts an analysis run asynchronously. Returns 202 Accepted immediately.

Request Body:

{
  "name": "Q1 Deep Dive"
}

Response (202):

{
  "run_id": "uuid",
  "status": "running",
  "name": "Q1 Deep Dive",
  "message": "Analysis started"
}

#### List Runs

GET /api/v1/runs

Returns all runs for the current tenant.

Parameters:

NameTypeDescription
dataset_idqueryFilter by dataset
statusqueryFilter: QUEUED, RUNNING, SUCCEEDED, FAILED
limitqueryMax results (default: 50)
offsetqueryPagination offset

#### Get Run Status

GET /api/v1/runs/{run_id}

Response:

{
  "run_id": "uuid",
  "dataset_id": "uuid",
  "status": "SUCCEEDED",
  "name": "Q1 Deep Dive",
  "findings_count": 181,
  "progress_phase": "complete",
  "progress_percent": 100,
  "message": null,
  "error": null,
  "created_at": "2026-02-09T10:00:00Z",
  "started_at": "2026-02-09T10:00:00Z",
  "finished_at": "2026-02-09T10:02:30Z"
}

Executive Summary Endpoints

These aggregated endpoints are designed for AI agents, dashboards, and third-party integrations. They return high-level KPIs without requiring knowledge of internal data structures.

#### Executive Summary

GET /api/v1/executive-summary

High-level briefing with system health, prioritized situations, and trend information.

Parameters:

NameTypeDescription
dataset_idqueryDataset ID (uses first dataset if omitted)
limitqueryMax priority situations, 1-10 (default: 5)

Response:

{
  "health_score": {
    "score": 62,
    "grade": "C"
  },
  "priority_situations": [
    {
      "situation_id": "sit-abc-123",
      "headline": "Level-1-Support: Kapazitätsengpass mit steigender Tendenz",
      "situation_type": "capacity_bottleneck",
      "impact_hours": 342.5,
      "trend": "worsening",
      "recommendation": "delegate",
      "finding_count": 4,
      "tier": "critical"
    }
  ],
  "open_delegations": 2,
  "trend_summary": {
    "direction": "worsening",
    "new_situations": 1,
    "resolved_situations": 0
  },
  "period": {
    "start": "2024-01-01T00:00:00",
    "end": "2024-12-31T00:00:00",
    "run_id": "run-xyz-456"
  }
}

#### Analysis Summary

GET /api/v1/analysis-summary

Aggregated analysis KPIs: anomaly distribution, trends, and most affected roles.

Response:

{
  "total_findings": 23,
  "tier_distribution": {
    "critical": 3,
    "notable": 8,
    "observed": 12
  },
  "new_findings": 2,
  "trend_distribution": {
    "worsening": 5,
    "stable": 12,
    "improving": 4,
    "new": 2
  },
  "top_affected_roles": [
    {
      "role": "Level-1-Support",
      "finding_count": 6,
      "dominant_tier": "critical"
    }
  ],
  "last_analysis_at": "2024-12-15T14:30:00"
}

#### Flow Health

GET /api/v1/flow-health

System-wide flow health metrics: bottleneck roles and capacity status.

Response:

{
  "roles_in_bottleneck": 2,
  "total_active_roles": 8,
  "bottleneck_roles": [
    {
      "role": "Level-1-Support",
      "severity": "critical",
      "state": "persistent overload"
    },
    {
      "role": "Incident-Management",
      "severity": "notable",
      "state": "growing overload"
    }
  ],
  "health_grade": "C"
}

Findings

#### List Findings

GET /api/v1/runs/{run_id}/findings

Returns all detected anomalies for an analysis run with filtering and sorting.

Parameters:

NameTypeDescription
run_idpathAnalysis run identifier
tierqueryFilter: critical, notable, observed
finding_typequeryFilter by finding type identifier
rolequeryFilter by affected role
categoryqueryFilter by affected category
locationqueryFilter by affected location
min_impactqueryMinimum impact in hours
ticket_statusqueryFilter: closed (default), open, all
workflow_statusqueryFilter: active (default), new, delegate, watching, ignore, escalated, all
sort_byquerySort: relevance_score (default), impact_hours, tier
sort_orderqueryDirection: asc or desc
limitqueryMax results (default: 50, max: 500)
offsetqueryPagination offset

Response:

{
  "findings": [
    {
      "finding_id": "cbf7fec4-f9a2-...",
      "finding_type": "F1_ROLE_HIGH_DWELL",
      "tier": "critical",
      "dimensions": ["role"],
      "constellation": {
        "role": "2nd Level Support"
      },
      "statements": {
        "de": "Die Rolle '2nd Level Support' zeigt eine deutlich höhere Verweildauer als der globale Median (89.4h vs. 12.0h, +645%). Robustheit: Hoch (234 Tickets).",
        "en": "Role '2nd Level Support' shows significantly higher dwell time than the global median (89.4h vs. 12.0h, +645%). Robustness: High (234 tickets)."
      },
      "confidence": {
        "level": "high",
        "description_de": "Hoch",
        "description_en": "High"
      },
      "volume": {
        "ticket_count": 234,
        "interval_count": 456
      },
      "impact_hours": 1250.5,
      "relevance_score": 0.82,
      "trend": "stable",
      "temporal": {
        "definition_id": "uuid",
        "first_seen": "2024-01-15",
        "last_seen": "2024-06-30",
        "trend": "stable",
        "status": "new"
      }
    }
  ],
  "total": 181,
  "returned": 50,
  "filters_applied": {}
}
Response fields like finding_type contain machine-readable identifiers. Use statements.de / statements.en for human-readable display. constellation names the affected dimension values; dimensions lists which dimensions define the finding.

#### Get Finding Detail

GET /api/v1/runs/{run_id}/findings/{finding_id}

Returns full details for a single finding including metrics, affected tickets, and trend data.

The response also carries type_explanation, a static, consumer-facing explanation of the finding type with four fields: what_de / what_en (what this finding type measures) and how_to_read_de / how_to_read_en (lead metric, reference baseline, unit). It never varies per run; the run-specific statement is the finding's kernaussage. null for types without an explanation. Hide the block in that case.


#### Get Finding Time Series

GET /api/v1/findings/{finding_id}/timeseries

Returns weekly time series data for a finding (affected tickets per ISO week), useful for sparklines and trend visualization.

Parameters:

NameTypeDescription
finding_idpathFinding identifier
run_idqueryRequired. Analysis run identifier
viewquerylast_52_weeks (default) or full_history
as_ofqueryTime anchor (ISO-8601 UTC; default: latest event in the run)

Response:

{
  "finding_id": "uuid",
  "run_id": "uuid",
  "as_of": "2026-01-03T15:05:42Z",
  "view": "last_52_weeks",
  "bucket": "week",
  "tz": "UTC",
  "points": [
    {
      "bucket_start": "2025-01-06T00:00:00Z",
      "bucket_end": "2025-01-13T00:00:00Z",
      "new_affected_count": 3,
      "cumulative_affected_count": 12
    }
  ]
}

#### Get Affected Tickets

GET /api/v1/findings/{finding_id}/tickets

Returns sample ticket IDs affected by this finding.

Parameters:

NameTypeDescription
finding_idpathFinding identifier
run_idqueryRequired. Analysis run identifier

Response:

{
  "finding_id": "uuid",
  "finding_type": "F7_PING_PONG",
  "finding_type_label": "Ping-Pong-Effekt",
  "finding_type_label_en": "Ping-pong effect",
  "tickets": [
    {
      "ticket_id": "INC-2024-0042",
      "ticket_id_hash": "5e68376293bc",
      "role": "1st Level Support",
      "dwell_hours": 76.6,
      "effective_dwell_hours": 76.6,
      "assigned_at": "2025-12-05T11:09:23",
      "outcome": "handover",
      "category": "Identity/Access",
      "location": "Berlin"
    }
  ],
  "total_count": 234,
  "returned_count": 20
}

Situations

#### List Situations

GET /api/v1/runs/{run_id}/situations

Returns situations (grouped findings) for a run.

Parameters:

NameTypeDescription
statusqueryFilter: new, delegate, watching, ignore, escalated
tierqueryFilter: critical, notable, observed

Response:

{
  "situations": [
    {
      "id": "sit-uuid",
      "title": "Level-1-Support: Kapazitätsengpass",
      "situation_type": "capacity_bottleneck",
      "anchor_role": "Level-1-Support",
      "impact_hours": 342.5,
      "finding_count": 4,
      "trend": "worsening",
      "status": "new",
      "has_narrative": true
    }
  ],
  "total": 12,
  "run_id": "uuid"
}

#### Get Situation Detail

GET /api/v1/situations/{situation_id}

Returns full situation details including member findings, delegations, and learnings.

**Concentration block (konzentration, optional):** where a situation is

concentrated across a hidden dimension. Present only when the situation has a

measurable concentration point; absent otherwise (no placeholder).

{
  "konzentration": {
    "titel_de": "Konzentration",
    "achse": "location",
    "achse_label_de": "Standorte",
    "zeile_pointe_de": "Bei IMS Operations konzentriert sich die Bearbeitungsverzögerung auf 6 von 32 geprüften Standorten (81 % der abgeschlossenen Durchläufe geprüft).",
    "zeile_kontrast_de": "Am stärksten: Karlsruhe (3,9×) · Metzingen (2,2×).",
    "zeile_entlastung_de": "26 Standorte mit 1.588 Tickets liegen im Normalband; 96 weitere sind mit unter 20 Tickets nicht bewertbar.",
    "k_auffaellig": 6,
    "n_geprueft": 32,
    "n_im_band": 26,
    "tickets_im_band": 1588,
    "n_nicht_bewertbar": 96,
    "ticket_deckung_geprueft": 0.812,
    "deckung_ok": true,
    "top_werte": [
      {"wert": "Karlsruhe", "faktor": 3.87, "faktor_deckel": false, "faktor_label_de": "3,9×", "tier": "critical"}
    ]
  }
}

Field notes:

  • k_auffaellig / n_geprueft: how many values of the anchor role's
  • population show a critical or notable finding, out of those with enough

    volume to be assessed (at least 20 tickets). k is always a subset of n.

  • n_im_band / tickets_im_band: values that were assessed and stay within
  • the normal band. Values below the volume threshold are never counted here;

    they are reported separately as n_nicht_bewertbar.

  • ticket_deckung_geprueft: share of completed role passes covered by the
  • assessed values. Below 0.75 the relief sentence is omitted (deckung_ok

    is false), while the concentration statement itself remains.

  • top_werte[].faktor: deviation factor of the strongest values. null means
  • the value has no measurable time-based finding: it still counts towards k

    but is rendered without a number. faktor_deckel marks display capping.

  • The three zeile_*_de fields are the rendered German sentences; the numeric
  • fields carry the same values for your own formatting.

    **Origin block (herkunft, optional):** where the waiting time accumulating

    at the situation's anchor role comes from. Decomposes the role's handover

    waiting time across its inbound edges and reports predecessor roles whose

    share of the waiting time is disproportionate to their share of handed-over

    tickets. Present only when at least one inbound edge is disproportionate;

    absent otherwise (no placeholder). The same block is served on the evidence

    poster endpoint with identical values.

    {
      "herkunft": {
        "titel_de": "Herkunft",
        "untertitel_de": "Übergaben",
        "anker_rolle": "IMS Finance",
        "zeile_pointe_de": "Bei IMS Finance trägt die Übergabe von WMD REB Support das 5,0-Fache ihres Ticket-Anteils an Wartezeit (4 % der übergebenen Tickets, 19 % der Übergabe-Wartezeit; Gesamtzeitraum der abgeschlossenen Durchläufe).",
        "zeile_hauptzulieferer_de": "Der Hauptzulieferer IT Comline liefert 88 % der übergebenen Tickets und trägt 49 % der Übergabe-Wartezeit (unauffällig).",
        "zeile_erstkontakt_de": null,
        "signal_kanten": [
          {"vorgaenger": "WMD REB Support", "tickets": 48, "ticket_anteil": 0.0382, "wartezeit_anteil": 0.1922, "faktor": 5.04, "faktor_deckel": false}
        ],
        "kanten_im_satz": 1,
        "hauptzulieferer": {"vorgaenger": "IT Comline", "tickets": 1113, "ticket_anteil": 0.8847, "wartezeit_anteil": 0.4912, "faktor": 0.56},
        "erstkontakt_wartezeit_anteil": 0.2377,
        "uebergabe_wartezeit_anteil": 0.7623
      }
    }

    Field notes:

  • signal_kanten: inbound edges whose waiting-time share is at least twice
  • their ticket share (with at least 10 distinct tickets and at least 10 % of

    the handover waiting time). Sorted by faktor descending; the rendered

    sentence names at most the first kanten_im_satz of them.

  • faktor: waiting-time share divided by ticket share of the edge, computed
  • on the anchor role's completed handed-over passes over the full data range.

    faktor_deckel marks display capping ("mehr als das 20-Fache").

  • hauptzulieferer: the edge delivering the most tickets, as contrast. The
  • rendered sentence appears only when this edge itself is inconspicuous.

  • erstkontakt_wartezeit_anteil: share of the role's total waiting time
  • that arises without any handover (first contact) and is therefore not part

    of the decomposition. Rendered as a separate honesty line from 0.25 upward.

  • The zeile_*_de fields are the rendered German sentences; the numeric
  • fields carry the same values for your own formatting.

  • When the block is present, the situation's owner slot
  • (statement.owner_question_slot) additionally carries herkunft_frage_de,

    a clarification question addressed to both sides of the strongest edge.


    #### Get Situation Narrative

    GET /api/v1/situations/{situation_id}/narrative

    Returns the AI-generated narrative for a situation: a plain-language explanation with context and recommended actions.

    Response:

    {
      "id": "uuid",
      "situation_id": "sit-uuid",
      "language": "de",
      "headline": "Level-1-Support: Kapazitätsengpass mit steigender Tendenz",
      "body": "Der Level-1-Support zeigt seit 8 Wochen eine kontinuierlich steigende Verweildauer...",
      "next_actions": [
        "Routing-Regeln für Kategorie 'Passwort-Reset' prüfen",
        "Kapazitätsplanung für KW 15-20 anpassen"
      ],
      "status": "completed",
      "generated_at": "2026-03-01T12:00:00Z"
    }

    #### Get Situation Headline Phrasings

    GET /api/v1/situations/{situation_id}/leitzahl

    Returns every available phrasing of a situation's headline figure. Each phrasing

    carries three parts: a compact wert_text for the tile, a short label_de

    naming the measure and window, and a full satz_de for surfaces with room.

    Also returns the concentration curve (which share of cases carries which share

    of the delay) and the time share per case.

    All values derive from the same per-case list that produces the situation's

    12-week magnitude, so switching the phrasing never changes the number. The share

    uses the same 12-week window as the numerator.

    Parameters:

    NameTypeDescription
    variantequeryOverride the leading phrasing for comparison (optional)

    Response:

    {
      "situation_id": "sit-uuid",
      "leitzahl": {
        "variante": "anteil",
        "wert_text": "88 %",
        "label_de": "der gebundenen Verzögerungszeit (12 Wochen)",
        "satz_de": "Diese Situation bindet 88 % der gesamten Verzögerungszeit der letzten zwölf Wochen.",
        "n_vorgaenge": 311,
        "stunden_gesamt": 146447.9,
        "euro_gesamt": 7322397,
        "anteil_am_lauf": 0.8846,
        "median_stunden": 122.0,
        "mittelwert_stunden": 470.9,
        "kurve": [
          {"vorgangs_anteil": 0.1, "n_vorgaenge": 31, "zeit_anteil": 0.491, "stunden": 71852.0}
        ],
        "zeit_anteil_je_vorgang": [
          {"vorgang": "ad27acedaa95", "vorgang_nummer": "TCK00030663", "stunden": 3534.0, "zeit_anteil": 0.0241}
        ],
        "varianten": {
          "anteil": {"wert_text": "88 %", "label_de": "der gebundenen Verzögerungszeit (12 Wochen)", "satz_de": "..."},
          "euro": {"wert_text": "7,3 Mio €", "label_de": "Verzögerungskosten (12 Wochen)", "satz_de": "..."},
          "stunden": {"wert_text": "146 Tsd h", "label_de": "über der Norm, 311 Vorgänge (12 Wochen)", "satz_de": "..."},
          "typisch": {"wert_text": "5,1 Tage", "label_de": "über der Norm je Vorgang, typisch (12 Wochen)", "satz_de": "..."},
          "konzentration": {"wert_text": "49 %", "label_de": "der Zeit aus 10 % der Vorgänge (31 von 311)", "satz_de": "..."}
        }
      }
    }

    The anteil phrasing is absent when no denominator is available. No percentage is invented.

    leitzahl is null when the situation has no evidence inside the 12-week

    window. That is an honest empty case rather than a zero sentence.


    Triage Queue

    GET /api/v1/runs/{run_id}/situations-queue

    Returns the prioritized triage queue combining situations and ungrouped findings.

    Parameters:

    NameTypeDescription
    tierqueryFilter by severity
    statusqueryFilter by workflow status
    limitqueryMax items (default: 20)

    Delegations

    #### Delegate Situation

    POST /api/v1/situations/{situation_id}/delegate

    Assigns responsibility for a situation to a team member via email. The recipient receives a magic-link email with context and guiding questions. Recipients need no login.

    Request Body:

    {
      "email": "[email protected]",
      "name": "Max Mustermann",
      "message": "Bitte prüfe die Routing-Regeln für Passwort-Reset Tickets.",
      "due_date": "2026-03-15"
    }

    Response (201):

    {
      "delegation_id": "uuid",
      "status": "sent",
      "feedback_url": "https://app.dkyra.com/feedback/abc123"
    }

    #### List Delegations

    GET /api/v1/delegations

    Returns all delegations for the current tenant with status tracking.


    System Health

    #### Get Health Score

    GET /api/v1/system-flow/health-score

    Returns the overall system health score for a run.

    Parameters:

    NameTypeDescription
    run_idqueryAnalysis run ID

    Response:

    {
      "score": 62,
      "grade": "C",
      "components": {
        "flow_balance": 55,
        "stock_trend": 70,
        "capacity_balance": 48,
        "throughput_trend": 75
      },
      "worst_roles": ["Level-1-Support", "Incident-Management"],
      "improving_roles": ["Change-Management"]
    }

    #### Get Role Assessments

    GET /api/v1/role-assessments

    Returns pre-computed per-role health assessments for a run.

    Parameters:

    NameTypeDescription
    run_idqueryAnalysis run ID
    severityqueryFilter: critical, notable, observed

    #### Get Role Field

    GET /api/v1/runs/{run_id}/role-field

    Returns every role of a run as one point: number of intervals, dwell

    distribution (p25/p50/p75/p90), concentration of waiting time, neighbouring

    roles above a handover threshold, and the finding profile of that role.

    Requires a Pro subscription.

    The dwell basis switches the axis, the distribution and the concentration. It

    does not change the finding counts: each detector measures on its own clock.

    run_at is when the analysis ran, as an ISO-8601 timestamp with an explicit UTC

    offset; run_at_source says whether that is the completion (finished) or the

    start (created) of the run. Both are null if the run carries neither.

    observation is the span the underlying data covers and ends earlier. Its two

    timestamps are wall-clock values taken from the source data and carry no zone.

    Display them as they are, do not convert them. Only run_at is a machine

    timestamp and therefore zone-aware.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID
    dwellqueryDwell basis: raw (default) or business
    min_edgequeryMinimum handovers for an edge to be listed (default 15)

    #### Get Contrasts

    GET /api/v1/runs/{run_id}/kontraste

    Returns the comparisons of a run: inside the area that carries most of the

    working time, which groups take markedly longer than is usual there. Everything

    the briefing page draws comes from this one call; the client computes nothing.

    Each comparison carries both numbers side by side: the added hours and how many

    times longer, plus the usual duration they are measured against, so a reader can

    redo the arithmetic. Added hours can be negative, and the multiple is null when

    the usual duration is too close to zero for a ratio to mean anything. Both are

    returned as they are, never clipped or dropped.

    geschwiegen lists what the run could not say anything about, and why.

    befund is the verdict for the data space as a whole and separates "nothing

    stands out" from "nothing can be measured here". Both are null when the run

    never computed them. That is a third state, not an empty result.

    Percentiles are given per group and are absent for groups below the minimum size

    or beyond the cap; the cap travels with the response, so a reader can tell a

    complete list from a trimmed one.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID
    sortierungqueryOrder: zusatzzeit (added hours, default) or faktor

    #### Get Evidence For The Leading Comparison

    GET /api/v1/runs/{run_id}/briefing-belege

    Returns what the leading comparison is made of: the individual durations behind

    it, and what the longest cases have in common. Three statements in this order:

    how strongly the time clusters, which attribute the clustered cases share, and

    who that attribute belongs to.

    Drivers are only reported among cases built the same way, that is, sections that

    ended by being resolved. Without that restriction the result would describe how

    the data is constructed rather than what happened: a section that ends in a

    handover is cut short by definition and can never grow long.

    A driver that never occurs outside the top tenth carries no multiple at all.

    hebel is null and the sentence says "all N cases of this kind are up here"

    instead. A ratio without a denominator is not a large number, it is no number.

    punkte carries every case of the stated population. When there are too many to

    draw, the response says so in punkte_info: all top-tenth cases plus every

    n-th of the rest, with schritt naming n. A silent sample never happens.

    grund is set, and everything else empty, when the method has nothing to say:

    too few cases to search, or the dimension missing from the run's artefact. That

    is an answer, not an error, and it arrives with 200.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID

    #### Get Stellen

    GET /api/v1/runs/{run_id}/stellen

    Returns every stelle of a run exactly once, grouped by one attribute. A stelle is one value of the

    chosen attribute: one role, one category, one location, or one origin. Each stelle carries a cell

    per finding column with its own count; columns are never summed or ranked against each other,

    since they measure in different units.

    Stellen already named on the leading briefing page come first (largest share of lost time first),

    then the rest, each group ordered by finding count. im_briefing marks that on each stelle;

    groesster_anteil marks the single stelle that carries the run.

    Each finding entry carries a plain-language label (type_label_de) and headline

    (kernaussage_short) instead of the internal type code; both are null when the label registry

    has none for that type. A finding whose type maps to no column still counts toward the stelle's

    total, listed under deckung.ohne_spalte by its type. Situations are listed per stelle in order of

    twelve-week impact; the client resolves their display label from the situation list it already has.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID
    merkmalqueryAttribute: role (default), category, location, or predecessor_role

    Response:

    {
      "run_id": "uuid",
      "merkmal": "role",
      "merkmal_wort": "Rolle",
      "spalten": [
        { "id": "laenger", "titel": "Dauert länger", "sortierung": "Wievielfaches des Üblichen" }
      ],
      "stellen": [
        {
          "merkmal": "role",
          "wert": "IMS Operations",
          "im_briefing": true,
          "groesster_anteil": true,
          "befunde": 20,
          "situationen": [
            { "id": "uuid", "title": "IMS Operations: Kapazitätsengpass", "impact_hours_12w": 66835.0, "ticket_count": 4622, "finding_count": 37 }
          ],
          "zellen": {
            "laenger": {
              "n": 5,
              "kritisch": 0,
              "eintraege": [
                {
                  "snapshot_id": "uuid", "definition_id": "uuid",
                  "type_label_de": "Prozess-Engpass", "kernaussage_short": "IMS braucht länger",
                  "tier": "notable", "deviation_factor": 10.7, "ticket_count": 32,
                  "time_impact_hours": null, "friction_impact_count": null, "trend": "stable",
                  "weitere_merkmale": [{ "merkmal": "predecessor_role", "wort": "Herkunft", "wert": "KC-Logistik" }],
                  "situation_id": "uuid"
                }
              ]
            }
          }
        }
      ],
      "deckung": {
        "befunde_scharf": 127, "befunde_mit_stelle": 102, "stellen": 27, "stellen_im_briefing": 11,
        "ohne_spalte": {}, "unbekannte_typen": []
      }
    }

    Building-Block Briefing

    #### Get The Building-Block Briefing

    GET /api/v1/runs/{run_id}/bausteine-briefing

    Returns one page in one payload: the same measures the leading briefing uses,

    arranged as a chain instead of side by side. Each block consumes the result of

    the one before it: which area carries the run, who carries that area, where it

    stalls, how it moved week by week, and what can be handed on.

    Every number arrives with the population it was measured on and the window it

    was measured in. Nothing here is ranked against anything from another block:

    hours, counts and rates are different units, and a ranking across them would be

    an arithmetic that has no meaning.

    traeger says who carries the area, and in which of three modes the page

    speaks: one value leads, no value leads, or the baseline is so close to zero

    that leading is not a claim anyone can make. The third case is checked first.

    A baseline near zero measures the denominator, not the difference.

    schwellen carries the thresholds the answer was gated with, so a reader can

    see why a block stayed silent instead of guessing. The thresholds are set

    values, not derived ones, and they are the same for every data space.

    entwicklung reports the weekly series computed from this run's own sections,

    on the same population and window as the rest of the page. Weeks at either edge

    of the window are marked, because a week cut off by the end of the data is not a

    week with less work in it.

    kandidaten holds the shortlists behind each block. Anything already handed on

    is filtered out server-side, so the next candidate moves up on its own rather

    than being remembered by the caller.

    adressen_werden_gemerkt says whether an address entered on this page will

    still be there next time. Programmatic access has no person to file an entry

    under, so nothing is kept then, and the page can say so before anyone types.

    ausserhalb counts the sharp findings that no block of this page mentions.

    A page that only ever describes what it already shows would report its own

    shape.

    grund is set, and the blocks carry neutral defaults, when the method has

    nothing to say for this run. That is an answer, not an error, and it arrives

    with 200. A run that does not exist, or whose underlying data file is gone,

    returns 404.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID

    #### Hand One Finding On

    POST /api/v1/runs/{run_id}/bausteine-briefing/anstoss

    Records that one finding of the building-block briefing has been handed to

    someone, together with the text as it stood on the page: the fact, the question,

    the lever, and the run it came from. The snapshot is deliberate. The numbers of

    the next run are different numbers, and the question was asked about these.

    The entry belongs to the data space, not to the run. A question that is open

    stays open even when the run it came from is gone, and deleting that run never

    removes it.

    Handing the same candidate on twice is a conflict, not a second entry: the call

    returns 409 and the first entry stays exactly as it was. That same uniqueness

    is what makes the next candidate move up on its own. The read endpoint filters

    everything already handed on out of its shortlists, so nothing has to be

    remembered by the caller.

    identity_key is the key the read endpoint delivered for that candidate. Take

    it from the answer rather than composing it: two places forming the same key

    drift apart. gruppe is the shortlist it came from: typisch, extrem,

    unterwegs or weitere. empfaenger_text names a role or a place, not a

    person.

    A hand-over usually names several places at once: the subject and the

    counterparts that work its longest cases. Send one entry per place in

    stellen, each with its own address or explicitly held back. One address

    cannot stand for several places, so stellen and the older single

    empfaenger_email are rejected together with 422.

    A message goes out for every place that carries an address, with the fact, the

    question and who sent it. Each message also names the other places that were

    written to and the ones nobody had an address for, so both sides see the same

    distribution. No message carries a link back or a token: the recipient answers

    a person, not a system. If sending is not configured or fails, the hand-over is

    still recorded and mail_versendet is false with a reason. That is a result,

    not an error.

    When every place is held back, nur_vermerkt is true: the hand-over is

    recorded as a note and nothing is sent. That is a normal outcome, not a

    rejected call.

    An address entered here is kept for next time, filed under the trait and value

    of its place, and comes back pre-filled on the next hand-over naming the same

    place. gemerkt lists what was kept. Programmatic access has no person to file

    an entry under, so nothing is kept then. The briefing says so in advance via

    adressen_werden_gemerkt.

    Request Body:

    {
      "identity_key": "extrem:location:Nord",
      "gruppe": "extrem",
      "empfaenger_text": "Standort Nord and role Dispatch",
      "stellen": [
        {"dimension": "location", "wert": "Nord", "name": "Standort Nord",
         "email": "[email protected]"},
        {"dimension": "role", "wert": "Dispatch", "name": "Rolle Dispatch",
         "zurueckgestellt": true}
      ],
      "payload": {
        "fakt": "Cases from this location wait 3.4x longer",
        "frage": "What happens to these cases before they reach you?",
        "hebel": 3.4
      }
    }

    Response (201):

    {
      "eintrag": {
        "id": "uuid",
        "identity_key": "extrem:location:Nord",
        "gruppe": "extrem",
        "empfaenger_text": "Standort Nord and role Dispatch",
        "empfaenger_email": null,
        "stellen": [
          {"dimension": "location", "wert": "Nord", "name": "Standort Nord",
           "email": "[email protected]", "zurueckgestellt": false},
          {"dimension": "role", "wert": "Dispatch", "name": "Rolle Dispatch",
           "email": null, "zurueckgestellt": true}
        ],
        "status": "delegiert",
        "angestossen_am": "2026-09-02T09:00:00",
        "payload": {"fakt": "...", "frage": "...", "run_id": "uuid"}
      },
      "mail_versendet": true,
      "mail_grund": null,
      "versendet_an": ["Standort Nord"],
      "gemerkt": ["Standort Nord"],
      "nur_vermerkt": false
    }

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID

    #### Look Up The Address Of Each Place

    POST /api/v1/runs/{run_id}/bausteine-briefing/adressen

    Returns, in the order asked, the address already on file for each place a

    hand-over names: the address, who it is filed under, and when that entry was

    last touched. A place with nothing on file comes back with email set to

    null. That is an answer, not a gap, so a client can show a visibly empty field

    instead of no field at all.

    Places are identified by trait and value together. A value like Anwendung can

    be a role and a topic at the same time, and those are two different places with

    two different addresses.

    Whether an address entered now will still be there next time is answered once

    for the whole page, by adressen_werden_gemerkt on the briefing itself, so it

    can be said before anyone starts typing.

    Request Body:

    {
      "stellen": [
        {"dimension": "location", "wert": "Nord"},
        {"dimension": "role", "wert": "Dispatch"}
      ]
    }

    Response (200):

    {
      "stellen": [
        {"dimension": "location", "wert": "Nord", "email": "[email protected]",
         "name": "Standort Nord", "zuletzt": "2026-09-02T09:00:00"},
        {"dimension": "role", "wert": "Dispatch", "email": null,
         "name": null, "zuletzt": null}
      ]
    }

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID

    Time In Transit

    #### Get Time In Transit

    GET /api/v1/runs/{run_id}/unterwegs

    Returns how much of the waiting time was spent at stations that did not solve

    the case: in hours, and as a share of the total waiting time. The share is what

    makes two data spaces comparable; the hours are what makes the share concrete.

    Counted are only completed cases. The filter matters: it changes the number

    substantially, and without it the result measures how the data was cut rather

    than how work flowed. Cases that passed through a single station have a value of

    zero by construction. That is a statement, not a gap.

    stufe says how much room the number deserves: fuehrt when transit time is the

    larger story, voll when it is a full one, satz when it is small enough that a

    single sentence is the honest form. The thresholds behind it are deliberate

    estimates, adjustable, and live in one place.

    Drivers name where the time sat, not why. The number of sections is reported

    next to the hours because the two together separate a busy way-station from a

    parked case: the same share of hours means something different across eight

    thousand short sections than across fifty long ones.

    ueberschneidung counts how many of these cases also appear among the longest

    waiting times overall. The two views are connected through the same cases, never

    by adding their numbers together.

    A run computed before this measure existed answers 200 with

    vorhanden: false and a reason. That is a third state, not an empty result.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID

    #### Get Time In Transit Over Runs

    GET /api/v1/runs/{run_id}/unterwegs/verlauf

    Returns the same measure across the runs of this data space, oldest first, so a

    reader can tell a snapshot from a trend. Each entry carries the run, its date,

    the share, the hours, and how much of it the top tenth carried.

    Only runs that computed the measure appear. Earlier runs are absent rather than

    zero. A missing value and a value of zero are different claims.

    Parameters:

    NameTypeDescription
    run_idpathAnalysis run ID (identifies the data space)

    Billing & Credits

    #### Get AI Credits Status

    GET /api/v1/billing/ai-credits

    Returns remaining AI analysis credits and current-period usage for the current tenant.

    Response:

    {
      "tenant_id": "uuid",
      "tier": "pro",
      "credits_total": 100,
      "credits_included": 80,
      "credits_purchased": 20,
      "warning_level": "ok",
      "runs_remaining": 77,
      "runs_used": 23,
      "runs_limit": 100,
      "enrichment_cost_eur_today": 0.42,
      "enrichment_tokens_today": 12500,
      "enrichment_count_today": 3,
      "period_start": "2026-06-01T00:00:00Z",
      "period_end": "2026-07-01T00:00:00Z"
    }

    Connectors

    Process Radar supports direct ITSM integrations for automated data import.

    #### List Available Connector Types

    GET /api/v1/connector-types

    Returns available connector types (e.g., Matrix42, Jira Service Management).


    #### Create Connector

    POST /api/v1/connectors

    Creates a new ITSM connector for automated data synchronization.


    #### Test Connection

    POST /api/v1/connectors/{connector_id}/test

    Tests connectivity to the ITSM system without importing data.


    #### Start Sync

    POST /api/v1/connectors/{connector_id}/sync

    Triggers a data synchronization from the connected ITSM system. Only a full

    synchronization is supported ("sync_type": "full"); every sync re-imports the

    complete ticket history to guarantee no data is lost.


    Authentication

    #### Login

    POST /api/v1/auth/login

    Request Body:

    {
      "email": "[email protected]",
      "password": "your-password"
    }

    Response:

    {
      "access_token": "eyJhbGci...",
      "refresh_token": "eyJhbGci...",
      "token_type": "bearer",
      "expires_in": 300,
      "user": {
        "user_id": "uuid",
        "email": "[email protected]",
        "role": "owner"
      }
    }

    #### Refresh Token

    POST /api/v1/auth/refresh

    Refreshes an expired access token using the refresh token.


    #### Current User

    GET /api/v1/auth/me

    Returns the current authenticated user and tenant information.


    AI Integration

    Process Radar is built for programmatic and AI-agent consumption. All endpoints return structured JSON with consistent schemas.

    Recommended Workflows for AI Agents

    Morning Briefing:

  • GET /api/v1/executive-summary: Top situations, health score, trends
  • Present to user with recommended actions
  • Continuous Monitoring:

  • GET /api/v1/flow-health: Check for new bottlenecks
  • GET /api/v1/analysis-summary: Track anomaly trends
  • Alert user when critical findings appear
  • Deep Dive:

  • GET /api/v1/runs/{id}/findings?tier=critical: Critical anomalies
  • GET /api/v1/situations/{id}: Situation context
  • GET /api/v1/situations/{id}/narrative: AI-generated explanation
  • Delegation Automation:

  • GET /api/v1/runs/{id}/situations-queue: Triage queue
  • POST /api/v1/situations/{id}/delegate: Assign responsibility
  • GET /api/v1/delegations: Track follow-ups
  • What Process Radar Detects

    Process Radar detects various types of anomalies including:

  • Dwell Time Anomalies: Teams or categories where tickets take significantly longer than expected
  • Routing Loops: Tickets bouncing between teams without resolution
  • Capacity Bottlenecks: Teams where incoming work exceeds processing capacity
  • Cascade Effects: Problems in one team causing delays in downstream teams
  • Seasonal Patterns: Time-dependent fluctuations in ticket processing
  • Wait Time Dominance: Processes where most time is spent waiting, not working
  • Each finding includes a human-readable label and a severity classification (critical, notable, observed).

    Response Conventions

  • Severity tiers: critical, notable, observed
  • Trends: improving, stable, worsening, new
  • Confidence levels: high, medium, low
  • Bilingual content: statements.de / statements.en
  • Timestamps: ISO 8601 format
  • Cache: Summary endpoints return Cache-Control: private, max-age=300

  • Error Handling

    All errors follow RFC 7807 Problem Details format:

    {
      "type": "https://dkradar/errors/NOT_FOUND",
      "title": "Not Found",
      "status": 404,
      "detail": "Run not found",
      "instance": "/api/v1/runs/unknown-id",
      "code": "NOT_FOUND",
      "trace_id": "e29f788f-ffbc-4686-be22-4a8e6d422997"
    }

    Validation errors (422) additionally carry a field-level errors list:

    {
      "type": "https://dkradar/errors/VALIDATION_ERROR",
      "title": "Validation Error",
      "status": 422,
      "detail": "Validation error",
      "code": "VALIDATION_ERROR",
      "errors": [
        { "loc": ["query", "run_id"], "msg": "Field required", "type": "missing" }
      ]
    }

    Common Status Codes:

    StatusMeaning
    200Success
    201Created
    202Accepted (async operation started)
    204Deleted (no content)
    400Bad request (validation error)
    401Authentication required
    403Insufficient permissions or feature not available
    404Resource not found
    409Conflict (e.g., run still in progress)
    422Validation error (detailed field-level errors)
    429Rate limit exceeded
    500Internal server error

    Rate Limits

    Standard: 200 requests/minute per client. Summary endpoints are cached for 5 minutes.

    Higher limits available on Enterprise plans.


    Integration Examples

    Python

    import requests
    
    API_KEY = "your-api-key"
    BASE = "https://api.dkyra.com/api/v1"
    headers = {"X-API-Key": API_KEY}
    
    # Get executive summary
    summary = requests.get(f"{BASE}/executive-summary", headers=headers).json()
    print(f"Health: {summary['health_score']['grade']}")
    for sit in summary["priority_situations"]:
        print(f"  [{sit['tier']}] {sit['headline']} ({sit['impact_hours']}h)")

    JavaScript

    const API_KEY = "your-api-key";
    const BASE = "https://api.dkyra.com/api/v1";
    
    const res = await fetch(`${BASE}/executive-summary`, {
      headers: { "X-API-Key": API_KEY }
    });
    const summary = await res.json();
    console.log(`Health: ${summary.health_score.grade}`);

    cURL

    curl -H "X-API-Key: YOUR_KEY" https://api.dkyra.com/api/v1/executive-summary

    Supported ITSM Systems

    SystemIntegrationStatus
    Matrix42Native connectorAvailable
    Jira Service ManagementNative connectorAvailable
    ServiceNowPlannedComing soon
    TOPdeskPlannedComing soon
    Any ITSMCSV importAvailable

    Support

  • Documentation: https://dkyra.com/docs
  • API Support: [email protected]
  • Website: https://datakyra.com