---
title: Errors
description: How an error is grouped by fingerprint, how an occurrence is captured, and how a group is acknowledged, resolved and reopened.
type: how-to
product: monitoring
summary: Grouping, capture, and the three calls that move a group through triage
updated: 2026-09-28
order: 0
---

An error arrives as an occurrence and is read as a group. The occurrence is
never changed once stored; what moves is the group, and only through the calls
on this page. [The error_groups collection](/docs/api/error_groups) has every
method, its scope and its examples.

## How a group is chosen

An Error Group is the deterministic grouping of error occurrences by
fingerprint. A capture is grouped by `fingerprint` when the caller sets one, and
otherwise by exception type and the symbolicated in-app stack.

Release is never part of the group key. `last_release` reports the release of
the latest occurrence, and nothing else, which is why one bug stays one group
across releases rather than starting again at every deploy.

Everything a group reports is derived from its occurrences:

<PropertyTable
	properties={[
		{
			name: 'fingerprint',
			type: 'string',
			description: 'The grouping fingerprint. Output only.',
		},
		{
			name: 'title',
			type: 'string',
			description: 'The exception type and message of the first occurrence. Output only.',
		},
		{
			name: 'culprit',
			type: 'string',
			description: 'The top in-app frame. Output only.',
		},
		{
			name: 'service_name',
			type: 'string',
			description: 'The emitting service. Output only.',
		},
		{
			name: 'state',
			type: 'ErrorGroupState',
			description: 'The triage state: unresolved, acknowledged or resolved. Output only.',
		},
		{
			name: 'first_seen_time',
			type: 'timestamp',
			description: 'The first occurrence. Output only.',
		},
		{
			name: 'last_seen_time',
			type: 'timestamp',
			description: 'The latest occurrence. Output only.',
		},
		{
			name: 'occurrence_count',
			type: 'int64',
			description: 'Occurrences, all time. Output only.',
		},
		{
			name: 'last_release',
			type: 'string',
			description: 'The release of the latest occurrence. Output only.',
		},
	]}
/>

## Capture an occurrence

`capture` takes one occurrence. `event_time` and `exception_type` are required;
everything else sharpens the group or the record.

<PropertyTable
	properties={[
		{
			name: 'event_time',
			type: 'timestamp',
			description: 'When it happened. Required.',
		},
		{
			name: 'exception_type',
			type: 'string',
			description: 'The exception type. Required.',
		},
		{
			name: 'message',
			type: 'string',
			description: 'The exception message.',
		},
		{
			name: 'stack',
			type: 'string',
			description: 'The stack as the runtime printed it. Scrubbed before storage.',
		},
		{
			name: 'stack_frames',
			type: 'StackFrame[]',
			description: 'Stack frames, innermost last. When set, it wins and stack is kept as sent.',
		},
		{
			name: 'service_name',
			type: 'string',
			description: 'The emitting service.',
		},
		{
			name: 'release',
			type: 'string',
			description: 'The release the emitter ran. It selects the source maps the frames are mapped through.',
		},
		{
			name: 'breadcrumbs',
			type: 'Breadcrumb[]',
			description: 'Breadcrumbs before the error, oldest first.',
		},
		{
			name: 'tags',
			type: 'map<string, string>',
			description: 'Typed tags.',
		},
		{
			name: 'fingerprint',
			type: 'string',
			description: 'Overrides the grouping fingerprint.',
		},
		{
			name: 'trace_id',
			type: 'string',
			description: 'W3C trace id (32 hex), which connects the occurrence to a trace.',
		},
		{
			name: 'span_id',
			type: 'string',
			description: 'W3C span id (16 hex).',
		},
	]}
/>

```bash
sylphx observability error-groups capture orgs/acme/projects/shop/envs/production
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/error_groups:capture" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"error_event":{"event_time":{},"exception_type":"…"}}'
```

```ts
const response = await sylphx.observability.errorGroups.capture({ errorEvent: { eventTime: {}, exceptionType: '…' }, parent: 'orgs/acme/projects/shop/envs/production' })
```

