Two artifacts appear in every API integration: a curl command someone pasted into Slack, and a Postman collection exported from someone's account. Both are presented as "the API docs." Neither is documentation — but both are salvageable as raw material for an OpenAPI spec, as long as you know what each conversion preserves and what it silently destroys.
What a curl command actually contains
Take a realistic capture:
curl -X POST 'https://api.example.com/v1/projects?dry_run=false' \
-H 'authorization: Bearer eyJhbGciOi...' \
-H 'content-type: application/json' \
-H 'idempotency-key: 7b32f1' \
--data '{"name":"Apollo","plan":"TEAM","region":"us-east-1"}'
A faithful parser extracts: method, base server (https://api.example.com), path (/v1/projects), one query parameter, three headers, and a JSON body inferring three string properties. That is the complete list. Everything else is missing:
- Which headers are required on every request versus one-off (the bearer token is really a security scheme, not a header parameter).
- Whether
dry_runaccepts onlytrue/falseor also other values; whether it defaults. - Whether
planis an enum (it is —PRO,TEAM). - Whether
namehas a max length or is required. - Every response — success shape, error envelope, status codes.
- Path parameters (there are none here, but a URL with
/projects/12requires inferring that12is an{id}).
A converter that emits a full operation with response schemas from this input is making things up. The honest output is a partial operation with gaps marked, which you then complete against the live service or the spec.
The conversion workflow that does not lie
-
Paste the command into the importer. The parser should normalize shell quoting, multiple
--datafragments,-u user:passinto basic auth, and-Ffields into multipart form bodies — these are the places naive regex parsers corrupt data. -
Parameterize the URL. Replace concrete ids with
{project_id}and add the path parameter; group everything under a server URL so the spec is environment-agnostic. -
Promote auth to a security scheme. A bearer header becomes
bearerAuthat the operation or global level; the captured token is discarded, never written into the spec. -
Infer, then tighten. The inferred body gets string types; you promote
planto an enum,regionto an enum, and marknamerequired in one review pass. - Capture the response from a real send. Send the request once against a sandbox and let the response body seed the 201 schema — observed, not invented.
- Save the operation into the spec under the right tag, not into a standalone scratch file.
In Powerduck this is the scratch-request flow: paste or record the command, debug it like a normal request, then promote it into the OpenAPI document through a dialog that asks for path, method, tag, and conflict handling (what happens if POST /projects already exists).
Postman collections: the mapping
Postman Collection v2.1 carries more structure than curl, and the mapping is mostly mechanical:
| Postman | OpenAPI |
|---|---|
Collection variable baseUrl |
servers[0].url with templated variables |
| Folder hierarchy |
tags (usually one level — nested folders collapse) |
| Request name + description |
summary / description
|
| Request headers / query params | parameter objects (required from the disabled flag) |
| Collection or request auth |
securitySchemes + security
|
Body raw JSON |
requestBody.content.application/json.schema (inferred) |
Body formdata
|
multipart/form-data schema |
| Saved example responses | response examples / media-type examples
|
| Pre-request scripts | Nothing — these are test harness, not contract |
Postman dynamic variables {{$randomEmail}}
|
Example values, stripped of the runtime syntax |
What routinely gets lost or mangled:
-
Response schemas. Postman stores example bodies, not schemas. They seed examples well; deriving
requiredand nullability from one example is guessing and should be labeled as such. - Multiple responses per status. A request with five saved 200 examples becomes one example; distinct error statuses only exist if someone saved them as examples with the right code.
- Auth inheritance. Auth set at the folder level is easy to miss; the converter must walk the inheritance chain or the spec comes out with no security at all.
-
Environment-specific URLs. Collections reference
{{baseUrl}}with the real value living in an environment JSON that is usually not exported. You end up withhttps://{{baseUrl}}/...and must fix servers by hand. - Folder-level descriptions and ordering. Nested folders beyond two levels do not map to tags; decide on a flat tag taxonomy before importing.
HAR: the better bulk source
When the source of truth is "whatever the frontend actually calls," a browser HAR export beats a hand-curated collection. It contains every request and response from a recorded session — headers, query strings, bodies, status codes, timings — so response schemas are seeded from observed traffic, and endpoints the collection forgot show up anyway. The same caveats as all traffic-based recovery apply: it only covers exercised paths, recorded payloads may contain real customer data (scrub them before importing), and one observed shape does not prove optionality.
The end state matters more than the import
Converting artifacts is a means, not a goal. The failure pattern is converting the collection into a 900-line YAML file, saving it once, and never touching it while the API moves on. To avoid that:
- Land the imported operations in the same document used for debugging, mocking, and tests, so editing the spec is part of daily work rather than a docs project.
- Re-run imports against the spec with conflict detection — duplicate path/method pairs should prompt to merge, not silently overwrite your edited descriptions.
- Treat curl and HAR imports as incremental: one captured request becomes one reviewed operation, the way the scratch-to-spec promotion works in the workspace, rather than a big-bang regeneration.
The online demo accepts curl, Postman, and HAR on the import screen; the longer case for spec-as-source is in a curl command is not an API handoff.
What to read next: stop pasting curl into ChatGPT covers what happens when these artifacts get pasted into AI chats instead of a contract, and generate OpenAPI from existing code is the path when you have the source instead of captures.
Top comments (0)