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:

  1. Sign up with GitHub — Log in at app.handshake.dev using your GitHub account.
  2. Create a team — Teams group your projects and manage member access. Invite collaborators with viewer, member, or admin roles.
  3. 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.
  4. 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

InputRequiredDefaultDescription
api-keyYesHandshake project API key (hsk_...)
validation-urlYesBase URL of the API to validate
base-urlNohttps://ci.handshake.devHandshake CI runner URL

Outputs

OutputDescription
statusValidation result: passed, failed, or error
run-idCI run ID for dashboard reference
totalTotal endpoints validated
passedEndpoints that passed
failedEndpoints that failed

Full workflow example

.github/workflows/contracts.yml
name: Contract Validation
on:
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate contracts
uses: Ethirajulu/handshake@main
id: handshake
with:
api-key: ${{ secrets.HANDSHAKE_API_KEY }}
validation-url: https://staging.myapp.com

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:

terminal
# Trigger validation via API
curl -X POST https://ci.handshake.dev/ci/trigger \
-H "X-API-Key: hsk_your_project_key" \
-H "Content-Type: application/json" \
-d '{ "baseUrl": "https://staging.myapp.com", "commitSha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" }'

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

PlanCI Runs / MonthRate Limit
Free10060 req/min
Pro1,000300 req/min
Team10,0001,000 req/min
EnterpriseUnlimitedUnlimited

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

terminal
# Test against mock server
curl https://my-project.mock.handshake.dev/users/1
# Or use the header for local development
curl http://localhost:3002/users/1 \
-H "X-Mock-Subdomain: my-project"

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

terminal
# Install globally
npm install -g @handshake/cli
# Or run directly without installing
npx @handshake/cli mock start --api-key YOUR_KEY

Quick Start

1. Get your API key from your project's Settings page in the Handshake dashboard.

2. Start the mock server:

terminal
handshake mock start --api-key hsk_...

3. Make requests against your local mock:

terminal
curl http://localhost:4010/api/users
# Returns faker-generated JSON matching your contract schema

Configuration

FlagDefaultDescription
--api-key$HANDSHAKE_API_KEYProject API key
--port4010Local server port
--latency0Simulated response latency (ms)
--error-rate0Fraction of requests returning 500 (0-1)
--watchfalseRe-fetch contracts every 30s
--validatefalseValidate 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
--verbosefalseLog request/response bodies (truncated)
--httpsfalseEnable 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
--reportfalsePost ci run results to Handshake API
--timeout <ms>10000Per-request timeout for ci run
--fail-on-warningfalseExit 1 on warnings in ci run
--no-telemetryfalseDisable anonymous usage telemetry
--config.handshake.jsonConfig file path
--api-urlhttps://api.handshake.devAPI base URL

You can also save configuration in a .handshake.json file:

.handshake.json
{
"apiKey": "hsk_...",
"port": 4010,
"latencyMs": 100,
"customResponses": {
"GET:/api/users/123": {
"statusCode": 200,
"body": { "id": "123", "name": "Test User" }
}
}
}

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:

terminal
handshake mock pull --api-key hsk_...

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:

.handshake.json
{
"customResponses": {
"GET:/api/users/123": { "statusCode": 200, "body": { "name": "Alice" } },
"POST:/api/users": { "statusCode": 201, "body": { "id": "new" } }
}
}

Request Validation

Enable --validate to check incoming requests against your OpenAPI contract schemas. The mock server validates:

  • Request body against requestBody schema (JSON Schema validation)
  • Query parameters — required presence and type checking
  • Path parameters — type checking against schema
  • Content-Type — expects application/json when 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).

terminal
handshake mock start --api-key hsk_... --validate

Example output with validation enabled:

terminal
08:42:15 POST /api/users → 200 3ms ⚠ Required body field "name" is missing
08:42:16 GET /api/users → 200 1ms ✓ validated
08:42:18 POST /api/users → 200 2ms ✓ validated

You can also enable validation in your config file:

.handshake.json
{ "validate": true }

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.

terminal
handshake mock start --api-key hsk_... --seed 42

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):

terminal
handshake mock start --api-key hsk_... --cors http://localhost:3000,http://localhost:5173

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:

handshake-mock.yml
services:
user-service:
apiKey: hsk_abc...
port: 4010
validate: true
seed: 42
payment-service:
apiKey: hsk_def...
port: 4011
errorRate: 0.1
notification-service:
apiKey: hsk_ghi...
port: 4012
terminal
handshake mock start --compose handshake-mock.yml

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).

terminal
handshake mock start --api-key hsk_... --https

To use your own certificate (e.g., from mkcert):

terminal
handshake mock start --api-key hsk_... --https --cert ./certs/localhost.pem --key ./certs/localhost-key.pem

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.

terminal
handshake init

Local Contract Validation

Use handshake ci run to validate a running service against your contract schemas. It makes real HTTP requests and checks responses.

terminal
handshake ci run --base-url http://localhost:3000

Exit code 0 means all endpoints passed. Exit code 1 means one or more failed. Use --report to post results to the Handshake dashboard:

terminal
handshake ci run --base-url http://localhost:3000 --report

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.

terminal
handshake mock start --proxy https://staging-api.example.com --validate

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 &check; contract valid
10:32:02 POST /api/users → 201 ◊ request missing "email"

You can also set the proxy target in your config file:

.handshake.json
{ "apiKey": "hsk_...", "proxy": "https://staging-api.example.com", "validate": true }

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.