Skip to content

GetWorkflowStatistics API

Returns comprehensive statistics for a specific workflow definition, including pending, completed, and overdue flow counts, with optional date range filtering.

Endpoint

/srv.asmx/GetWorkflowStatistics

Methods

  • GET /srv.asmx/GetWorkflowStatistics?authenticationTicket=...&domainName=...&workflowName=...&startDate=...&endDate=...
  • POST /srv.asmx/GetWorkflowStatistics (form data)
  • SOAP Action: http://tempuri.org/GetWorkflowStatistics

Parameters

Parameter Type Required Description
authenticationTicket string Yes Authentication ticket obtained from AuthenticateUser
domainName string Yes Name of the domain/library containing the workflow
workflowName string Yes Name of the workflow definition
startDate DateTime No Start date for range filtering (null for all-time only)
endDate DateTime No End date for range filtering (null for all-time only)

Response Behavior

All-Time Statistics Only (No Date Range)

When startDate and endDate are not provided or null:

GET /srv.asmx/GetWorkflowStatistics?authenticationTicket=xxx&domainName=Engineering&workflowName=DocumentReview

Response includes: - Domain and workflow information - Planned length of the workflow (hours and text) - Total pending count (all-time) - Total completed count (all-time) - Running workflows past their own due date - Running workflows with an overdue task

With Date Range Statistics

When startDate and endDate are provided:

GET /srv.asmx/GetWorkflowStatistics?authenticationTicket=xxx&domainName=Engineering&workflowName=DocumentReview&startDate=2024-01-01&endDate=2024-01-31

Response includes all-time statistics PLUS date range specific data: - Submitted count in range - Completed count in range - Pending count in range

Response Structure

All-Time Statistics (No Dates)

<response success="true">
  <Value>
    <DomainName>Engineering</DomainName>
    <WorkflowName>DocumentReview</WorkflowName>
    <ActiveFolderPath>/Engineering/Reviews/Active</ActiveFolderPath>
    <AverageTimeSpanInHours>27</AverageTimeSpanInHours>
    <AverageTimeSpanInText>1 day 3 hours</AverageTimeSpanInText>
    <TotalPending>15</TotalPending>
    <TotalCompleted>892</TotalCompleted>
    <TotalOverdue>2</TotalOverdue>
    <TotalWithOverdueTasks>6</TotalWithOverdueTasks>
  </Value>
</response>

With Date Range Statistics

<response success="true">
  <Value>
    <DomainName>Engineering</DomainName>
    <WorkflowName>DocumentReview</WorkflowName>
    <ActiveFolderPath>/Engineering/Reviews/Active</ActiveFolderPath>

    <!-- Date range specific (only when dates provided) -->
    <PendingInRange>3</PendingInRange>
    <SubmittedInRange>45</SubmittedInRange>
    <CompletedInRange>42</CompletedInRange>

    <!-- All-time statistics -->
    <AverageTimeSpanInHours>27</AverageTimeSpanInHours>
    <AverageTimeSpanInText>1 day 3 hours</AverageTimeSpanInText>
    <TotalPending>15</TotalPending>
    <TotalCompleted>892</TotalCompleted>
    <TotalOverdue>2</TotalOverdue>
    <TotalWithOverdueTasks>6</TotalWithOverdueTasks>
  </Value>
</response>

Error Response

<response success="false" error="Error message" errorCode="4000" />

WorkflowStatistics Properties

Property Type Description When Included
DomainName string Name of the domain Always
WorkflowName string Name of the workflow Always
ActiveFolderPath string Path to workflow's active folder Always
AverageTimeSpanInHours integer The planned length of the workflow in hours: each step's longest task deadline, added up. It is read from the definition, not measured from runs, so it has a value when nothing has completed. Despite the name it is not an average. Always
AverageTimeSpanInText string The same planned length as text, for example "8 Days" Always
TotalPending integer Workflows of this definition that are running now (not finished) Always
TotalCompleted integer Workflows of this definition that have finished, all-time Always
TotalOverdue integer Running workflows past the workflow's own due date. Task due dates are not looked at. See How overdue is counted. Always
TotalWithOverdueTasks integer Running workflows that have at least one task past its due date. Added in 9.0. See How overdue is counted. Always
PendingInRange integer (nullable) Pending in date range Only with dates
SubmittedInRange integer (nullable) Submitted in date range Only with dates
CompletedInRange integer (nullable) Completed in date range Only with dates

