Newman is Postman's open-source CLI runner (Apache-2.0, still just npx newman run) and it remains the simplest way to execute a Postman collection in a pipeline. The awkward part was never Newman β it's the URL your collection points at. Point it at staging and your API tests inherit staging's downtime, stale data, and whoever deployed last. Point it at Postman's own hosted mock servers and you're example-based (writes don't persist) and on a metered free tier. And Postman's newer closed-source CLI wants you to postman login --with-api-key before it does anything β one more secret to provision in CI.
Here's the zero-account alternative: Mockbird generates a ready-to-run Postman collection from your mock API β most tools make you go the other direction β and Newman can run a collection straight from a URL. Which means this works, with no flags, no files, and nothing to sign up for:
curl -s -X POST https://mockbird.mockbird.workers.dev/api/projects \
-H 'Content-Type: application/json' -d '{"preset": "ecommerce"}'
# β {"id": "dcnrhevv7k", "adminKey": "..."} β yours will differ; note both
npx newman run https://mockbird.mockbird.workers.dev/m/YOUR_ID/postman.json
The first command creates a free anonymous mock project seeded with realistic data (swap the preset for blog or saas, or import your OpenAPI spec / db.json / CSV to get your schema). The second runs the collection Mockbird generated for it. Real output from the run we did before publishing:
βββββββββββββββββββββββββββ¬βββββββββββββββββββββ¬ββββββββββββββββββββ
β β executed β failed β
βββββββββββββββββββββββββββΌβββββββββββββββββββββΌββββββββββββββββββββ€
β requests β 21 β 0 β
βββββββββββββββββββββββββββ΄βββββββββββββββββββββ΄ββββββββββββββββββββ
total run duration: 2.6s
average response time: 115ms
21 requests, zero failures, zero configuration. The generated collection bakes in a baseUrl collection variable pointing at your project, so there is nothing to template.
The generated collection has a folder per resource with the full CRUD cycle β List, Get one, Create, PATCH, Delete β plus a GraphQL sample request. Every list request carries the interesting query params pre-filled but disabled: pagination, sorting, search, relations (_expand/_embed), and the whole failure-simulation toolkit (mock_status, mock_delay, mock_chaos, mock_seq, mock_ratelimitβ¦). In the Postman app you tick a checkbox to turn one on; in a collection file you flip "disabled": true.
Two things that make this a real CI target rather than a demo: writes persist (the Create really inserts; the next GET really sees it β Postman's example-based mocks can't do that), and the data is yours (define resources by hand or import a spec; the collection regenerates to match).
The generated collection proves the API shape. Your pipeline wants assertions. Newman runs ordinary pm.test scripts, so here's a compact suite that proves persistence and drills the failure path β save as smoke.postman_collection.json:
{
"info": {
"name": "mock smoke tests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [{ "key": "baseUrl", "value": "https://mockbird.mockbird.workers.dev/m/YOUR_PROJECT" }],
"item": [
{
"name": "create a product",
"request": {
"method": "POST",
"url": "{{baseUrl}}/products",
"header": [{ "key": "Content-Type", "value": "application/json" }],
"body": { "mode": "raw", "raw": "{\"name\": \"Newman test\", \"price\": 9.99}" }
},
"event": [{
"listen": "test",
"script": { "exec": [
"pm.test('create returns 201', () => pm.response.to.have.status(201));",
"pm.collectionVariables.set('pid', pm.response.json().id);"
] }
}]
},
{
"name": "read it back (writes persist)",
"request": { "method": "GET", "url": "{{baseUrl}}/products/{{pid}}" },
"event": [{
"listen": "test",
"script": { "exec": [
"pm.test('the record we created is really there', () => {",
" pm.response.to.have.status(200);",
" pm.expect(pm.response.json().name).to.eql('Newman test');",
"});"
] }
}]
},
{
"name": "outage drill: first call fails",
"request": { "method": "GET", "url": "{{baseUrl}}/products?mock_seq=503,200&mock_seq_key=newman&mock_seq_reset=1" },
"event": [{
"listen": "test",
"script": { "exec": [
"pm.test('deterministic 503 on call 1', () => {",
" pm.response.to.have.status(503);",
" pm.expect(pm.response.headers.get('x-mockbird-seq')).to.eql('1/2');",
"});"
] }
}]
},
{
"name": "outage drill: service recovers",
"request": { "method": "GET", "url": "{{baseUrl}}/products?mock_seq=503,200&mock_seq_key=newman" },
"event": [{
"listen": "test",
"script": { "exec": [
"pm.test('real data on call 2', () => {",
" pm.response.to.have.status(200);",
" pm.expect(pm.response.headers.get('x-mockbird-seq')).to.eql('2/2');",
" pm.expect(pm.response.json()).to.be.an('array');",
"});"
] }
}]
},
{
"name": "cleanup",
"request": { "method": "DELETE", "url": "{{baseUrl}}/products/{{pid}}" },
"event": [{
"listen": "test",
"script": { "exec": [
"pm.test('deleted', () => pm.response.to.have.status(200));"
] }
}]
}
]
}
npx newman run smoke.postman_collection.json \
--env-var baseUrl=https://mockbird.mockbird.workers.dev/m/YOUR_PROJECT
# β 5 assertions, 0 failed, ~1.1s
The outage drill uses mock_seq=503,200 β a deterministic status sequence, not a probabilistic one: request 1 gets a 503, request 2 gets the real response, every run, in order. mock_seq_reset=1 on the first request restarts the sequence so re-runs stay deterministic, and the x-mockbird-seq: 1/2 response header lets the assertion prove exactly where in the sequence it landed. This is how you pin down "the client survives one 503 and recovers" as a green/red CI check instead of a hope.
Sharing one long-lived mock project between pipelines invites test pollution. Projects are free and created in one request, so give every run its own backend and throw it away after β this exact block, run verbatim before publishing:
HOST=https://mockbird.mockbird.workers.dev
# 1) fresh backend for this run
PROJECT=$(curl -s -X POST "$HOST/api/projects" \
-H "Content-Type: application/json" -d '{"preset": "ecommerce"}')
ID=$(echo "$PROJECT" | jq -r .id)
KEY=$(echo "$PROJECT" | jq -r .adminKey)
# 2) the generated collection, with JUnit output for your CI's test tab
npx newman run "$HOST/m/$ID/postman.json" \
-r cli,junit --reporter-junit-export results.xml
# 3) your assertions against the same fresh backend
npx newman run smoke.postman_collection.json --env-var "baseUrl=$HOST/m/$ID"
# 4) throw the backend away
curl -s -X DELETE "$HOST/api/projects/$ID" -H "x-admin-key: $KEY"
In GitHub Actions that's a single run: step on any ubuntu-latest runner β jq, curl and npx are all preinstalled, and actions/upload-artifact or a JUnit annotator picks up results.xml. The same shape works in GitLab CI, CircleCI, or a Makefile. More variations in the GitHub Actions guide.
Flip a project to protected mode and every endpoint starts demanding a JWT:
curl -s -X PUT "$HOST/api/projects/$ID/settings" \
-H "Content-Type: application/json" -H "x-admin-key: $KEY" \
-d '{"authMode": "protected"}'
curl -s -o /dev/null -w '%{http_code}\n' "$HOST/m/$ID/products"
# β 401
Re-fetch postman.json and the generated collection now leads with an auth (mock) folder whose Login request carries a test-script that stores the JWT in a {{token}} variable, plus collection-level bearer auth that applies it everywhere else. Newman executes folders in order, so:
npx newman run "$HOST/m/$ID/postman.json"
# β Login runs first, token stored, 23 requests, 0 failed
That's a realistic login-then-call-with-bearer flow in CI β real signed tokens, real 401s when they're missing β with zero scripting on your side. Details in the mock JWT auth guide.
| Tool | Best for | Account needed? | Watch out for |
|---|---|---|---|
| Newman + Mockbird | collection runs in CI against a deterministic, disposable backend; writes persist; failure drills on demand | none (both free, no signup) | we're the functional target, not a load target β 10k req/day per project |
| Postman CLI | tight integration with your Postman workspace (cloud collections, governance checks) | Postman account + API key (postman login) | closed source; one more CI secret; still needs a target URL β this page's approach works with it too |
| Postman hosted mock servers | mocking straight from saved examples inside Postman | Postman account | example-based (writes don't persist), metered free tier β see our comparison |
| Bruno CLI | the same idea with Bruno's git-native .bru files | none | different format β we cover it here |
| k6 | load and performance (plus checks) | none | JS scripts, not collections β our k6 guide |
Where Newman itself deserves a caveat: Postman's development energy visibly goes to the Postman CLI these days, and Newman's release pace is slow. It's still maintained, open source, and β for running a collection file or URL with assertions in CI β still the least-ceremony option there is.
curl -s -X POST https://mockbird.mockbird.workers.dev/api/projects \
-H 'Content-Type: application/json' -d '{"preset": "ecommerce"}'
β¦grab the id, then npx newman run https://mockbird.mockbird.workers.dev/m/<id>/postman.json. Or create a project in one click and find the postman β link in the dashboard. Free, no signup. Full docs Β· machine-readable API index.
Related: Postman mock servers vs Mockbird, mock APIs in GitHub Actions, Bruno, k6, testing loading and error states, deterministic test data with snapshots.
Verification: every command on this page was run verbatim with Newman 6.2.2 (via npx, Node 20) against production on 6 Sep 2026 β including the 21-request zero-flag run (2.6s), the 5-assertion smoke suite, the JUnit export, the protected-mode 23-request run, and the full createβrunβdelete CI block. Outputs shown are excerpts from those real runs. Scratch projects were deleted afterwards. If a snippet here doesn't work, that's a bug: tell us.