multi

yapi

CLI-first API testing for HTTP, GraphQL, gRPC, and TCP.

109 stars

How yapi Compares

Feature / AgentyapiStandard Approach
Platform SupportmultiLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

CLI-first API testing for HTTP, GraphQL, gRPC, and TCP.

Which AI agents support this skill?

This skill is compatible with multi.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# yapi

CLI-first API testing for HTTP, GraphQL, gRPC, and TCP.

## The Workflow

yapi enables test-driven API development. Write the test first, then implement until it passes:

1. **Write the test** - Create a `.yapi.yml` file with the expected behavior
2. **Run it** - `yapi run file.yapi.yml` (it will fail)
3. **Implement/fix** - Build the API endpoint
4. **Iterate** - Refine assertions, add edge cases

This loop is the core of agentic API development with yapi.

---

## Environment Setup (Do This First)

Before writing any tests, set up your environments. Create `yapi.config.yml` in your project root:

```yaml
yapi: v1
default_environment: local

environments:
  local:
    url: http://localhost:3000
    vars:
      API_KEY: dev_key_123

  staging:
    url: https://staging.example.com
    vars:
      API_KEY: ${STAGING_API_KEY}  # from shell env

  prod:
    url: https://api.example.com
    vars:
      API_KEY: ${PROD_API_KEY}
    env_files:
      - .env.prod  # load secrets from file
```

Now your tests use `${url}` and `${API_KEY}` - same test, any environment:

```bash
yapi run get-users.yapi.yml              # uses local (default)
yapi run get-users.yapi.yml --env staging
yapi run get-users.yapi.yml --env prod
```

**Variable resolution order** (highest priority first):
1. Shell environment variables
2. Environment-specific `vars`
3. Environment-specific `env_files`
4. Default `vars`
5. Default `env_files`

---

## A) Smoke Testing

Quick health checks to verify endpoints are alive.

### HTTP

```yaml
yapi: v1
url: ${url}/health
method: GET
expect:
  status: 200
```

### GraphQL

```yaml
yapi: v1
url: ${url}/graphql
graphql: |
  query { __typename }
expect:
  status: 200
  assert:
    - .data.__typename != null
```

### gRPC

```yaml
yapi: v1
url: grpc://${host}:${port}
service: grpc.health.v1.Health
rpc: Check
plaintext: true
body:
  service: ""
expect:
  status: 200
```

### TCP

```yaml
yapi: v1
url: tcp://${host}:${port}
data: "PING\n"
encoding: text
expect:
  status: 200
```

---

## B) Integration Testing

Multi-step workflows with data passing between requests. Use chains when steps depend on each other.

### Authentication Flow

```yaml
yapi: v1
chain:
  - name: login
    url: ${url}/auth/login
    method: POST
    body:
      email: test@example.com
      password: ${TEST_PASSWORD}
    expect:
      status: 200
      assert:
        - .token != null

  - name: get_profile
    url: ${url}/users/me
    method: GET
    headers:
      Authorization: Bearer ${login.token}
    expect:
      status: 200
      assert:
        - .email == "test@example.com"
```

### CRUD Flow

```yaml
yapi: v1
chain:
  - name: create
    url: ${url}/posts
    method: POST
    body:
      title: "Test Post"
      content: "Hello World"
    expect:
      status: 201
      assert:
        - .id != null

  - name: read
    url: ${url}/posts/${create.id}
    method: GET
    expect:
      status: 200
      assert:
        - .title == "Test Post"

  - name: update
    url: ${url}/posts/${create.id}
    method: PATCH
    body:
      title: "Updated Post"
    expect:
      status: 200

  - name: delete
    url: ${url}/posts/${create.id}
    method: DELETE
    expect:
      status: 204
```

### Running Integration Tests

Name test files with `.test.yapi.yml` suffix:
```
tests/
  auth.test.yapi.yml
  posts.test.yapi.yml
  users.test.yapi.yml
```

Run all tests:
```bash
yapi test ./tests                    # sequential
yapi test ./tests --parallel 4       # concurrent
yapi test ./tests --env staging      # against staging
yapi test ./tests --verbose          # detailed output
```

---

## C) Uptime Monitoring

Create test suites for monitoring your services in production.

### Monitor Suite Structure

```
monitors/
  api-health.test.yapi.yml
  auth-service.test.yapi.yml
  database-check.test.yapi.yml
  graphql-schema.test.yapi.yml
```