How Overdue Is Counted

The response counts late workflows in two ways, and they answer different questions.

Count A running workflow is counted when Use it for
TotalOverdue the workflow's own due date has passed "Which workflows should have finished by now?"
TotalWithOverdueTasks it has at least one task past its due date "Which workflows are stuck on a late task?"
  • The workflow's due date is set when a document is submitted: the submit time plus the planned length (AverageTimeSpanInHours). It does not move when a task's due date is changed.
  • An overdue task is one that has started, is not finished, is in progress, and whose due date has passed. This is the same rule the server's task lists use when they are filtered to overdue tasks.
  • A workflow is counted once in TotalWithOverdueTasks however many of its tasks are late.

The two can differ a lot. With a planned length of 8 days and a first step due after 2, a workflow whose first task is three days late is in TotalWithOverdueTasks and not yet in TotalOverdue:

<TotalPending>9</TotalPending>
<TotalOverdue>0</TotalOverdue>
<TotalWithOverdueTasks>9</TotalWithOverdueTasks>

A running workflow that has no due date stored is never counted in TotalOverdue. Until 9.0 TotalOverdue was the only count, and it was described as the number of workflows with overdue tasks, which it is not.

Required Permissions

  • User must be authenticated (anonymous users cannot access workflow statistics)
  • User must have access to the specified domain
  • No specific workflow permissions required to view statistics

Use Cases

  1. Single Workflow Monitor
  2. Track specific workflow performance
  3. Monitor real-time status
  4. Alert on overdue instances

  5. Monthly Performance Report

  6. Analyze workflow performance over time
  7. Compare month-over-month metrics
  8. Track submission and completion trends

  9. Dashboard Integration

  10. Real-time workflow status displays
  11. KPI monitoring
  12. Performance metrics visualization

  13. Capacity Planning

  14. Analyze workflow load
  15. Identify bottlenecks
  16. Plan resource allocation

Example Requests

Request (GET)

GET /srv.asmx/GetWorkflowStatistics?authenticationTicket=abc123-def456&domainName=Engineering&workflowName=DocumentReview HTTP/1.1
Host: server.example.com

Request (POST)

POST /srv.asmx/GetWorkflowStatistics HTTP/1.1
Content-Type: application/x-www-form-urlencoded

authenticationTicket=abc123-def456&domainName=Engineering&workflowName=DocumentReview

Request (SOAP 1.1)

POST /srv.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/GetWorkflowStatistics"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetWorkflowStatistics xmlns="http://tempuri.org/">
      <authenticationTicket>abc123-def456</authenticationTicket>
      <domainName>Engineering</domainName>
      <workflowName>DocumentReview</workflowName>
      <startDate></startDate>
      <endDate></endDate>
    </GetWorkflowStatistics>
  </soap:Body>
</soap:Envelope>

Success Response Example

<?xml version="1.0" encoding="utf-8"?>
<response success="true">
  <Value>
    <DomainName>Quality</DomainName>
    <WorkflowName>QualityApproval</WorkflowName>
    <ActiveFolderPath>/Quality/Pending Approvals</ActiveFolderPath>
    <AverageTimeSpanInHours>27</AverageTimeSpanInHours>
    <AverageTimeSpanInText>1 day 3 hours</AverageTimeSpanInText>
    <TotalPending>45</TotalPending>
    <TotalCompleted>892</TotalCompleted>
    <TotalOverdue>7</TotalOverdue>
    <TotalWithOverdueTasks>11</TotalWithOverdueTasks>
  </Value>
</response>

