Bruno API Client | Blog & News

curl to GUI and Back: How to Import curl Commands Into a Visual API Client (2026)

Written by Anthony Dombrowski | Aug 24, 2026

 

curl commands reach you from a lot of directions. Browser DevTools has a "Copy as cURL" option on every request, most API references include a curl example, support tickets and bug reports might carry one, and coding assistants usually answer HTTP questions in curl. It's a sensible default, since curl is already installed on most machines and needs no setup.

The short answer

Paste the command into a client that reads curl natively. In Bruno: sidebar ··· on your collection → New Request → set Request type to From cURL → paste → Create. Method, URL, headers, and body come across, and the request is saved as a plain-text YAML file in your collection folder, not into a cloud workspace. To go the other way, open any request and use Generate Code to get curl back out. The rest of this guide maps exactly how much comes across, from auth and multipart uploads to cookies and compression, and how to get back to the terminal whenever you want.

What is a curl GUI?

A curl GUI is a visual HTTP client that can read and write curl commands. You paste a command in, it becomes an editable request with tabs for headers, body, and auth; you click a button, and you get a runnable curl command back. The distinction that matters isn't just the graphical interface. It's also whether the conversion runs both directions, and what format your requests are stored in once they're inside.

Tools that import curl but can't export it, or that store your requests in a proprietary cloud workspace, are a one-way door. That's the thing developers who live in the terminal are often objecting to when they say they don't want a GUI.

The complexity of curl commands

Two things make working with curl commands awkward. The first shows up on a single command, before any accumulation happens. Real commands get complicated fast: a dozen headers, a nested JSON body, an auth flag, and a couple of query params, all flattened into one line where a missing brace or a quote in the wrong position is genuinely hard to see. Reading it means parsing shell syntax in your head, and changing one value means editing a string rather than a field. An interactive client splits the same command into headers, body, auth, and params, so you can see what's actually being sent and adjust one part without disturbing the rest. That's useful even if you only ever run the command once.

The second is storage. A command worth running twice ends up in your shell history, a scratch file, or a Slack thread, none of which you can search reliably when you need it again a month later.

That isn't a case against curl. It's a case for having somewhere to open a command up, see its parts clearly, and keep it around if it turns out to be worth keeping.

How to import a curl command into Bruno

The flow takes just a few clicks:

  • In the sidebar, click ··· on your collection → New Request.
  • Set Request type to From cURL.
  • Paste the command and click Create.

Bruno opens the new request with the method, URL, headers, and body already filled in. Multi-line commands with trailing backslashes paste fine, so you don't need to flatten them first.

Both problems from the last section get handled at once. The command is now a set of editable fields, with headers, body, auth, and params each in their own tab, so you can read any one of them without picking apart a long line and change one without disturbing the rest. It's also a plain-text YAML file inside your collection folder, which you can open in your editor, grep, diff, and commit. Nothing gets uploaded, and there's no account to create, which also means the import works on an air-gapped machine.

Try it with a command you already have. Download Bruno. It's open source, MIT-licensed, and your collections stay on your machine.

Which curl flags carry over

Most "paste your curl here" boxes are vague about how much of your command they actually understand. Here's the specific mapping:

curl flag What Bruno does with it
-X, --request Sets the HTTP method
-H, --header Adds a header
-A, --user-agent Sets the User-Agent header
-d, --data, --data-ascii, --data-urlencode Becomes the request body; repeated flags are joined with &
--data-raw Becomes the request body, subject to the same parsing as -d
--data-binary @payload.json becomes a file body, taking its content type from the command's Content-Type header
--json JSON body, sets Content-Type: application/json, and promotes GET/HEAD to POST
-F, --form Multipart form body; @path/to/file entries become file parts
-u, --user Username and password land in the Auth tab as Basic auth
--digest, --ntlm Alongside -u, switches the auth mode to Digest or NTLM
-b, --cookie Becomes a Cookie header
-G, --get Moves -d data into query parameters
-I, --head Method becomes HEAD
--compressed Adds Accept-Encoding: deflate, gzip
-k, --insecure Recognized, but not applied to the request. TLS verification is a client setting, not a per-request flag

One behavior sits underneath several of those body rows. For a form body, or a request with no Content-Type at all, the data string is parsed into individual fields. For a structured content type such as application/json, it's kept as a single raw string. That's why --data-raw 'raw&stuff' with no Content-Type arrives as two empty fields, while the same flag alongside Content-Type: application/json arrives untouched.

Three conveniences worth knowing about, because they're what usually breaks a naive parser:

  • Scheme-less URLs work. curl api.example.com/users imports as a real URL instead of failing, the same way curl itself would.
  • -XPOST is handled. The concatenated form gets normalized to -X POST.
  • Chrome's escaping is handled. "Copy as cURL" on macOS and Linux emits bash ANSI-C strings for anything with special characters; those get decoded rather than pasted in literally.

