Search log lines with the Public API (V2). This is the way to
query logs programmatically, including structured (JSON) log data. The GraphQL
API does not expose log lines.
If you’re building an AI agent or assistant, AppSignal MCP
exposes log search and other monitoring data to agents directly, without
calling this API yourself.
Finding your site and source IDs
A log search needs a site_id and one or more source_ids. Both appear in the
URL of a log source in AppSignal. Open a log source and read them from the path:
https://appsignal.com/<organization>/sites/<SITE_ID>/logs/sources/<SOURCE_ID>
Search log lines
Request body
| Field | Type | Required | Description |
|---|
site_id | string | Yes | Site to query. |
source_ids | string[] | Yes | Log source IDs to search. At least one is required. |
query | string | Yes | Query string that filters log lines. See query language. |
use_expressions | boolean | No | When true, parse query with the expression-based query language. |
from | string (ISO 8601) | No | Time range start. Defaults to the retention lower bound. |
to | string (ISO 8601) | No | Time range end. Defaults to the current time. |
pagination | object | Yes | Pagination cursor and direction (see the following section). |
Set use_expressions to true to use the expression-based query language
documented on this page.
pagination takes per_page (number), order (ASC or DESC), and a cursor
with a time field (an ISO 8601 timestamp, or null for the first page). To
fetch the next page, send the timestamp of the last line you received as the
cursor.time of the next request.
Example request
curl --request POST \
--url "https://appsignal.com/api/v2/logs/lines" \
--header "Authorization: Bearer YOUR-PERSONAL-API-TOKEN" \
--header "Content-Type: application/json" \
--data @body.json
{
"site_id": "YOUR-SITE-ID",
"source_ids": ["YOUR-LOG-SOURCE-ID"],
"query": "severity=error status_code=401",
"use_expressions": true,
"from": "2026-06-22T00:00:00Z",
"to": "2026-06-22T23:59:59Z",
"pagination": {
"per_page": 100,
"order": "DESC",
"cursor": { "time": null }
}
}
Response
The endpoint returns an array of log lines. Each line includes these fields:
| Field | Type | Description |
|---|
id | string | Log line identifier. |
timestamp | string (ISO 8601) | When the log line was recorded. |
source_id | string | Log source the line belongs to. |
severity | string | Severity level, such as info or error. |
message | string | The log message body. |
hostname | string | Host that produced the line. |
group | string | Log group. |
json | string | The raw structured (JSON) body of the log line, when available. |
attributes | object | Legacy typed key-value attributes. Being phased out — prefer json. |
Structured fields such as status_code, path, or detail live in the json
field, not in attributes. Query them by their exact key as it appears in your
log’s JSON (for example, status_code=401 or detail.message:"required").
Query language
When use_expressions is true, the query string supports boolean logic over
log line fields and the structured json body.
Operators
| Operator | Name | Behavior |
|---|
= | Equals | Exact match. |
!= | Not equals | Exact non-match. |
: | Contains | Substring match. |
!: | Not contains | Substring non-match. |
> | Greater than | Numeric comparison. |
>= | Greater or equal | Numeric comparison. |
< | Less than | Numeric comparison. |
<= | Less or equal | Numeric comparison. |
String operators (=, !=, :, !:) compare both sides as strings. Numeric
operators (>, >=, <, <=) cast both sides to a number; if a value cannot be
parsed as a number, that log line is skipped.
Combining expressions
- AND / OR:
severity=error AND hostname:web, or severity=info OR severity=warn.
- Implicit AND: a space between expressions means AND.
severity=error hostname:web is the same as severity=error AND hostname:web.
- Nesting with parentheses:
message:"API request" AND (severity=info OR severity=warn).
- Precedence: AND binds tighter than OR.
Any operator can be used inside an OR group, not only equality — for example,
message:"API request" AND (user.id>1000 OR user.email:"example.com").
Fields
Given a log line with this JSON body:
{
"message": "API request completed",
"severity": "info",
"duration": 156.7,
"status_code": 200,
"user": {
"id": 12345,
"roles": ["admin", "developer"],
"location.country": "US"
}
}
- Base fields:
message, severity, hostname, and group match the log line’s top-level fields.
- Nested JSON: use dot notation to reach into the
json body, such as status_code=200 or user.id=12345.
- JSON arrays: index array elements, such as
user.roles.0=admin.
- Escaped dots: to match a literal dot in a key name, escape it.
user.location\.country=US matches the key location.country inside user, rather than a nested country object.
- Default field: a bare term with no field matches
message with the contains operator. timeout is the same as message:timeout.
You do not need to prepend attributes. or append a type suffix to query a field
— reference it directly by name. See legacy query
syntax if you have older queries.
Quoting values
Wrap values that contain spaces or parentheses in double quotes. Otherwise the
space splits the value into separate expressions joined by implicit AND.
message:"hello world" matches message containing "hello world"
message:hello world parsed as message:hello AND message:world
severity=error message:"oh no" severity equals error AND message contains "oh no"
Only double quotes are recognized. Inside a quoted value, escape a quote with
\" and a backslash with \\. For example, message:"value with \"quotes\""
matches the text value with "quotes".
Legacy query syntax
Older queries stored attributes as typed fields, so they had to prepend
attributes., append a type suffix (_string, _int), and use a [ ] list for
multiple values. The current query language removes all three — reference fields
directly and use OR instead of a list:
message:"API request" attributes.user_id_int=12345 severity=[info, warn]
A query that still uses the legacy syntax returns a 422 with the
legacy_log_query_syntax error slug.