Integration Examples

JavaScript/Client-Side

async function getWorkflowStats(domainName, workflowName, startDate = null, endDate = null) {
    const ticket = getUserAuthTicket();

    let url = `/srv.asmx/GetWorkflowStatistics?` +
              `authenticationTicket=${encodeURIComponent(ticket)}&` +
              `domainName=${encodeURIComponent(domainName)}&` +
              `workflowName=${encodeURIComponent(workflowName)}`;

    if (startDate && endDate) {
        url += `&startDate=${startDate.toISOString().split('T')[0]}`;
        url += `&endDate=${endDate.toISOString().split('T')[0]}`;
    }

    const response = await fetch(url);
    const xmlText = await response.text();
    const parser = new DOMParser();
    const xmlDoc = parser.parseFromString(xmlText, "text/xml");

    const root = xmlDoc.documentElement;
    if (root.getAttribute("success") === "true") {
        const stats = xmlDoc.querySelector("Value");
        return {
            domainName: stats.querySelector("DomainName").textContent,
            workflowName: stats.querySelector("WorkflowName").textContent,
            activeFolderPath: stats.querySelector("ActiveFolderPath").textContent,
            avgTimeHours: parseInt(stats.querySelector("AverageTimeSpanInHours").textContent),
            avgTimeText: stats.querySelector("AverageTimeSpanInText").textContent,
            totalPending: parseInt(stats.querySelector("TotalPending").textContent),
            totalCompleted: parseInt(stats.querySelector("TotalCompleted").textContent),
            totalOverdue: parseInt(stats.querySelector("TotalOverdue").textContent),
            // Date range specific (if provided)
            pendingInRange: stats.querySelector("PendingInRange")?.textContent 
                ? parseInt(stats.querySelector("PendingInRange").textContent) : null,
            submittedInRange: stats.querySelector("SubmittedInRange")?.textContent 
                ? parseInt(stats.querySelector("SubmittedInRange").textContent) : null,
            completedInRange: stats.querySelector("CompletedInRange")?.textContent 
                ? parseInt(stats.querySelector("CompletedInRange").textContent) : null
        };
    } else {
        const error = root.getAttribute("error");
        throw new Error(error);
    }
}

// Usage example
async function displayWorkflowMonitor() {
    try {
        const stats = await getWorkflowStats("Engineering", "DocumentReview");

        console.log(`Workflow: ${stats.workflowName}`);
        console.log(`Average Time: ${stats.avgTimeText} (${stats.avgTimeHours} hours)`);
        console.log(`Pending: ${stats.totalPending}`);
        console.log(`Completed: ${stats.totalCompleted}`);
        console.log(`Overdue: ${stats.totalOverdue}`);

        // Calculate efficiency
        const totalFlows = stats.totalCompleted + stats.totalPending;
        const completionRate = (stats.totalCompleted / totalFlows * 100).toFixed(1);
        console.log(`Completion Rate: ${completionRate}%`);

        // Alert on overdue
        if (stats.totalOverdue > 0) {
            alert(`Warning: ${stats.totalOverdue} overdue workflow instances!`);
        }
    } catch (error) {
        console.error("Failed to get workflow statistics:", error);
    }
}

C# Client Usage