Which curl flags don't carry over (and why that's fine)

Flags that describe how curl connects rather than what the request is are ignored on import:

-L / --location, -o / -O, -x / --proxy, --cert / --key / --cacert, --max-time, --connect-timeout, --retry, -v, -T

That's a deliberate split. Redirect behavior, proxies, client certificates, timeouts, and TLS verification are environment concerns. In Bruno they live in the app's preferences and collection settings, set once and applied to every request in the collection, instead of being copy-pasted into each one.

One thing worth knowing if the command used -k. That flag tells curl to skip TLS certificate verification, which is how a command keeps working against a self-signed or expired certificate on a staging box. Since it isn't applied to the imported request, the same request will fail on Send with a certificate error. Turn off SSL/TLS Certificate Verification in Preferences to get the same behavior, bearing in mind that it's an app-wide setting rather than a per-request one.

Whatever the command contained, give the imported request a quick once-over before you rely on it. Read the Body, Headers, and Auth tabs against the original and confirm they say what you expect. Form bodies deserve the closest look, since characters like &, +, and = are structural there rather than literal, so a value carrying one can end up read as syntax instead of data. Sending once against an echo endpoint such as https://echo.usebruno.com or https://httpbin.org/post shows you exactly what left the client, which settles the question faster than reading the request pane ever will.

Getting back out again

This is the half many tools skip, and it's the half that decides whether importing was safe.

Generate curl from any saved request

Open a request in Bruno and click the </> icon in the url bar to open Generate Code, then pick your language. curl is in the list, alongside 35+ others (Python, JavaScript, Go, and so on). It's often the fastest way to answer "what exactly did you send?" in a bug report, a support ticket, or a docs example, outside of sharing a Bruno workspace or collection itself. You build the request in the GUI where iterating is cheap, then hand over a one-liner anyone can run. Full details are in the code generator documentation.

One habit worth keeping: a generated snippet contains whatever your request contains. If the header holds a real token, the curl you just copied holds it too. Swap in a placeholder before it goes anywhere public. Variables are helpful here.

Or skip curl and run the whole collection

Once a request is created, the more useful "back to terminal" move usually isn't exporting a single command. It's having a way of running the collection programmatically:

npm install -g @usebruno/cli
cd my-collection
bru run --env staging

Same requests, same assertions, no GUI required.

For CI you don't have to hand-install anything. On GitHub Actions there's an official action:

- uses: usebruno/bruno-cli-action@v1
  with:
    working-directory: collections/api
    command: 'run --env ci --reporter-junit results.xml'

Everywhere else, including GitLab CI, Jenkins, Azure Pipelines, and a local pre-push hook, there's an official Docker image with bru as the entrypoint. Mount the collection at /bruno and everything after the image name goes straight to bru:

docker run --rm -v "$(pwd):/bruno" usebruno/cli run --env ci --reporter-junit results.xml

Pin both to a specific release rather than a floating tag, so a pipeline that passes today passes for the same reasons next month. The image is also on GHCR as ghcr.io/usebruno/cli, runs non-root, and is multi-arch. Both are covered in detail in the post on the official Docker image and GitHub Action.

All three are the same bru run underneath, which means the command you debug on your laptop is the command CI executes. The GUI becomes the place you author requests; the terminal stays the place you run them. (More on that in the Bruno CLI guide.)

The files are the export

The collection is a folder of plain YAML files. You can open them up and read them if you wanted. git diff shows a changed endpoint as a changed line. A new request shows up in a pull request as a new modification. Whatever you imported this morning is reviewable by a teammate this afternoon, which is the whole premise behind keeping API collections in Git.

From one-off command to something maintained

An imported curl command is a starting point, not a finished test. The upgrade path is short:

  • Import the command using the flow above.
  • Replace the hardcoded host with an environment variable, using /users instead of https://api-staging.example.com/users, so the same request runs against staging and prod. (How to manage variables in Bruno.)
  • Move the token out of the header. Use a secret variable rather than committing an Authorization header that a browser handed you. (Managing secrets in Bruno.)
  • Add an assertion, so the request tells you whether it passed instead of making you read the response.
  • Commit it. Now the request is versioned alongside the code that serves it.
  • Run it in CI with bru run, so a breaking change shows up in a pull request instead of in production.
test("returns 200", function() {
  expect(res.status).to.equal(200);
});

None of these steps takes long, and each one removes a different limitation. Variables free the request from a single environment, secret variables keep credentials out of the repo, an assertion turns a request you have to read into one that reports on itself, and committing it makes any of that reviewable. A curl command in your shell history has none of them.

curl, a cloud GUI, or both?

