Testing LLM endpoints by hand saves hours before you wire them into app code. This tutorial builds a postman collection openai api starter that covers auth, chat completions, and model listing with environment variables and test scripts. You’ll end with a runnable collection you can export, share, or drive from CI.
Prerequisites
- Postman v10+ (or Newman for headless runs)
- A valid OpenAI API key (any OpenAI-compatible key works)
- Bash terminal for the CLI section
jqoptional, for piping curl output
If you only have a gateway key, the requests below still apply as long as the endpoint speaks the OpenAI shape.
Environment setup
Never hardcode secrets in a collection JSON. Create a Postman environment named openai-env with two values:
{
"name": "openai-env",
"values": [
{ "key": "base_url", "value": "https://api.openai.com", "type": "default" },
{ "key": "api_key", "value": "sk-your-key", "type": "secret" }
]
}
Reference them later as {{base_url}} and {{api_key}}. The secret type prevents the value from rendering in shared exports.
Build the postman collection openai api skeleton
Start with a minimal v2.1.0 collection. The structure below defines two folders and one request each. You can paste this directly into a .json file and import it.
{
"info": {
"name": "OpenAI API Starter",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Models",
"item": [
{
"name": "List models",
"request": {
"method": "GET",
"header": [ { "key": "Authorization", "value": "Bearer {{api_key}}" } ],
"url": { "raw": "{{base_url}}/v1/models", "host": ["{{base_url}}"], "path": ["v1","models"] }
}
}
]
},
{
"name": "Chat",
"item": [
{
"name": "Create completion",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{api_key}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"model\": \"gpt-4o-mini\",\n \"messages\": [ { \"role\": \"user\", \"content\": \"Say hello in JSON.\" } ],\n \"temperature\": 0.2\n}"
},
"url": { "raw": "{{base_url}}/v1/chat/completions", "host": ["{{base_url}}"], "path": ["v1","chat","completions"] }
}
}
]
}
]
}
That is the backbone. Now we tighten each request with tests and variables.
List models request
The GET /v1/models call verifies connectivity and key scope. In Postman’s Tests tab for that request, add:
pm.test("status 200", () => pm.response.to.have.status(200));
pm.test("returns list", () => {
const body = pm.response.json();
pm.expect(body.object).to.equal("list");
pm.expect(body.data).to.be.an("array");
});
Equivalent curl for a quick local check:
curl {{base_url}}/v1/models \
-H "Authorization: Bearer $API_KEY" | jq '.object'
Expected output from the API:
{
"object": "list",
"data": [
{ "id": "gpt-4o", "object": "model", "owned_by": "openai" },
{ "id": "gpt-4o-mini", "object": "model", "owned_by": "openai" }
]
}
Chat completion request
The POST /v1/chat/completions request is where most integration bugs hide. Use a fixed low temperature for reproducible tests. In the Tests tab:
pm.test("status 200", () => pm.response.to.have.status(200));
pm.test("has assistant message", () => {
const body = pm.response.json();
pm.expect(body.choices).to.be.an("array").with.length.of.at.least(1);
pm.expect(body.choices[0].message.content).to.be.a("string");
pm.expect(body.usage.total_tokens).to.be.a("number");
});
Expected response shape:
{
"id": "chatcmpl-123",
"object": "chat.completion",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "{\"greeting\":\"hello\"}" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15 }
}
Parameterize model and prompt
Hardcoding gpt-4o-mini in the body forces you to edit raw JSON to test another model. Add collection variables model and prompt, then rewrite the body as:
{
"model": "{{model}}",
"messages": [ { "role": "user", "content": "{{prompt}}" } ],
"temperature": 0.2
}
Set defaults in the collection’s Variables tab:
{ "model": "gpt-4o-mini", "prompt": "Say hello in JSON." }
Now a runner can override them per iteration without touching the request.
Full collection with tests
Here is the completed importable collection. It includes the test scripts inline.
{
"info": {
"name": "OpenAI API Starter",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{ "key": "model", "value": "gpt-4o-mini" },
{ "key": "prompt", "value": "Say hello in JSON." }
],
"item": [
{
"name": "Models",
"item": [
{
"name": "List models",
"request": {
"method": "GET",
"header": [ { "key": "Authorization", "value": "Bearer {{api_key}}" } ],
"url": { "raw": "{{base_url}}/v1/models", "host": ["{{base_url}}"], "path": ["v1","models"] }
},
"event": [
{ "listen": "test", "script": { "exec": [
"pm.test('status 200', () => pm.response.to.have.status(200));",
"const b = pm.response.json();",
"pm.expect(b.object).to.equal('list');"
] } }
]
}
]
},
{
"name": "Chat",
"item": [
{
"name": "Create completion",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{api_key}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"model\": \"{{model}}\",\n \"messages\": [ { \"role\": \"user\", \"content\": \"{{prompt}}\" } ],\n \"temperature\": 0.2\n}"
},
"url": { "raw": "{{base_url}}/v1/chat/completions", "host": ["{{base_url}}"], "path": ["v1","chat","completions"] }
},
"event": [
{ "listen": "test", "script": { "exec": [
"pm.test('status 200', () => pm.response.to.have.status(200));",
"const b = pm.response.json();",
"pm.expect(b.choices[0].message.content).to.be.a('string');",
"pm.expect(b.usage.total_tokens).to.be.a('number');"
] } }
]
}
]
}
]
}
Run from CLI with Newman
Postman GUI is fine for exploration, but CI needs headless execution. Export the collection and environment, then:
npm install -g newman
newman run openai-starter.json -e openai-env.json --reporters cli
Newman prints per-request pass/fail. If a model is deprecated, the chat test fails fast instead of silently returning garbage in your app.
If you point the same base_url at an OpenAI-compatible gateway such as n4n.ai, the postman collection openai api requests exercise 240+ models behind one endpoint without changing auth shape or test logic. That’s useful when you want fallback coverage during provider incidents.
Error-path testing
A starter collection is incomplete without the negative case. Duplicate the chat request, name it Create completion bad key, and override api_key in a local environment with sk-invalid. Expect:
{
"error": { "message": "Incorrect API key provided", "type": "invalid_request_error", "code": "invalid_api_key" }
}
Add a test that asserts status 401:
pm.test("401 on bad key", () => pm.response.to.have.status(401));
This catches env misconfiguration before a deploy.
Python sanity check
Sometimes you need to confirm Postman isn’t masking a transport issue. A minimal Python equivalent:
import os, requests
r = requests.get(
"https://api.openai.com/v1/models",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"}
)
print(r.status_code, r.json()["object"])
If this prints 200 list but Postman fails, the problem is environment or proxy, not the API.
Checkpoint summary
| Request | Expected status | Key assertion |
|---|---|---|
| List models | 200 | object == "list" |
| Chat completion | 200 | choices[0].message.content is string |
| Chat bad key | 401 | error type invalid_request_error |
Keep the collection in version control. Treat the postman collection openai api file as executable documentation—when the contract drifts, the tests tell you first.