using (var client = new SrvSoapClient())
{
    try
    {
        var response = await client.GetWorkflowStatisticsAsync(
            authTicket,
            "Engineering",
            "DocumentReview",
            null,  // startDate
            null   // endDate
        );

        var root = response.Root;
        if (root.Attribute("success")?.Value == "true")
        {
            var stats = root.Element("Value");

            var workflowStats = new
            {
                DomainName = stats.Element("DomainName")?.Value,
                WorkflowName = stats.Element("WorkflowName")?.Value,
                ActiveFolderPath = stats.Element("ActiveFolderPath")?.Value,
                AvgTimeHours = int.Parse(stats.Element("AverageTimeSpanInHours")?.Value ?? "0"),
                AvgTimeText = stats.Element("AverageTimeSpanInText")?.Value,
                TotalPending = int.Parse(stats.Element("TotalPending")?.Value ?? "0"),
                TotalCompleted = int.Parse(stats.Element("TotalCompleted")?.Value ?? "0"),
                TotalOverdue = int.Parse(stats.Element("TotalOverdue")?.Value ?? "0")
            };

            Console.WriteLine($"Workflow: {workflowStats.WorkflowName} in {workflowStats.DomainName}");
            Console.WriteLine($"Active Folder: {workflowStats.ActiveFolderPath}");
            Console.WriteLine($"Average Time: {workflowStats.AvgTimeText} ({workflowStats.AvgTimeHours} hours)");
            Console.WriteLine($"Pending: {workflowStats.TotalPending}");
            Console.WriteLine($"Completed: {workflowStats.TotalCompleted}");
            Console.WriteLine($"Overdue: {workflowStats.TotalOverdue}");

            // Calculate metrics
            int totalFlows = workflowStats.TotalCompleted + workflowStats.TotalPending;
            double completionRate = totalFlows > 0 
                ? (double)workflowStats.TotalCompleted / totalFlows * 100 
                : 0;

            Console.WriteLine($"Completion Rate: {completionRate:F1}%");

            if (workflowStats.TotalOverdue > 0)
            {
                Console.WriteLine($"ALERT: {workflowStats.TotalOverdue} overdue workflow instances");
            }
        }
        else
        {
            var error = root.Attribute("error")?.Value;
            Console.WriteLine($"Error: {error}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception: {ex.Message}");
    }
}

Dashboard Integration

This API is ideal for building workflow monitoring dashboards:

<!-- Single Workflow Statistics Dashboard -->
<div class="workflow-stats">
    <h2 id="workflow-title"></h2>
    <div class="stats-grid">
        <div class="stat-card">
            <h3>Pending</h3>
            <p id="pending-count" class="stat-value"></p>
        </div>
        <div class="stat-card">
            <h3>Completed</h3>
            <p id="completed-count" class="stat-value"></p>
        </div>
        <div class="stat-card warning">
            <h3>Overdue</h3>
            <p id="overdue-count" class="stat-value"></p>
        </div>
        <div class="stat-card">
            <h3>Avg. Time</h3>
            <p id="avg-time-text" class="stat-value"></p>
            <small id="avg-time-hours"></small>
        </div>
    </div>
    <div class="workflow-details">
        <p><strong>Active Folder:</strong> <span id="active-folder"></span></p>
    </div>
</div>

<script>
async function updateDashboard(domainName, workflowName) {
    const stats = await getWorkflowStats(domainName, workflowName);

    document.getElementById("workflow-title").textContent = `${stats.workflowName} (${stats.domainName})`;
    document.getElementById("active-folder").textContent = stats.activeFolderPath;
    document.getElementById("pending-count").textContent = stats.totalPending;
    document.getElementById("completed-count").textContent = stats.totalCompleted;
    document.getElementById("overdue-count").textContent = stats.totalOverdue;
    document.getElementById("avg-time-text").textContent = stats.avgTimeText;
    document.getElementById("avg-time-hours").textContent = `(${stats.avgTimeHours} hours)`;
}

// Refresh every 30 seconds
setInterval(() => updateDashboard("Engineering", "DocumentReview"), 30000);
updateDashboard("Engineering", "DocumentReview");
</script>

JavaScript

Every call answers XML with HTTP 200, success or not, so success is the thing to branch on and errorCode is the number to report.

async function call(action, params) {
  const response = await fetch(`/srv.asmx/${action}?${new URLSearchParams(params)}`);
  const root = new DOMParser()
    .parseFromString(await response.text(), 'text/xml')
    .documentElement;

  if (root.getAttribute('success') !== 'true') {
    throw new Error(`${root.getAttribute('errorCode')}: ${root.getAttribute('error')}`);
  }
  return root;
}

Counts what a definition has done. Without a date range the three ...InRange counters come back as xsi:nil; with one they carry a number.

const root = await call('GetWorkflowStatistics', {
  authenticationTicket: ticket,
  domainName: 'Public',
  workflowName: 'Invoice approval',
  startDate: '2026-01-01',
  endDate: '2026-12-31'
});

const value = root.querySelector('Value');
console.log(value.querySelector('TotalPending').textContent,
            value.querySelector('TotalCompleted').textContent,
            value.querySelector('TotalOverdue').textContent,
            value.querySelector('SubmittedInRange').textContent,
            value.querySelector('AverageTimeSpanInHours').textContent);

Notes

  • TotalCompleted: All workflow instances that have reached completion
  • TotalPending: Workflow instances currently in progress (all-time)
  • TotalOverdue: Workflow instances with overdue tasks
  • AverageTimeSpanInText: Human-readable format (e.g., "2 days 5 hours")
  • AverageTimeSpanInHours: Numeric value for programmatic comparisons
  • PendingInRange, SubmittedInRange, CompletedInRange: Only present when date range provided
  • Real-time Data: Statistics are calculated in real-time from current database state
  • Performance: Optimized for single workflow - faster than bulk reporting

Statistics Calculations

All-Time Metrics

  • TotalCompleted: Counts all workflow instances that reached completion
  • TotalPending: Counts workflow instances at any step (not completed)
  • TotalOverdue: Based on task due dates compared to current date

Date Range Metrics (Optional)

  • SubmittedInRange: Workflows submitted during the specified period
  • CompletedInRange: Workflows completed during the specified period
  • PendingInRange: Workflows still pending within the date range

Average TimeSpan

  • Calculated from workflow start to completion
  • Based on completed workflow instances only
  • AverageTimeSpanInText: Human-readable string
  • AverageTimeSpanInHours: Numeric value for calculations
  • May return 0 or empty string if no completed instances exist

Best Practices

  1. Caching: Cache results for 1-5 minutes for dashboard displays
  2. Monitor Overdue: Set up alerts when overdue count exceeds threshold
  3. Date Range Queries: Use for monthly/quarterly reporting
  4. Polling Frequency: For real-time dashboards, poll every 30-60 seconds
  5. Error Handling: Always handle workflow not found and access denied errors
  6. Performance: Single workflow queries are fast and efficient

Performance Considerations

  • Optimized: Single workflow statistics are calculated quickly
  • Real-Time: No caching - always returns current state
  • Efficient: Significantly faster than bulk reporting
  • Date Range Impact: Date range queries may take slightly longer
  • Recommended: For bulk reporting, call API multiple times for different workflows

Benefits

? Fast Performance: Optimized for single workflow queries
? Real-Time Data: Always returns current workflow state
? Flexible: Supports all-time or date range statistics
? Efficient: Minimal server load for targeted queries
? Simple: Returns single object, easy to parse and use
? Comprehensive: All key workflow metrics in one call

  • GetFlowDef - Get detailed workflow definition
  • GetAllWorkflowStatistics - The statistics of every workflow of a domain, or of all domains, in one call (no call per workflow)
  • GetDomainFlows - List all workflows in a domain
  • getTasks - Get workflow tasks with filtering
  • GetTask - Get specific task details
  • CompleteTask - Complete a workflow task
  • ReassignTask - Reassign task to different user

Version History

  • Compatible with infoRouter 8.7 and later
  • Statistics model is serializable for client-side deserialization
  • Supports both synchronous SOAP and REST access patterns
  • Workflow management features may require Workflow module license

See Also

  • GetFlowDef - Get workflow definition details
  • GetDomainFlows - List all workflows in domain
  • getTasks - Get workflow tasks
  • Control Panel UI: WorkflowReport.aspx - Workflow statistics report

Error Codes

The errorCode values this operation returns, checked against a running server:

errorCode When
4010 the ticket is expired or unknown
4000 no definition by that name in the library
4030 the caller may not see that library
HTTP 400 a required string parameter was empty; refused by model binding, so there is no error document