The search_events tool provides a unified interface for searching Sentry events across different datasets (errors, logs, spans, and metrics). This document covers the API patterns, query structures, and best practices for both individual event queries and aggregate queries.
Sentry uses two different query-building patterns depending on the dataset, even though they share the same /events/ endpoint:
-
Discover-style events queries (
errors,metrics)- Use repeated
fieldparameters - Use raw aggregate expressions in both
fieldandsort - Generate Discover-style URLs for
errorsand Metrics page URLs formetrics
- Use repeated
-
Modern EAP (Event Analytics Platform) queries (
spans,logs)- Use the same
/events/endpoint with EAP-oriented field conventions - Support richer span/log search semantics
- Generate Explore URLs for traces and logs
- Use the same
All queries use the same base endpoint:
/api/0/organizations/{organizationSlug}/events/
The tool forwards the dataset name directly to the API:
- User specifies
errors→ API useserrors - User specifies
spans→ API usesspans - User specifies
logs→ API useslogs - User specifies
metrics→ API usestracemetrics
Returns raw event data with full details. This is the default mode when no aggregate functions are used.
Key Characteristics:
- Returns actual event occurrences
- Includes default fields plus any user-requested fields
- Sorted by timestamp (newest first) by default
- Limited to a specific number of results (default: 10, max: 100)
Example API URL:
https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=id&field=span.op&field=span.description&field=span.duration&field=transaction&field=timestamp&field=ai.model.id&field=ai.model.provider&field=project&field=trace&per_page=50&query=&sort=-timestamp&statsPeriod=24h
Default Fields by Dataset:
- Spans:
id,span.op,span.description,span.duration,transaction,timestamp,project,trace - Errors:
issue,title,project,timestamp,level,message,error.type,culprit - Logs:
timestamp,project,message,severity,trace - Tracemetrics:
id,timestamp,project,trace,metric.name,metric.type,metric.unit,value
Returns grouped and aggregated data, similar to SQL GROUP BY queries.
Key Characteristics:
- Activated when ANY field contains a function (e.g.,
count(),avg()) - Fields should ONLY include aggregate functions and groupBy fields
- Do NOT include default fields (id, timestamp, etc.)
- Automatically groups by all non-function fields
Example API URLs:
Single groupBy field:
https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=ai.model.id&field=count()&per_page=50&query=&sort=-count()&statsPeriod=24h
Multiple groupBy fields:
https://us.sentry.io/api/0/organizations/sentry/events/?dataset=spans&field=ai.model.id&field=ai.model.provider&field=sum(span.duration)&per_page=50&query=&sort=-sum(span.duration)&statsPeriod=24h
| Parameter | Description | Example |
|---|---|---|
dataset |
Which dataset to query | spans, errors, logs, metrics |
field |
Fields to return (repeated for each field) | field=span.op&field=count() |
query |
Sentry query syntax filter | has:db.statement AND span.duration:>1000 |
sort |
Sort order (prefix with - for descending) |
-timestamp, -count() |
per_page |
Results per page | 50 |
statsPeriod |
Relative time window filter | 1h, 24h, 7d, 14d, 30d |
start |
Absolute start time (ISO 8601) | 2025-06-19T07:00:00 |
end |
Absolute end time (ISO 8601) | 2025-06-20T06:59:59 |
project |
Project ID (numeric, not slug) | 4509062593708032 |
- Supports timestamp filters in query (e.g.,
timestamp:-1h) - Rich performance metrics available
- Common aggregate functions:
count(),avg(span.duration),p95(span.duration)
- Supports timestamp filters in query
- Issue grouping available via
issuefield - Common aggregate functions:
count(),count_unique(user.id),last_seen()
- Does NOT support timestamp filters in query (use
statsPeriodinstead) - Severity levels: fatal, error, warning, info, debug, trace
- Common aggregate functions:
count(),epm() - Regex filters on string attributes:
message://timeout after \d+ms//(RE2, unquoted, max 64 characters); prefer wildcards for plain substrings, prefixes, and suffixes
- Represents newer span metrics, including counters, gauges, and distributions
- Uses the same
/events/endpoint withdataset=tracemetrics - Attribute discovery uses
/trace-items/attributes/?itemType=tracemetrics - Aggregate fields must encode the target metric directly:
fn(value,metric.name,metric.type,metric.unit) - Sort fields for aggregate results must stay raw, for example
-p95(value,http.request.duration,distribution,millisecond)
- Exact match:
field:value - Wildcards:
field:*pattern* - Comparison:
field:>100,field:<500 - Boolean:
AND,OR,NOT - Phrases:
message:"database connection failed" - Attribute existence:
has:field(recommended for spans)
Instead of using span.op patterns, use has: queries for more flexible attribute-based filtering:
- HTTP requests:
has:request.urlinstead ofspan.op:http* - Database queries:
has:db.statementorhas:db.systeminstead ofspan.op:db* - AI/LLM calls:
has:ai.model.id - MCP tool calls:
has:gen_ai.tool.name
count()- Count of eventscount_unique(field)- Count of unique valuesepm()- Events per minute rate
avg(field)- Average valuesum(field)- Sum of valuesmin(field)- Minimum valuemax(field)- Maximum valuep50(field),p75(field),p90(field),p95(field),p99(field)- Percentiles
- Use the metric-aware form
fn(value,metric.name,metric.type,metric.unit) - Counter examples:
sum(value,http.server.request.count,counter,-),per_minute(value,http.server.request.count,counter,-) - Gauge examples:
avg(value,cpu.usage,gauge,percent),max(value,cpu.usage,gauge,percent) - Distribution examples:
p75(value,http.request.duration,distribution,millisecond),p95(value,http.request.duration,distribution,millisecond)
Spans, logs, and metrics append _if to an aggregate and take a backtick-wrapped search filter as the first argument. The remaining arguments match the base function.
- Spans:
count_if(`span.op:db`),count_if(`span.duration:>100`),avg_if(`span.duration:<=100`,span.duration),p95_if(`has:db.statement`,span.duration) - Logs:
count_if(`severity:error`),avg_if(`severity:error`,severity_number) - Metrics:
sum_if(`environment:production`,value,request_count,counter,-),p95_if(`release:1.2.3`,value,http.request.duration,distribution,millisecond)
The same form applies to count_unique_if, sum_if, min_if, max_if, percentile _if functions, and metric per_second_if / per_minute_if.
count_if(field,equals,value)- Conditional count, e.g.count_if(error.handled,equals,false). This three-argument form is errors-onlylast_seen()- Most recent timestampeps()- Events per second rate
Query: has:db.statement
Fields: ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp", "project", "trace", "db.system", "db.statement"]
Sort: -span.duration
Dataset: spans
Query: is_transaction:true
Fields: ["transaction", "count()", "avg(span.duration)", "p95(span.duration)"]
Sort: -avg(span.duration)
Dataset: spans
Query: level:error
Fields: ["error.type", "count()"]
Sort: -count()
Dataset: errors
Query: (empty)
Fields: ["severity", "count()", "epm()"]
Sort: -count()
Dataset: logs
Query: metric.name:http.request.duration AND metric.type:distribution
Fields: ["transaction", "p75(value,http.request.duration,distribution,millisecond)", "p95(value,http.request.duration,distribution,millisecond)", "count(value,http.request.duration,distribution,millisecond)"]
Sort: -p95(value,http.request.duration,distribution,millisecond)
Dataset: metrics
Query: has:gen_ai.tool.name
Fields: ["ai.model.id", "gen_ai.tool.name", "count()"]
Sort: -count()
Dataset: spans
Query: has:request.url
Fields: ["id", "span.op", "span.description", "span.duration", "transaction", "timestamp", "project", "trace", "request.url", "request.method"]
Sort: -timestamp
Dataset: spans
- Mixing aggregate and non-aggregate fields: Don't include fields like
timestamporidin aggregate queries - Wrong sort field: The field you sort by must be included in the fields array
- Timestamp filters on logs: Use
statsPeriodparameter instead of query filters - Using project slugs: API requires numeric project IDs, not slugs
The tool automatically generates shareable Sentry web UI URLs after making API calls. These URLs allow users to view results in the Sentry interface:
- Errors dataset:
/organizations/{org}/discover/results/ - Spans dataset:
/organizations/{org}/explore/traces/ - Logs dataset:
/organizations/{org}/explore/logs/ - Tracemetrics dataset:
/organizations/{org}/explore/metrics/
Note: The web UI URLs use different parameter formats than the API:
- Legacy Discover uses simple field parameters
- Modern Explore uses dataset-specific parameter formats
- The Metrics page uses one or more JSON-encoded
metric=parameters - The tool handles this transformation automatically in
buildDiscoverUrl()andbuildEapUrl()
The getEventsExplorerUrl() method accepts these parameters to determine URL format:
- organizationSlug: Organization identifier
- query: The Sentry query string
- projectSlug: Numeric project ID (optional)
- dataset: "spans", "errors", or "logs"
- fields: Array of fields (used to detect if it's an aggregate query)
- sort: Sort parameter
- aggregateFunctions: Array of aggregate functions (e.g.,
["count()", "avg(span.duration)"]) - groupByFields: Array of fields to group by (e.g.,
["span.op", "ai.model.id"])
Based on these parameters:
- If
aggregateFunctionshas items → generates aggregate query URL - For errors dataset → routes to Legacy Discover URL format
- For spans/logs datasets → routes to Modern Explore URL format with JSON-encoded
aggregateFieldparameters
The API and Web UI use different parameter formats:
API (Backend): Always uses the same format regardless of dataset
- Endpoint:
/api/0/organizations/{org}/events/ - Parameters:
field,query,sort,dataset, etc. - Example:
?dataset=spans&field=span.op&field=count()&sort=-count()
Web UI (Frontend): Different formats for different pages
- Legacy Discover:
/organizations/{org}/discover/results/ - Modern Explore:
/organizations/{org}/explore/{dataset}/ - Uses different parameter encoding (e.g.,
aggregateFieldwith JSON for explore pages)
The API always uses this format for all datasets:
Individual Events:
?dataset=spans
&field=id
&field=span.op
&field=span.description
&query=span.op:db
&sort=-timestamp
&statsPeriod=24h
Aggregate Queries:
?dataset=spans
&field=span.op
&field=count()
&query=span.op:db*
&sort=-count()
&statsPeriod=24h
The only difference between datasets is the dataset parameter value and available fields.
All API endpoints support time range filtering using either relative or absolute time parameters:
Relative Time (statsPeriod):
- Format: number + unit (e.g.,
1h,24h,7d,30d) - Default:
14d(last 14 days) - Example:
?statsPeriod=7d
Absolute Time (start and end):
- Format: ISO 8601 timestamps
- Both parameters must be provided together
- Example:
?start=2025-06-19T07:00:00&end=2025-06-20T06:59:59
Important: Cannot use both statsPeriod and start/end parameters in the same request.
Applies to:
- Events API:
/organizations/{org}/events/ - Tags API:
/organizations/{org}/tags/ - Trace Items Attributes API:
/organizations/{org}/trace-items/attributes/
Before translating queries, the tool fetches available attributes/fields for the organization. This ensures the AI knows about custom attributes specific to the organization.
Endpoint: /api/0/organizations/{org}/tags/
Parameters:
dataset: Alwayseventsfor error dataproject: Numeric project ID (optional)statsPeriod: Time range (e.g.,24h)useCache: Set to1for performanceuseFlagsBackend: Set to1for latest features
Example:
https://us.sentry.io/api/0/organizations/sentry/tags/?dataset=events&project=4509062593708032&statsPeriod=24h&useCache=1&useFlagsBackend=1
Response Format:
[
{
"key": "browser.name",
"name": "Browser Name"
},
{
"key": "custom.payment_method",
"name": "Payment Method"
}
]Processing:
- Filters out
sentry:prefixed tags (internal tags) - Maps to key-value pairs for the AI prompt
Endpoint: /api/0/organizations/{org}/trace-items/attributes/
Parameters:
itemType: Eitherspansorlogs(plural!)attributeType: Optional filter forstring,number, orboolean. Omit to return all types.project: Numeric project ID (optional)statsPeriod: Time range
Examples:
Spans string attributes:
https://us.sentry.io/api/0/organizations/sentry/trace-items/attributes/?attributeType=string&itemType=spans&project=4509062593708032&statsPeriod=24h
Spans number attributes:
https://us.sentry.io/api/0/organizations/sentry/trace-items/attributes/?attributeType=number&itemType=spans&project=4509062593708032&statsPeriod=24h
Logs string attributes:
https://us.sentry.io/api/0/organizations/sentry/trace-items/attributes/?attributeType=string&itemType=logs&project=4509062593708032&statsPeriod=24h
Response Format:
[
{
"key": "span.duration",
"name": "Span Duration",
"type": "number"
},
{
"key": "ai.model.id",
"name": "AI Model ID",
"type": "string"
}
]The tool fetches attributes with a single request per dataset:
- For errors: Single request to tags endpoint with optimized parameters
- For spans/logs/metrics: Single request to trace-items attributes without
attributeType(Sentry returns all types)
// For errors dataset
const tagsResponse = await apiService.listTags({
organizationSlug,
dataset: "events",
statsPeriod: "14d",
useCache: true,
useFlagsBackend: true,
});
// For spans/logs datasets
const attributesResponse = await apiService.listTraceItemAttributes({
organizationSlug,
itemType: "spans", // or "logs"
statsPeriod: "14d",
});When callers pass attributeTypes, listTraceItemAttributes filters the combined response client-side.
After fetching, custom attributes are merged with base fields:
const allFields = {
...BASE_COMMON_FIELDS, // Common fields across datasets
...DATASET_FIELDS[dataset], // Dataset-specific fields
...customAttributes, // Organization-specific fields
};This ensures the AI knows about all available fields when translating queries.
If attribute fetching fails:
- The tool continues with just the base fields
- Logs the error for debugging
- Does not fail the entire query
This graceful degradation ensures queries still work even if custom attributes can't be fetched.
- Be specific with fields: Only request fields you need
- Use appropriate limits: Default 10, max 100 per page
- Leverage aggregate functions: For summaries and statistics
- Include context fields: Add fields like
project,environmentwhen grouping - Sort meaningfully: Use
-count()for popularity,-timestampfor recency - Handle custom attributes: Tool automatically fetches org-specific attributes
- Understand dataset differences: Each dataset has different capabilities and constraints
The search_events tool handles the complexity of multiple API patterns:
-
AI Translation Layer
- Uses OpenAI GPT-5 to translate natural language to Sentry query syntax
- Maintains dataset-specific system prompts with examples
- Aggregate functions and groupBy fields are derived from the fields array
-
Field Handling
- Aggregate queries: Only includes aggregate functions and groupBy fields
- Non-aggregate queries: Uses default fields or AI-specified fields
- Validates that sort fields are included in the field list
- Detects aggregate queries by checking for function syntax in fields
-
Field Type Validation
- Validates numeric aggregate functions (avg, sum, min, max, percentiles) are only used with numeric fields
- Tracks field types from both known fields and custom attributes
- Returns error messages when invalid combinations are attempted
-
Web UI URL Generation (for shareable links)
buildDiscoverUrl()for errors dataset → creates Discover page URLsbuildEapUrl()for spans/logs datasets → creates Explore page URLs- Transforms API response format to web UI parameter format
- Note: These methods generate web URLs, not API URLs
Legacy Discover Response (errors):
{
"data": [
{
"error.type": "TypeError",
"count()": 150,
"last_seen()": "2025-01-16T12:00:00Z"
}
]
}EAP Response (spans/logs):
{
"data": [
{
"span.op": "db.query",
"count()": 1250,
"avg(span.duration)": 45.3
}
]
}This occurs when sorting by a field not included in the field list. Ensure your sort field is in the fields array.
- Check query syntax is valid
- Verify time range (
statsPeriod) - Ensure project has data for the selected dataset
- Try broadening the query
- 400: Invalid query syntax or parameters (often due to field mismatch in aggregates)
- 404: Project or organization not found
- 500: Internal error (check Sentry status)