Private/ApiClient.ps1

function Invoke-OpApi {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')]
        [string]$Method,

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

        [object]$Body,

        [switch]$NoAuth,

        [int]$MaxRetries = 3
    )

    $cfg   = Get-OpConfig
    $token = Get-OpToken
    $uri   = "$($cfg.api_base)$Path"

    if (-not $NoAuth) {
        if (-not $token) {
            throw 'Not authenticated. Run Connect-OnePAM first.'
        }
        if (Test-OpTokenNearExpiry -Token $token) {
            if (-not (Invoke-OpTokenRefresh)) {
                throw 'Token expired and refresh failed. Run Connect-OnePAM to re-authenticate.'
            }
            $token = Get-OpToken
        }
    }

    $splat = @{
        Uri         = $uri
        Method      = $Method
        Headers     = @{
            'User-Agent' = "onepam-powershell/$script:ModuleVersion"
        }
        ContentType = 'application/json'
        ErrorAction = 'Stop'
    }
    if (-not $NoAuth -and $token) {
        $splat.Headers['Authorization'] = "Bearer $($token.access_token)"
    }

    if ($Body) {
        $jsonBody = if ($Body -is [string]) { $Body } else { $Body | ConvertTo-Json -Depth 10 -Compress }
        $splat['Body'] = $jsonBody
    }

    $lastError = $null
    for ($attempt = 0; $attempt -lt $MaxRetries; $attempt++) {

        try {
            $resp = Invoke-RestMethod @splat
            return $resp
        }
        catch {
            $statusCode = $null
            if ($_.Exception.Response) {
                $statusCode = [int]$_.Exception.Response.StatusCode
            }

            if ($statusCode -eq 429) {
                $wait = [math]::Pow(2, $attempt + 1)
                Start-Sleep -Seconds $wait
                $lastError = $_
                continue
            }

            if ($statusCode -eq 401 -and $attempt -eq 0 -and -not $NoAuth) {
                if (Invoke-OpTokenRefresh) {
                    $token = Get-OpToken
                    $splat.Headers['Authorization'] = "Bearer $($token.access_token)"
                    $lastError = $_
                    continue
                }
            }

            if ($statusCode -eq 403 -and -not $NoAuth) {
                try {
                    $profileUri = "$($cfg.api_base)/api/v1/account/profile"
                    Invoke-RestMethod -Uri $profileUri -Method Get -Headers $splat.Headers -ErrorAction Stop | Out-Null
                }
                catch {
                    $profileStatus = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { 0 }
                    if ($profileStatus -eq 401 -or $profileStatus -eq 403) {
                        Remove-OpToken
                        throw 'Session revoked: your account has been deactivated or removed. You have been logged out.'
                    }
                }
            }

            throw
        }
    }

    if ($lastError) { throw $lastError }
}

function Invoke-OpApiRaw {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('GET', 'POST', 'PUT', 'PATCH', 'DELETE')]
        [string]$Method,

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

        [object]$Body,

        [switch]$NoAuth
    )

    $cfg   = Get-OpConfig
    $token = Get-OpToken
    $uri   = "$($cfg.api_base)$Path"

    if (-not $NoAuth) {
        if (-not $token) {
            throw 'Not authenticated. Run Connect-OnePAM first.'
        }
        if (Test-OpTokenNearExpiry -Token $token) {
            if (-not (Invoke-OpTokenRefresh)) {
                throw 'Token expired and refresh failed. Run Connect-OnePAM to re-authenticate.'
            }
            $token = Get-OpToken
        }
    }

    $headers = @{
        'User-Agent' = "onepam-powershell/$script:ModuleVersion"
    }
    if (-not $NoAuth -and $token) {
        $headers['Authorization'] = "Bearer $($token.access_token)"
    }

    $splat = @{
        Uri         = $uri
        Method      = $Method
        Headers     = $headers
        ContentType = 'application/json'
        ErrorAction = 'Stop'
    }
    if ($Body) {
        $splat['Body'] = ($Body | ConvertTo-Json -Depth 10 -Compress)
    }

    Invoke-WebRequest @splat
}