DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

Convert curl commands and Postman collections into OpenAPI (without losing fields)

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"}'
Enter fullscreen mode Exit fullscreen mode

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_run accepts only true/false or also other values; whether it defaults.
  • Whether plan is an enum (it is — PRO, TEAM).
  • Whether name has 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/12 requires inferring that 12 is 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

  1. Paste the command into the importer. The parser should normalize shell quoting, multiple --data fragments, -u user:pass into basic auth, and -F fields into multipart form bodies — these are the places naive regex parsers corrupt data.
  2. 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.
  3. Promote auth to a security scheme. A bearer header becomes bearerAuth at the operation or global level; the captured token is discarded, never written into the spec.
  4. Infer, then tighten. The inferred body gets string types; you promote plan to an enum, region to an enum, and mark name required in one review pass.
  5. 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.
  6. 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 required and 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 with https://{{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)