Trabalhando com APIs REST diretamente do PowerShell

1. Fundamentos do Invoke-RestMethod e Invoke-WebRequest

O PowerShell oferece dois cmdlets principais para interagir com APIs REST: Invoke-RestMethod e Invoke-WebRequest. Ambos realizam requisições HTTP, mas diferem no tratamento da resposta.

Invoke-RestMethod automaticamente desserializa respostas JSON ou XML em objetos PowerShell. Invoke-WebRequest retorna um objeto BasicHtmlWebResponseObject com propriedades como Content, StatusCode e Headers.

# Invoke-RestMethod - retorna objetos diretamente
$resultado = Invoke-RestMethod -Uri "https://api.github.com/users/octocat"
$resultado.login

# Invoke-WebRequest - retorna resposta bruta
$resposta = Invoke-WebRequest -Uri "https://api.github.com/users/octocat"
$resposta.Content
$resposta.StatusCode

Parâmetros essenciais comuns a ambos:
- -Uri: URL do endpoint
- -Method: GET, POST, PUT, PATCH, DELETE
- -Headers: hashtable com cabeçalhos HTTP
- -Body: conteúdo da requisição (para POST/PUT)

2. Autenticação em APIs REST

Autenticação via Bearer Token

$headers = @{
    "Authorization" = "Bearer seu_token_aqui"
    "Content-Type" = "application/json"
}

$resposta = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/dados" `
    -Headers $headers

Autenticação Básica (Basic Auth)

$usuario = "admin"
$senha = "minha_senha"
$credenciais = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${usuario}:${senha}"))

$headers = @{
    "Authorization" = "Basic $credenciais"
}

Invoke-RestMethod -Uri "https://api.exemplo.com/v1/login" -Headers $headers

Gerenciamento de Tokens Expirados

$token = $null
$tokenExpiracao = Get-Date

