Spall
Break free. Hit the endpoint.
spall is a dynamic CLI tool that parses OpenAPI 3.x specifications at runtime and generates fully-featured command-line interfaces for making API requests — with validation, auth, colored output, and schema-aware help.
A spall is the fragment that breaks free from a corroding metal surface and flies. Your request — shaped by the spec, launched from the terminal, sent across the gap.
Think Restish, but Rust.
Features
- Dynamic CLI from OpenAPI specs — no codegen required.
- Runtime spec loading from file path or URL.
- Two-phase parsing for fast startup and rich per-operation help.
- Schema-aware argument validation and typed flags.
- Colored, formatted response output with TTY detection.
- Request history, shell completions, pagination, and JMESPath filtering.
Quick Usage
# Register an API
spall api add petstore https://petstore.swagger.io/v2/swagger.json
# List operations
spall petstore --help
# Make a request
spall petstore get-pet-by-id 1
# POST with a body
spall petstore add-pet --data '{"name":"Rex","status":"available"}'
Next Steps
- Installation — download and set up spall.
- Your First Request — register an API and make a call.
- Key Concepts — understand how spall works.
Installation
Spall ships as a single static binary with no runtime dependencies.
Download
Pre-built binaries are available for Linux, macOS, and Windows on the releases page.
# Linux / macOS
curl -sL https://github.com/rustpunk/spall/releases/latest/download/spall-$(uname -s)-$(uname -m) -o spall
chmod +x spall
sudo mv spall /usr/local/bin/
# Verify
spall --version
Build from Source
Requires Rust 1.75+.
git clone https://github.com/rustpunk/spall.git
cd spall
cargo build --release --workspace
# Binary will be at:
# ./target/release/spall
Shell Completion Setup
Generate completion scripts for your shell:
# Bash
spall completions bash > /etc/bash_completion.d/spall
# Zsh
spall completions zsh > "${fpath[1]}/_spall"
# Fish
spall completions fish > ~/.config/fish/completions/spall.fish
Next Steps
- Your First Request — register an API and make a call.
Your First Request
This walkthrough registers the classic Petstore API, explores its operations, and makes a few requests.
1. Register the API
spall api add petstore https://petstore.swagger.io/v2/swagger.json
Spall will fetch the spec, cache it locally, and build an internal index. You only need to do this once.
2. List registered APIs
spall api list
Output:
Registered APIs:
petstore https://petstore.swagger.io/v2/swagger.json
3. Explore operations
spall petstore --help
Spall loads the spec and prints every operation, grouped by OpenAPI tags. Operation names are derived from operationId (kebab-cased) or synthesized from the HTTP method and path.
4. Make a GET request
Path parameters become positional arguments. Query parameters become --flags.
# GET /pet/{petId}
spall petstore get-pet-by-id 1
If the terminal is a TTY, the response is pretty-printed JSON with syntax highlighting. If piped, raw JSON is emitted.
5. Make a POST request
Post a JSON body with --data:
spall petstore add-pet --data '{"name":"Rex","status":"available"}'
You can also read body data from a file or stdin:
spall petstore add-pet --data @new-pet.json
# or
cat new-pet.json | spall petstore add-pet --data -
6. Override the server
Point the same operation at a staging server without re-registering:
spall petstore get-pet-by-id 1 --spall-server https://staging.petstore.io
7. Dry-run and preview
See what spall will send without hitting the network:
spall petstore get-pet-by-id 1 --spall-dry-run
Or preview the fully resolved request (URL, headers, body):
spall petstore add-pet --data '{"name":"Rex"}' --spall-preview
What just happened
- Phase 1 — spall scanned your config registry and matched
petstoreas a registered API name. - Phase 2 — spall loaded the cached spec, resolved all
$refs, merged parameters, and built a dynamic clap command tree. - Execution — spall validated your inputs against the schema, built the HTTP request, sent it, and formatted the response.
Next Steps
- Key Concepts — understand the mental model behind spall.
- Registering APIs — deep dive on API management.
- Making Requests — all the ways to call an operation.
Key Concepts
Two-Phase Parse
Spall uses a two-phase argument parser so that --help feels instant even when the underlying spec is large.
Phase 1 — Index scan (~1ms) reads only your config files (~/.config/spall/config.toml, apis/*.toml, and any spec_dirs). It registers each API as a clap subcommand stub with disable_help_flag(true) so that --help falls through.
Phase 2 — Spec load (~50–200ms) happens only when you actually invoke an API. The spec is loaded from cache or fetched from its source, $refs are resolved, and a full clap command tree is built. If the spec is unreachable, spall falls back to a lightweight cached SpecIndex so you still see the operation list.
Dynamic Command Building
Unlike tools that generate static Rust code from a spec, spall constructs clap Command and Arg objects at runtime. This means:
- No recompilation when an API changes.
- Schema enums become clap
possible_values. - Defaults from the spec become clap
default_value. - No generated code bloat.
Parameter Namespacing
OpenAPI allows a path param id and a query param id on the same operation. Spall namespaces them internally while preserving user-facing names:
OpenAPI in | Internal ID | User-facing |
|---|---|---|
| path | path-id | positional argument |
| query | query-id | --id |
| header | header-id | --header-id |
| cookie | cookie-id | --cookie-id |
IR Cache
After the first parse, spall serializes the resolved spec to a compact binary format (postcard) keyed by SHA-256 of the raw spec bytes. On subsequent runs it skips YAML/JSON parsing and $ref resolution entirely. Cache invalidation is automatic when the spec content changes or when spall upgrades its IR format.
Credential Stack
Auth resolution follows a strict priority chain so that scripts, CI, and interactive sessions can all coexist:
--spall-authCLI override- Per-API config
[auth]section - Environment variable (
SPALL_<API>_TOKEN) - Interactive prompt (Basic auth only, TTY)
All credentials are wrapped in secrecy::SecretString — they are zeroized on drop and redacted from debug output and history.
Exit Codes
Spall returns structured exit codes so scripts can branch on outcome:
| Code | Meaning |
|---|---|
| 0 | Success (2xx response) |
| 1 | CLI usage error |
| 2 | Network / connection error |
| 3 | Spec loading / parsing error |
| 4 | HTTP 4xx response |
| 5 | HTTP 5xx response |
| 10 | Request body / parameter validation failed |
Next Steps
Registering APIs
Before you can call an API, spall needs to know where its OpenAPI 3.x spec lives.
Add an API
spall api add <name> <source>
The source can be a local file path or a URL:
# Local file
spall api add internal ./specs/internal-api.yaml
# Remote URL
spall api add petstore https://petstore.swagger.io/v2/swagger.json
This creates ~/.config/spall/apis/{name}.toml. The spec is fetched, cached, and indexed immediately.
List registered APIs
spall api list
Output:
Registered APIs:
petstore https://petstore.swagger.io/v2/swagger.json
internal ./specs/internal-api.yaml
Remove an API
spall api remove petstore
This deletes ~/.config/spall/apis/petstore.toml and invalidates the IR cache.
Refresh a cached spec
Remote specs are cached with a TTL and conditional GET (ETag). If the spec has changed on the server, refresh it:
# Refresh one API
spall api refresh petstore
# Refresh everything
spall api refresh --all
Refresh also invalidates the IR cache so the next request rebuilds it from the new spec.
Discover an API from a base URL
If a server advertises its OpenAPI spec via RFC 8631 service-desc link relation, spall can probe and auto-register:
spall api discover https://api.example.com
Spall will follow Link: <...spec...>; rel="service-desc" headers, derive a name from the spec title, and register it just like api add.
Auto-scanning spec directories
You can point spall at a directory full of spec files instead of registering each one manually:
# ~/.config/spall/config.toml
spec_dirs = [
"~/.config/spall/specs",
]
Files in these directories are auto-registered. Names are derived from the filename minus extension:
| Filename | Registered name |
|---|---|
petstore.json | petstore |
my-internal-api.yaml | my-internal-api |
v2_billing.yml | v2-billing |
Priority (highest → lowest):
apis/*.tomlfiles[[api]]inline entries inconfig.tomlspec_dirsscanned files
Lower-priority entries with duplicate names are discarded.
Next Steps
Making Requests
Once an API is registered, every OpenAPI operation becomes a subcommand. Parameter types, enums, and defaults are preserved from the spec.
Path Parameters
Path parameters are supplied as positional arguments in the order they appear in the path template.
Given a path /pets/{petId}:
spall petstore get-pet-by-id 1
Path parameters are always required. If you omit one, clap prints a usage error before any network activity occurs.
Query Parameters
Query parameters become --flags. Given an operation with in: query params status and limit:
spall petstore find-pets-by-status --status available --limit 10
If the spec declares an enum for status, spall registers it as clap possible_values so typos are caught immediately:
spall petstore find-pets-by-status --status availbale
# error: invalid value 'availbale' for '--status'
# [possible values: available, pending, sold]
Defaults from the spec are honored:
# If limit defaults to 20 in the spec, this is equivalent:
spall petstore find-pets-by-status --status available
Header Parameters
Header parameters become --header-{name} flags:
spall petstore create-order --header-x-request-id abc-123
Cookie Parameters
Cookie parameters become --cookie-{name} flags:
spall petstore login --cookie-session abc123
Spall collects all cookie params and sends them as a single Cookie header.
Injecting Arbitrary Headers
For headers not declared in the spec, use --spall-header (repeatable):
spall petstore get-pet-by-id 1 --spall-header "X-Custom: value" --spall-header "X-Another: 42"
Overriding the Server URL
Use --spall-server to target a different base URL for a single request:
spall petstore get-pet-by-id 1 --spall-server https://staging.petstore.io
The resolution order is:
--spall-serverCLI flag- Per-API config
base_url - Operation-level
serversfrom the spec - Spec-level
servers - Fallback
/
Deprecation Warnings
If an operation is marked deprecated: true in the spec, spall prints a [DEPRECATED] banner in the help text. The operation still works — this is a heads-up, not a gate.
Next Steps
Request Bodies
When an operation declares a request body, spall adds --data, --form, and --field arguments. They are mutually exclusive.
JSON Body with --data
The default for JSON APIs:
spall petstore add-pet --data '{"name":"Rex","status":"available"}'
From a file
Prefix the path with @:
spall petstore add-pet --data @new-pet.json
From stdin
Use -:
cat new-pet.json | spall petstore add-pet --data -
Optional body
If the spec says the body is not required, spall also registers --no-data so you can skip it explicitly:
spall petstore update-pet --no-data
Content-Type override
If the spec supports multiple content types, spall defaults to application/json. Override with --spall-content-type:
spall petstore upload-spec --data @spec.yaml --spall-content-type text/yaml
Multipart Upload with --form
For multipart/form-data uploads, use --form (repeatable):
spall petstore upload-file --form file=@image.png --form description="avatar"
File values are auto-detected by the @ prefix and streamed as binary parts.
URL-Encoded with --field
For application/x-www-form-urlencoded, use --field (repeatable):
spall oauth token --field grant_type=client_credentials --field client_id=abc
Validation
Before the request is sent, spall validates the body against the operation’s request schema (when application/json is declared). Validation errors are printed to stderr and spall exits with code 10.
spall petstore add-pet --data '{"status":"invalid"}'
# Validation failed:
# /body/name: required property 'name' is missing
Next Steps
Response Output
Spall automatically chooses an output format based on whether stdout is a TTY.
Default Behavior
| Context | Format |
|---|---|
| TTY (interactive terminal) | Pretty-printed JSON with syntax highlighting |
| Pipe / file redirect | Raw JSON |
# Pretty JSON (TTY)
spall petstore get-pet-by-id 1
# Raw JSON (piped)
spall petstore get-pet-by-id 1 | jq '.name'
Output Modes
Use --spall-output to override:
# Raw JSON
spall petstore get-pet-by-id 1 --spall-output raw
# YAML
spall petstore get-pet-by-id 1 --spall-output yaml
# Table (requires a JSON array of objects)
spall petstore find-pets-by-status --status available --spall-output table
# CSV (requires a JSON array of objects)
spall petstore find-pets-by-status --status available --spall-output csv
Table and CSV modes walk the array and collect all unique top-level keys as headers. If the response is not a JSON array, spall warns and falls back to pretty JSON.
Saving Responses
Save the response body to a file without touching stdout:
spall petstore get-pet-by-id 1 --spall-download pet.json
Or use the @file syntax with --spall-output:
spall petstore get-pet-by-id 1 --spall-output @pet.json
Binary responses are streamed raw to the file. When writing to stdout on a TTY, spall emits a warning and suggests --spall-download.
Filtering with JMESPath
Extract fields without installing jq:
spall petstore find-pets-by-status --status available --filter "[].name"
# ["Rex","Fluffy"]
If the filter expression is invalid, spall warns and falls back to the unfiltered response.
Verbose Mode
Print request and response headers to stderr:
spall petstore get-pet-by-id 1 --spall-verbose
With --spall-time, the duration is included:
spall petstore get-pet-by-id 1 --spall-verbose --spall-time
Sensitive headers (Authorization, Cookie, X-Api-Key, etc.) are redacted.
Next Steps
Authentication
Spall supports Bearer tokens, Basic auth, API keys, and OAuth2 pass-through. Auth resolution follows a strict priority chain so that scripts, CI, and interactive sessions can coexist.
Quick Pass-Through with --spall-auth
For one-off testing, pass a token or credentials directly:
# Bearer token
spall github get-user octocat --spall-auth "Bearer ghp_xxxxxxxx"
# Basic auth (user:pass)
spall internal get-data --spall-auth "Basic alice:secret"
# Shorthand basic (no space, one colon)
spall internal get-data --spall-auth "alice:secret"
# Bare token (treated as Bearer)
spall github get-user octocat --spall-auth "ghp_xxxxxxxx"
--spall-auth is the highest-priority auth source. It overrides everything else for that single request.
Environment Variables
If --spall-auth is omitted, spall looks for SPALL_<API>_TOKEN (hyphens become underscores):
export SPALL_GITHUB_TOKEN=ghp_xxxxxxxx
spall github get-user octocat
Per-API Config
Store auth settings in ~/.config/spall/apis/{name}.toml:
source = "https://api.example.com/openapi.json"
base_url = "https://api.example.com"
[auth]
kind = "Bearer"
token_env = "MY_API_TOKEN"
Supported kind values: Bearer, Basic, ApiKey, OAuth2.
API Key
[auth]
kind = "ApiKey"
token_env = "MY_API_KEY"
location = "header" # or "query"
header_name = "X-Api-Key" # ignored when location = "query"
query_name = "api_key" # ignored when location = "header"
Basic Auth
[auth]
kind = "Basic"
username = "alice"
password_env = "ALICE_PASSWORD"
If password_env is not set and stdin is a TTY, spall prompts for the password interactively.
Inline Tokens (Discouraged)
You can embed a token directly in the config file:
[auth]
kind = "Bearer"
token = "ghp_xxxxxxxx"
Spall will accept it but prints a warning recommending token_env or a keyring instead.
Credential Hygiene
All credentials are wrapped in secrecy::SecretString:
- Memory is zeroized on drop.
- Debug output prints
[REDACTED]. - History redacts sensitive headers (
Authorization,Cookie,X-Api-Key, etc.). --spall-debugwire logs also redact secrets.
Next Steps
Pagination
Many APIs paginate large result sets via Link headers (RFC 5988). Spall can auto-follow these links and concatenate the results into a single JSON array.
Basic Usage
spall github list-repos --spall-paginate
Spall sends the initial request, inspects the Link header for rel="next", and follows it until:
- No
nextlink is present. - The maximum page limit (default 100) is reached.
- A non-2xx response is returned (spall exits with the appropriate code).
Result Concatenation
If every page is a JSON array, all elements are flattened into one array:
// Page 1: [{"id":1},{"id":2}]
// Page 2: [{"id":3},{"id":4}]
// Output: [{"id":1},{"id":2},{"id":3},{"id":4}]
If a page is not an array, it is wrapped as a single item:
// Page 1: [{"id":1}]
// Page 2: {"meta": {...}}
// Output: [{"id":1},{"meta": {...}}]
Combining with Output and Filtering
Pagination works with all output modes and filtering:
spall github list-repos --spall-paginate --spall-output csv
spall github list-repos --spall-paginate --filter "[].full_name"
Limitations
--spall-paginatecannot be combined with--form(multipart uploads).- Pagination requires JSON responses. If a page returns non-JSON, spall exits with a usage error.
Next Steps
Global Flags
All internal spall flags use the --spall-* prefix so they never collide with API parameters.
Output and Formatting
| Flag | Short | Description |
|---|---|---|
--spall-output | -O | Output format: json/pretty, raw, yaml, table, csv, or @file |
--spall-download | -o | Save response body to a file |
--spall-verbose | -v | Print request/response headers to stderr |
--spall-debug | Wire-level debug logging (redacts secrets) | |
--spall-time | Include request/response timing in verbose output | |
--filter | JMESPath filter expression for JSON responses |
Network Control
| Flag | Short | Description |
|---|---|---|
--spall-server | -s | Override base URL for this request |
--spall-timeout | -t | Timeout in seconds (default: 30) |
--spall-retry | Retry count for failed requests (default: 1, max: 3) | |
--spall-follow | -L | Follow HTTP redirects (default: off) |
--spall-max-redirects | Maximum redirects (default: 10) | |
--spall-insecure | Skip TLS certificate verification | |
--spall-ca-cert | Path to custom CA certificate | |
--spall-proxy | HTTP/SOCKS proxy URL |
Request Modification
| Flag | Short | Description |
|---|---|---|
--spall-header | -H | Inject a non-sensitive header (repeatable) |
--spall-auth | -A | Pass-through auth token/header |
--spall-content-type | -c | Override request content type |
Execution Control
| Flag | Short | Description |
|---|---|---|
--spall-dry-run | Print curl equivalent without executing | |
--spall-preview | Show resolved URL, headers, and body without sending | |
--spall-paginate | Auto-follow Link header pagination | |
--spall-repeat | Replay the most recent request from history | |
--profile | Active config profile (e.g., staging, production) |
Examples
Verbose request with timing
spall petstore get-pet-by-id 1 --spall-verbose --spall-time
Staging override with custom header
spall petstore get-pet-by-id 1 \
--spall-server https://staging.petstore.io \
--spall-header "X-Debug: true"
Retry with redirect following
spall petstore get-pet-by-id 1 --spall-retry 3 --spall-follow
Replay last request
spall --spall-repeat
Next Steps
Config Layout
Spall stores all configuration under your platform’s config directory (typically ~/.config/spall/ on Linux, ~/Library/Application Support/spall/ on macOS, %APPDATA%\spall\ on Windows).
Directory Structure
~/.config/spall/
├── config.toml # Global settings
├── apis/
│ ├── github.toml # Per-API overrides
│ └── petstore.toml
├── specs/ # Optional auto-scan directory
│ ├── internal-api.yaml
│ └── partner-api.json
└── cache/
├── <hash>.raw # Cached remote spec bytes
├── <hash>.raw-meta # TTL + ETag metadata
├── <hash>.ir # Compiled IR (postcard)
├── <hash>.idx # Lightweight SpecIndex
├── <hash>.meta # IR cache metadata (SHA-256 + version)
└── history.db # SQLite request history
Global Config (config.toml)
# Register APIs inline
[[api]]
name = "github"
spec = "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json"
[[api]]
name = "petstore"
spec = "https://petstore.swagger.io/v2/swagger.json"
# Auto-scan directories
spec_dirs = [
"~/.config/spall/specs",
]
[defaults]
output = "json" # json | yaml | table | raw
color = "auto" # auto | always | never
Per-API Config (apis/{name}.toml)
Created automatically by spall api add. You can edit these to add auth or overrides:
source = "https://petstore.swagger.io/v2/swagger.json"
base_url = "https://staging.petstore.io"
[auth]
kind = "Bearer"
token_env = "PETSTORE_TOKEN"
[default_headers]
X-Client = "spall-cli"
[profiles]
[profiles.staging]
base_url = "https://staging.petstore.io"
[profiles.production]
base_url = "https://petstore.io"
Per-API fields
| Field | Type | Description |
|---|---|---|
source | string | Spec file path or URL (required) |
base_url | string | Override the spec’s server URL |
auth | table | Auth configuration (see Authentication) |
default_headers | table | Headers added to every request |
profiles | table | Named environment overlays |
Cache Files
Spall manages the cache/ directory automatically. You should not need to edit these files directly.
- Raw cache (
*.raw,*.raw-meta) stores fetched spec bytes with TTL and ETag for conditional GET. - IR cache (
*.ir) stores the resolved spec in postcard format for instant reload. - Index cache (
*.idx) stores a lightweightSpecIndexfor degraded--helpwhen the spec is unreachable. - History (
history.db) is a SQLite database of recent requests.
Next Steps
Profiles
Profiles let you switch between environments (staging, production, local) without re-registering the API or editing files before every command.
Defining Profiles
Add a [profiles.{name}] section to any per-API config file:
# ~/.config/spall/apis/petstore.toml
source = "https://petstore.swagger.io/v2/swagger.json"
base_url = "https://petstore.io"
[auth]
kind = "Bearer"
token_env = "PETSTORE_TOKEN"
[profiles.staging]
base_url = "https://staging.petstore.io"
auth = { kind = "Bearer", token_env = "PETSTORE_STAGING_TOKEN" }
[profiles.production]
base_url = "https://petstore.io"
Using Profiles
Activate a profile with --profile:
# Hit staging
spall petstore get-pet-by-id 1 --profile staging
# Hit production
spall petstore get-pet-by-id 1 --profile production
Profile Overlay Rules
When a profile is active, its values override the base config:
base_urlreplaces the base config value entirely.authreplaces the base config auth entirely.headersare merged: profile headers with the same key override base headers; new keys are appended.
If the requested profile does not exist, spall exits with a usage error.
Next Steps
CLI Reference
Top-Level Commands
spall [OPTIONS] <COMMAND>
| Command | Description |
|---|---|
spall api ... | Manage registered APIs |
spall auth ... | Authentication commands |
spall history ... | Request/response history |
spall completions ... | Generate shell completion scripts |
spall <api> <operation> [args] | Execute an API operation |
spall api
| Subcommand | Description |
|---|---|
spall api add <name> <source> | Register a new API |
spall api list | List registered APIs |
spall api remove <name> | Unregister an API |
spall api refresh [<name>] | Refresh cached remote spec |
spall api refresh --all | Refresh all cached remote specs |
spall api discover <url> | Discover and register an API via RFC 8631 |
spall auth
| Subcommand | Description |
|---|---|
spall auth status <api> | Show auth status for an API |
spall auth login <api> | Initiate OAuth2 PKCE login (stub) |
spall history
| Subcommand | Description |
|---|---|
spall history list | List recent requests |
spall history show <id> | Show full request details |
spall history clear | Erase all history |
Global Flags
See Global Flags for the full list of --spall-* options.
Next Steps
Request History
Spall records every request to a local SQLite database (cache/history.db). This is useful for debugging, auditing, and replay.
Listing History
spall history list
Output:
42 2025-04-25 14:32 GET 200 124ms petstore get-pet-by-id
41 2025-04-25 14:30 POST 201 312ms petstore add-pet
40 2025-04-25 14:28 GET 404 89ms github get-repo
Viewing a Single Request
spall history show 42
This prints the method, URL, status code, duration, request headers, and response headers. Sensitive headers are redacted.
Replaying a Request
# Replay the most recent request
spall --spall-repeat
# Replay a specific request by ID
spall history show 42 --spall-repeat
Replay reconstructs the exact method, URL, headers, and body from the history record, then re-executes it.
Clearing History
spall history clear
This deletes all rows and vacuums the database.
Privacy Notes
- Request and response headers are recorded, but sensitive headers (
Authorization,Cookie,X-Api-Key, etc.) are stored as[REDACTED]. - Request bodies are not stored in history.
- The history database is local to your machine and never transmitted.
Next Steps
Shell Completions
Spall can generate completion scripts for bash, zsh, and fish. Because operations are loaded dynamically from specs, the completion scripts query spall itself at runtime to suggest API names and operation IDs.
Bash
spall completions bash > /etc/bash_completion.d/spall
# or user-local:
spall completions bash > ~/.local/share/bash-completion/completions/spall
Zsh
spall completions zsh > "${fpath[1]}/_spall"
Fish
spall completions fish > ~/.config/fish/completions/spall.fish
How It Works
The generated scripts are thin wrappers around spall’s __complete hidden subcommand:
spall __complete <api> <partial-word>
This loads the spec (or its cached index if offline) and prints matching operation IDs and parameter names. Completion is fast even for large APIs because it uses the lightweight SpecIndex cache.
Next Steps
Exit Codes
Spall returns structured exit codes so shell scripts and CI pipelines can branch on outcome without parsing stderr.
| Code | Meaning | When It Happens |
|---|---|---|
| 0 | Success | 2xx HTTP response |
| 1 | Usage error | Missing required argument, unknown API/operation, bad flag, config parse failure |
| 2 | Network error | DNS failure, TCP timeout, TLS error, proxy failure, stale cache with no fallback |
| 3 | Spec error | YAML/JSON parse failure, dangling $ref, invalid OpenAPI structure, cache corruption that cannot be rebuilt |
| 4 | HTTP 4xx | Client error responses (400, 401, 403, 404, etc.) |
| 5 | HTTP 5xx | Server error responses (500, 502, 503, etc.) |
| 10 | Validation failed | Preflight parameter or body schema validation failed |
Scripting Examples
Retry on network failure
spall petstore get-pet-by-id 1 || [ $? -eq 2 ] && sleep 5 && spall petstore get-pet-by-id 1
Skip downstream steps on 4xx
spall github get-repo rustpunk/spall || {
code=$?
if [ "$code" -eq 4 ]; then
echo "Repo not found, skipping build..."
exit 0
fi
exit "$code"
}
Fail CI on validation errors
spall internal create-order --data @order.json || {
code=$?
if [ "$code" -eq 10 ]; then
echo "Validation failed — check your payload."
fi
exit "$code"
}