Get Campaign Status Reports

Dear all,

Can someone help me with the following. It’s been a while since I have done this, but basically a couple years ago it was possible to export Campaign Status Reports using an API, post-refresh…etc etc but long story short, I am unable to do it now. I have been using v2025 as much as possible, but hit a road block and exporting the Campaign Status reports to csv seems to be the last step I am struggling with.

Can some advise accordingly and let me know where I am going wrong, cause I am sure its something quite silly.

# ================================
# CONFIGURATION
# ================================
$AppProps = ConvertFrom-StringData (Get-Content "$PSScriptRoot/config/appdata.properties" -Raw)

$TenantWeb    = $AppProps.'tenantweb'
$TenantAPI    = $AppProps.'tenantapi'
$ClientID     = $AppProps.'clientId'
$ClientSecret = $AppProps.'clientSecret'

# ================================
# FUNCTIONS
# ================================
function Get-APIData {
    param(
        [string]$Url,
        [hashtable]$Headers
    )
    try {
        return Invoke-RestMethod -Uri $Url -Headers $Headers -Method Get
    } catch {
        Write-Warning "Failed to get data from $Url : $_"
        return $null
    }
}

# ================================
# AUTHENTICATION
# ================================
Write-Host "Authenticating to SailPoint ISC..."
$AuthBody = @{
    grant_type    = "client_credentials"
    client_id     = $ClientID
    client_secret = $ClientSecret
}
$TokenResponse = Invoke-RestMethod -Uri "https://$TenantAPI/oauth/token" -Method POST -Body $AuthBody -ContentType "application/x-www-form-urlencoded"

$Headers = @{
    "Authorization" = "Bearer $($TokenResponse.access_token)"
    "Content-Type"  = "application/json"
}

$AuthTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$AuthStatus = if ($TokenResponse.access_token) { "Authentication succeeded" } else { "Authentication failed" }
Write-Host "$AuthStatus at $AuthTimestamp"

# ================================
# STEP 1: LIST ACTIVE CAMPAIGNS
# ================================
$Limit = 250
$Offset = 0
$AllCampaigns = @()
$MoreData = $true

while ($MoreData) {
    $CampaignsURL = "https://$TenantAPI/v2025/campaigns?detail=FULL&limit=$Limit&offset=$Offset&count=true"
    $Response = Get-APIData -Url $CampaignsURL -Headers $Headers

    if ($Response) {
        $ActiveCampaigns = $Response | Where-Object { $_.status -eq "ACTIVE" } | ForEach-Object {
            [PSCustomObject]@{
                id              = $_.id
                name            = $_.name
                ReportTriggered = $false
                ReportId        = $null
                Processed       = $false
            }
        }

        $AllCampaigns += $ActiveCampaigns
        if ($Response.Count -lt $Limit) { $MoreData = $false } else { $Offset += $Limit }
    } else {
        $MoreData = $false
    }
}

Write-Host "Total active campaigns retrieved: $($AllCampaigns.Count)"

# ================================
# STEP 2: TRIGGER REPORTS
# ================================
$ReportType = "CAMPAIGN_STATUS_REPORT"
$PollIntervalSeconds = 15

foreach ($campaign in $AllCampaigns) {
    $CampaignID   = $campaign.id
    $CampaignName = $campaign.name -replace '[\\\/:*?"<>|]', ''  # sanitize filename
    $RunReportURL = "https://$TenantAPI/v2025/campaigns/$CampaignID/run-report/$ReportType"

    try {
        $RunResponse = Invoke-RestMethod -Uri $RunReportURL -Headers $Headers -Method Post
        $campaign.ReportTriggered = $true
        $campaign.ReportId = $RunResponse.id
        Write-Host "Triggered report for $CampaignName, Run ID: $($campaign.id)"
    } catch {
        Write-Warning "Failed to trigger report for $CampaignName"
        $campaign.ReportTriggered = $false
    }
}

# ================================
# STEP 3: POLL REPORTS AND EXPORT TO CSV USING REPORT ID
# ================================
$Remaining = $AllCampaigns | Where-Object { $_.ReportTriggered -eq $true } | Measure-Object | Select-Object -ExpandProperty Count

