View all services
Talk to QA Advisor
Free tool · Automation and API

cURL converter for API tests

Paste a curl command from your terminal, the API docs, Postman or the browser, and get the same request as Python, JavaScript or a ready-to-run API test with a status assertion. Tokens and cookies can move to environment variables, and the request is never sent.

cURL converter

Paste a curl command, from your terminal, API docs, Postman or the browser's "Copy as cURL", and get the same request as code or as an API test. Nothing is sent anywhere: the request is never run.

Read as bash: POST request, 2 headers, JSON body

Examples

Tokens, cookies, API keys and -u passwords are read from the environment, so they stay out of the test code.

sec-ch-ua, sec-fetch-*, priority, accept-encoding and cache validators that a browser adds.

The status code the generated tests assert.

Generate

Python with the requests library. Install it with pip install requests.

request.py
# Reads API_TOKEN from the environment.
import os

import requests

url = 'https://api.example.com/v1/orders'
headers = {
    'Authorization': 'Bearer ' + os.environ['API_TOKEN'],
    'Content-Type': 'application/json',
}
json_data = {
    'customerId': 381,
    'items': [
        {
            'sku': 'TSHIRT-M',
            'qty': 2,
        },
    ],
    'currency': 'EUR',
}

response = requests.post(url, headers=headers, json=json_data)

print(response.status_code)
print(response.text)

Set before running: API_TOKEN.

What curl would send

POSThttps://api.example.com/v1/orders

Headers (2)

NameValue
AuthorizationBearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJxYSJ9.c2lnbmF0dXJlenv API_TOKEN
Content-Typeapplication/json

Body: JSON

{
  "customerId": 381,
  "items": [
    {
      "sku": "TSHIRT-M",
      "qty": 2
    }
  ],
  "currency": "EUR"
}

Settings

Redirects
Not followed by curl

Notes (1)

  • Infocurl does not follow redirects without -L, but the generated code does, because that is the default in each library.
Ready
How to use

How to use the cURL converter

  1. 1Paste a curl command, or pick an example. Chrome exports in both "Copy as cURL (bash)" and "Copy as cURL (cmd)" format are recognised, line continuations included.
  2. 2Check the parsed request on the right: method, URL, query parameters, headers and body, plus a note for every flag that has no equivalent in code.
  3. 3Choose the output: Python, pytest, JavaScript fetch, Playwright, Cypress, REST Assured or k6. Set the status code the test should expect.
  4. 4Keep "Move secrets to environment variables" on, then copy or download the code and set the variables it lists before you run it.

Getting a curl command from DevTools or Postman

The quickest way to reproduce a request exactly is to copy it from the place it already works. In Chrome or Edge, open DevTools, select the Network panel, right-click the request and choose Copy, then Copy as cURL. On Windows, Chrome offers two versions: "Copy as cURL (bash)" for Git Bash, WSL or a Mac, and "Copy as cURL (cmd)" for the Windows command prompt, which escapes characters with ^. Firefox has the same menu with POSIX and Windows variants. This converter reads both formats.

In Postman, open the request and select the code icon in the right-hand sidebar, then choose cURL from the list. API documentation and support tickets often include curl examples too, and those convert in the same way.

A browser export carries everything the browser sent: session cookies, sec-ch-ua and sec-fetch headers, cache validators such as If-None-Match, and an accept-language. Most of that does not belong in an API test. The option to leave out browser-only headers removes the ones that describe the browser rather than the request, and the parsed view shows what was left out and why, so nothing disappears silently.

What each part of a curl command maps to

curl is quiet about some of what it sends, which is why a hand-written conversion often differs from the original. These are the parts that matter and how each one appears in code.

  • URL

    The address, anywhere on the command line. Its query string becomes separate parameters where the library supports them, such as params= in requests or qs in Cypress.

  • -X, --request

    The method. Without it, curl uses GET, or POST as soon as there is a body from -d, --json or -F, PUT with -T, and HEAD with -I.

  • -H, --header

    One header per flag. -H "Name:" with nothing after the colon removes a header curl would otherwise add.

  • -d, --data-raw, --data-binary, --json

    The body. Each implies POST, and every form except --json sets Content-Type: application/x-www-form-urlencoded unless you set your own.

  • -F, --form

    A multipart/form-data body. name=value is a text field, name=@file uploads a file, and name=<file sends the file content as a text field.

  • -u, --user

    Basic authentication, sent with the first request. The value is split at the first colon, so a password may contain colons but a user name may not.

  • -b, --cookie

    Cookies as name=value pairs. A value without an equals sign is a file name curl reads cookies from.

  • -G, --get

    Moves everything given with -d or --data-urlencode into the query string and sends a GET.

  • -k, -L, -m

    Skip certificate checks, follow redirects and limit the total time in seconds. curl does not follow redirects without -L, but most HTTP libraries do by default.

-d, --data-raw, --data-binary and --json are not the same

All four send a request body, and the differences explain a lot of "it works in curl but not in my test". -d, also written --data or --data-ascii, sends the text as given, but when the value starts with @ it reads a file and strips carriage returns, line breaks and null bytes from it. --data-binary @file sends the file exactly as it is. --data-raw is -d without the special meaning of @, which is why browsers use it: a body that happens to start with @ is still sent as text.

