Documentation
Handshake Documentation
Everything you need to integrate Handshake into your workflow.
Getting Started
Handshake is an API contract testing platform that validates your backend API against its OpenAPI specification on every pull request. It catches breaking changes before they reach production and keeps frontend and backend teams in sync.
Get up and running in four steps:
- Sign up with GitHub — Log in at
app.handshake.devusing your GitHub account. - Create a team — Teams group your projects and manage member access. Invite collaborators with viewer, member, or admin roles.
- Create a project — Each project represents one API service. Upload your OpenAPI spec (YAML or JSON, OpenAPI 3.x or Swagger 2.0) to define the contract.
- Add the GitHub Action — Drop the Handshake action into your CI workflow. It validates your staging API against the contract on every PR.
GitHub Action
The Handshake GitHub Action runs contract validation as part of your CI pipeline. It sends requests to your staging API and compares responses against the contract spec.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
| api-key | Yes | — | Handshake project API key (hsk_...) |
| validation-url | Yes | — | Base URL of the API to validate |
| base-url | No | https://ci.handshake.dev | Handshake CI runner URL |
Outputs
| Output | Description |
|---|---|
| status | Validation result: passed, failed, or error |
| run-id | CI run ID for dashboard reference |
| total | Total endpoints validated |
| passed | Endpoints that passed |
| failed | Endpoints that failed |
Full workflow example
API Keys
Each project has its own API key, used to authenticate CI validation requests. API keys are prefixed with hsk_ and stored as bcrypt hashes — Handshake never stores the raw key.
Your API key
Your project API key is generated automatically when the project is created. Navigate to your project settings to view or rotate it. Click Rotate Key to invalidate the old key and generate a new one. Copy the key immediately — it won't be shown again.
After rotating
Update your CI secrets immediately after rotating. The old key is permanently invalidated and cannot be recovered.
Security best practices
- Store keys in GitHub Actions secrets (
Settings → Secrets → Actions) - Never commit API keys to your repository
- Rotate keys if you suspect they've been exposed
- Use separate projects (and keys) for separate API services
CI Configuration
Handshake validates your API by sending real HTTP requests to your staging environment and comparing responses against the contract specification. Each endpoint is tested for status codes, response schema, and required headers.
Triggering validation
The GitHub Action is the simplest way to trigger validation. You can also trigger runs directly via the CI runner API:
Validation results
Each CI run produces a detailed validation report visible in the dashboard. For each endpoint, Handshake checks:
- Status code — Does the response return the expected HTTP status?
- Response schema — Does the body match the OpenAPI schema definition?
- Required fields — Are all required properties present in the response?
Plan limits
| Plan | CI Runs / Month | Rate Limit |
|---|---|---|
| Free | 100 | 60 req/min |
| Pro | 1,000 | 300 req/min |
| Team | 10,000 | 1,000 req/min |
| Enterprise | Unlimited | Unlimited |
CI run counts reset monthly. Validation runs have a 120-second timeout with a 10-second limit per HTTP request.
Mock Server
Handshake can generate a mock server from your contract specification. Frontend teams can build against realistic mock data while the backend is still in development.
Starting the mock server
Enable the mock server from your project's dashboard. Once started, your mock API is available at:
https://{project-slug}.mock.handshake.dev
Making requests
Mock data generation
Mock responses are generated using faker.js based on your schema definitions. String formats ( email, uuid, date-time) produce realistic values. Arrays return 1–3 items by default.
Configuration
The mock server respects your contract's response schemas and status codes. You can configure the mock server from the dashboard to customize response delays and default status codes.
CLI — Local Mock Server
Run a local mock server powered by your Handshake contracts. No Docker or cloud dependency — just install the CLI and start mocking.
Installation
Quick Start
1. Get your API key from your project's Settings page in the Handshake dashboard.
2. Start the mock server:
3. Make requests against your local mock:
Configuration
| Flag | Default | Description |
|---|---|---|
| --api-key | $HANDSHAKE_API_KEY | Project API key |
| --port | 4010 | Local server port |
| --latency | 0 | Simulated response latency (ms) |
| --error-rate | 0 | Fraction of requests returning 500 (0-1) |
| --watch | false | Re-fetch contracts every 30s |
| --validate | false | Validate requests against OpenAPI schemas |
| --seed <number> | (random) | Faker seed for reproducible responses |
| --cors <origins> | * | CORS origin(s), comma-separated |
| --compose <path> | (none) | Compose file for multi-project setup |
| --verbose | false | Log request/response bodies (truncated) |
| --https | false | Enable HTTPS with self-signed cert |
| --cert <path> | (auto) | Custom TLS certificate path |
| --key <path> | (auto) | Custom TLS key path |
| --no-dashboard | (enabled) | Disable local web dashboard |
| --proxy <url> | (none) | Upstream URL for transparent proxy mode |
| --base-url <url> | (none) | Target server for contract validation |
| --report | false | Post ci run results to Handshake API |
| --timeout <ms> | 10000 | Per-request timeout for ci run |
| --fail-on-warning | false | Exit 1 on warnings in ci run |
| --no-telemetry | false | Disable anonymous usage telemetry |
| --config | .handshake.json | Config file path |
| --api-url | https://api.handshake.dev | API base URL |
You can also save configuration in a .handshake.json file:
Offline Mode
The CLI caches your contracts locally. If the API is unreachable, it falls back to the cached version.
To pre-cache for offline use:
Cache is stored in .handshake/cache/. Add this directory to your .gitignore.
Custom Responses
Override any route with a static response using the customResponses config. Keys use exact paths (e.g., GET:/api/users/123), not route templates with parameters:
Request Validation
Enable --validate to check incoming requests against your OpenAPI contract schemas. The mock server validates:
- Request body against
requestBodyschema (JSON Schema validation) - Query parameters — required presence and type checking
- Path parameters — type checking against schema
- Content-Type — expects
application/jsonwhen a body schema is defined
Validation is non-blocking — requests still receive mock responses. Warnings appear in the terminal output and as an X-Mock-Validation-Warnings response header (contains the warning count).
Example output with validation enabled:
You can also enable validation in your config file:
Reproducible Responses
Use --seed to get deterministic mock responses. The same seed with the same schema produces identical output every time — useful for snapshot testing and screenshots.
Or set it in your config file: "seed": 42
CORS Configuration
By default, the mock server allows all origins (*). To restrict CORS to specific origins (required for credentials: 'include' requests):
When specific origins are set, the server adds Access-Control-Allow-Credentials: true and exposes custom headers like X-Mock-Contract.
Multi-Project Setup
Use a compose file to run multiple mock servers with a single command. Each service gets its own port and configuration:
Flags like --watch and --verbose are global and apply to all services. Per-service settings (port, seed, validate, cors) are defined in the compose file.
Local Dashboard
The mock server includes a built-in web dashboard at /_handshake/. It provides three tabs:
- Routes — browse all mocked routes with expandable schemas and a filter
- Requests — live request log with status, latency, validation, and expandable bodies
- Config — server info, feature flags, mock config, and contract list
The dashboard is enabled by default. Disable it with --no-dashboard.
HTTPS
Enable HTTPS with --https. On first use, a self-signed certificate is generated and stored in ~/.handshake/certs/. Subsequent runs reuse the same cert (valid for 365 days).
To use your own certificate (e.g., from mkcert):
Quick Setup with Init
Run handshake init to set up a new project interactively. The wizard validates your API key, creates .handshake.json, updates .gitignore, and optionally adds a mock script to your package.json.
Local Contract Validation
Use handshake ci run to validate a running service against your contract schemas. It makes real HTTP requests and checks responses.
Exit code 0 means all endpoints passed. Exit code 1 means one or more failed. Use --report to post results to the Handshake dashboard:
Proxy Mode
Use --proxy to run the CLI as a transparent contract validation proxy. Instead of generating mock responses, it forwards all requests to a real upstream server and validates both requests and responses against your contract schemas in real-time.
Handshake Proxy Server v0.6.0
Project: user-api
Proxy: https://staging-api.example.com
Mode: proxy | validate
10:32:01 GET /api/users → 200 ✓ contract valid
10:32:02 POST /api/users → 201 ◊ request missing "email"
You can also set the proxy target in your config file:
In proxy mode, --latency, --error-rate, --seed, and custom responses are ignored since real upstream responses are used. Validation is passive—it logs warnings but never blocks requests.
Troubleshooting
Invalid API key — verify your key in Project Settings. Pass it via --api-key flag or set the HANDSHAKE_API_KEY environment variable.
No contracts found — upload a contract at app.handshake.dev first, then run the CLI.
Port already in use — use --port <number> to pick a different port.
Cannot reach API (no cache) — run handshake mock pull while online to download contracts for offline use.
Cannot reach API (with cache) — the CLI automatically falls back to cached data. Reconnect to refresh.
For other issues, please open an issue on GitHub.