Veriflow is a simple yet powerful CLI tool for defining and running end-to-end API test flows using a simple JSON configuration file.
{
"baseUrl": "http://localhost:8080",
"flows": {
"user-auth": {
"steps": [
{
"name": "register",
"request": {
"method": "POST",
"path": "/api/auth/v3/register",
"json": {
"email": "user+{{RUN_ID}}@example.com", // RUN ID injectable
"password": "secret"
}
},
"assert": {
"status": 201,
"all": [{ "jsonpath": "$.data.username", "exists": true }]
},
"exports": { "username": "$.data.username" } // Export variable for next requests
},
{
"name": "login", // auth headers/cookies are persisted automatically
"request": {
"method": "POST",
"path": "/api/auth/v3/login",
"json": {
"username": "{{bind:username}}", // Use a defined var
"password": "secret"
}
},
"assert": {
"status": 200,
"all": [{ "jsonpath": "$.data.email", "contains": "{{RUN_ID}}" }]
}
},
// ...
]
}
}
}Because it's the simplest form of targeted flow testing that:
- Treats your API as a black box
- Runs ordered, stateful flows (auth -> action -> verify)
- Passes data between steps (JWTs, IDs, etc.)
- Requires zero code changes in your server
If your API works, Veriflow passes. If it doesn't, it fails. Simple.
Veriflow is production-ready with continuous features and general enhancements.
Expect:
- Minor config changes
- Better error messages
- Improved reporting
Core concepts are stable.
Represents a real user or system journey.
Example:
- user-onboarding
- checkout
- admin-create-user
Flows are ordered. If a step fails, the flow stops.
A step is a single HTTP request with assertions.
Each step defines:
- HTTP method + path
- Request body / headers / files
- Assertions on the response
- Optional exported values for the next steps
You can extract values from a response and reuse them later.
Common use cases:
- JWT tokens
- User ID created in a test to be asserted in the following GET request
Exports are resolved via JSONPath (for JSON responses) or XPath (for XML responses).
Veriflow supports:
- Status code checks
- JSONPath existence / equality / containment (for JSON responses)
- XPath existence / equality / containment (for XML responses)
Assertions are explicit. No magic.
Veriflow automatically detects response content types and uses the appropriate parser:
- JSON: Uses JSONPath for assertions and exports (e.g.,
$.data.user.id) - XML: Uses XPath for assertions and exports (e.g.,
/response/data/user/id)
You can send JSON, XML, or files in your requests using the json, xml, or files fields.
go install github.com/okira-e/veriflow@latestOr download a prebuilt binary from releases.
veriflow initThis creates a veriflow.json at the project root.
veriflow runRuns all flows.
veriflow run user-onboardingRuns a single flow.
- Black-box testing only
- Deterministic execution
- Explicit over implicit
- No framework assumptions
- Human-readable configs
The configuration file (veriflow.json) has the following structure:
{
"projectName": "my-api-tests",
"baseUrl": "http://localhost:3000",
"beforeRun": ["echo 'Starting tests'"],
"afterRun": ["echo 'Tests complete'"],
"flows": [
{
"name": "user-onboarding",
"steps": [...]
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
projectName |
string | No | Name of the project |
baseUrl |
string | Yes | Base URL for all requests |
beforeRun |
string[] | No | Hook: shell commands to run before tests start |
afterRun |
string[] | No | Hook: shell commands to run after tests complete |
flows |
array | Yes | Array of flow objects |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique identifier for the flow |
steps |
array | Yes | Ordered array of step objects |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique identifier within the flow |
request |
object | Yes | HTTP request configuration |
assert |
object | Yes | Assertions to validate the response |
exports |
object | No | Variables to extract from the response |
options |
object | No | Step-level options |
| Field | Type | Required | Description |
|---|---|---|---|
method |
string | Yes | HTTP method (GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD) |
path |
string | Yes | Request path (appended to baseUrl) |
json |
object | No | JSON request body |
xml |
string | No | XML request body (alternative to json) |
files |
object | No | File uploads (map of fieldName to relative file path) |
headers |
object | No | Custom HTTP headers (map of header name to value) |
disableHeaders |
boolean | No | If true, disables automatic cookie handling |
| Field | Type | Required | Description |
|---|---|---|---|
status |
integer | Yes | Expected HTTP status code |
all |
array | No | Array of assertion objects (all must pass) |
| Field | Type | Required | Description |
|---|---|---|---|
jsonpath |
string | No* | JSONPath expression to evaluate (for JSON responses) |
xpath |
string | No* | XPath expression to evaluate (for XML responses) |
exists |
boolean | No | Assert the path exists (true) or doesn't exist (false) |
equals |
string | No | Assert the value equals this string |
isNot |
string | No | Assert the value does NOT equal this string |
contains |
string | No | Assert the value contains this substring |
*Either jsonpath or xpath is required. The appropriate one is used based on the response Content-Type.
A map of variable names to JSONPath or XPath expressions:
{
"user_id": "$.data.user.id",
"token": "$.data.token"
}For XML responses, use XPath:
{
"user_id": "/response/data/user/id",
"token": "/response/data/token"
}| Field | Type | Description |
|---|---|---|
timeout |
string | Request timeout (e.g., "5s", "100ms", "1m"). Default: 30s |
These flags work with all commands:
| Flag | Short | Description |
|---|---|---|
--config |
Config file path (default: veriflow.json) | |
--json-output |
Output machine-readable JSON | |
--no-color |
Disable colored output | |
--non-interactive |
Disable interactive prompts | |
--verbose |
-v |
Enable verbose output |
--silent |
Suppress all output except errors |
The NO_COLOR environment variable is also respected.
Creates a new veriflow.json configuration file.
veriflow init
# Anything can be non interactive with required flags
veriflow init --base-url "https://base.com" --non-interactiveRun flows defined in the configuration.
| Flag | Description |
|---|---|
--base-url |
Override the baseUrl from config |
--skip |
Skip specific flows or steps (repeatable) |
--keep-going |
Continue running even if tests fail |
--show-full-error-response |
Display entire server response payload on error |
--show-hooks |
Print stdout/stderr from beforeRun and afterRun hooks |
--skip-hooks |
Skip executing beforeRun and afterRun hooks |
--show-server-responses |
View responses sent from the server on every request |
Examples:
# Run all flows
veriflow run
# Run specific flow(s)
veriflow run user-onboarding checkout
# Run a specific step
veriflow run user-onboarding/register
# Mixed targets. Specific flows with specific steps
veriflow run user-onboarding checkout/payment
# Run veriflow against a specific config
veriflow run --config veriflow-configs/seeding-flows.json
# Override base URL
veriflow run --base-url http://staging.example.com
# Skip specific tests
veriflow run --skip user-onboarding/register --skip checkout
# Continue on failures
veriflow run --keep-going
# CI mode
veriflow run --json-output --no-color --non-interactiveExport flows or steps to external request formats (e.g., curl commands) for inspection or manual execution.
| Flag | Description |
|---|---|
--to |
Export format (currently only "curl" supported) |
--out |
Write output to file instead of stdout |
--base-url |
Override the baseUrl from config |
Output format:
- Single target: outputs raw curl command
- Multiple targets: outputs JSON array with
stepNameandcurlfields
Note: Bindings like {{RUN_ID}}, {{bind:var}}, etc. are NOT resolved and will appear as-is in the exported output. Exports capture the request structure but not runtime state (cookies, auth tokens, etc.).
Examples:
# Export entire flow as curl commands
veriflow export user-onboarding
# Export specific step
veriflow export user-onboarding/register
# Export multiple targets (outputs JSON array)
veriflow export user-onboarding checkout/payment
# Export to file
veriflow export user-onboarding --out requests.json
# Override base URL
veriflow export user-onboarding --base-url http://staging.example.com
# Explicit format (curl is default)
veriflow export --to curl user-onboarding
# Export step with file upload
veriflow export user-onboarding/upload-avatar
# Output: curl -X 'POST' 'https://api.example.com/users/avatar' -F 'avatar=@test-files/avatar.jpg'Add a new flow to the configuration.
# Interactive
veriflow flow add
# With name
veriflow flow add user-onboarding| Flag | Description |
|---|---|
--no-save |
Modify config in memory only (don't write to disk) |
Delete a flow from the configuration.
# Interactive
veriflow flow delete
# With name
veriflow flow delete user-onboarding| Flag | Short | Description |
|---|---|---|
--yes |
-y |
Skip confirmation prompt |
--no-save |
Modify config in memory only |
Add a new step to a flow.
# Interactive
veriflow step add
# With name
veriflow step add register
# Fully specified (for scripts/CI)
veriflow step add register \
--flow user-onboarding \
--method POST \
--path /auth/register \
--json '{"email": "test@example.com"}' \
--status 201 \
--assert "exists $.data.id" \
--assert "equals $.data.email test@example.com" \
--assert "isNot $.data.stats PENDING" \
--export "user_id $.data.id" \
--non-interactive
# With file upload
veriflow step add upload-avatar \
--flow user-onboarding \
--method POST \
--path /users/avatar \
--file "avatar:test-files/avatar.jpg" \
--status 200
# With custom headers
veriflow step add protected-endpoint \
--flow user-onboarding \
--method GET \
--path /api/protected \
--header "Authorization:Bearer token123" \
--header "X-API-Key:secret" \
--status 200| Flag | Description |
|---|---|
--flow |
Flow this step belongs to |
--method |
HTTP method |
--path |
Request path |
--json |
JSON body (mutually exclusive with --xml and --file) |
--xml |
XML body (mutually exclusive with --json and --file) |
--file |
File upload (format: fieldName:path, mutually exclusive with --json and --xml, repeatable for multiple files) |
--header |
Custom HTTP header (format: Header-Name:value, repeatable) |
--status |
Expected HTTP status code |
--assert |
Assertion expression (repeatable) |
--export |
Export expression (repeatable) |
--no-save |
Modify config in memory only |
Note on file uploads:
- File paths are relative to the config file location (veriflow.json)
- Files must be under 100MB in size
- Multiple files can be uploaded:
--file "doc:file1.pdf" --file "image:file2.jpg" - MIME types are auto-detected from file extensions
Assertion syntax (supports both JSONPath and XPath):
exists <path>
equals <path> <value>
isNot <path> <value>
contains <path> <value>
Where <path> is either:
- JSONPath:
$.data.user.id - XPath:
/response/data/user/id
Export syntax:
<varname> <path>
Examples:
# JSON API
veriflow step add get-user \
--flow users \
--method GET \
--path /api/users/1 \
--status 200 \
--assert "exists $.data.id" \
--assert "equals $.data.name John" \
--export "user_id $.data.id"
# XML API
veriflow step add get-user-xml \
--flow users \
--method GET \
--path /api/users/1 \
--status 200 \
--assert "exists /user/id" \
--assert "equals /user/name John" \
--export "user_id /user/id"
# XML request
veriflow step add create-user \
--flow users \
--method POST \
--path /api/users \
--xml '<user><name>John</name></user>' \
--status 201Bindings are template variables that get replaced at runtime. They can be used in request bodies, paths, and assertion values.
Bindings can be referenced and used from a different flow but they have to be defined first.
| Binding | Description |
|---|---|
{{RUN_ID}} |
Unique identifier for the current test run |
{{RAND_DIGIT}} |
Random single digit (0-9) |
Example:
{
"email": "test-{{RUN_ID}}@example.com",
"code": "{{RAND_DIGIT}}{{RAND_DIGIT}}{{RAND_DIGIT}}{{RAND_DIGIT}}"
}Use {{bind:variable_name}} to reference exported values from previous steps:
{
"name": "register",
"exports": {
"user_id": "$.data.id"
}
}{
"name": "get-user",
"request": {
"path": "/users/{{bind:user_id}}"
}
}Bindings are case-sensitive. {{bind:user_id}} and {{bind:User_Id}} are different.
Hooks are shell commands that run before and after your test suite.
{
"beforeRun": ["docker-compose up -d", "sleep 2"],
"afterRun": ["docker-compose down"]
}beforeRun: Executes before any tests run (e.g., start services, seed database)afterRun: Executes after all tests complete (e.g., cleanup)
Commands run in order. If a beforeRun command fails, tests do not run.
Use --skip-hooks to skip hook execution, or --show-hooks to see their output.
Veriflow automatically maintains cookies across steps within a run. This enables stateful flows like:
- Login (receives session cookie)
- Protected action (cookie sent automatically)
- Logout
To disable automatic cookie handling for a specific step, set disableHeaders: true in the request.
| Code | Meaning |
|---|---|
| 0 | All tests passed |
| 1 | One or more assertions failed |
| Non-zero | Configuration or runtime error |
Feel free to suggest improvements, start a discussion, file an issue, or open a PR.
This project is licensed under the MIT license - see the LICENSE file for details.
