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

# Grouping rules API

> List, add, change, and remove the error grouping rules of a project, and preview how they group recent errors.

Grouping rules set the fingerprint, and so the issue, of new error events. Squasher checks the
rules of a project in order. The first enabled rule that matches and can act sets the fingerprint.
Existing issues keep their fingerprint. See [Grouping rules](/features/issues#grouping-rules) for
the conditions, the actions, and the template placeholders.

## Operations

| Operation | Method and path | Access | Purpose |
| - | - | - | - |
| `groupingRules.list` | `GET /v1/projects/{project_id}/grouping-rules` | `errors:read` | List the rules in evaluation order. |
| `groupingRules.create` | `POST /v1/projects/{project_id}/grouping-rules` | `errors:write` | Add a rule. |
| `groupingRules.update` | `PATCH /v1/projects/{project_id}/grouping-rules/{rule_id}` | `errors:write` | Change a rule, turn it off, or move it. |
| `groupingRules.delete` | `DELETE /v1/projects/{project_id}/grouping-rules/{rule_id}` | `errors:write` | Remove a rule. |
| `groupingRules.preview` | `POST /v1/projects/{project_id}/grouping-rules/preview` | `errors:read` | Dry run of rules over recent error events. |

## List the rules

```bash theme={null}
curl --url "https://api.squasher.ai/v1/projects/$SQUASHER_PROJECT_ID/grouping-rules" \
  --header "x-squasher-key: $SQUASHER_API_KEY"
```

```json theme={null}
{
  "limit": 50,
  "rules": [
    {
      "id": "6f1c2a9e-4b7d-4e8a-9c3f-1d2e5a7b8c90",
      "name": "Timeouts by route",
      "position": 0,
      "enabled": true,
      "match": {
        "exception_type": "TimeoutError",
        "message_regex": null,
        "service": null,
        "frame_path": null,
        "tags": {}
      },
      "action": "fingerprint",
      "fingerprint_template": "{{ error.type }} {{ tags.http.route }}",
      "stack_frame_count": null,
      "merge_into": null,
      "created_at": "2026-10-10T09:00:00.000Z",
      "updated_at": "2026-10-10T09:00:00.000Z"
    }
  ]
}
```

`position` is the evaluation order, 0 first. `limit` is the most rules the project can have. For a
`merge_into` rule, `merge_into` has the `issue_id`, the `short_id`, and the `fingerprint` of the
target issue.

## Add a rule

| Field | Description |
| - | - |
| `name` | Required. 1 to 120 characters. |
| `action` | Required. `fingerprint`, `stack_frames`, or `merge_into`. |
| `match` | The conditions: `exception_type`, `message_regex`, `service`, `frame_path`, and `tags`. All must match. |
| `fingerprint_template` | Required for `fingerprint`. Up to 512 characters. |
| `stack_frame_count` | For `stack_frames`: how many in-app frames, 1 to 10. Default 3. |
| `merge_into_issue_id` | Required for `merge_into`: the ID or short ID (`iss_42`) of an issue of this project. |
| `enabled` | Default `true`. |
| `position` | Where to put the rule, 0 first. Default: last. |

```bash theme={null}
curl --request POST \
  --url "https://api.squasher.ai/v1/projects/$SQUASHER_PROJECT_ID/grouping-rules" \
  --header "x-squasher-key: $SQUASHER_API_KEY" \
  --header "content-type: application/json" \
  --data '{
    "name": "Fetch failures by caller",
    "match": { "message_regex": "^Failed to fetch", "service": "web" },
    "action": "stack_frames",
    "stack_frame_count": 2
  }'
```

The response has status `201` and the new rule in `rule`. The rule applies to new error events
within about a minute.

## Change a rule

Send only the fields that change. Fields of `match` merge with the saved conditions. To remove a
condition, set it to `null`.

```bash theme={null}
curl --request PATCH \
  --url "https://api.squasher.ai/v1/projects/$SQUASHER_PROJECT_ID/grouping-rules/$RULE_ID" \
  --header "x-squasher-key: $SQUASHER_API_KEY" \
  --header "content-type: application/json" \
  --data '{ "enabled": false, "position": 0 }'
```

The response has the changed rule in `rule`.

## Remove a rule

```bash theme={null}
curl --request DELETE \
  --url "https://api.squasher.ai/v1/projects/$SQUASHER_PROJECT_ID/grouping-rules/$RULE_ID" \
  --header "x-squasher-key: $SQUASHER_API_KEY"
```

The response is `{ "deleted": true }`. New events then group by the next matching rule, or by the
default fingerprint.

## Preview rules

The preview groups recent error events again and writes nothing. Without `rules`, it uses the saved
rules. With `rules`, it uses those drafts, in order, instead of the saved rules.

| Field | Description |
| - | - |
| `rules` | Draft rules with the same fields as a new rule, and an optional `id`. Up to 50. |
| `limit` | Recent error events to read, newest first. Default 200, at most 1000. |
| `hours` | How far back to read. Default 168 (7 days), at most 720. |

```bash theme={null}
curl --request POST \
  --url "https://api.squasher.ai/v1/projects/$SQUASHER_PROJECT_ID/grouping-rules/preview" \
  --header "x-squasher-key: $SQUASHER_API_KEY" \
  --header "content-type: application/json" \
  --data '{
    "hours": 24,
    "rules": [
      {
        "name": "Timeouts by route",
        "match": { "exception_type": "TimeoutError" },
        "action": "fingerprint",
        "fingerprint_template": "{{ error.type }} {{ tags.http.route }}"
      }
    ]
  }'
```

```json theme={null}
{
  "scanned_events": 200,
  "changed_events": 37,
  "window": { "from": "2026-10-09T10:00:00.000Z", "to": "2026-10-10T10:00:00.000Z" },
  "rules": [{ "id": "draft-0", "name": "Timeouts by route", "matched_events": 37 }],
  "groups": [
    {
      "fingerprint": "9c41e07a2b5d8f13",
      "rule_id": "draft-0",
      "rule_name": "Timeouts by route",
      "events": 21,
      "current_fingerprints": ["4a7e19c2d03b6f58"],
      "existing_issue": null,
      "sample": {
        "message": "Request to /api/checkout timed out after 30000 ms",
        "error_type": "TimeoutError",
        "service": "api",
        "timestamp": "2026-10-10T09:58:12.000Z"
      }
    }
  ]
}
```

A draft without `id` is named `draft-<index>`. `changed_events` counts the events whose fingerprint
would differ from the one they have. `current_fingerprints` lists the fingerprints that the events
of a group have today. `existing_issue` is the issue that already has the group's fingerprint, or
`null` when the rule would start a new issue. A group with `rule_id: null` uses the default
fingerprint.

## Errors

| Status | When |
| - | - |
| `400` | A field is not valid: an unknown placeholder, an empty template, a message pattern with unsupported syntax, or a `merge_into` issue that is not in the project. The preview names the draft rule. |
| `403` | The key does not have the necessary access to the project. |
| `404` | No rule of this project has the given ID. |
| `409` | The project already has 50 rules. Remove a rule before you add one. |

## TypeScript client and CLI

```typescript theme={null}
const { rules } = await client.groupingRules.list();
const { rule } = await client.groupingRules.create({
  name: "Timeouts by route",
  match: { exception_type: "TimeoutError" },
  action: "fingerprint",
  fingerprint_template: "{{ error.type }} {{ tags.http.route }}",
});
const preview = await client.groupingRules.preview({ hours: 24 });
await client.groupingRules.update(rule.id, { enabled: false });
await client.groupingRules.delete(rule.id);
```

```bash theme={null}
squasher grouping-rules list --project <project_id>
squasher grouping-rules preview --project <project_id>
```

The CLI also has `squasher grouping-rules create`, `update`, and `delete`. For their flags, see the
[CLI reference](/integrations/cli-reference).

## Related guides

* [Issues and error grouping](/features/issues)
* [Error groups API](/api-reference/errors)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.