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

# Querying logs

Search log lines with the [Public API (V2)](/api/v2/overview). This is the way to
query logs programmatically, including structured (JSON) log data. The GraphQL
API does not expose log lines.

<Tip>
  If you're building an AI agent or assistant, [AppSignal MCP](/mcp-server)
  exposes log search and other monitoring data to agents directly, without
  calling this API yourself.
</Tip>

## 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:

<CodeGroup>
  ```text URL theme={null}
  https://appsignal.com/<organization>/sites/<SITE_ID>/logs/sources/<SOURCE_ID>
  ```
</CodeGroup>

## Search log lines

<CodeGroup>
  ```shell Endpoint theme={null}
  POST /api/v2/logs/lines
  ```
</CodeGroup>

### 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](#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

<CodeGroup>
  ```shell cURL theme={null}
  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
  ```
</CodeGroup>

<CodeGroup>
  ```json body.json theme={null}
  {
    "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 }
    }
  }
  ```
</CodeGroup>

### 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`. |

<Note>
  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"`).
</Note>

## 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:

<CodeGroup>
  ```json Example log theme={null}
  {
    "message": "API request completed",
    "severity": "info",
    "duration": 156.7,
    "status_code": 200,
    "user": {
      "id": 12345,
      "roles": ["admin", "developer"],
      "location.country": "US"
    }
  }
  ```
</CodeGroup>

* **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](#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.

```text theme={null}
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:

<CodeGroup>
  ```text Old query theme={null}
  message:"API request" attributes.user_id_int=12345 severity=[info, warn]
  ```

  ```text New query theme={null}
  message:"API request" user.id=12345 (severity=info OR severity=warn)
  ```
</CodeGroup>

A query that still uses the legacy syntax returns a `422` with the
`legacy_log_query_syntax` error slug.
