View the project landing page →
Generate runnable Postman collections, environments, workflow-aware test scripts, negative cases, and Newman reports from Swagger 2.0 or OpenAPI 3.x specifications.
The core generator is deterministic. An optional AI planner uses structured model output to infer operation order and identifier mappings, while the deterministic generator remains responsible for producing the collection.
- Swagger 2.0 and OpenAPI 3.x files or URLs
- JSON, form-urlencoded, multipart uploads, text, and binary responses
- Path, query, header, cookie, array, and deep-object parameters
- Bearer, Basic, OAuth token placeholders, API keys, and combined security requirements
- Status-specific JSON Schema assertions and media-type-aware response handling
- Response assertions respect write-only fields; generated authentication variables retain configured values.
- Synthesized examples are checked against supported type, numeric, string, array, object, and composition constraints. Unsupported synthesis or conflicting constraints produce an error; provide a valid explicit example for complex patterns, formats, or uniqueness constraints. This is a bounded generator, not a complete JSON Schema validator.
- CRUD-oriented ordering with configurable operation order
- Identifier extraction and target substitution between dependent requests
- Optional missing-field, invalid-enum, boundary, and unauthorized cases
- Named environment profiles and JSON/CSV iteration data
- Safe mode that excludes
DELETEoperations - A tag filter (
--tag,--exclude-tag) to generate the tests of a part of the API - A coverage report: which operations have a request and which documented responses a test expects
- OAuth2 client credentials: the collection can start by fetching its own access token
- A GitHub Action that generates, runs and reports in one step
- Setup and teardown requests, async job polling, and runtime variables such as OTPs
- Newman execution with CLI, JSON, JUnit, and standalone HTML reports
- Per-request HTML reporting with status, duration, and assertion counts
- Provider-neutral AI planner with schema-validated output and fallback providers
- Built-in OpenAI SDK, Codex CLI, Claude Code, and Antigravity CLI adapters
diffcommand that lists the changes between two versions of a spec and fails on the ones that break clients- The same tests as a Bruno collection folder or a k6 script, with the same checks, variable capture and job polling
- Safe custom-command adapter and a public provider registry for future integrations
- Node.js 22 or newer
- A Swagger 2.0 or OpenAPI 3.x document
- Newman available on
PATHwhen running generated collections (npm install --global newman) - An authenticated provider CLI when using Codex, Claude, or Antigravity
OPENAI_API_KEYonly when the OpenAI SDK provider is selected
Install the published CLI (Node.js 22 or newer):
npm install --global openapi-postman-test-generator@0.9.0
openapi-postman --help
openapi-postman generate --spec ./openapi.yaml --out ./collection.json --env ./environment.jsonUpgrade with the same install command. Version 0.9.0 generates for part of an API (--tag,
--exclude-tag), reports how much of the document the tests cover (--coverage), signs in with
OAuth2 client credentials (--oauth-token), and comes as a GitHub Action. Collections generated
without these options are unchanged; see release notes.
For use as a library:
npm install openapi-postman-test-generator@0.9.0const { OpenApiPostmanGenerator } = require('openapi-postman-test-generator');
const spec = require('./openapi.json'); // Parsed, resolved OpenAPI document.
const generator = new OpenApiPostmanGenerator(spec);
const collection = generator.generate();TypeScript declarations are included. Newman and authenticated AI providers are optional external tools; deterministic collection generation does not need them.
npm install
npm run buildnode dist/index.js generate \
--spec ./fixtures/petstore.openapi.yaml \
--out ./generated/api.collection.json \
--env ./generated/api.environment.jsonGenerate additional negative tests and skip destructive operations:
node dist/index.js generate \
--spec ./openapi.yaml \
--negative \
--safe \
--config ./examples/openapi-postman.config.yamlGenerate the tests of a part of the API. A tag filter keeps the operations that carry one of the tags, or
leaves out the ones that do; names are compared without regard to case, a list may be comma-separated or
the flag repeated, and Default names the operations without tags:
node dist/index.js generate --spec ./openapi.yaml --tag Orders,Payments --exclude-tag InternalincludeTags and excludeTags in the configuration file hold the usual choice; a flag adds to it. Setup
and teardown requests are never filtered.
The legacy syntax remains supported:
npm run generate -- --spec ./openapi.yamlnode dist/index.js run \
--collection ./generated/api.collection.json \
--environment ./generated/api.environment.json \
--report-dir ./generated/reportsOr generate and run in one command by adding --run. The report directory contains report.html, junit.xml, and newman.json.
Use a JSON or CSV data file for data-driven runs and optionally cap total runtime:
node dist/index.js run \
--collection ./generated/api.collection.json \
--iteration-data ./test-data.csv \
--run-timeout 300000 \
--bailgenerate ends with a summary of what the collection tests of the document:
Generated 14 requests
Coverage: 9/10 operations, 12/21 documented responses (1 operation left out: 1 in safe mode)
Uncovered: 9 documented error responses have no test; --negative generates them where it can
An operation is covered when the collection holds a request for it; the ones left out are counted with the
reason (disabled in the configuration, the tag filter, safe mode). A documented response is covered when a
generated request is written to expect it: the success responses always, the error responses that the
negative variants provoke (--negative). It is a statement about the collection, not about a run.
--coverage <file> writes the whole report as JSON: every operation with its tags, whether it is in the
collection, the responses the document describes and the ones a request expects. default is not a status
and is not counted.
node dist/index.js generate --spec ./openapi.yaml --negative --coverage ./generated/api.coverage.jsonAn API protected by OAuth2 needs an access token before its first request. By default the token is a
value you supply (<scheme>_token in the environment). With --oauth-token, or oauthClientCredentials: true
in the configuration, the collection fetches it itself: for every security scheme with a client credentials
flow (flows.clientCredentials in OpenAPI 3, flow: application in Swagger 2.0) a Get access token
request is put first in the Setup folder. It posts grant_type=client_credentials with the client id,
the client secret and the scopes of the document to the token endpoint, and keeps access_token where the
other requests read it.
node dist/index.js generate --spec ./openapi.yaml --oauth-token --run \
--env-var oauth_clientId=my-client --env-var oauth_clientSecret="$CLIENT_SECRET"The environment gains <scheme>_clientId and <scheme>_clientSecret (empty, to fill in or to pass with
--env-var) and <scheme>_tokenUrl (the endpoint the document names, to override for another
environment). The token request sends no API credentials. When no scheme has that flow, generate says so
and changes nothing. The same request is written to the Bruno folder and the k6 script.
# .github/workflows/api-tests.yml
name: API tests
on: [pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# start the API under test here, or point base-url at a deployed one
- uses: vannt-dev/openapi-postman-test-generator@v0.9.0
with:
spec: openapi.yaml
base-url: http://localhost:3000 # optional
negative: true # optional
exclude-tag: Internal # optional; tag and exclude-tag take a list
oauth-token: true # optional
env-vars: | # optional, one KEY=VALUE a line
oauth_clientId=ci-client
oauth_clientSecret=${{ secrets.API_CLIENT_SECRET }}The action generates the collection, the environment and the coverage report into generated/
(output-dir), adds the coverage lines and the result of the run to the job summary, and fails the job
when an assertion fails. run: false only generates. Its outputs are collection, environment,
coverage, report-dir (with report.html, junit.xml and newman.json) and failures. It runs on
Linux and macOS runners.
The generated tests can also be written for Bruno and for k6, next to the Postman files or from a collection generated earlier:
# While generating
node dist/index.js generate --spec ./openapi.yaml --bruno ./generated/bruno --k6 ./generated/api.k6.js
# From an existing collection
node dist/index.js convert \
--collection ./generated/api.collection.json \
--environment ./generated/api.environment.json \
--bruno ./generated/bruno --k6 ./generated/api.k6.jsRun them with the tools themselves:
cd generated/bruno && bru run -r --env "<environment name>" --env-var bearerAuth_token=...
k6 run -e baseUrl=https://staging.example.com -e bearerAuth_token=... generated/api.k6.jsRequests, headers, bodies, authentication and variables are written in each tool's own form: .bru files in numbered folders with an environment file for Bruno, one script with a request table for k6. The test scripts are the collection's own. Each output carries a small stand-in for the part of Postman's pm object those scripts use, so a status check, a response-time limit, a schema assertion, an extracted identifier or a polled background job behaves the same in all three tools. In Bruno every pm.test is a test; in k6 it is a check, and the script's threshold fails the run when any check fails.
Things to know:
- The schema assertion uses a validator written for these outputs that covers the keywords the generator emits (types,
required,properties,additionalProperties,items,enum,const, numeric and length bounds,pattern,allOf/anyOf/oneOf). Other keywords are ignored rather than failed. Postman and Newman keep using Ajv. - The k6 script runs one virtual user once. Raise
vusanditerationsin itsoptions, or pass--vusand--duration, to use it as a load test; the checks stay the same. - A Bruno collection that has both folders and requests in a set order (setup, ordered operations, teardown) gets a
Requestsfolder for the ordered ones, because Bruno runs subfolders before the requests beside them. - Multipart file fields are sent as text parts in k6, and
--iteration-datafiles are for Newman only. - Secret environment values are not written out: Bruno lists them under
vars:secret, and k6 takes them with-e.
Also exported as toBrunoCollection and toK6Script.
Before regenerating tests for a new version of an API, see what changed and whether existing clients survive it:
openapi-postman diff --old ./openapi.v1.yaml --new ./openapi.v2.yamlBreaking changes (2)
DELETE /pets/{petId} operation removed
POST /pets request body application/json: kind new required property
Non-breaking changes (1)
GET /pets query parameter "cursor" optional parameter added
The command exits with status 1 when it finds a breaking change, so a pipeline can stop on it; --allow-breaking keeps the exit status at 0, and --format json prints the changes as JSON (severity, code, operation, location, message). Either side may be a file or a URL, and a Swagger 2.0 document can be compared with the OpenAPI 3.x one that replaced it.
A change is breaking when a client written against the old version can fail against the new one:
| Where | Breaking | Not breaking |
|---|---|---|
| Operations | removed | added, deprecated |
| Parameters | new required one, became required, type changed, enum value removed | new optional one, removed, became optional, enum value added |
| Request body | became required, media type removed, new required property, property became required, type changed | new optional property, property removed, media type added |
| Responses | success (2xx) response removed, media type removed, property removed, property no longer required, type changed | response added, other response removed, property added, enum value added |
| Security | an operation that needed no credentials now needs them |
Path parameters are matched by position, so renaming {id} to {petId} is not a change. Not compared: descriptions and examples, response headers, numeric and length limits, additionalProperties, callbacks, links and servers. A changed oneOf or anyOf is reported as non-breaking with a note to compare it by hand, because the command does not judge it.
The local CLI providers reuse the account session already established by their own login command. They do not require this project to store an API key.
Use Codex with its cached login:
node dist/index.js generate \
--spec ./openapi.yaml \
--ai \
--ai-provider codexClaude Code and Antigravity work the same way:
node dist/index.js generate --spec ./openapi.yaml --ai --ai-provider claude
node dist/index.js generate --spec ./openapi.yaml --ai --ai-provider antigravityConfigure automatic fallback when a CLI is unavailable, logged out, times out, or returns an invalid plan:
node dist/index.js generate \
--spec ./openapi.yaml \
--ai \
--ai-provider codex \
--ai-fallback claude,antigravity,openaiThe OpenAI SDK provider still supports direct API access:
Set credentials and explicitly choose a model:
export OPENAI_API_KEY="your-key"
export OPENAI_MODEL="your-supported-model"
node dist/index.js generate \
--spec ./openapi.yaml \
--ai \
--ai-provider openai \
--plan-out ./generated/agent-plan.jsonEvery provider receives the same read-only planning prompt and must return the same schema-validated plan. Codex runs with a read-only sandbox and Claude runs in plan permission mode. The deterministic generator—not the AI provider—writes Postman scripts. Planned operation ordering, variable mappings, target substitutions, and negative scenarios are all validated and applied by the generator.
Define it in the project configuration without modifying source code:
ai:
provider: local-agent
fallback: [codex]
timeoutMs: 120000
maxOutputBytes: 1048576
providers:
local-agent:
type: command
command: my-agent
args: [--json-schema, "{schema}"]
input: stdin
output: stdout-jsonCommands are launched directly without a shell. Supported argument placeholders are {prompt}, {schema}, {schemaFile}, {outputFile}, and {model}. The command must print either the plan JSON itself or a JSON envelope containing structured_output, output_parsed, result, or response; set output: output-file when it writes to {outputFile} instead. Library consumers can register an AiProvider implementation with AiProviderRegistry for SDK-based integrations.
See examples/openapi-postman.config.yaml. Explicit CLI flags override config values.
baseUrl: https://test-api.example.com
responseTimeMs: 2000
safeMode: true
includeNegative: true
variables:
tenantId: test-tenant
operationOrder: [login, createUser, getUser, deleteUser]
variableMappings:
- sourceOperationId: createUser
responseJsonPath: $.data.id
variable: userId
targetOperationIds: [getUser, deleteUser]
disabledOperations: [chargeCreditCard]
includeTags: [Orders, Payments]
excludeTags: [Internal]
oauthClientCredentials: true
negativeScenarios:
- operationId: createUser
name: email is required
kind: missing_required
field: email
profiles:
staging:
baseUrl: https://staging-api.example.com
environmentName: Staging API
variables:
tenantId: staging-tenant
ai:
provider: codex
fallback: [claude, antigravity]
providers:
codex:
type: codex
command: codexSelect a profile with --profile staging. Base variables are merged with profile variables, with profile values taking precedence. A login operation can bootstrap later authenticated requests by mapping its returned token to the security variable (for example, bearerAuth_token) and placing login first in operationOrder.
OpenAPI does not describe seed data, background jobs, or one-time passwords, so the generator does not guess them. Declare them in the project configuration instead:
setup:
- name: Seed tenant
method: POST
url: /admin/seed # a leading "/" is prefixed with {{baseUrl}}
headers: { X-Admin-Key: "{{adminKey}}" }
body: { name: test } # objects are sent as JSON, strings as raw text
expectStatus: [201] # default: any 2xx
extract: { tenantId: $.id } # stored as a collection variable
teardown:
- name: Delete tenant
method: DELETE
url: /admin/tenants/{{tenantId}}
asyncOperations:
- operationId: createExport
statusJsonPath: $.status
successValues: [done]
failureValues: [failed]
intervalMs: 1000 # default 1000
maxAttempts: 30 # default 30
extract: { exportUrl: $.url } # applied when the job succeeds-
setuprequests run first, in aSetupfolder;teardownrequests run last, in aTeardownfolder. They are explicit, so safe mode keeps them. They send no API credentials unlessinheritAuth: trueis set, because their URLs may point outside the API. -
Extracted values (and identifiers captured by
variableMappings) also replace a variable of the same name in the environment, so placeholders in the generated environment file do not hide them. -
Each async operation gets a
Poll: <operationId>request right after it. By default it polls the URL in the operation's 2xxLocationresponse header, resolved against the request URL; setstatusUrl(for example/exports/{{exportId}}) to poll a fixed endpoint instead. The poll repeats until a success or failure value appears, or fails aftermaxAttempts. It fails at once, without retrying, when there is no status URL. -
The poll reuses the operation's credentials, API-key headers, and cookies. Set
inheritAuth: falsewhen the status URL is a presigned storage link that rejects them. Newman follows redirects, so a303 See OtherAPI should usestatusUrl. -
For OTP flows, either fetch the code from a test-only endpoint with a
setuprequest andextract: { otp: $.code }, or pass it when running:node dist/index.js run --collection ./generated/api.collection.json --env-var otp=123456
--env-var KEY=VALUEcan be repeated and also works withgenerate --run. The values are masked in this tool's error messages, but they are visible to other local processes and Newman writes the environment intonewman.json; prefer CI secrets for real credentials and do not publish the report directory.
npm run lint
npm test
npm run check
npm pack --dry-runThe test suite includes Swagger 2.0 and OpenAPI 3.x smoke tests, regression tests, provider contract and fallback tests, a real local HTTP/Newman end-to-end run, tests that run the generated k6 script and Bruno scripts against stand-ins for those tools, and Windows runner tests. Newman remains an external runtime tool so its legacy transitive dependencies are not shipped to library consumers.
OpenAPI describes an HTTP contract, not every business prerequisite. Seed data, OTP flows, asynchronous jobs, and environment cleanup need explicit configuration (see Setup, teardown, async jobs, and OTP); payment providers and other third-party flows are not automated. Teardown requests do not run when --bail stops a run early or Newman is interrupted, so use a disposable test environment. Use --safe first against unfamiliar APIs, review generated requests, and never store secrets in committed environment files. Enabling AI sends the summarized API contract to the selected provider; do not enable it for specifications that your provider is not authorized to process.