DEV Community

Cover image for Data-Driven API Testing With the Apidog CLI: CSV and JSON Iterations
Hassann
Hassann

Posted on • Originally published at apidog.com

Data-Driven API Testing With the Apidog CLI: CSV and JSON Iterations

You built a checkout API scenario that works locally: three chained requests, assertions on each response, and a green run every time. Then CI needs the same scenario to run against 40 input combinations, load data from a QA-maintained file, and target staging and production with different credentials. Don’t clone the scenario 40 times. Use Apidog CLI iteration data.

Try Apidog today

With Apidog, you build the scenario once in the visual builder, then run it from a terminal with apidog-cli. The key flag is -d, short for --iteration-data. It accepts a CSV file, a JSON file, or a test data set stored in your Apidog project, then runs the scenario once per row/object.

How -d reads iteration data

From apidog run --help:

-d, --iteration-data <path|testDataId>   Define the data which use for iterations (either JSON or CSV)
Enter fullscreen mode Exit fullscreen mode

The argument can be either:

  • a local CSV or JSON file path
  • a test data ID from your Apidog project

For a local CSV:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./test-data/checkout-cases.csv \
  -r cli
Enter fullscreen mode Exit fullscreen mode

This runs test scenario 605067 once per CSV row. On each iteration, Apidog binds the row values to variables used in the scenario.

For JSON, use the same flag:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./test-data/checkout-cases.json \
  -r cli
Enter fullscreen mode Exit fullscreen mode

You do not need a separate format flag. Keep CSV headers or JSON keys aligned with the variable names used in your requests and assertions.

CSV data: best for flat test cases

Use CSV when each case fits naturally into columns.

Example checkout-cases.csv:

sku,quantity,coupon,expected_status,expected_total
DESK-01,1,SAVE10,200,89.10
DESK-01,0,SAVE10,422,0
CHAIR-09,3,,200,447.00
DESK-01,1,EXPIRED,410,0
GHOST-99,1,SAVE10,404,0
Enter fullscreen mode Exit fullscreen mode

In your Apidog scenario, reference those columns as variables:

{
  "sku": "{{sku}}",
  "quantity": {{quantity}},
  "coupon": "{{coupon}}"
}
Enter fullscreen mode Exit fullscreen mode

Then assert against row-specific expectations:

status == {{expected_status}}
total == {{expected_total}}
Enter fullscreen mode Exit fullscreen mode

Each CSV row becomes one iteration:

Row Case
1 Valid coupon
2 Invalid quantity
3 No coupon
4 Expired coupon
5 Unknown SKU

The empty coupon value in row three is passed as an empty string, which makes it useful for no-coupon coverage.

JSON data: best for nested payloads

Use JSON when your test input contains nested objects or arrays.

Example checkout-cases.json:

[
  {
    "label": "valid order, two items",
    "order": {
      "items": [
        { "sku": "DESK-01", "qty": 1 },
        { "sku": "CHAIR-09", "qty": 2 }
      ],
      "shipping": { "country": "US", "method": "ground" }
    },
    "expected_status": 200
  },
  {
    "label": "unshippable country",
    "order": {
      "items": [{ "sku": "DESK-01", "qty": 1 }],
      "shipping": { "country": "ZZ", "method": "ground" }
    },
    "expected_status": 422
  }
]
Enter fullscreen mode Exit fullscreen mode

In the scenario, reference the object directly:

{
  "order": {{order}}
}
Enter fullscreen mode Exit fullscreen mode

Then assert against:

status == {{expected_status}}
Enter fullscreen mode Exit fullscreen mode

The label field is useful for reports. Instead of debugging “iteration 2,” you see a named case like unshippable country.

For JSONPath-based assertions and variable extraction, see setting assertions and extracting variables from a JSON response.

Practical rules for iteration files

Follow these rules to avoid false-positive runs in CI:

  • Match names exactly. A CSV header named qty will not bind to {{quantity}}.
  • Quote CSV fields that contain commas.
  • Use JSON instead of CSV for nested payloads.
  • Store expected results in the data file, not hardcoded in the scenario.
  • Commit local data files next to the tests when they should version with the code.
  • Keep credentials out of CSV and JSON files.

A good data file contains test inputs and expected outputs, not environment-specific secrets.

Run from a stored Apidog data set

Instead of passing a file path, you can pass a stored test data ID:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d 38291 \
  -r cli
Enter fullscreen mode Exit fullscreen mode

This tells the CLI to fetch data set 38291 from the Apidog project.

Use this when:

  • QA owns and maintains the case table in Apidog
  • the data should not live in your repository
  • multiple team members need a shared editable data source

