Logs Query API
Use Logs Query API for searching and retrieving logs programmatically from your services.
Last9 provides a powerful API for querying logs from your services. This document explains how to use the Logs Query API to search and retrieve logs programmatically.
API Access
You can find necessary credentials on the API Access page. For detailed instructions on generating tokens, see Getting Started with API.
Base URL
https://app.last9.io/api/v4/organizations/{org}Replace {org} with your organization slug.
Endpoint
The endpoint for querying logs is:
POST /logs/api/v2/query_range/jsonFull URL:
https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/jsonAuthentication
The API requires a Bearer token in the X-LAST9-API-TOKEN header. Use your Read Access Token for querying logs.
X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>See Getting Started with API for instructions on generating tokens.
Query Parameters
The endpoint accepts the following URL parameters:
| Parameter | Description | Required | Example |
|---|---|---|---|
start | Start time in Unix nanoseconds | Yes | 1743500000000000000 |
end | End time in Unix nanoseconds | Yes | 1743510000000000000 |
limit | Maximum number of logs to return | No | 100 |
direction | Sort order: forward or backward | No | backward |
step | Time step for aggregations | No | 200ms |
offset | Pagination offset | No | 0 |
Request Body
The request body contains the JSON pipeline:
{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] } ] } } ]}Filter Operators
The JSON pipeline supports the following filter operators:
| Operator | JSON Key | Description | Example |
|---|---|---|---|
| Equals | $eq | Exact match | { "$eq": ["service", "api-gateway"] } |
| Not Equals | $neq | Does not match | { "$neq": ["level", "debug"] } |
| Contains | $contains | Substring match | { "$contains": ["body", "error"] } |
| Not Contains | $notcontains | Does not contain substring | { "$notcontains": ["body", "test"] } |
| Regex | $regex | Regular expression match | { "$regex": ["path", "/api/v[0-9]+"] } |
| Not Regex | $notregex | Does not match regex | { "$notregex": ["path", "health"] } |
| Greater Than | $gt | Numeric greater than | { "$gt": ["status_code", "400"] } |
| Less Than | $lt | Numeric less than | { "$lt": ["status_code", "500"] } |
| Greater or Equal | $gte | Numeric greater than or equal | { "$gte": ["duration", "1000"] } |
| Less or Equal | $lte | Numeric less than or equal | { "$lte": ["duration", "5000"] } |
Logic Operators
Combine multiple conditions using logic operators:
AND (All conditions must match)
{ "$and": [ { "$eq": ["service", "api-gateway"] }, { "$contains": ["body", "error"] } ]}OR (Any condition can match)
{ "$or": [ { "$eq": ["level", "error"] }, { "$eq": ["level", "fatal"] } ]}NOT (Negate conditions)
{ "$not": [ { "$and": [{ "$eq": ["level", "debug"] }] } ]}Example Queries
Basic Query
This example queries logs from a service named “api-gateway”:
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=100&direction=backward' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] } ] } } ] }'Replace {org} with your organization slug and <READ_ACCESS_TOKEN> with your token.
Filter by Multiple Conditions
Query logs with multiple conditions (service AND log level):
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=100' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "payment-service"] }, { "$eq": ["level", "error"] } ] } } ] }'Text Search
Search for logs containing specific text:
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=50' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] }, { "$contains": ["body", "timeout"] } ] } } ] }'Regex Pattern Matching
Use regex to match complex patterns:
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=100' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] }, { "$regex": ["body", "error.*timeout|connection.*refused"] } ] } } ] }'OR Conditions
Query logs matching any of multiple conditions:
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=100' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] } ] } }, { "type": "where", "query": { "$or": [ { "$eq": ["level", "error"] }, { "$eq": ["level", "fatal"] } ] } } ] }'Response Format
A successful response returns a JSON object with the following structure:
{ "status": "success", "data": { "resultType": "streams", "result": [ { "stream": { "service": "api-gateway", "level": "error", "env": "production" }, "values": [ ["1743505000000000000", "Connection timeout after 30s"], ["1743504990000000000", "Request failed: upstream unavailable"], ["1743504980000000000", "Error processing request"] ] } ], "stats": { "summary": { "bytesProcessedPerSecond": 1048576, "linesProcessedPerSecond": 500, "totalBytesProcessed": 2097152, "totalLinesProcessed": 1000, "execTime": 0.25 } } }}If no logs are found, the result field will be null:
{ "status": "success", "data": { "resultType": "streams", "result": null, "stats": { "summary": { "bytesProcessedPerSecond": 0, "linesProcessedPerSecond": 0, "totalBytesProcessed": 0, "totalLinesProcessed": 0, "execTime": 0 } } }}Advanced Usage
Discovering Available Labels
To discover what labels are available for filtering:
curl -X GET 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v1/labels?start=1743000000000000000&end=1743600000000000000' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>'Discovering Label Values
To discover what values exist for a specific label:
curl -X GET 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v1/label/service/values?start=1743000000000000000&end=1743600000000000000' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>'Querying From Specific Indices
To query logs from a specific index, add the index parameter:
For Physical Indices
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=100&index=physical_index:Pt_prod_k8s' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] } ] } } ] }'For Rehydration Indices
curl -X POST 'https://app.last9.io/api/v4/organizations/{org}/logs/api/v2/query_range/json?start=1743500000000000000&end=1743510000000000000&limit=100&index=rehydration_index:Rh_prod_archive' \ -H 'X-LAST9-API-TOKEN: Bearer <READ_ACCESS_TOKEN>' \ -H 'Content-Type: application/json' \ -d '{ "pipeline": [ { "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] } ] } } ] }'The format for the index parameter is:
physical_index:<index_name>for physical indicesrehydration_index:<index_name>for rehydration indices
Time Range Conversion
The API uses Unix nanoseconds for timestamps. To convert:
# Current time in nanosecondsecho $(($(date +%s) * 1000000000))
# Time from 1 hour ago in nanosecondsecho $((($(date +%s) - 3600) * 1000000000))
# Convert seconds to nanosecondsSECONDS=1743500000NANOSECONDS=$((SECONDS * 1000000000))Pipeline Stages
The JSON pipeline supports multiple stage types that can be chained together:
Filter Stage
Filter logs based on conditions:
{ "type": "filter", "query": { "$and": [ { "$eq": ["service", "api-gateway"] } ] }}Where Stage (Advanced Filters)
Add additional filter conditions with OR/NOT logic:
{ "type": "where", "query": { "$or": [ { "$eq": ["level", "error"] }, { "$eq": ["level", "warn"] } ] }}Parse Stage
Extract fields from log body:
{ "type": "parse", "parser": "json", "field": "body", "labels": { "user_id": null, "request_id": null }}Debugging Common Scenarios
No Data Returned
If your query returns no data ("result": null), check the following:
- Field names: Verify the field names are correct. Field names are case-sensitive.
- Time range: Ensure your
startandendtimestamps cover a period where logs exist. Remember timestamps are in nanoseconds. - Data retention: Check if the queried time range is within your organization’s data retention period.
- Pipeline syntax: Make sure your JSON pipeline is correctly formatted.
Invalid Pipeline Error
If you receive a pipeline parsing error:
- JSON syntax: Ensure your pipeline is valid JSON.
- Operator format: Each operator should be an array with exactly 2 elements:
["field", "value"]. - Stage type: Verify the
typefield is one of:filter,where,parse.
Authorization Issues
If you receive authentication errors:
- Token validity: Verify your access token is valid and not expired. Tokens expire in 24 hours.
- Token scope: Ensure you’re using a Read Access Token for querying logs.
- Header format: The header must be
X-LAST9-API-TOKEN: Bearer <TOKEN>(notAuthorization). - Token refresh: If expired, generate a new token using the refresh token. See Getting Started with API.
Troubleshooting
Please get in touch with us on Discord or Email if you have any questions.