Testing an API by hand catches today's bug; running the same tests in CI catches next
month's. The SchemaClient CLI (@schemaclient/cli) runs collections from the command
line and exits non-zero the moment a response drifts from its contract — which makes
it a natural gate in any pipeline.
Install
npm install -g @schemaclient/cli
Requires Node.js 20+. Two equivalent commands are installed: schemaclient and the
short alias scli. Free, MIT-licensed, open source at
github.com/schemaclient/schemaclient-cli.
Quick start
schemaclient init my-api-suite # scaffold a starter suite + example schema
cd my-api-suite
schemaclient run suite.scoll.json --var baseUrl=http://127.0.0.1:8000
A run prints each request with its assertions and ends with a pass/fail summary:
Products
✓ List products 200 · 62ms
✓ matches schema schemas/product.sc → GET /api/products/ (200)
✗ Get product 200 · 13ms
✗ matches schema → $.price: expected number [TYPE_MISMATCH]
FAILED — 1 request exit code 1
Note the failure: the endpoint returned 200 OK and still failed the build, because
price came back as the wrong type. That is
the whole argument for schema validation — in
pipeline form.
The commands
| Command | Purpose |
| --- | --- |
| run <collection> | Run a collection; exit 0 on pass, 1 on failure |
| run … --dry-run | Resolve and print what would be sent, without sending |
| send <method> <url> | Fire a single ad-hoc request |
| schema lint <file.sc> | Compile a schema and report diagnostics |
| schema mock <file.sc> | Generate mock data from a schema (--seed for determinism) |
| schema infer <response.json> | Generate a schema from a captured response |
| validate <file> --schema <s> | Validate a JSON document offline |
| init [dir] | Scaffold a starter suite |
Collections, variables and capture
Collections are JSON: folders, per-folder auth inheritance, {{variables}}, timeouts
and retries, and capture expressions that pull values (a session id, a token) out of
one response for use in the next request. Collections exported from the
desktop app run unmodified; CI-only assertions live
in a sidecar *.tests.json file, so the app owns the requests and the pipeline owns
the contract without overwriting each other.
Schema contracts
Contracts are written in the same schema language the desktop app uses — the engine is shared, so validation behaves identically in both:
type Money = string & pattern("^\\d+\\.\\d{2}$")
@http("GET /api/products/{id}/")
schema Response(200) {
id: integer & min(1)
name: string & minLength(1)
price: Money
tags: string[]
}
Responses are matched to a schema by method, path and status code. If you already define schemas in the app (guide) or import them from OpenAPI, the same files gate your CI.
A minimal GitHub Actions job
api-contract-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g @schemaclient/cli
- run: schemaclient run suite.scoll.json --var baseUrl=${{ secrets.STAGING_URL }}
Green means every endpoint answered and every response still matches its contract. That second half is the part status-code smoke tests never give you.