Use a committed CSV or JSON file when:

  • the data must be reviewed in pull requests
  • the test data should be pinned to a commit
  • changes to test cases should move with code changes

Run offline from an exported scenario file

You can also export a test case from Apidog and run the exported file directly:

apidog run ./checkout.apidog-cli.json -r cli,html
Enter fullscreen mode Exit fullscreen mode

In this mode, the first argument is the exported test file. You do not need -t or -e.

You can still add iteration data:

apidog run ./checkout.apidog-cli.json \
  -d ./checkout-cases.csv \
  -r cli,junit
Enter fullscreen mode Exit fullscreen mode

This is useful for:

  • locked-down CI runners that cannot fetch scenarios from Apidog cloud
  • reproducible test runs from a frozen scenario snapshot
  • keeping the exact scenario definition versioned with the repo

For setup details, see the Apidog CLI installation guide and the complete Apidog CLI reference.

Combine -d with -n

-n controls iteration count:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -n 50 \
  -r cli
Enter fullscreen mode Exit fullscreen mode

When you provide -d, the data file usually controls the number of iterations. For example, 40 CSV rows means 40 scenario runs.

Use -n when:

  • you want to repeat a scenario without a data file
  • you are running a simple soak test
  • you intentionally want to repeat the whole data-driven run

For most data-driven API tests, leave -n out and let the data file define the run count.

Pass environment-specific values at runtime

Use --env-var and --global-var for values that change between environments:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./checkout-cases.csv \
  --env-var "base_url=https://staging.internal" \
  --global-var "api_key=$RUNTIME_API_KEY" \
  -r cli,junit
Enter fullscreen mode Exit fullscreen mode

This keeps responsibilities separate:

Data source Contains
CSV/JSON test inputs and expected outputs
Apidog environment reusable environment config
CI secrets tokens, API keys, credentials
CLI overrides per-run values

Do not put secrets in iteration data. Pass them from your CI secret store instead.

Generate per-iteration reports

Use reporters to make failures visible in CI:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./checkout-cases.csv \
  -r cli,junit \
  --out-dir ./test-reports
Enter fullscreen mode Exit fullscreen mode

Common reporter options:

Reporter Use case
cli readable terminal output
junit CI test results and dashboards
html browsable report artifact
json structured output for post-processing

--out-dir controls where report files are written.

For CI, cli,junit is a practical default.

Continue after failed iterations

By default, a run can stop on the first failed assertion. For data-driven tests, you often want all failures in one report.

Use:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./checkout-cases.csv \
  --on-error continue \
  -r cli,junit \
  --out-dir ./test-reports
Enter fullscreen mode Exit fullscreen mode

--on-error continue runs all rows even if some fail. The command still exits non-zero if any iteration fails, so it remains a valid CI gate.

Useful modes:

Mode Behavior
end stop on first error
continue run all iterations and report all failures
ignore continue and ignore errors

Use continue for broad data tables. Use end for quick smoke tests.

Debug variable binding issues

The most dangerous failure mode is a green run that did not actually bind data.

Example symptoms:

  • request body still contains {{sku}}
  • values are blank even though the CSV has data
  • assertions pass against default or empty payloads

Run with --verbose:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./checkout-cases.csv \
  --verbose \
  -r cli
Enter fullscreen mode Exit fullscreen mode

Then inspect the actual request body sent for each iteration.

Common causes:

  1. Header and variable mismatch

CSV:

   product_sku,quantity
   DESK-01,1
Enter fullscreen mode Exit fullscreen mode

Request:

   {
     "sku": "{{sku}}"
   }
Enter fullscreen mode Exit fullscreen mode

Fix by renaming product_sku to sku, or update the scenario to use {{product_sku}}.

  1. Unquoted CSV comma

Bad:

   sku,note,expected_status
   DESK-01,priority, customer,200
Enter fullscreen mode Exit fullscreen mode

Better:

   sku,note,expected_status
   DESK-01,"priority, customer",200
Enter fullscreen mode Exit fullscreen mode
  1. Wrong path in CI

The file may exist locally but not in the CI working directory. Add a check before the test run:

   ls -la ./test-data
   cat ./test-data/checkout-cases.csv
Enter fullscreen mode Exit fullscreen mode

If a data-driven run passes suspiciously, inspect one verbose request before trusting the result.

Add the run to GitHub Actions

Here is a full GitHub Actions workflow:

name: Data-driven API tests

on: [pull_request]

