The same call from PowerShell
Windows already has everything the first API call needs: PowerShell and its Invoke-RestMethod command. Send the OpenAI call from the previous page from PowerShell, keep the key in an environment variable, save the answer to a file, and watch for the four things that go wrong on Windows. The other three vendors differ only in the address, the key header and the body fields in that page's table.
Put the key in the environment
Open PowerShell (Windows PowerShell 5.1 or PowerShell 7 both work). This prompts for the key without showing it and keeps it for this window only:
$secure = Read-Host "Paste your OpenAI key" -AsSecureString
$env:OPENAI_API_KEY = [System.Net.NetworkCredential]::new("", $secure).Password
Close the window and the key is gone. OpenAI's quickstart also shows setx OPENAI_API_KEY "...", which saves the key for every future window (OpenAI quickstart). That is convenient, but it types the key on a command line (so it lands in PowerShell's history file) and leaves it stored in your Windows profile. While you are learning, the prompt above is the safer habit. Microsoft documents how the three scopes (this window, your user, the whole machine) work in about Environment Variables.
One call
Build the body as a PowerShell object and let PowerShell write the JSON. That sidesteps every quoting problem at once.
$body = @{
model = "gpt-6-astra"
max_output_tokens = 300
input = "Rewrite as one plain sentence: the meeting is moved to Thursday."
} | ConvertTo-Json -Depth 6
$reply = Invoke-RestMethod -Uri "https://api.openai.com/v1/responses" -Method Post `
-Headers @{ Authorization = "Bearer $env:OPENAI_API_KEY" } `
-ContentType "application/json; charset=utf-8" `
-Body ([System.Text.Encoding]::UTF8.GetBytes($body))
($reply.output | Where-Object type -eq "message").content.text
$reply.usage
Invoke-RestMethod turns the JSON reply into an object, so you read fields with dots instead of parsing text (Invoke-RestMethod). The last two lines print the answer and the token counts. For Anthropic, change the address, send x-api-key and anthropic-version headers, put the text in messages, and read $reply.content.text.
Save the output
Save both the answer and the whole reply. The answer is what you use; the full reply is what you check when something looks wrong.
New-Item -ItemType Directory -Path .\out -Force | Out-Null
($reply.output | Where-Object type -eq "message").content.text |
Set-Content -Path .\out\answer.txt -Encoding utf8
$reply | ConvertTo-Json -Depth 20 | Set-Content -Path .\out\reply.json -Encoding utf8
-Encoding utf8 matters in Windows PowerShell 5.1, which otherwise writes some files in an older encoding that mangles curly quotes and accented names.
Four things that trip Windows users
| Symptom | Cause | Fix |
|---|---|---|
| "running scripts is disabled on this system" | The execution policy blocks .ps1 files |
Paste the commands into the window instead, or ask IT. Get-ExecutionPolicy -List shows which scope sets it; a policy set by your organization wins over anything you change (about Execution Policies) |
| "Could not create SSL/TLS secure channel" | An older Windows PowerShell 5.1 setup offering an outdated TLS version | Run [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 in the window first, or use PowerShell 7 |
| A curl command from a web page fails with a parameter error | In Windows PowerShell 5.1, curl is a nickname for Invoke-WebRequest, which takes different arguments |
Type curl.exe to reach the real curl that ships with Windows (curl on Windows) |
| 400 "invalid JSON" from a body you typed by hand | PowerShell expands $ and treats quotes differently inside double quotes (about Quoting Rules) |
Build the body as @{ } and ConvertTo-Json -Depth 6 as above. Without -Depth, a list of messages is flattened to text and the vendor rejects it |
When a call fails, Invoke-RestMethod throws, and the vendor's error message is in the exception, not on the screen. This shows it:
try { Invoke-RestMethod ... } catch { $_.ErrorDetails.Message }
Put your call where the three dots are. A 401 means the key variable is empty or wrong in this window; $env:OPENAI_API_KEY.Length prints its length without showing it.
What to do with this
Send the call above, open out\reply.json, and find the usage block. Then take the same pattern to the recipes, which run the same way from Python on Windows.
Vendor facts checked ; re-verify before relying on them.