function Get-Token {
    if ($token -eq $null -or (Get-Date) -ge $tokenExpiracao) {
        $body = @{
            grant_type = "client_credentials"
            client_id = "seu_id"
            client_secret = "seu_secret"
        }

        $resposta = Invoke-RestMethod -Uri "https://auth.exemplo.com/token" `
            -Method Post -Body $body

        $token = $resposta.access_token
        $tokenExpiracao = (Get-Date).AddSeconds($resposta.expires_in - 60)
    }
    return $token
}

3. Métodos HTTP e Operações CRUD

GET - Consultando dados

# Listar usuários
$usuarios = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/usuarios"
$usuarios | Select-Object id, nome, email

POST - Criando recursos

$novoUsuario = @{
    nome = "João Silva"
    email = "joao@exemplo.com"
    cargo = "Desenvolvedor"
} | ConvertTo-Json

$criado = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/usuarios" `
    -Method Post `
    -Body $novoUsuario `
    -ContentType "application/json"

PUT/PATCH - Atualizando recursos

$atualizacao = @{
    cargo = "Desenvolvedor Sênior"
    salario = 15000
} | ConvertTo-Json

Invoke-RestMethod -Uri "https://api.exemplo.com/v1/usuarios/123" `
    -Method Patch `
    -Body $atualizacao `
    -ContentType "application/json"

DELETE - Removendo recursos

Invoke-RestMethod -Uri "https://api.exemplo.com/v1/usuarios/123" `
    -Method Delete

4. Manipulação de Parâmetros e Query Strings

Construção dinâmica de URIs

function Get-Usuarios {
    param(
        [string]$Nome,
        [int]$Limite = 10,
        [int]$Pagina = 1
    )

    $parametros = @()
    if ($Nome) { $parametros += "nome=$([System.Uri]::EscapeDataString($Nome))" }
    if ($Limite) { $parametros += "limite=$Limite" }
    if ($Pagina) { $parametros += "pagina=$Pagina" }

    $queryString = $parametros -join "&"
    $uri = "https://api.exemplo.com/v1/usuarios?$queryString"

    Invoke-RestMethod -Uri $uri
}

Uso de hashtable para parâmetros

$parametros = @{
    "filtro" = "ativo"
    "ordem" = "nome_asc"
    "campos" = "id,nome,email"
}

$uri = "https://api.exemplo.com/v1/usuarios"
$queryParams = $parametros.GetEnumerator() | ForEach-Object {
    "$($_.Key)=$([System.Uri]::EscapeDataString($_.Value))"
}
$uriCompleta = "$uri?$($queryParams -join '&')"

Invoke-RestMethod -Uri $uriCompleta

Tratamento de paginação

function Get-TodosUsuarios {
    $todos = @()
    $pagina = 1
    $limitePorPagina = 100

    do {
        $uri = "https://api.exemplo.com/v1/usuarios?pagina=$pagina&limite=$limitePorPagina"
        $resultado = Invoke-RestMethod -Uri $uri

        $todos += $resultado.dados
        $pagina++
    } while ($resultado.dados.Count -eq $limitePorPagina)

    return $todos
}

5. Tratamento de Erros e Respostas HTTP

Verificação de status codes

try {
    $resposta = Invoke-WebRequest -Uri "https://api.exemplo.com/v1/usuarios/9999" `
        -ErrorAction Stop

    switch ($resposta.StatusCode) {
        200 { Write-Host "Sucesso" -ForegroundColor Green }
        201 { Write-Host "Recurso criado" -ForegroundColor Green }
        204 { Write-Host "Sem conteúdo" -ForegroundColor Yellow }
    }
}
catch {
    $statusCode = $_.Exception.Response.StatusCode.value__

    switch ($statusCode) {
        400 { Write-Warning "Requisição inválida: $($_.Exception.Message)" }
        401 { Write-Error "Não autorizado - verifique suas credenciais" }
        403 { Write-Error "Acesso proibido" }
        404 { Write-Warning "Recurso não encontrado" }
        429 { Write-Warning "Limite de requisições excedido" }
        500 { Write-Error "Erro interno do servidor" }
        default { Write-Error "Erro HTTP $statusCode: $($_.Exception.Message)" }
    }
}

Retry automático com política de espera

function Invoke-ComRetry {
    param(
        [string]$Uri,
        [int]$MaxTentativas = 3,
        [int]$EsperaBase = 2
    )

    $tentativa = 0
    do {
        $tentativa++
        try {
            return Invoke-RestMethod -Uri $Uri -ErrorAction Stop
        }
        catch {
            if ($tentativa -ge $MaxTentativas) {
                throw
            }

            $statusCode = $_.Exception.Response.StatusCode.value__
            if ($statusCode -eq 429 -or $statusCode -ge 500) {
                $espera = $EsperaBase * $tentativa
                Write-Warning "Tentativa $tentativa falhou. Aguardando ${espera}s..."
                Start-Sleep -Seconds $espera
            }
            else {
                throw
            }
        }
    } while ($tentativa -lt $MaxTentativas)
}

6. Serialização e Desserialização de Dados

Conversão de objetos para JSON

$dados = @(
    @{
        id = 1
        nome = "Produto A"
        preco = 99.90
        categorias = @("Eletrônicos", "Promoção")
    },
    @{
        id = 2
        nome = "Produto B"
        preco = 149.90
        categorias = @("Casa", "Cozinha")
    }
)

# ConvertTo-Json com profundidade personalizada
$json = $dados | ConvertTo-Json -Depth 5
Write-Host $json

# Compressão para reduzir tamanho
$jsonCompacto = $dados | ConvertTo-Json -Depth 5 -Compress

Trabalhando com estruturas aninhadas

# Acessar propriedades aninhadas
$resposta = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/pedidos/100"
$resposta.cliente.nome
$resposta.itens | ForEach-Object {
    "$($_.produto.nome) - Quantidade: $($_.quantidade) - Preço: $($_.preco)"
}

# Filtrar dados complexos
$resposta.itens | Where-Object { $_.quantidade -gt 2 } | 
    Select-Object @{Name="Produto";Expression={$_.produto.nome}}, quantidade, preco

7. Automação de Fluxos com APIs

Encadeamento de chamadas sequenciais

function New-PedidoCompleto {
    param(
        [int]$ClienteId,
        [array]$Itens
    )

    # 1. Validar cliente
    $cliente = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/clientes/$ClienteId"
    if (-not $cliente.ativo) {
        throw "Cliente inativo"
    }

    # 2. Verificar estoque
    foreach ($item in $Itens) {
        $estoque = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/produtos/$($item.produtoId)/estoque"
        if ($estoque.disponivel -lt $item.quantidade) {
            throw "Estoque insuficiente para produto $($item.produtoId)"
        }
    }

    # 3. Criar pedido
    $pedidoBody = @{
        clienteId = $ClienteId
        itens = $Itens
        data = (Get-Date -Format "yyyy-MM-dd")
    } | ConvertTo-Json -Depth 3

    $pedido = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/pedidos" `
        -Method Post -Body $pedidoBody -ContentType "application/json"

    # 4. Processar pagamento
    $pagamentoBody = @{
        pedidoId = $pedido.id
        valor = $pedido.total
        metodo = "cartao_credito"
    } | ConvertTo-Json

    $pagamento = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/pagamentos" `
        -Method Post -Body $pagamentoBody -ContentType "application/json"

    return @{
        Pedido = $pedido
        Pagamento = $pagamento
    }
}

Processamento em lote com loops

$idsProdutos = 1..100
$resultados = @()

foreach ($id in $idsProdutos) {
    try {
        $produto = Invoke-RestMethod -Uri "https://api.exemplo.com/v1/produtos/$id"
        $resultados += $produto
        Write-Progress -Activity "Processando produtos" `
            -Status "Produto $id" `
            -PercentComplete (($id / $idsProdutos.Count) * 100)
    }
    catch {
        Write-Warning "Erro ao processar produto $id"
    }
}

Criação de funções reutilizáveis

function Invoke-MinhaAPI {
    param(
        [Parameter(Mandatory)]
        [string]$Endpoint,

        [ValidateSet("GET","POST","PUT","PATCH","DELETE")]
        [string]$Method = "GET",

        [object]$Body,

        [hashtable]$QueryParams
    )

    $headers = @{
        "Authorization" = "Bearer $(Get-MeuToken)"
        "Content-Type" = "application/json"
        "Accept" = "application/json"
    }

    $uri = "https://api.exemplo.com/v1$Endpoint"

    if ($QueryParams) {
        $queryParts = $QueryParams.GetEnumerator() | ForEach-Object {
            "$($_.Key)=$([System.Uri]::EscapeDataString($_.Value))"
        }
        $uri += "?$($queryParts -join '&')"
    }

    $params = @{
        Uri = $uri
        Method = $Method
        Headers = $headers
    }

    if ($Body) {
        $params.Body = $Body | ConvertTo-Json -Depth 5 -Compress
    }

    return Invoke-RestMethod @params
}

# Exemplo de uso
$usuarios = Invoke-MinhaAPI -Endpoint "/usuarios" -QueryParams @{limite=50;ativo=true}
$novoUsuario = Invoke-MinhaAPI -Endpoint "/usuarios" -Method POST -Body @{nome="Maria";email="maria@exemplo.com"}

Referências