jobs:
  api-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Apidog CLI
        run: npm install -g apidog-cli

      - name: Run data-driven scenario
        env:
          APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
        run: |
          apidog run --access-token $APIDOG_ACCESS_TOKEN \
            -t 605067 -e 1629989 \
            -d ./test-data/checkout-cases.csv \
            --on-error continue \
            -r cli,junit \
            --out-dir ./test-reports

      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: api-test-report
          path: ./test-reports
Enter fullscreen mode Exit fullscreen mode

What this does:

  • installs the CLI with npm
  • reads APIDOG_ACCESS_TOKEN from GitHub Secrets
  • runs the scenario once per CSV row
  • continues after failed rows
  • writes CLI and JUnit reports
  • uploads reports even when the test fails

To switch to JSON:

-d ./test-data/checkout-cases.json
Enter fullscreen mode Exit fullscreen mode

To switch to a stored Apidog data set:

-d 38291
Enter fullscreen mode Exit fullscreen mode

Everything else can stay the same.

The same pattern works in GitLab CI, Jenkins, CircleCI, and other systems:

  1. Install Node.js.
  2. Install apidog-cli.
  3. Expose APIDOG_ACCESS_TOKEN.
  4. Run apidog run with -d.
  5. Publish the report directory as an artifact.

For more CI detail, see automating API tests in GitHub Actions. For the full CLI flag surface, see the complete Apidog CLI guide.

Recommended workflow

Start small:

  1. Build one scenario in Apidog.
  2. Replace hardcoded request values with variables.
  3. Put expected results in assertions using variables.
  4. Create a CSV with:
    • one happy path
    • one known failure
    • one boundary case
  5. Run locally with -d.
  6. Add --verbose if bindings look wrong.
  7. Add more rows as bugs and edge cases appear.
  8. Move the command into CI with --on-error continue and junit.

The scenario stays small. Coverage grows by adding rows.

Example local run:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./test-data/checkout-cases.csv \
  --on-error continue \
  -r cli,junit \
  --out-dir ./test-reports
Enter fullscreen mode Exit fullscreen mode

Example staging run:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1629989 \
  -d ./test-data/checkout-cases.csv \
  --env-var "base_url=https://staging.internal" \
  --global-var "api_key=$STAGING_API_KEY" \
  --on-error continue \
  -r cli,junit
Enter fullscreen mode Exit fullscreen mode

Example production smoke run:

apidog run --access-token $APIDOG_ACCESS_TOKEN \
  -t 605067 -e 1630001 \
  -d ./test-data/checkout-smoke.csv \
  --global-var "api_key=$PROD_API_KEY" \
  -r cli,junit
Enter fullscreen mode Exit fullscreen mode

For a comparison of tool choices, see which tool to choose for data-driven API testing with CSV or JSON. For a CLI comparison from the Postman ecosystem, see Apidog CLI vs Newman.

Frequently asked questions

Can -d take both a file path and a stored data set?

No. Pass one value per run: either a local CSV/JSON path or a stored test data ID.

Use a file path when the data should version with the repo. Use a stored data ID when the team maintains the table inside Apidog.

Do I have to specify CSV or JSON?

No. Use the same -d flag for both.

-d ./cases.csv
Enter fullscreen mode Exit fullscreen mode

or:

-d ./cases.json
Enter fullscreen mode Exit fullscreen mode

Keep CSV headers or JSON keys matched to your scenario variables.

What happens if I use -d and -n together?

The data file usually defines the iteration count. Use -n only when you intentionally want extra repeats or when you are running without a data file.

Why did my data-driven run pass without testing the right input?

Most likely the data did not bind. Check for:

  • mismatched variable names
  • malformed CSV rows
  • wrong file paths in CI

Run once with:

--verbose
Enter fullscreen mode Exit fullscreen mode

Then inspect the actual request payload.

How do I keep credentials out of the data file?

Pass credentials at runtime:

--global-var "api_key=$RUNTIME_API_KEY"
Enter fullscreen mode Exit fullscreen mode

or:

--env-var "token=$TOKEN"
Enter fullscreen mode Exit fullscreen mode

Store the actual values in your CI secret manager.

Can I run the same data against staging and production?

Yes. Keep the scenario and data fixed, then switch the environment with -e or override environment variables at runtime.

Wrapping up

-d is the core flag for data-driven runs in Apidog CLI. It can read a local CSV file, a local JSON file, or a stored Apidog test data set. Pair it with --on-error continue, cli,junit reporters, and runtime variable overrides to make it CI-friendly.

Build the scenario once. Add cases as rows. Let CI run the whole table on every pull request.

Download Apidog, point apidog run at your first data file, and turn one scenario into repeatable API coverage.

Top comments (0)