while ($Remaining -gt 0) {
    foreach ($campaign in $AllCampaigns) {
        # Skip if already processed or report hasn't been triggered
        if ($campaign.Processed -eq $true -or $campaign.ReportTriggered -ne $true) { continue }

        $CampaignName = $campaign.name -replace '[\\\/:*?"<>|]', ''
        $ReportId     = $campaign.id  # <-- Use the run-report ID saved during Step 2

        if (-not $ReportId) { 
            Write-Warning "No report ID available yet for $CampaignName. Skipping for now."
            continue 
        }

        $StatusUrl = "https://$TenantAPI/v2025/report-results/$ReportId"
        try {
            $StatusResponse = Invoke-RestMethod -Uri $StatusUrl -Headers $Headers -Method GET
            Write-Host "DEBUG: Report $ReportId for $CampaignName status = $($StatusResponse.status) at $(Get-Date)"
        } catch {
            Write-Warning "Error checking status for $CampaignName - $_"
            continue
        }

        if ($StatusResponse.status -eq "SUCCESS") {
            $CsvURL    = "https://$TenantAPI/cc/api/report/get/$ReportId?format=csv"
            $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
            $CsvFile   = "$PSScriptRoot\$CampaignName`_$Timestamp.csv"

            # Optional short delay to ensure the report is fully available
            Start-Sleep -Seconds 5

            try {
                $CsvResponse = Invoke-RestMethod -Uri $CsvURL -Headers @{ "Authorization" = $Headers.Authorization } -Method GET -ContentType "text/csv"
                Set-Content -Path $CsvFile -Value $CsvResponse -Encoding UTF8
                Write-Host "Exported report for $CampaignName to $CsvFile"
                $campaign.Processed = $true
            } catch {
                Write-Warning "Failed to export report for $CampaignName $($_.Exception.Message)"
            }
        }
    }

    Start-Sleep -Seconds $PollIntervalSeconds
    $Remaining = $AllCampaigns | Where-Object { $_.ReportTriggered -eq $true -and $_.Processed -ne $true } | Measure-Object | Select-Object -ExpandProperty Count
}

Write-Host "All active campaigns processed."

it looks to me like you’re using a deprecated API endpoint to get the csv report

You should probably try the API endpoint(s) that replaced it.

I have a sample powershell script here using the v3 version

This seems to be the 2025 version

Cool cool, let me give it a go! Thanks

Sounds good. It’s the URL you’re populating $CsvURL with just FYI

@aricoux any luck with the change?

No unfortunately not, I did try and for whatever reason it was not working for me. Will attempt again this week. Just have not had the chance to get around to it. Let me see if I can retest today.

Hey Mark, could you breakdown the steps the script should be doing. Which APIs in Steps I should be running

  1. Refresh reports based on Active status
  2. Filter to Campaign Status Report
  3. Download latest Campaign Status report to csv once it read

You can use the CURL commands to import them into your postman and then convert back to powershell / whatever your preferred language is. Finally fill the gaps to have your new script.

Step 1: Get all active campaigns with below API call.

curl --location 'https://tenant.api.identitynow-demo.com/v2024/campaigns?filters=status%20eq%20%22ACTIVE%22' \
--header 'Accept: application/json' \

Step 2:

Run / refresh the report that you need with below API call.

curl --location --request POST 'https://tenant.api.identitynow-demo.com/v2024/campaigns/<<CAMPAIGN ID>>/run-report/CAMPAIGN_STATUS_REPORT' \
--header 'Accept: application/json' \

Step 3:

Get the reports currently available for the certification. Poll until you see latest timestamp. API call is provided below. Response would contain report IDs for all reports.

curl --location 'https://tenant.api.identitynow-demo.com/v2024/campaigns/<<CAMPAING ID>>/reports' \
--header 'Accept: application/json' \

Step 4:

Get the report with “Get reports” API.

curl --location 'https://tenant.api.identitynow-demo.com/v2024/reports/<<REPORT_ID>>' \
--header 'Accept: application/csv' \

Okay great thank you! Sorry been away the last two weeks but will attempt these steps today :slight_smile: And Let you know how I get on!

Thank you! Those steps really helped and worked a charm. I have now managed to create a script to do all 4 steps on repeat which is exactly what I wanted! Many many thanks!