Steady

April 2, 2026 · View on GitHub

Pronounced /ˈstiːdi/, like "steed-y"

OpenAPI 3.0/3.1 mock server built to be fast and reliable. Validates requests against specs and generates responses from schemas or examples.

steady logo

Note from the artist: the sword is to fight off bugs in your sdks

Installation

# npm
npm install -g @stdy/cli

# npx (no install)
npx @stdy/cli api.yaml

# Deno
deno install -gAn steady jsr:@steady/cli

# deno run (no install)
deno run -A jsr:@steady/cli api.yaml

Usage

# Start mock server
steady api.yaml

# Validate spec without starting server
steady validate api.yaml

# Explain a diagnostic code
steady explain E3008

# List all diagnostic codes
steady explain

# Watch for spec changes
steady -r api.yaml

# Interactive mode with expandable request logs
steady -i api.yaml

Options

steady [command] [options] <spec-file>

Commands:
  validate <spec>    Validate an OpenAPI spec (doesn't start server)
  explain [code...]  Explain diagnostic codes (e.g., steady explain E3008)
  <spec>             Start mock server (default)

Options:
  -p, --port <port>       Override server port (default: from spec or 3000)
  -r, --auto-reload       Restart on spec file changes
  -i, --interactive       Interactive TUI with expandable logs
  --log-level <level>     summary | details | full (default: summary)
  --log-bodies            Show request/response bodies
  --log=false             Disable request logging
  --reject-on-sdk-error   Return 400 for SDK issues instead of mock response
  --fail-on-ambiguous     Exit 1 if any ambiguous diagnostics found (CI mode)
  --fail-on-warnings      Exit 1 if any warning-level diagnostics found (CI mode)
  --no-color              Disable colored output (also respects NO_COLOR env)
  -h, --help              Show help

Validator Options:
  --validator-strict-oneof             Require exactly one oneOf variant to match
  --validator-query-array-format=<fmt> Array query param serialization (see below)
  --validator-query-object-format=<fmt> Object query param serialization (see below)
  --validator-form-array-format=<fmt>  Array form field serialization (see below)
  --validator-form-object-format=<fmt> Object form field serialization (see below)

Generator Options:
  --generator-array-size=<n>   Exact size for all generated arrays
  --generator-array-min=<n>    Minimum array size (default: 1)
  --generator-array-max=<n>    Maximum array size (default: 1)
  --generator-seed=<n>         Seed for deterministic generation (-1 for random)

Query Parameter Serialization

Steady supports the full OpenAPI 3.x parameter serialization matrix. By default (auto), Steady reads the style and explode properties from your OpenAPI spec for each parameter. You can override this globally via CLI flags or per-request via headers.

Array formats (--validator-query-array-format):

FormatExampleOpenAPI Equivalent
autoRead from spec (default)-
repeatcolors=red&colors=greenstyle=form, explode=true
commacolors=red,green,bluestyle=form, explode=false
spacecolors=red%20green%20bluestyle=spaceDelimited
pipecolors=red|green|bluestyle=pipeDelimited
bracketscolors[]=red&colors[]=greenPHP/Rails style (non-standard)

Object formats (--validator-query-object-format):

FormatExampleOpenAPI Equivalent
autoRead from spec (default)-
flatrole=admin&firstName=Alexstyle=form, explode=true
flat-commaid=role,admin,firstName,Alexstyle=form, explode=false
bracketsid[role]=admin&id[firstName]=Alexstyle=deepObject
dotsid.role=admin&id.firstName=AlexNon-standard (SDK compat)

Form Parameter Serialization

Form body parameters (application/x-www-form-urlencoded) use the same formats as query parameters. Configure via CLI flags or per-request headers.

Array formats (--validator-form-array-format):

FormatExampleOpenAPI Equivalent
autoRead from spec (default)-
repeattags=a&tags=bstyle=form, explode=true
commatags=a,bstyle=form, explode=false
spacetags=a%20bstyle=spaceDelimited
pipetags=a|bstyle=pipeDelimited
bracketstags[]=a&tags[]=bPHP/Rails style (non-standard)

Object formats (--validator-form-object-format):

FormatExampleOpenAPI Equivalent
autoRead from spec (default)-
flatname=sam&age=30style=form, explode=true
flat-commaid=role,admin,firstName,Alexstyle=form, explode=false
bracketsuser[name]=samstyle=deepObject
dotsuser.name=samNon-standard (SDK compat)

Port Configuration

The server port is determined in this order:

  1. -p, --port CLI flag
  2. servers[0].url port in your spec
  3. Default: 3000
# Option 1: CLI flag takes precedence
steady -p 8080 api.yaml

# Option 2: Set in spec
servers:
  - url: http://localhost:8080

Response Generation

Steady generates responses in this order:

  1. example field on the media type
  2. First entry from examples map
  3. Generated from schema (if present)
responses:
  200:
    content:
      application/json:
        # Option 1: explicit example (preferred)
        example:
          id: 123
          name: "Alice"

        # Option 2: multiple examples
        examples:
          success:
            value: { id: 123, name: "Alice" }

        # Option 3: generate from schema
        schema:
          $ref: "#/components/schemas/User"

Request Validation

Requests are validated against:

  • Path parameters - type coercion and schema validation
  • Query parameters - required check, type validation
  • Headers - required headers, schema validation
  • Cookies - required cookies, schema validation
  • Request body - JSON Schema validation, content-type check

By default, validation issues are reported in X-Steady-* response headers while still returning mock responses. Use --reject-on-sdk-error to return 400 for SDK issues (structural validation failures) instead.

Request Headers

Override server behavior for individual requests:

HeaderDescription
X-Steady-Reject-On-Errortrue to return 400 for SDK issues on this request
X-Steady-Query-Array-FormatOverride array query param serialization format
X-Steady-Query-Object-FormatOverride object query param serialization format
X-Steady-Form-Array-FormatOverride array form field serialization format
X-Steady-Form-Object-FormatOverride object form field serialization format
X-Steady-Array-SizeOverride array size (sets both min and max)
X-Steady-Array-MinOverride minimum array size
X-Steady-Array-MaxOverride maximum array size
X-Steady-SeedOverride random seed (-1 for non-deterministic)
X-Steady-Stream-CountNumber of items to stream (default: 5)
X-Steady-Stream-Interval-MsInterval between streamed items in ms (default: 100)
# Reject SDK issues for this request
curl -H "X-Steady-Reject-On-Error: true" http://localhost:3000/users

# Request 50 items in arrays
curl -H "X-Steady-Array-Size: 50" http://localhost:3000/users

# Get random (non-deterministic) responses
curl -H "X-Steady-Seed: -1" http://localhost:3000/users

# Override query format for SDK testing
curl -H "X-Steady-Query-Object-Format: dots" "http://localhost:3000/search?filter.level=high"

Response Headers

Informational headers returned by the server:

HeaderDescription
X-Steady-Request-ValidWhether the request passed SDK validation
X-Steady-Error-CountNumber of diagnostic issues found
X-Steady-Matched-PathThe OpenAPI path pattern that matched
X-Steady-Example-SourceHow the response was generated: generated or none
X-Steady-StreamingSet to true for streaming responses

Streaming Responses

Steady supports streaming responses for NDJSON and Server-Sent Events (SSE):

Content TypeFormatDescription
application/x-ndjsonNDJSONNewline-delimited JSON
application/jsonlNDJSONJSON Lines
application/json-seqNDJSONJSON Sequence
text/event-streamSSEServer-Sent Events

Streaming Headers

HeaderDescription
X-Steady-Stream-CountNumber of items to stream (default: 5)
X-Steady-Stream-Interval-MsInterval between items in ms (default: 100)
# Stream 10 items with 50ms delay
curl -H "X-Steady-Stream-Count: 10" \
     -H "X-Steady-Stream-Interval-Ms: 50" \
     http://localhost:3000/events

NDJSON Example

/metrics:
  get:
    responses:
      "200":
        content:
          application/x-ndjson:
            schema:
              type: object
              properties:
                id: { type: integer }
                value: { type: number }

Output:

{"id":1,"value":42.5,"_stream":{"index":0,"total":5,"timestamp":"..."}}
{"id":2,"value":43.1,"_stream":{"index":1,"total":5,"timestamp":"..."}}
...

SSE with Event Sequences

Define realistic SSE flows with different event types using array examples:

/events:
  get:
    responses:
      "200":
        content:
          text/event-stream:
            example:
              - event: start
                data: { status: "processing" }
              - event: progress
                data: { percent: 50 }
              - event: progress
                data: { percent: 100 }
              - event: complete
                data: { result: "success" }

Output:

id: 0
event: start
data: {"status":"processing"}

id: 1
event: progress
data: {"percent":50}

id: 2
event: progress
data: {"percent":100}

id: 3
event: complete
data: {"result":"success"}

SSE events support these fields:

  • event - Event type name (default: "message"; set to null or "" to omit)
  • data - Event payload (required; strings output as-is, objects JSON-encoded)
  • id - Custom event ID (auto-generated if omitted; set to null to omit)
  • retry - Reconnection timeout in milliseconds

If the last event isn't done, complete, or end, Steady automatically appends a done event to signal stream completion.

OpenAI-Style SSE

For APIs like OpenAI that use data-only terminal events, set event and id to null:

example:
  - event: message
    data: { delta: { content: "Hello" } }
  - event: null
    id: null
    data: "[DONE]"

Output:

id: 0
event: message
data: {"delta":{"content":"Hello"}}

data: [DONE]

Special Endpoints

  • GET /_x-steady/health - Health check with schema stats
  • GET /_x-steady/spec - Returns the loaded OpenAPI spec as JSON

JSON Schema Support

Supports JSON Schema draft 2020-12 with ~91% compliance.

Supported:

  • Types: string, number, integer, boolean, null, array, object
  • String: minLength, maxLength, pattern, format
  • Number: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
  • Array: items, prefixItems, minItems, maxItems, uniqueItems, contains, unevaluatedItems
  • Object: properties, required, additionalProperties, patternProperties, propertyNames, minProperties, maxProperties, unevaluatedProperties
  • Composition: allOf, anyOf, oneOf, not
  • Conditional: if/then/else
  • References: $ref, $defs, $anchor
  • const, enum, default

Not supported:

  • $dynamicRef / $dynamicAnchor
  • External $ref (http://, file://)

Diagnostics

Steady attributes every validation issue to a responsible party — SDK, spec, or ambiguous — using compiler-style output:

error[E3002]: Missing required query parameter
 --> GET /users
  |
  |  Required parameter 'limit' not found in query string
  |
  = expected: query parameter 'limit' (type: integer)
  = Add the required parameter to the request

For details, try: steady explain E3002

Use steady explain <code> for detailed documentation on any diagnostic code, including what it means, why it's categorized that way, and what to do about it.

Development

git clone https://github.com/dgellow/steady.git
cd steady
git submodule update --init  # fetch test fixtures

# Run tests
deno task test

# Type check
deno task check

# Lint + format
deno task lint
deno task fmt

# Run all checks
deno task test-all

Project Structure

steady/
├── cmd/steady.ts              # CLI entry point
├── src/
│   ├── server.ts              # HTTP server, route matching
│   ├── validator.ts           # Request validation
│   ├── errors.ts              # Error types with attribution
│   └── logging/               # Request logging utilities
├── packages/
│   ├── json-pointer/          # @steady/json-pointer - RFC 6901
│   ├── json-schema/           # @steady/json-schema - JSON Schema processor
│   └── openapi/               # @steady/openapi - OpenAPI 3.x parser
└── tests/
    └── edge-cases/            # Edge case tests

Tasks

deno task dev               # Dev server with watch
deno task start             # Production server
deno task test              # Run all tests
deno task test:json-schema  # JSON Schema tests only
deno task test:parser       # OpenAPI parser tests only
deno task test:json-pointer # JSON Pointer tests only
deno task check             # Type check
deno task lint              # Lint
deno task fmt               # Format
deno task check-boundaries  # Verify package dependencies

Acknowledgements

Thanks to Stephen Downward for contributing the logo design.