### Health Check with Timeout

```yaml
yapi: v1
url: ${url}/health
method: GET
timeout: 5s  # fail if response takes longer
expect:
  status: 200
  assert:
    - .status == "healthy"
    - .database == "connected"
```

### Run Monitoring Suite

```bash
# Check all monitors in parallel
yapi test ./monitors --parallel 10 --env prod

# With verbose output for debugging
yapi test ./monitors --parallel 10 --env prod --verbose
```

### CI/CD Integration (GitHub Actions)

```yaml
name: API Health Check
on:
  schedule:
    - cron: '*/5 * * * *'  # every 5 minutes
  workflow_dispatch:

jobs:
  monitor:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install yapi
        run: curl -fsSL https://yapi.run/install/linux.sh | bash

      - name: Run health checks
        env:
          PROD_API_KEY: ${{ secrets.PROD_API_KEY }}
        run: yapi test ./monitors --env prod --parallel 5
```

### Load Testing

Stress test endpoints or entire workflows:

```bash
# 1000 requests, 50 concurrent
yapi stress api-flow.yapi.yml -n 1000 -p 50

# Run for 30 seconds
yapi stress api-flow.yapi.yml -d 30s -p 25

# Against production (with confirmation)
yapi stress api-flow.yapi.yml -e prod -n 500 -p 10
```

---

## D) Async Job Polling with `wait_for`

For endpoints that process data asynchronously, use `wait_for` to poll until conditions are met.

### Fixed Period Polling

```yaml
yapi: v1
url: ${url}/jobs/${job_id}
method: GET

wait_for:
  until:
    - .status == "completed" or .status == "failed"
  period: 2s
  timeout: 60s

expect:
  assert:
    - .status == "completed"
```

### Exponential Backoff

Better for rate-limited APIs or long-running jobs:

```yaml
yapi: v1
url: ${url}/jobs/${job_id}
method: GET

wait_for:
  until:
    - .status == "completed"
  backoff:
    seed: 1s       # Initial wait
    multiplier: 2  # 1s -> 2s -> 4s -> 8s...
  timeout: 300s
```

### Async Workflow Chain

Complete example: create job, poll until done, download result:

```yaml
yapi: v1
chain:
  - name: create_job
    url: ${url}/jobs
    method: POST
    body:
      type: "data_export"
      filters:
        date_range: "last_30_days"
    expect:
      status: 202
      assert:
        - .job_id != null

  - name: wait_for_job
    url: ${url}/jobs/${create_job.job_id}
    method: GET
    wait_for:
      until:
        - .status == "completed" or .status == "failed"
      period: 2s
      timeout: 300s
    expect:
      assert:
        - .status == "completed"
        - .download_url != null

  - name: download_result
    url: ${wait_for_job.download_url}
    method: GET
    output_file: ./export.csv
```

### Webhook/Callback Waiting

Wait for a webhook to be received:

```yaml
yapi: v1
chain:
  - name: trigger_action
    url: ${url}/payments/initiate
    method: POST
    body:
      amount: 100
    expect:
      status: 202

  - name: wait_for_webhook
    url: ${url}/webhooks/received
    method: GET
    wait_for:
      until:
        - . | length > 0
        - .[0].event == "payment.completed"
      period: 1s
      timeout: 30s
```

---

## E) Integrated Test Server

Automatically start your dev server, wait for health checks, run tests, and clean up. Configure in `yapi.config.yml`:

```yaml
yapi: v1

test:
  start: "npm run dev"
  wait_on:
    - "http://localhost:3000/healthz"
    - "grpc://localhost:50051"
  timeout: 60s
  parallel: 8
  directory: "./tests"

environments:
  local:
    url: http://localhost:3000
```

### Running with Integrated Server

```bash
# Automatically starts server, waits for health, runs tests, kills server
yapi test

# Skip server startup (server already running)
yapi test --no-start

# Override config from CLI
yapi test --start "npm start" --wait-on "http://localhost:4000/health"

# See server stdout/stderr
yapi test --verbose
```

### Health Check Protocols

| Protocol | URL Format | Behavior |
|----------|------------|----------|
| HTTP/HTTPS | `http://localhost:3000/healthz` | Poll until 2xx response |
| gRPC | `grpc://localhost:50051` | Uses `grpc.health.v1.Health/Check` |
| TCP | `tcp://localhost:5432` | Poll until connection succeeds |