What you need curl alone Cloud-first GUI Bruno
One-off exploratory request Ideal Fine Fine
Re-run the same request weekly Shell history archaeology Good Good
Import an existing curl command n/a Usually supported natively
Export back to curl n/a Varies Generate Code, 35+ languages
Share with a teammate Paste a command Invite them to a workspace Send a file, or git pull
Review API changes in a PR n/a Usually not visible in Git Plain-text YAML diff
Where secrets live Shell history The vendor's cloud Local secret vars, gitignored
Run in CI Bash scripts you maintain Varies, often paid bru run
Works offline / air-gapped Usually not
Account required No Usually never
License Open source (curl) Typically proprietary Open source (MIT)

The honest summary: curl is not the thing to replace. It's the thing to interoperate with. A visual client earns its place by making the round trip cheap in both directions, and by storing your requests as files you could still read, run, and hand to someone else if you stopped using that client tomorrow.

What to look for in a curl GUI

Whichever client you land on, these are the questions worth asking before you paste anything into it:

  • Does the import handle auth flags, not just headers? -u, --digest, and --ntlm are where most parsers quietly drop information.
  • Does it export back to curl? If the answer is no, everything you import is captive.
  • Where do requests get stored? A file on your disk can be diffed, reviewed, and grepped. A row in someone's database can't.
  • Can it run headless? A GUI that has no CLI equivalent will be re-implemented as bash scripts the moment you need CI.
  • Does it need an account to open? This one decides whether the tool works on a locked-down or air-gapped machine.
  • What happens to secrets? A browser-copied command is full of them. Check where they land before you paste.

Common gotchas

PowerShell's curl isn't really curl

On Windows, curl is an alias for Invoke-WebRequest unless you call curl.exe explicitly. If a command copied from documentation errors out before you can even import it, that's usually why. In Chrome DevTools, choose Copy as cURL (bash) rather than the cmd variant for the cleanest import.

Browser-copied commands carry a lot of noise

"Copy as cURL" from DevTools includes every header the browser sent: sec-ch-ua, sec-fetch-*, Referer, and the whole cookie jar. Import it, then clean up by deleting everything that isn't actually required by the API. Most endpoints need a handful of headers, not twenty.

That cookie is your session

The Cookie header in a browser-copied command is often your live login, and you usually have no way of knowing how long it stays valid. Some expire within the hour; others keep working for months. Both cases cause problems, in opposite directions. A short-lived one makes the request start failing later for reasons that have nothing to do with the request itself. A long-lived one is a working key to your account sitting in a file, still valid for whoever reads it next. Treat it as a real credential either way: replace it with proper auth on the Auth tab, or move it to a secret variable if you genuinely need it.

Import one command at a time

The new request creation From cURL flow creates a single request per paste. If you're facing a file with fifty commands in it, importing an OpenAPI spec, or generating the YAML request files directly, or having AI import for you will be faster than fifty pastes. And if your curl commands started life inside a Postman collection, import the collection instead: the Postman to Bruno migration guide covers scripts, environments, and the conversion errors worth knowing about up front.

FAQ

How do I import a curl command into a GUI API client?

Create a new request in Bruno: sidebar ··· on the collection → New Request → set Request type to From cURL → paste the command → Create. The method, URL, headers, and body are filled in automatically.

Does importing a curl command require an account or cloud sync?

No. Bruno stores collections as files on your filesystem, so the import works offline and nothing is uploaded.

Can I convert a request back into a curl command?

Yes. Open the request, click the </> icon for Generate Code, and select cURL. The same menu generates snippets for 35+ languages.

Is there a curl GUI for Windows?

Yes. Bruno runs on Windows, macOS, and Linux. On Windows, remember that PowerShell's curl is an alias for Invoke-WebRequest, so copy commands as cURL (bash) from DevTools for a clean import.

Does the import support file uploads?

Yes. -F name=@file.png becomes a multipart file part, and --data-binary @payload.json becomes a file body.

What happens to --insecure?

It's recognized but not applied to the imported request. TLS verification is configured in the client's settings rather than per request.

Does it handle Basic auth from -u?

Yes. Credentials land in the request's Auth tab as Basic auth. Add --digest or --ntlm and the mode switches accordingly.

Can I import many curl commands at once?

Not in a single paste. From cURL creates one request at a time. For bulk work, import an OpenAPI spec or a Postman collection, or write the YAML request files directly using AI.

Try it with a command you already have

Grab any curl command lying around, whether from browser DevTools, an API doc, or your shell history, and import it. Then generate the curl back out and compare. If the round trip is lossless in the ways that matter to you, the GUI isn't a one-way door.

Download Bruno free: open source, offline-first, no account required.

Coming from another client? See how Bruno compares against Postman and against Thunder Client.

Related reading: Postman to Bruno migration guide · Bruno CLI · Docker image and GitHub Action · Managing variables · Managing secrets