Postman Collections can be organized by team and domain, but the real power comes from treating them as living documentation and executable specifications, not just static lists of requests.
Let’s see this in action. Imagine a microservice architecture where user-service and order-service interact.
// user-service/postman/collection.json
{
"info": {
"_postman_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"name": "User Service - Core API",
"description": "Defines the contract for user management operations.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "User Management",
"item": [
{
"name": "Create User",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"username\": \"{{username}}\",\n \"email\": \"{{email}}\"\n}"
},
"url": {
"raw": "{{base_url}}/users",
"host": [
"{{base_url}}"
],
"path": [
"users"
]
},
"description": "Creates a new user account."
},
"response": []
},
{
"name": "Get User by ID",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/users/{{user_id}}",
"host": [
"{{base_url}}"
],
"path": [
"users",
"{{user_id}}"
]
},
"description": "Retrieves details for a specific user."
},
"response": []
}
]
}
],
"variable": [
{
"key": "base_url",
"value": "http://localhost:8080",
"type": "string"
},
{
"key": "username",
"value": "testuser",
"type": "string"
},
{
"key": "email",
"value": "test@example.com",
"type": "string"
},
{
"key": "user_id",
"value": "123e4567-e89b-12d3-a456-426614174000",
"type": "string"
}
]
}
This user-service collection defines its core API. A separate collection, order-service/postman/collection.json, would define its API for managing orders.
// order-service/postman/collection.json
{
"info": {
"_postman_id": "f0e9d8c7-b6a5-4321-0987-fedcba012345",
"name": "Order Service - Core API",
"description": "Defines the contract for order processing operations.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Order Management",
"item": [
{
"name": "Create Order",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"userId\": \"{{userId}}\",\n \"items\": [\n {\n \"productId\": \"{{productId}}\",\n \"quantity\": {{quantity}}\n }\n ]\n}"
},
"url": {
"raw": "{{base_url}}/orders",
"host": [
"{{base_url}}"
],
"path": [
"orders"
]
},
"description": "Creates a new order."
},
"response": []
},
{
"name": "Get Order by ID",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/orders/{{order_id}}",
"host": [
"{{base_url}}"
],
"path": [
"orders",
"{{order_id}}"
]
},
"description": "Retrieves details for a specific order."
},
"response": []
}
]
}
],
"variable": [
{
"key": "base_url",
"value": "http://localhost:8081",
"type": "string"
},
{
"key": "userId",
"value": "123e4567-e89b-12d3-a456-426614174000",
"type": "string"
},
{
"key": "productId",
"value": "prod-abc-123",
"type": "string"
},
{
"key": "quantity",
"value": "2",
"type": "string"
},
{
"key": "order_id",
"value": "ord-xyz-789",
"type": "string"
}
]
}
Now, how do you organize these by team and domain?
Domain Organization: This is inherent in the file structure. user-service’s collection lives with user-service code, and order-service’s with order-service. This keeps the API contract tied to its implementation.
Team Organization: This is achieved through Postman Workspaces. You’d create a workspace for the "Platform Team" which owns user-service, and another for the "E-commerce Team" which owns order-service. Each team imports their respective service collections into their workspace.
The actual magic happens when you start linking these collections. Imagine a "System Integration" workspace. You can import the User Service - Core API collection and the Order Service - Core API collection into this workspace. Then, you can create a new request in the "System Integration" workspace that calls POST /orders.
In the body of this new request, you’d use Postman’s variable referencing:
{
"userId": "{{$shared.user_id}}", // Assuming user_id is defined in a shared environment
"items": [
{
"productId": "prod-abc-123",
"quantity": 1
}
]
}
And in the URL:
{{$shared.order_base_url}}/orders
This is where collections become more than just documentation. They become executable integration tests. You can set up "Environments" in Postman. A "Development" environment might have base_url set to http://localhost:8080 for user-service and http://localhost:8081 for order-service. A "Staging" environment would point to your staging deployment URLs.
You can then use Postman’s scripting capabilities. In the Create Order request for the order-service collection, you can add a "Tests" script:
// In the Tests tab of the Create Order request in Order Service collection
pm.test("Order created successfully", function () {
pm.response.to.have.status(201);
const responseData = pm.response.json();
pm.expect(responseData.orderId).to.exist;
pm.expect(responseData.status).to.equal("PENDING");
// Store the created order ID for potential downstream use
pm.collectionVariables.set("created_order_id", responseData.orderId);
});
This script asserts that the order was created successfully and captures the orderId. This captured orderId can then be used in subsequent requests within the same collection run or even across collections in a more advanced setup (e.g., using collection variables or shared environments).
The key insight is that a Postman Collection is not just a static dump of API endpoints. It’s a dynamic, executable specification. The description fields can become rich documentation, the variable fields define configuration, and the Tests and Pre-request Script tabs allow you to define complex workflows, assertions, and data dependencies. By organizing collections by domain and leveraging workspaces and environments for team collaboration and deployment stages, you transform Postman from a simple API testing tool into a central hub for API understanding, development, and validation.
Many teams don’t realize they can define complex inter-request dependencies directly within a collection run. For instance, you can chain requests so that the output of one request (like a newly created user_id) is automatically used as input for another request (like Get User by ID or Create Order using that userId). This is achieved by using pm.environment.set("variable_name", value) or pm.collectionVariables.set("variable_name", value) in the "Tests" tab of the first request and then referencing {{variable_name}} in the second request’s URL or body. This allows you to build entire end-to-end scenarios that are executable and verifiable directly from your Postman collection.
The next step is to explore how to automate these collection runs using the Postman CLI (newman) for CI/CD integration.