Migrating from Postman to an OpenAPI-First Workflow
· The Routebase Team

Almost nobody migrates off Postman because Postman is bad at being Postman. Teams migrate because of something that happened without anyone deciding it: the collection became the documentation. Then it became the contract. And a collection was never designed to be either.
The difference is easy to state and easy to miss. A collection is a record of calls that worked — this URL, these headers, this body, this is what came back that one time. A spec is a statement about what the API promises — these fields always exist, this one is optional, this enum has exactly four values, this endpoint can fail this way. One is a transcript. The other is a contract.
Most teams know this. They postpone the move anyway, and always for the same reason: somebody estimates it in weeks. Six hundred requests, years of accumulated test scripts, environments nobody fully understands — a quarter of work with no feature at the end of it.
That estimate was correct until recently. It isn't anymore, and the reason is worth being precise about.
Why this is agent work
Look closely at what a migration actually consists of, and there are no hard
decisions in it. There is a large amount of transformation: this Postman
field becomes that OpenAPI field; this pm.test line becomes that assertion
row; this {{authToken}} becomes that environment variable. Every step is
well-defined, every step is verifiable, and there are thousands of them.
That is the exact shape of work that is miserable for a person and trivial for a machine. What made it a quarter-long project was never difficulty — it was volume.
The reason an agent can now do it end to end is that every one of those steps is a tool call, not a click. Routebase ships an MCP server on every plan: 400+ tools covering the same operations your team performs in the UI, under the same role-based permissions. Connect Claude, Cursor, or your agent of choice, point it at your collection export, and the migration becomes something it can actually perform — import the spec, correct the schemas, rebuild the suites, run them, read the failures, fix them.
Here is what that looks like, step by step, and where you still matter.
Step 1: Import — with a dry run first
Hand the agent your collection export and it can preview the result before anything is written: a validation call in import mode returns exactly what the real import would create — endpoint, schema and folder counts — plus every warning, and writes nothing. The collection is recognized as a Postman file automatically; nobody has to tell it what format it's looking at.
The structural mapping is more faithful than people expect:
- Folders become tags. A request inside a
Customersfolder comes out taggedCustomers. Nested folders collapse to the innermost name — a request inAPI / v2 / Customersgets one tag,Customers. - Request names become summaries. Whatever you called it in the sidebar is now the human-readable name of the operation.
:idsegments become path parameters.{{baseUrl}}/customers/:customerIdbecomes/customers/{customerId}with a declared, required path parameter.- Query params and headers become parameters — minus the ones the client
generates anyway (
Content-Type,Host,User-Agentand friends), which would be noise in a spec. - Saved responses become response schemas, inferred from the example body.
- Auth becomes a security scheme, at collection level and per request.
So this request:
{
"name": "Get customer",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/customers/:customerId?include=invoices",
"path": ["customers", ":customerId"],
"query": [{ "key": "include", "value": "invoices" }],
"variable": [{ "key": "customerId", "value": "c-1024" }]
},
"header": [{ "key": "X-Tenant-Id", "value": "acme" }]
},
"response": [{ "name": "200 OK", "code": 200, "body": "…" }]
}— with that saved 200 OK body being an ordinary customer record:
{
"id": "9f2c1d4e-5b6a-4c3d-8e7f-0a1b2c3d4e5f",
"email": "ada@example.com",
"createdAt": "2026-03-04T09:12:00Z",
"plan": "pro",
"seats": 12
}comes out as this operation:
/customers/{customerId}:
get:
summary: Get customer
tags: [Customers]
parameters:
- name: customerId
in: path
required: true
schema: { type: string }
- name: include
in: query
required: true
schema: { type: string }
- name: X-Tenant-Id
in: header
required: false
schema: { type: string }
responses:
"200":
description: 200 OK
content:
application/json:
schema:
type: object
required: [id, email, createdAt, plan, seats]
properties:
id: { type: string, format: uuid }
email: { type: string, format: email }
createdAt: { type: string, format: date-time }
plan: { type: string }
seats: { type: integer }Look closely at include. In Postman it was an enabled row in the query table,
which is all a query row can be — so it arrived as required: true. If
include is really an optional filter, that line is wrong.
Which is the point of the whole exercise: the import produces a structurally valid spec, and then somebody has to supply the intent that was never written down. The warnings tell you where. Pre-request and test scripts, collection variables beyond the base URL, duplicate requests merged into one operation, duplicate parameters dropped — each one is flagged, with the request it came from.
Traditionally that list is the reason migrations stall: a few hundred lines of "you'll want to look at this." For an agent it's the opposite — it's a work queue, already itemized, already located.
Step 2: Closing the inference gap
Inferred schemas are better than nothing and worse than they look. The example
above is a fair result — types are real, uuid, email and date-time
formats were recognized from the values, seats came through as an integer
rather than a string. But every line of it was derived from one recorded
response, which produces three predictable errors:
Everything present became required. plan and seats happened to have
values in that body, so both are now required. If a trial account returns no
seats, the spec is wrong in the direction that breaks generated clients.
Everything absent doesn't exist. Optional fields the example didn't include aren't marked optional — they're missing entirely. Consumers will discover them in production and depend on them anyway.
Nothing knows its own range. plan: "pro" is type: string. It is almost
certainly an enum with three or four members, and that enum is one of the most
valuable things you can write down.
Here the agent has an advantage no single import pass has: it can read your entire collection at once. Four saved responses for the same endpoint, scattered across four duplicate requests, are four observations of the same schema — and a field that's present in three of them and absent in the fourth is optional, demonstrably. The same sweep surfaces the enum members, the nullable fields, and the formats that only one example happened to show. It then corrects the spec directly: parameters updated, schemas rewritten, responses added.
Then there's the half of the contract collections rarely record at all: the
failures. Most saved responses are the happy path, because that's what you were
debugging when you hit Save — so a freshly imported spec often documents
every success and no errors, while 401, 404, 409 and 422 are exactly
the responses consumers write code against. An agent that has read your
handlers or your error middleware can draft those too, ideally in a
consistent error format, and you
review the result rather than typing it.
Step 3: Every script has a destination
This is the part everyone braces for, and the fear is misplaced. Yes: your
pm.test("status is 200", …) JavaScript does not survive the import, because
assertions were never part of the OpenAPI contract. But "doesn't import"
is not the same as "has nowhere to go." Sort your scripts by what they actually
do and each group has a specific home:
Checks become assertions. The overwhelming majority of pm.test bodies
verify something about a response, and that translates into a row of data —
type, operator, target, expected value — across six assertion types and ten
operators. pm.response.to.have.status(200) becomes a status / equals /
200 assertion. pm.expect(json.plan).to.eql("pro") becomes a jsonPath
assertion on $.plan. This is the single best case for handing work to a
machine: the target is structured data, so the agent isn't writing logic you'd
have to audit line by line — it's filling in fields you can read in a diff.
Token stashing becomes response extraction. The classic
pm.environment.set("token", json.access_token) at the end of a login request
becomes an extraction rule: capture $.access_token from the response into a
runtime variable that later steps reference as {{token}}. Declarative,
inspectable, and no longer invisible in a script tab.
Chained requests become scenarios. The sequence you encoded by ordering requests in a folder and passing variables between them becomes an explicit multi-step scenario, with the extraction rules wiring one step's output to the next step's input.
Environments become environments. Every {{apiVersion}}, {{tenantId}},
{{authToken}} the importer warned about is a value with a proper place —
environment variables and auth schemes, with secrets marked as secrets instead
of sitting in a collection file that gets shared.
And genuinely computational logic stays code. Signing a request, computing an HMAC, deriving a nonce — that is real work a script should do, and it belongs in a reusable pre-request or post-response snippet. Note the word reusable: the same signing logic copy-pasted into forty Postman requests becomes one snippet referenced forty times. The migration is where that duplication gets collapsed.
So the honest count isn't "your scripts are lost." It's that most of them stop being scripts, a few stay scripts and get deduplicated, and an agent can place every one of them.
Step 4: The loop that makes this trustworthy
Everything above would still be a leap of faith if the output were only generated. It isn't — the agent can check its own work, and this is the part that separates a migration you can trust from a pile of plausible YAML.
The same MCP connection that creates a test suite can run it. So the loop closes:
- Import the collection, correct the schemas, rebuild the suites.
- Lint the spec against your style rules; fix what the linter reports.
- Run the suites against a real environment.
- Read the failures — and a failure here is information, not a setback. Each one is either a test the agent got wrong, or a place where your collection was already lying about your API.
- Fix and repeat until it's green.
That fourth step is the one worth sitting with. A failing assertion after migration usually isn't a migration bug. It's a contract violation that has been in your API for months, invisible because the collection recorded what the API did rather than what it promised. The migration finds them because, for the first time, something is comparing the two.
None of this runs unsupervised in a way you can't see. The agent works through
the same permission system as your team — creating specs needs specs:write,
touching tests needs tests:write — so what an agent may do is what its role
allows, and every change is a diff you can review before it ships.
Step 5: One artifact, not four
Once the spec is honest, the payoff isn't "we have a spec now." It's that mocks, docs, and tests stop being separate objects kept aligned by hand.
In the collection world, a mock server, a docs page, and a monitor each encode their own copy of the contract, and each drifts on its own schedule. Derive all three from one spec and there is nothing left to synchronize — not because the sync got better, but because there is only one document to change. That is also the most practical defence against contract drift: the drift you never have is the drift between artifacts that don't exist separately.
What stays in Postman
Probably a scratchpad, and that's fine. Poking at a third-party endpoint, reproducing a bug with a hand-edited header, trying something once — that's what a request client is good at, and none of this removes the need. The distinction worth keeping is which artifact is allowed to be the source of truth. A scratchpad nobody treats as documentation is a useful tool. A scratchpad that quietly became the contract is how you got here.
Start with one folder
You do not have to commit to the whole collection to find out whether this works. Export one folder — a dozen requests you know well — connect an agent to your workspace, and let it run the loop: import, correct, rebuild, run. You'll have a governed spec, working test suites, and a mock server for that folder in an afternoon, and a precise sense of what the remaining folders will cost.
The reason to do it now rather than next quarter is simply that the estimate changed. The migration was always mechanical; what's new is that mechanical work no longer has to be done by the person who understands the API. Their job is to review it — which is the only part that ever needed them.
Import your collection and see what the first pass gives you. Even the warning list is a useful read.