<Callout tone="note" title="The stack is read for you">
When `stack_frames` is empty, V8 lines (`at fn (file:line:col)`) and
Gecko/WebKit lines (`fn@file:line:col`) are parsed into frames, innermost last.
When `stack_frames` is set, it wins and `stack` is kept as sent. Either way the
frames are mapped through the source maps uploaded for `release` before the
group is chosen — [Source maps](/docs/monitoring/source-maps) is the upload.
</Callout>

Secrets and personal data are scrubbed by the environment's
[Scrubbing Policy](/docs/monitoring/scrubbing) before the occurrence is stored,
and `stack` is scrubbed with the rest.

## Read a group and its occurrences

An Error Event is one immutable error occurrence, with breadcrumbs and
correlation, and it lives under the group it joined. Nothing rewrites an
occurrence, so a group's history cannot be edited — only its state moves.

```bash
sylphx observability error-groups list orgs/acme/projects/shop/envs/production
sylphx observability error-groups get orgs/acme/projects/shop/envs/production/error_groups/error-group
sylphx observability error-events list orgs/acme/projects/shop/envs/production/error_groups/error-group
sylphx observability error-events get orgs/acme/projects/shop/envs/production/error_groups/error-group/error_events/error-event
```

Listing groups filters by `state`, `service_name` and `last_seen_time`, which is
how a triage list is built: the groups still unresolved, for one service, seen
in the interval you care about.

An occurrence carries what the capture sent — its breadcrumbs oldest first, its
tags, its `release` — and the `trace_id` and `span_id` that connect it to a
trace. Together those are the difference between knowing a group fired and
knowing what the request was doing when it did.

## Triage a group

Acknowledge, resolve and reopen require the current etag: read the group, and
send the `etag` you read back with the call. A call whose etag no longer
matches fails with `ETAG_MISMATCH` and changes nothing, which is what stops two
people triaging the same group from overwriting each other.

```bash
sylphx observability error-groups acknowledge orgs/acme/projects/shop/envs/production/error_groups/error-group --etag …
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/error_groups/error-group:acknowledge" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```ts
const response = await sylphx.observability.errorGroups.acknowledge({ name: 'orgs/acme/projects/shop/envs/production/error_groups/error-group' })
```

`acknowledge` says the group is seen and being worked on. `resolve` says it is
done — and a new occurrence reopens it, so a resolve is a statement about now
rather than a promise about later:

```bash
sylphx observability error-groups resolve orgs/acme/projects/shop/envs/production/error_groups/error-group --etag …
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/error_groups/error-group:resolve" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```ts
const response = await sylphx.observability.errorGroups.resolve({ name: 'orgs/acme/projects/shop/envs/production/error_groups/error-group' })
```

`reopen` takes a resolved or an acknowledged group back to triage, which is the
call to reach for when the bug was not fixed after all:

```bash
sylphx observability error-groups reopen orgs/acme/projects/shop/envs/production/error_groups/error-group --etag …
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/error_groups/error-group:reopen" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```ts
const response = await sylphx.observability.errorGroups.reopen({ name: 'orgs/acme/projects/shop/envs/production/error_groups/error-group' })
```

A call the group's state forbids — reopening one that is already unresolved —
fails with `INVALID_STATE`. The [error_groups commands](/docs/cli/error_groups)
show the `--etag` flag on each of the three.

<RelatedDocs
	links={[
		{
			href: '/docs/monitoring/source-maps',
			label: 'Source maps',
			description: 'Upload the map that symbolicates a release before it is grouped.',
		},
		{
			href: '/docs/monitoring/scrubbing',
			label: 'Scrubbing',
			description: 'What is removed from an occurrence before it is stored.',
		},
		{
			href: '/docs/monitoring/quickstart',
			label: 'Quickstart',
			description: 'Capture an error and read its group back.',
		},
	]}
/>
