DEV Community

Manohari Jayachandran
Manohari Jayachandran

Posted on

Postman for API Work: Collections, Environment Variables, Secrets, Auth, and Tests

Postman is the tool most people use constantly and never actually learn properly, pasting URLs into a blank request, hardcoding a token directly into a header, clicking Send and eyeballing the response. This post covers it the way it's actually meant to be used, with the Product API from the earlier CRUD and Swagger posts as the working example throughout.

Collections and Requests

A Collection is a named folder of related requests, keeping every endpoint for one API grouped together, instead of a scattered pile of unsaved tabs that disappear when Postman restarts.

Think of a filing cabinet drawer labeled "Products API," with individual folders inside for each request type, versus loose papers scattered across a desk. Nothing is lost, nothing has to be rebuilt from memory the next time you need it.

Products API (Collection)
  |- GET All Products
  |- GET Product By Id
  |- POST Create Product
  |- PUT Update Product
  |- DELETE Product
Enter fullscreen mode Exit fullscreen mode

Each request saves its method, URL, headers, body, and any tests written for it, permanently, reusable any time, by anyone the collection is shared with.

Environment Variables

An Environment is a named set of variables, like baseUrl or apiKey, that requests reference using double curly brace syntax, instead of hardcoding literal values. Switching the active environment changes what every request in the collection actually points to, without editing a single request by hand.

Think of a universal remote's device selector. The same physical buttons, play, pause, volume, work identically regardless of which device is currently selected, the remote just sends the command to whatever's active. An environment switch works the same way for your requests.

Request URL, written once:
  {{baseUrl}}/api/products

Dev environment:
  baseUrl = https://localhost:5001

Staging environment:
  baseUrl = https://staging-products-api.azurewebsites.net

Production environment:
  baseUrl = https://products-api.azurewebsites.net
Enter fullscreen mode Exit fullscreen mode

Switching the dropdown at the top-right of Postman from Dev to Staging instantly redirects every request in the collection, GetAll, GetById, Create, Update, Delete, without touching any of them individually.

Where Secrets Actually Belong

Never paste a real API key, token, or password directly into a request URL, header value, or body. Store it as a variable in an Environment instead, specifically marked as the secret type, not just a regular default variable.

Environment variables panel:

Variable          Type      Initial Value      Current Value
baseUrl           default   {{prod url}}       {{prod url}}
apiKey            secret    dots                dots
Enter fullscreen mode Exit fullscreen mode

Secret-type variables are masked in the UI, shown as dots, excluded from Postman's own sync and export in plain text where possible, and meaningfully reduce the risk of a real key ending up visible in a screen recording, a shared workspace, or an accidentally committed export file.

Why this matters in practice: a request URL with a literal API key baked in gets saved into the collection permanently, anyone with access to that collection, a teammate, an accidental public share, now has that key too. A secret-type environment variable referenced by the apiKey variable keeps the actual value out of the request definition entirely.

Auth Headers, Handled Properly, Not Hand-Built

Postman's Authorization tab builds the correct header automatically for several common schemes, rather than requiring you to manually type the Authorization Bearer header into the Headers tab yourself.

Bearer Token: paste the token, ideally as a variable, and Postman adds the Authorization Bearer header automatically.
API Key: choose header or query param, provide the key name and value, Postman places it correctly either way.
Basic Auth: username and password, Postman handles the Base64 encoding automatically, you never touch it directly.
OAuth 2.0: Postman can run the actual token-fetching flow, Authorization Code, Client Credentials, and so on, directly inside the tool, then automatically attach the resulting token to the request.

Setting it once at the collection level: rather than configuring auth on every single request individually, set it once on the Collection itself, every request inside inherits it automatically, unless a specific request deliberately overrides it.

Building a Real Request

POST {{baseUrl}}/api/products

Headers:
  Content-Type: application/json

Body (raw, JSON):
{
  "name": "Wireless Mouse",
  "price": 24.99,
  "stockQuantity": 150
}
Enter fullscreen mode Exit fullscreen mode

This maps directly to the CreateProductDto from the CRUD post, Postman sends this JSON body, ASP.NET Core model-binds it into that exact DTO, and the Required and Range validation attributes from that post apply before the action method body even runs.

Status Codes Worth Actually Checking

Checking only "did this return 200" misses most of what actually matters about an API's behavior.

  • 200 OK is the default happy-path check, necessary but not sufficient on its own.

  • 404 Not Found tests whether GetById correctly returns this for a genuinely missing product, matching the fix from the CRUD post, NotFound() instead of Ok(null).

  • 400 Bad Request tests whether Create correctly rejects an empty Name or a negative Price, confirming the validation attributes are actually being enforced.

  • 401 versus 403 are genuinely different meanings worth testing separately: 401 means you aren't authenticated at all, 403 means you are authenticated, but you don't have permission for this specific action.

  • 201 Created tests whether Create returns this specifically, not 200, with a Location header pointing to the new resource, matching the fix from the CRUD post.

  • 204 No Content tests whether Delete returns this specifically for a successful deletion with nothing further to return.

The Tests Tab: Real Assertions, Not Eyeballing

The Tests tab lets you write JavaScript assertions that run automatically every time the request is sent, checking the actual response against expected conditions, rather than a person visually scanning the JSON and hoping nothing looks wrong.

// Written in the Tests tab of the GetById request
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

pm.test("Response has expected fields", function () {
    const product = pm.response.json();
    pm.expect(product).to.have.property("id");
    pm.expect(product).to.have.property("name");
    pm.expect(product).to.have.property("price");
});