Several -d flags are joined with &, so -d a=1 -d b=2 sends a=1&b=2. None of them encode anything. If a value contains a space or an ampersand, use --data-urlencode, which percent-encodes the value: everything after the first =, or the whole argument when there is no =.

The common mistake is sending JSON with -d and no Content-Type header. curl then labels the JSON as application/x-www-form-urlencoded, and many frameworks reject it or parse an empty body. --json, added in curl 7.82.0, avoids this: it sends the data as given and sets both Content-Type: application/json and Accept: application/json. The converter warns when a JSON body would go out with the form type.

The same JSON body, sent correctly two ways
curl https://api.example.com/v1/orders \
  -H 'Content-Type: application/json' \
  --data-raw '{"sku": "MUG-01", "qty": 1}'

curl https://api.example.com/v1/orders \
  --json '{"sku": "MUG-01", "qty": 1}'

Turning a request into a test with assertions

A converted request on its own is a script: it proves the call can be made, not that the answer is right. Every test output here includes an assertion on the status code, which is the minimum. Set the expected status before you copy, for example 201 for a create or 404 for a deliberate miss. Cypress fails a cy.request on any status outside 2xx and 3xx, so for an expected 4xx or 5xx the code adds failOnStatusCode: false.

Then assert on what the response means. Check the fields the client relies on and their types, not only that a body came back: an order total that becomes a string, or a list that comes back empty, passes a status check. For data that changes on every call, such as IDs and timestamps, assert the format rather than the value.

In k6, a failed check is recorded but does not stop the script or fail the run on its own. Add a threshold such as checks: ['rate==1.0'] to the options when the check should fail the test.

Keeping secrets out of test code

A copied request carries live credentials: a bearer token, a session cookie, an API key. Pasted into a test file, they end up in version control, in CI logs and in the history of every branch. With "Move secrets to environment variables" on, the converter replaces Authorization, Cookie, API-key and token headers, secret-looking query parameters such as api_key, and -u credentials with lookups in the idiom of each language: os.environ in Python, process.env in Node and Playwright, cy.env in Cypress, System.getenv in Java and __ENV in k6.

The scheme stays in the code and only the credential moves, so Authorization: Bearer becomes "Bearer " plus API_TOKEN. The list under the code shows which variables to set. In CI, store them as secrets in your pipeline. Locally, a .env file that is listed in .gitignore keeps them off the repository.

os.environ[...] in Python raises an error when a variable is missing, which is what a test should do: fail at once, rather than send an empty token and report a confusing 401. Cypress reads operating system variables only when they carry the CYPRESS_ prefix, so API_TOKEN is set as CYPRESS_API_TOKEN.

API tests in Playwright or a dedicated API tool

Playwright's request fixture is a good fit when the team already writes Playwright tests. API calls can set up data before a UI test, check the backend after one, and run in the same report and the same CI job. The fixture uses the baseURL and extraHTTPHeaders from the Playwright config, and page.request shares cookies with the browser context, so an API call can act as the user who is logged in on the page.

A dedicated tool earns its place when the API suite is the main product. REST Assured suits Java teams that want API tests next to the service code, with JSON path and schema assertions. Postman and its collections suit exploratory work and sharing requests with developers. k6 is for load: the same request run by many virtual users, with thresholds on latency and error rate rather than a single pass or fail.

Whichever you choose, keep the request and its assertions together and parameterise the base URL, so the same test runs against a local build, staging and production smoke checks.

QAble service

API testing

QAble turns the requests your product depends on into API test suites that check status, structure and data, keep credentials out of the code and run on every build.

Explore the service
FAQ

Frequently asked questions

Is my curl command sent anywhere?

No. The command is parsed and converted entirely in your browser, and the request itself is never run, so tokens and cookies in a copied command stay on your machine.

How do I convert curl to Python?

Paste the command and choose Python. The output uses the requests library, with params= for the query string, json= for a JSON body, data= for a form and files= for multipart uploads. Choose pytest instead to get the same request wrapped in a test function with an assertion on the status code.

Which curl options are supported?

The ones that change the request: -X, -H, -d, --data-raw, --data-binary, --data-urlencode, --json, -F, -u, -b, -A, -e, -G, -I, -T, -k, -L, -m, --connect-timeout, -x, --url and --url-query, in short, long and combined forms such as -sSL. Output options such as -o, -s, -v and -w only affect curl itself, so they are listed as notes. Unknown options become warnings rather than errors.

Why does my converted request get a 304 or a 401?

A 304 Not Modified usually comes from If-None-Match or If-Modified-Since headers that the browser added from its cache; leave out browser-only headers to drop them. A 401 or 403 usually means the token or session cookie in the copied command has expired. Copy a fresh request, or set a current token in the environment variable.

Does it work with curl in Windows PowerShell?

In Windows PowerShell 5.1, curl is an alias for Invoke-WebRequest, so type curl.exe to run the real curl. Chrome's "Copy as PowerShell" produces Invoke-WebRequest code, not curl, so use "Copy as cURL (cmd)" or "Copy as cURL (bash)" for this converter. Both formats are recognised.

What happens to files in -d @file or -F [email protected]?

The browser cannot read files from your disk, so the generated code refers to them by path: open() in Python, fs in Node and Playwright, cy.readFile in Cypress, new File in Java and open() in k6. Keep the files next to the test, or change the paths.

Need the edge cases found for you?

QAble testers work through the inputs, payment paths and data that break products in production, before your users find them.

Talk to QA Advisor