Private/Invoke-SIARequest.ps1

function Invoke-SIARequest {
    <#
    Single entry point every authenticated public cmdlet uses to call a SIA
    API. Centralizes auth headers, JSON handling, retry-on-transient-failure
    for idempotent requests, and error translation so individual cmdlets stay
    thin.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')]
        [string]$Method,

        [Parameter(Mandatory)]
        [string]$Path,

        [ValidateSet('Dpa', 'Uap', 'Uar', 'UserPortal')]
        [string]$Service = 'Dpa',

        [hashtable]$QueryParameter,

        $Body,

        [switch]$NoRetry
    )

    $session = Test-SIASession -PassThru
    $uri = Resolve-SIAUri -Path $Path -Service $Service -Subdomain $session.Subdomain -QueryParameter $QueryParameter
    $operation = (Get-PSCallStack)[1].Command
    $plainToken = [System.Net.NetworkCredential]::new('', $session.AccessToken).Password

    $invokeParams = @{
        Method      = $Method
        Uri         = $uri
        Headers     = @{
            Authorization = "Bearer $plainToken"
            Accept        = 'application/json'
            'User-Agent'  = "psSIA/$($script:ModuleVersion)"
        }
        TimeoutSec  = $script:SIAModuleConfig.RequestTimeout
        ErrorAction = 'Stop'
    }

    if ($null -ne $Body) {
        $invokeParams.Body = ConvertTo-SIARequestBody -InputObject $Body
        $invokeParams.ContentType = 'application/json'
    }

    Write-Verbose (Protect-SIALogValue "$Method $uri")
    if ($invokeParams.Body) {
        Write-Debug (Protect-SIALogValue "Request body: $($invokeParams.Body)")
    }

    $maxAttempts = if ($NoRetry -or $Method -ne 'GET') { 1 } else { [math]::Max(1, $script:SIAModuleConfig.RetryCount) }
    $attempt = 0

    while ($true) {
        $attempt++
        try {
            return Invoke-RestMethod @invokeParams
        } catch {
            $response = $_.Exception.Response
            $statusCode = if ($response) { [int]$response.StatusCode } else { $null }
            $isTransient = $statusCode -in 429, 500, 502, 503, 504

            if ($isTransient -and $attempt -lt $maxAttempts) {
                $delaySeconds = $script:SIAModuleConfig.RetryDelay * $attempt
                if ($response.Headers -and $response.Headers.RetryAfter -and $response.Headers.RetryAfter.Delta) {
                    $delaySeconds = $response.Headers.RetryAfter.Delta.TotalSeconds
                }
                Write-Verbose "Transient failure (HTTP $statusCode) on attempt $attempt. Retrying in $delaySeconds seconds."
                Start-Sleep -Seconds $delaySeconds
                continue
            }

            throw (Resolve-SIAError -ErrorRecord $_ -Operation $operation -Uri $uri)
        }
    }
}