pm.test("Price is a positive number", function () {
    const product = pm.response.json();
    pm.expect(product.price).to.be.above(0);
});

// On the Create request specifically - testing the
// exact fix from the CRUD post
pm.test("Create returns 201, not 200", function () {
    pm.response.to.have.status(201);
});

pm.test("Response includes a Location header", function () {
    pm.response.to.have.header("Location");
});
Enter fullscreen mode Exit fullscreen mode

Pre-request scripts set up state before the request fires. Postman also supports a Pre-request Script tab, running JavaScript before the request is sent, commonly used to generate a timestamp, compute a signature, or fetch a fresh token programmatically.

// Pre-request Script - saving a value from one response
// for use in a LATER request in the same collection
pm.test("Save the created product's id", function () {
    const response = pm.response.json();
    pm.collectionVariables.set("createdProductId", response.id);
});

// The NEXT request (GetById) can now reference
// {{createdProductId}} directly, chaining requests
// together using real data from a previous step
Enter fullscreen mode Exit fullscreen mode

Running a Whole Collection at Once

The Collection Runner executes every request in a collection sequentially, running each request's tests automatically and producing a pass/fail summary, genuinely useful for confirming an entire API still behaves correctly after a change, not just one endpoint in isolation.

Collection Runner output (conceptual):

Products API - Run Summary
  GET All Products         PASS (2 tests)
  GET Product By Id        PASS (3 tests)
  POST Create Product      PASS (2 tests)
  PUT Update Product       PASS (1 test)
  DELETE Product           PASS (1 test)

  Total: 5 requests, 9 tests, 9 passed, 0 failed
Enter fullscreen mode Exit fullscreen mode

This same collection can also run outside Postman's UI entirely, using Newman, Postman's command-line runner, making it possible to run this exact test suite inside a CI/CD pipeline, the same GitHub Actions workflow covered in an earlier post on this blog.

Closing the Loop: Importing From the Swagger Post

The earlier Swagger and OpenAPI post ended with exactly this workflow: with the app running, the OpenAPI spec is available at /swagger/v1/swagger.json. In Postman, choose Import and paste that URL, or upload the file, and Postman generates a complete collection automatically, every endpoint from ProductsController, correct verbs, correct routes, example bodies shaped from the DTOs. Everything covered in this post, environments, auth, tests, gets added on top of that auto-generated starting point, rather than building the whole collection by hand from nothing.

Problem Scenario and Solving Strategy

The problem: a team manually tests the Products API by clicking through Postman requests one at a time before every deployment, eyeballing each response. A regression, Create returning 200 instead of 201, went unnoticed for two weeks because nobody happened to check that specific detail during manual testing.

THE STRATEGY, STEP BY STEP: 

  1. Recognize this is a verification gap, not a testing effort gap - the team WAS testing, but manually, checking for "does it generally work" rather than specific, precise conditions
  2. Import the API directly from its Swagger/OpenAPI spec (covered above) to get a complete, accurate starting collection instead of one built and maintained by hand 
  3. Add specific Tests tab assertions to every request - not just "status is 2xx" but the EXACT expected code (201 for Create, 204 for Delete, 404 for a missing GetById) - this is precisely the kind of narrow, easy-to-overlook detail manual eyeballing missed 
  4. Use the Collection Runner to execute the entire suite in one pass before each deployment, producing a clear pass/fail summary instead of relying on someone remembering to manually check every endpoint 
  5. Install Newman and add a step to the existing GitHub Actions CI/CD pipeline (covered in an earlier post) that runs this exact collection automatically on every push - turning "someone should really test this manually" into a check that happens whether anyone remembers to or not 
  6. The specific regression from the problem (201 vs 200) is now caught immediately, automatically, the next time it happens - rather than silently shipping for two weeks

Key Lessons

Environment variables let one collection work against dev, staging, and production without editing a single request, the variable changes, the requests don't.

Secrets belong in secret-type environment variables, never hardcoded into a request directly, the difference matters the moment a collection gets shared or exported.

The Authorization tab builds the correct header for Bearer, API Key, Basic Auth, and OAuth 2.0 automatically, and setting it once at the collection level avoids repeating it on every request.

Checking only for a 200 misses most of what an API actually promises, 404, 400, 401 versus 403, 201, and 204 each represent a genuinely different, separately-testable behavior.

The Tests tab turns "does this look right" into an actual, repeatable assertion, the same regression that manual eyeballing misses is exactly what a specific status-code test catches immediately.

A Postman collection isn't just a manual tool, via Newman, the exact same tests can run inside a CI/CD pipeline, automatically, on every push.

Summary

Postman's real value shows up once it moves past being a place to paste a URL and click Send. Collections keep requests organized and shareable, environment variables make the same collection work across every stage without editing anything, secret-type variables keep real credentials out of a shared file, the Authorization tab handles auth schemes correctly without manual header-building, and the Tests tab turns a visual check into a repeatable, automatable assertion. Combined with importing directly from a Swagger and OpenAPI spec and running the whole suite through Newman in CI/CD, Postman becomes a genuine verification tool, not just a way to manually poke at an API before hoping for the best.


Originally published on my blog: TechStack Blog

More from TechStack Blog: C# / .NET: https://www.techstackblog.com/category.html?cat=csharp
CS Fundamentals: https://www.techstackblog.com/category.html?cat=cs-fundamentals

Top comments (0)