> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getgriffinapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sequential Builder

> The simplest way to write linear API and browser monitor flows

The sequential builder is the easiest way to write Griffin monitors. You add steps in order and edges are created automatically: `START → step 1 → step 2 → ... → END`.

For monitors that need branching or parallel paths, see the [Graph Builder](/writing-tests/graph-builder). For browser-based monitoring, see [Browser Monitors](/writing-tests/browser-monitors).

## Basic example

```typescript theme={null}
import { createMonitorBuilder, GET, Json, Assert, Frequency } from "@griffin-app/griffin";

const monitor = createMonitorBuilder({
  name: "health-check",
  frequency: Frequency.every(1).minute(),
})
  .request("check", {
    method: GET,
    base: "https://api.example.com",
    response_format: Json,
    path: "/health",
  })
  .assert((state) => [
    Assert(state["check"].status).equals(200),
  ])
  .build();

export default monitor;
```

## Configuration

```typescript theme={null}
createMonitorBuilder({
  name: "my-test",                          // Required: unique monitor name
  frequency: Frequency.every(5).minutes(),  // Required: how often to run
  locations: ["us-east-1"],                 // Optional: where to execute
  notifications: [                          // Optional: alert rules
    notify.onFailure().toSlack("#alerts"),
  ],
})
```

## Adding requests

Use `.request(name, config)` to add an HTTP request step:

```typescript theme={null}
builder
  .request("get-users", {
    method: GET,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users",
  })
  .request("create-user", {
    method: POST,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users",
    headers: { "Content-Type": "application/json" },
    body: { name: "Test User" },
  })
```

**Response format:** Use `Json` or `Xml` for JSON/XML bodies. Use `NoContent` (import from `@griffin-app/griffin`) for endpoints that return **204 with no body** (e.g. successful DELETE); assert only on `state["node"].status` (e.g. `.equals(204)`), not on body.

See the [Graph Builder](/writing-tests/graph-builder#http-requests) page for the full config reference.

## Adding waits

Use `.wait(name, duration)` to pause between steps:

```typescript theme={null}
builder
  .request("create", { ... })
  .wait("pause", { seconds: 2 })
  .request("verify", { ... })
```

Duration formats:

* `1000` — milliseconds
* `{ seconds: 2 }` — seconds
* `{ minutes: 1 }` — minutes

## Adding assertions

Use `.assert(callback)` to validate results from previous steps. The callback receives a type-safe `state` proxy with autocomplete for all node names defined above it:

```typescript theme={null}
builder
  .request("get-users", {
    method: GET,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users",
  })
  .assert((state) => [
    // Check status code
    Assert(state["get-users"].status).equals(200),

    // Check response body
    Assert(state["get-users"].body["data"]).not.isEmpty(),
    Assert(state["get-users"].body["data"].at(0)["name"]).isDefined(),

    // Check headers
    Assert(state["get-users"].headers["content-type"]).contains("application/json"),

    // Check latency
    Assert(state["get-users"].latency).lessThan(2000),
  ])
```

See [Assertions](/writing-tests/assertions) for the full assertion API.

## Adding browser steps

Use `.browser(name, browserAction)` to add a Playwright browser step:

```typescript theme={null}
import {
  BrowserAction, navigate, waitForSelector,
  extractText, screenshot,
} from "@griffin-app/griffin";

builder
  .browser("check_page", BrowserAction({
    browser: "chromium",
    steps: [
      navigate("https://example.com"),
      waitForSelector("h1"),
      extractText("heading", "h1"),
      screenshot(),
    ],
  }))
  .assert((state) => [
    Assert(state["check_page"].page.url).contains("example.com"),
    Assert(state["check_page"].extracts["heading"]).equals("Welcome"),
    Assert(state["check_page"].console.errors).isEmpty(),
  ])
```

Browser steps can be mixed freely with `.request()` and `.wait()` in any order. See [Browser Monitors](/writing-tests/browser-monitors) for the full browser API, step reference, and examples.

## Complete example

A multi-step API monitor that creates a resource, verifies it, and cleans up:

```typescript theme={null}
import {
  createMonitorBuilder, GET, POST, DELETE, Json,
  Assert, Frequency, secret
} from "@griffin-app/griffin";

const monitor = createMonitorBuilder({
  name: "user-lifecycle",
  frequency: Frequency.every(15).minutes(),
})
  .request("create", {
    method: POST,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users",
    headers: { "Authorization": secret("API_KEY") },
    body: { name: "Test User", email: "test@example.com" },
  })
  .assert((state) => [
    Assert(state["create"].status).equals(201),
    Assert(state["create"].body["id"]).isDefined(),
  ])
  .wait("pause", { seconds: 1 })
  .request("verify", {
    method: GET,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users/1",
    headers: { "Authorization": secret("API_KEY") },
  })
  .assert((state) => [
    Assert(state["verify"].status).equals(200),
    Assert(state["verify"].body["name"]).equals("Test User"),
  ])
  .request("cleanup", {
    method: DELETE,
    base: "https://api.example.com",
    response_format: Json,
    path: "/users/1",
    headers: { "Authorization": secret("API_KEY") },
  })
  .assert((state) => [
    Assert(state["cleanup"].status).equals(204),
  ])
  .build();

export default monitor;
```