### Local vs CI Parity

The same workflow works locally and in CI:

**Local development:**
```bash
yapi test  # starts server, runs tests, cleans up
```

**GitHub Actions:**
```yaml
- uses: jamierpond/yapi/action@main
  with:
    start: npm run dev
    wait-on: http://localhost:3000/healthz
    command: yapi test -a
```

---

## Commands Reference

| Command | Description |
|---------|-------------|
| `yapi run file.yapi.yml` | Execute a request |
| `yapi run file.yapi.yml --env prod` | Execute against specific environment |
| `yapi test ./dir` | Run all `*.test.yapi.yml` files |
| `yapi test ./dir --all` | Run all `*.yapi.yml` files (not just tests) |
| `yapi test ./dir --parallel 4` | Run tests concurrently |
| `yapi validate file.yapi.yml` | Check syntax without executing |
| `yapi watch file.yapi.yml` | Re-run on every file save |
| `yapi stress file.yapi.yml` | Load test with concurrency |
| `yapi list` | List all yapi files in directory |

---

## Assertion Syntax

Assertions use JQ expressions that must evaluate to true.

### Body Assertions

```yaml
expect:
  status: 200
  assert:
    - .id != null                    # field exists
    - .name == "John"                # exact match
    - .age > 18                      # comparison
    - . | length > 0                 # array not empty
    - .[0].email != null             # first item has email
    - .users | length == 10          # exactly 10 users
    - .type == "admin" or .type == "user"  # alternatives
    - .tags | contains(["api"])      # array contains value
```

### Header Assertions

```yaml
expect:
  status: 200
  assert:
    headers:
      - .["Content-Type"] | contains("application/json")
      - .["X-Request-Id"] != null
      - .["Cache-Control"] == "no-cache"
    body:
      - .data != null
```

### Status Code Options

```yaml
expect:
  status: 200           # exact match
  status: [200, 201]    # any of these
```

---

## Protocol Examples

### HTTP with Query Params and Headers

```yaml
yapi: v1
url: ${url}/api/users
method: GET
headers:
  Authorization: Bearer ${API_KEY}
  Accept: application/json
query:
  limit: "10"
  offset: "0"
  sort: "created_at"
expect:
  status: 200
```

### HTTP POST with JSON Body

```yaml
yapi: v1
url: ${url}/api/users
method: POST
body:
  name: "John Doe"
  email: "john@example.com"
  roles:
    - admin
    - user
expect:
  status: 201
  assert:
    - .id != null
```

### HTTP Form Data

```yaml
yapi: v1
url: ${url}/upload
method: POST
content_type: multipart/form-data
form:
  name: "document.pdf"
  description: "Q4 Report"
expect:
  status: 200
```

### GraphQL with Variables

```yaml
yapi: v1
url: ${url}/graphql
graphql: |
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
variables:
  id: "123"
expect:
  status: 200
  assert:
    - .data.user.id == "123"
```

### gRPC with Metadata

```yaml
yapi: v1
url: grpc://${host}:${port}
service: users.UserService
rpc: GetUser
plaintext: true
headers:
  authorization: Bearer ${API_KEY}
body:
  user_id: "123"
expect:
  status: 200
  assert:
    - .user.id == "123"
```

### TCP Raw Connection

```yaml
yapi: v1
url: tcp://${host}:${port}
data: |
  GET / HTTP/1.1
  Host: example.com

encoding: text
read_timeout: 5
expect:
  status: 200
```

---

## File Organization

Recommended project structure:

```
project/
  yapi.config.yml          # environments
  .env                     # local secrets (gitignored)
  .env.example             # template for secrets

  tests/
    auth/
      login.test.yapi.yml
      logout.test.yapi.yml
    users/
      create-user.test.yapi.yml
      get-user.test.yapi.yml

  monitors/
    health.test.yapi.yml
    critical-endpoints.test.yapi.yml
```

---

## Tips

- **Start simple**: Begin with status code checks, add body assertions as needed
- **Use watch mode**: `yapi watch file.yapi.yml` for rapid iteration
- **Validate before running**: `yapi validate file.yapi.yml` catches syntax errors
- **Keep tests focused**: One logical flow per file
- **Name steps clearly**: In chains, use descriptive names like `create_user`, `verify_email`
- **Reference previous steps**: Use `${step_name.field}` to pass data between chain steps