openrun is a spec-driven Go library and CLI for running HTTP requests directly
from an OpenAPI 3.x document.
Validate a spec, discover operations, generate request values, send one request, or run a stateful collection with assertions and response extraction. No server instrumentation or generated client is required.
Status:
v0.1.0is the first tagged release. The project is usable for local development and API smoke tests; interfaces may still evolve beforev1.0.0.
- OpenAPI 3.x YAML/JSON loading and runner-focused validation.
- Operation selection by
operationIdorMETHOD /path. - Generated path, query, header, auth, and JSON body values.
- Single requests,
curloutput, run-all, and YAML/JSON collections. - Status, header, JSONPath, body, regex, and response-schema assertions.
- Retries, per-attempt timeouts, bounded parallel execution, and JSON reports.
- Stateful workflows with dependencies, conditions, extraction, and safe hooks.
- Named environments and Postman Collection v2.1 import/export.
openrun requires Go 1.22 or newer:
go install github.com/dmedovich/openrun/cmd/openrun@v0.1.0
go install github.com/dmedovich/openrun/cmd/openrun-mcp@v0.1.0Or build from source:
git clone https://github.com/dmedovich/openrun.git
cd openrun
make verify
./openrun version
./openrun-mcp -VStart with any OpenAPI 3.x document:
openrun validate -spec openapi.yaml
openrun list -spec openapi.yaml
openrun template -spec openapi.yaml -op createUser -json
openrun curl -spec openapi.yaml -op createUser -base http://localhost:8080
openrun run -spec openapi.yaml -op createUser -base http://localhost:8080 -expect 201-op accepts an operationId or a selector such as POST /users/{id}.
For daily use, initialize project defaults once and pass the operation as a
positional argument:
openrun init
openrun run createUser -b http://localhost:8080 -d '{"email":"alice@example.com"}'
openrun run getUser -p id=42openrun init creates .openrun.yaml. Commands discover it from the current
directory or a parent directory, so a typical project can keep the spec,
environment, and base URL out of every invocation:
spec: docs/openapi.yaml
env_file: environments.yaml
environment: localExplicit flags always override project defaults. The long forms remain available for scripts, while common interactive overrides have short aliases:
| Short | Long | Purpose |
|---|---|---|
-s |
-spec |
OpenAPI spec path |
-b |
-base |
Base URL |
-e |
-environment |
Named environment |
-p |
-path |
Path parameter |
-q |
-query |
Query parameter |
-H |
-H |
Header |
-a |
-auth |
Security-scheme credential |
-d |
-body |
JSON body or @file.json |
Flags may appear before or after the positional operation. Running openrun run
in a terminal without an operation opens a filterable operation selector.
Generated request values can be overridden without editing the spec:
openrun run \
-spec openapi.yaml \
-op createUser \
-base http://localhost:8080 \
-path id=42 \
-query notify=true \
-H X-Trace-ID=local-run \
-body '{"email":"alice@example.com"}' \
-expect 201 \
-validate-responseUse -body @body.json to load the JSON body from a file.
| Command | Purpose |
|---|---|
init |
Create .openrun.yaml project defaults. |
validate |
Load and validate runner-facing OpenAPI fields. |
list [filter] |
List or filter methods, paths, tags, and operation IDs. |
template |
Generate a request template for one operation. |
export |
Export one or every generated template as JSON. |
curl |
Print an equivalent curl command without sending it. |
run |
Run one operation or a collection. |
run-all |
Run every operation in the spec. |
import-postman |
Convert Postman Collection v2.1 JSON to openrun files. |
export-postman |
Convert a simple openrun collection to Postman v2.1. |
Run openrun without arguments for the compact command reference.
If an operation declares OpenAPI security schemes, provide credentials by
scheme name and openrun will place them according to the spec:
openrun run \
-spec openapi.yaml \
-op getProfile \
-base http://localhost:8080 \
-auth bearerAuth="$TOKEN" \
-auth apiKey="$API_KEY"Supported auto-auth schemes are:
type: http, including bearer.type: apiKeyinheader,query, orcookie.
OAuth, OpenID Connect, and mutual TLS specs are accepted, but credentials for those schemes are not applied automatically yet.
openrun pairs naturally with
apiary: generate the contract from Go,
then immediately exercise it.
go install github.com/yaop-labs/apiary/cmd/apiary@latest
apiary -out openapi.yaml ./...
openrun validate -spec openapi.yaml
openrun run -spec openapi.yaml -op userCreateUser -base http://localhost:8080 -expect 201openrun remains framework-independent: it reads the OpenAPI contract and does
not inspect a Gin router or application process at runtime.
openrun-mcp exposes the same library operations to MCP clients over stdio.
It lets an agent inspect a spec, construct or send requests, run stateful
collections, and synchronize an apiary-generated contract without parsing CLI
text.
| Tool | Purpose | Effect |
|---|---|---|
validate_spec |
Validate runner-facing OpenAPI fields | Read-only |
list_operations |
Search by method, path, ID, tag, or summary | Read-only |
template_operation |
Build an editable request template | Read-only |
curl_operation |
Produce curl without sending a request | Read-only |
run_operation |
Send one generated request | External HTTP |
run_collection |
Execute an openrun collection | External HTTP |
generate_openapi |
Regenerate the configured spec with apiary | Writes spec |
check_openapi_stale |
Check source/spec synchronization | Read-only |
diff_openapi |
Return saved-versus-generated unified diff | Read-only |
Configure a client to launch the binary from the API project:
{
"mcpServers": {
"openrun": {
"command": "openrun-mcp",
"args": ["-config", ".openrun.yaml"]
}
}
}The configured working directory is the project root containing
.openrun.yaml. A minimal project configuration is:
spec: docs/openapi.yaml
env_file: environments.yaml
environment: localExample tool inputs:
list_operations:
{"filter":"users"}run_operation:
{
"operation": "getUser",
"path": {"id":"42"},
"expect": "2xx",
"validate_response": true
}run_collection:
{"collection":"smoke.collection.yaml","timeout":"10s","retries":2}An agent can use the tools as one deterministic workflow:
check_openapi_stale
→ diff_openapi when stale
→ generate_openapi after approval
→ validate_spec
→ list_operations
→ run_operation or run_collection
The last three tools invoke the existing apiary CLI. generate_openapi
explicitly writes the configured spec, while stale-check and diff are read-only.
The diff tool generates the current document through apiary -out - and compares
it without overwriting the saved file.
By default apiary is resolved from PATH and reads its own apiary.yaml.
Optional overrides belong in .openrun.yaml:
spec: docs/openapi.yaml
apiary:
command: apiary
workdir: .
patterns: ["./internal/handler/...", "./internal/dto/..."]
args: ["-title", "My API", "-version", "1.0.0"]MCP-provided file paths are restricted to the project root. Tool annotations mark HTTP execution as an external action and OpenAPI generation as a destructive file write so clients can apply their approval policy.
Environment files provide a base URL, reusable variables, and auth credentials:
base_url: http://localhost:8080
vars:
user_id: "42"
email: alice@example.com
auth:
bearerAuth: local-token
apiKey: local-api-keyUse them with single requests, curl generation, collections, or run-all:
openrun run -spec openapi.yaml -env local.yaml -op getUser -path id="{{user_id}}"One file can also contain named environments while preserving the same selected environment shape:
default: local
environments:
local:
base_url: http://localhost:8080
vars:
email: local@example.com
auth:
bearerAuth: local-token
staging:
base_url: https://staging.example.com
vars:
email: staging@example.com
auth:
bearerAuth: staging-tokenOmit -environment to use default, or select one explicitly:
openrun run -spec openapi.yaml -env environments.yaml -environment staging -collection smoke.yamlNamed selection is supported by curl, run, and run-all. Files without a
default require an explicit name. Single-environment files remain fully
backward compatible.
A collection is a small YAML/JSON file with runnable request cases:
name: smoke
mode: sequential
headers:
X-Test-Suite: smoke
query:
locale: en
auth:
bearerAuth: local-token
requests:
- name: create user
id: create_user
operation: createUser
body:
email: "{{email}}"
username: alice
assert:
status: 2xx
schema: true
headers:
Content-Type: application/json
json:
$.username: alice
body_contains:
- alice
body_matches:
- '"id":[0-9]+'
extract:
user_id: $.id
- name: get user
operation: getUser
depends_on:
- create_user
path:
id: "{{user_id}}"
assert:
status: 200-299
schema: true
json:
$.id: "{{user_id}}"Assertions support exact status codes (201), status classes (2xx), inclusive
ranges (200-299), exact response headers, JSON equality using paths such as
$.user.id and $.items[0].id, body substring checks, and regular expressions.
The older expect: 201 field remains supported as an exact status assertion.
Set schema: true to validate the response selected by status code and
Content-Type against its OpenAPI schema. Single operations and run-all can
enable the same check with -validate-response.
The built-in validator currently covers the runner-facing JSON Schema subset:
types (including integer vs number), required properties, objects, arrays,
enums, local component refs, additionalProperties, allOf, anyOf, and
oneOf. More advanced constraints remain on the roadmap.
A passed request can atomically extract JSON values into runtime variables for later sequential requests:
requests:
- name: create user
operation: createUser
assert:
status: 201
extract:
user_id: $.id
access_token: $.auth.token
- name: get created user
operation: getUser
path:
id: "{{user_id}}"
headers:
Authorization: "Bearer {{access_token}}"Strings are stored directly; numbers, booleans, objects, arrays, and null are
stored as compact JSON. Extraction is applied only when every assertion and
every extraction for the request succeeds. Reports expose extracted variable
names but not their potentially sensitive values. The caller's environment is
copied and is never mutated. Collections with extraction must use sequential
mode.
Sequential requests can declare dependencies by stable request id. A request
is skipped when any dependency failed or was skipped. Dependencies must refer
to earlier requests, which prevents cycles and keeps execution deterministic:
requests:
- id: create_user
operation: createUser
extract:
user_id: $.id
- id: load_user
operation: getUser
depends_on: [create_user]
path:
id: "{{user_id}}"Requests can also be skipped from environment or extracted runtime variables:
skip_if:
variable: environment
equals: productionThe predicate can contain exactly one of equals, not_equals, or missing.
For example, missing: true skips when the variable does not exist. Skipped
requests are reported separately, do not count as failures, and expose the
variable name but never its value. depends_on is intentionally unavailable in
parallel mode.
Reusable request defaults can be declared once at collection level:
name: smoke
headers:
X-Test-Suite: smoke
X-Tenant: "{{tenant_id}}"
query:
locale: en
auth:
bearerAuth: "{{access_token}}"
requests:
- operation: listUsers
- operation: getUser
headers:
X-Test-Suite: focusedMerge precedence is OpenAPI-generated values, collection defaults, request
overrides, then CLI overrides. Shared values are expanded before every request,
so sequential collections can reference variables extracted by earlier
requests. CLI -H, -query, and -auth apply to every collection request;
operation-specific -path and -body require a single -op.
Sequential requests can run small declarative hooks without executing scripts or arbitrary code:
pre_request:
- require: access_token
- set:
variable: request_id
value: $uuid
- unset: stale_value
when:
variable: environment
equals: local
post_response:
- set:
variable: last_status
value: $response.status
- set:
variable: trace_id
value: $response.header.X-Trace-ID
- set:
variable: created_id
value: $response.json.$.idSupported safe values are $uuid, $timestamp, $unix, $response.status,
$response.body, $response.header.<name>, and
$response.json.<JSONPath>. Ordinary values support {{variable}}
substitution; prefix a literal dollar with $$. Each action contains exactly
one of set, unset, or require, and may use a when condition with the same
equals, not_equals, or missing predicates as skip_if.
Hook mutations, response extraction, and post-response updates form one request transaction: they become visible to following requests only when the request and every hook succeeds. Hooks are intentionally unavailable in parallel mode.
Run it:
openrun run -spec openapi.yaml -env local.yaml -collection smoke.yaml -report-jsonEach request can override timeout and retry behavior:
requests:
- operation: createUser
timeout: 5s
retries: 2
retry_backoff: 200ms
retry_statuses: [429, 500, 502, 503, 504]Retries use exponential backoff and always rebuild the request body. Transport
errors are retried as well. CLI-wide defaults are available through -timeout,
-retries, and -retry-backoff; collection request values override them.
Failed collection results include the final URL, attempt count, and a compact
response body in text and JSON reports.
Collections run sequentially by default. Independent requests can use a bounded worker pool while keeping report results in file order:
name: read-only checks
mode: parallel
concurrency: 4
requests:
- operation: health
- operation: listProducts
- operation: getVersionCLI flags -parallel -concurrency 4 override execution mode for a collection
or run-all. Keep stateful request chains sequential: response extraction,
dependencies, and hooks are intentionally unavailable in parallel mode.
run-all builds a temporary collection from every operation in the spec:
openrun run-all -spec openapi.yaml -env local.yaml -expect 200Simple Postman Collection v2.1 files can be converted to spec-driven openrun collections. Every request is matched to an OpenAPI operation by HTTP method and path; an unmatched or ambiguous URL is reported as an error.
openrun import-postman \
-spec openapi.yaml \
-in postman.json \
-out smoke.collection.yaml \
-env-out local.env.yamlPostman collection variables are written separately through -env-out so
their values are never silently discarded. Nested folders are flattened into
request names. Headers, path and query values, raw JSON bodies, bearer auth,
literal basic auth, and header/query API keys are supported.
See examples/simple.postman.json for a small
importable collection.
A simple openrun collection can also be exported:
openrun export-postman \
-spec openapi.yaml \
-collection smoke.collection.yaml \
-env local.env.yaml \
-out postman.collection.jsonThe exporter resolves OpenAPI-generated defaults and collection/request overrides into Postman requests. It intentionally rejects assertions, timeouts/retries, extraction, dependencies, skip conditions, hooks, and parallel execution because a simple Postman document cannot preserve those openrun semantics. Import likewise rejects scripts, non-JSON raw bodies, form-data, URL-encoded bodies, GraphQL, and unsupported auth types. Saved Postman response examples are ignored because they do not affect execution.
spec := openrun.MustLoad("openapi.yaml")
resp, err := spec.Run(ctx, server.URL, "createUser")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 201 {
t.Fatalf("status = %d", resp.StatusCode)
}Timeouts and retries are also available from Go:
resp, err := spec.Run(
ctx,
server.URL,
"createUser",
openrun.WithTimeout(5*time.Second),
openrun.WithRetries(2, 200*time.Millisecond),
)Templates can be edited before execution:
tmpl, err := spec.Template("createUser")
if err != nil {
t.Fatal(err)
}
tmpl.
WithPathParam("id", "42").
WithHeader("Authorization", "Bearer "+token).
WithBody(map[string]any{
"email": "alice@example.com",
})
resp, err := spec.RunTemplate(ctx, server.URL, tmpl)Responses can be checked directly from Go without running a collection:
checks := openrun.CheckResponse(resp, openrun.ResponseAssertions{
Status: "2xx",
Headers: map[string]string{"Content-Type": "application/json"},
JSON: map[string]any{
"$.user.id": 42,
"$.ok": true,
},
BodyContains: []string{"created"},
})
for _, check := range checks {
if !check.Passed {
t.Error(check.Error)
}
}Schema-aware checks use the loaded spec and operation selector:
checks := spec.CheckResponse("createUser", resp, openrun.ResponseAssertions{
Status: "2xx",
Schema: true,
})To inspect the final request without sending it:
target, err := openrun.URL(server.URL, tmpl)
curl, err := openrun.CurlTemplate(server.URL, tmpl)- OpenAPI YAML/JSON loading.
- Spec validation for required runner-facing OpenAPI fields, refs, path params, responses, and supported security scheme shapes.
- Operation listing by method, path, and
operationId. - Request templates for path, query, header, and JSON body fields.
- Sample generation from JSON Schema types, refs, recursive refs, maps, arrays,
allOf,oneOf,anyOf, enums, defaults, and examples. - HTTP execution and curl output.
- Environment files, collection files, run-all reports, and template export.
- Structured response assertions in collections and JSON reports.
- Response validation against documented status codes, media types, and JSON schemas.
- Per-attempt timeouts, retries with exponential backoff, and failure diagnostics.
- Sequential and bounded-parallel collection execution with stable report ordering.
- Atomic JSONPath response extraction into runtime variables for sequential request chains.
- Deterministic request dependencies and variable-based skip conditions.
- Shared collection-level headers, query values, and auth credentials with explicit precedence.
- Backward-compatible single and named multi-environment files with default selection.
- Transactional declarative pre-request and post-response hooks with safe built-in values.
- Spec-matched import and export for simple Postman Collection v2.1 documents.
- Project defaults, compact positional CLI commands, and interactive operation selection.
- MCP tools for validation, discovery, execution, collections, and apiary synchronization.
This is intentionally not a full OpenAPI validator yet. The first release focuses on building useful request templates and running them predictably from Go tests or a CLI.
Future development continues across reports, OpenAPI compatibility, security, and release automation.
Apache-2.0.