Skip to content

Query Console

Query and analyze your security telemetry using LimaCharlie Query Language (LCQL).

Documentation


Running Queries Programmatically

Prerequisites

All API examples require an API key with the insight permission. See API Keys for setup.

Search API Endpoint

There is no single search hostname. Each organization's search endpoint lives in the datacenter for the region where the organization was created, so you discover it from the API first and then send queries to that host. The Python SDK, Go SDK, and CLI do this automatically.

# Returns a bare hostname such as 9157798c50af372c.replay-search.limacharlie.io
SEARCH_HOST=$(curl -s "https://api.limacharlie.io/v1/orgs/YOUR_OID/url" \
  -H "Authorization: Bearer $LC_JWT" | jq -r '.url.search')

The Python SDK resolves the search endpoint automatically from your OID; no manual step is required.

The CLI resolves the search endpoint automatically from your OID; no manual step is required.

The bootstrap API host https://api.limacharlie.io is the same for every region and routes to the correct datacenter based on your OID. The REST examples below reuse the $SEARCH_HOST variable for the discovered hostname. For reference, the current production search endpoints per region are:

Region Search endpoint
USA https://9157798c50af372c.replay-search.limacharlie.io
Europe https://b76093c3662d5b4f.replay-search.limacharlie.io
Canada https://aae67d7e76570ec1.replay-search.limacharlie.io
UK https://70182cf634c346bd.replay-search.limacharlie.io
Australia https://abc32764762fce67.replay-search.limacharlie.io
India https://4d897015b0815621.replay-search.limacharlie.io

Use the bootstrap API, not a direct endpoint

Discovering your search endpoint through the bootstrap API (https://api.limacharlie.io/v1/orgs/{oid}/url, the url.search field) is the recommended approach. The direct per-region hostnames above can change over time, whereas the bootstrap API always returns the current endpoint for your organization. Treat the table as a convenience reference only, and do not hardcode a direct endpoint.

Run an LCQL Query

# Start and end times are unix epoch seconds
START=$(date -d '1 hour ago' +%s)
END=$(date +%s)

# Discover your org's search endpoint (see "Search API Endpoint" above)
SEARCH_HOST=$(curl -s "https://api.limacharlie.io/v1/orgs/YOUR_OID/url" \
  -H "Authorization: Bearer $LC_JWT" | jq -r '.url.search')

curl -s -X POST \
  "https://$SEARCH_HOST/v1/search" \
  -H "Authorization: Bearer $LC_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "oid": "YOUR_OID",
    "query": "event/FILE_PATH ends with .exe",
    "startTime": "'"$START"'",
    "endTime": "'"$END"'",
    "stream": "event"
  }'
import time

from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.search import Search

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)

end = int(time.time())
start = end - 3600  # 1 hour ago

for result in Search(org).execute(
    query="event/FILE_PATH ends with .exe",
    start_time=start,
    end_time=end,
    stream="event",
    limit=100,
):
    print(result)
package main

import (
    "fmt"

    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    // The Go SDK's Query takes the full LCQL string (time range included).
    resp, err := org.Query(limacharlie.QueryRequest{
        Query:      "-1h | * | * | event/FILE_PATH ends with '.exe'",
        Stream:     "event",
        LimitEvent: 1000,
    })
    if err != nil {
        panic(err)
    }
    for _, r := range resp.Results {
        fmt.Println(r)
    }
}
START=$(date -d '1 hour ago' +%s)
END=$(date +%s)

limacharlie search run \
  --query "event/FILE_PATH ends with .exe" \
  --start "$START" \
  --end "$END" \
  --stream event \
  --limit 100

Validate Query Syntax

curl -s -X POST \
  "https://$SEARCH_HOST/v1/search/validate" \
  -H "Authorization: Bearer $LC_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "oid": "YOUR_OID",
    "query": "event/FILE_PATH ends with .exe",
    "startTime": "'"$(date -d '1 hour ago' +%s)"'",
    "endTime": "'"$(date +%s)"'"
  }'
from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.search import Search

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)
result = Search(org).validate("event/FILE_PATH ends with .exe")
print(result)
package main

import (
    "fmt"

    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    result, err := org.ValidateLCQLQuery("-1h | * | * | event/FILE_PATH ends with '.exe'")
    if err != nil {
        panic(err)
    }
    if result.Error != "" {
        fmt.Printf("invalid query: %s\n", result.Error)
    }
}
limacharlie search validate \
  --query "event/FILE_PATH ends with .exe"

Estimate Query Cost

START=$(date -d '1 hour ago' +%s)
END=$(date +%s)

curl -s -X POST \
  "https://$SEARCH_HOST/v1/search/validate" \
  -H "Authorization: Bearer $LC_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "oid": "YOUR_OID",
    "query": "event/FILE_PATH ends with .exe",
    "startTime": "'"$START"'",
    "endTime": "'"$END"'"
  }'
import time

from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.search import Search

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)

end = int(time.time())
start = end - 3600

result = Search(org).estimate(
    query="event/FILE_PATH ends with .exe",
    start_time=start,
    end_time=end,
)
print(result)
package main

import (
    "fmt"

    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    est, err := org.EstimateLCQLQueryBilling("-1h | * | * | event/FILE_PATH ends with '.exe'")
    if err != nil {
        panic(err)
    }
    fmt.Printf("billed events: %d, estimated price: %.2f %s\n",
        est.BilledEvents, est.EstimatedPrice.Price, est.EstimatedPrice.Currency)
}
START=$(date -d '1 hour ago' +%s)
END=$(date +%s)

limacharlie search estimate \
  --query "event/FILE_PATH ends with .exe" \
  --start "$START" \
  --end "$END"

Note

The validate response also includes query-size fields (batchesInScope, eventsInScope, bytesInScope) and a running search returns per-page progress and actual billing. See Query Progress and Cost Reporting for how to build a progress bar and read the real cost.

Saved Queries

List Saved Queries

curl -s -X GET \
  "https://api.limacharlie.io/v1/hive/query/YOUR_OID" \
  -H "Authorization: Bearer $LC_JWT"
from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.hive import Hive

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)
queries = Hive(org, "query").list()
print(queries)
package main

import (
    "fmt"

    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    hive := limacharlie.NewHiveClient(org)
    queries, err := hive.List(limacharlie.HiveArgs{
        HiveName:     "query",
        PartitionKey: "YOUR_OID",
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(queries)
}
limacharlie search saved-list

Create Saved Query

curl -s -X POST \
  "https://api.limacharlie.io/v1/hive/query/YOUR_OID/my-saved-query/data" \
  -H "Authorization: Bearer $LC_JWT" \
  -d data='{"query": "event/FILE_PATH ends with .exe", "stream": "event"}' \
  -d usr_mtd='{"enabled": true}'
from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.hive import Hive, HiveRecord

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)
Hive(org, "query").set(HiveRecord(
    name="my-saved-query",
    data={
        "query": "event/FILE_PATH ends with .exe",
        "stream": "event",
    },
    enabled=True,
))
package main

import (
    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    hive := limacharlie.NewHiveClient(org)
    enabled := true
    if _, err := hive.Add(limacharlie.HiveArgs{
        HiveName:     "query",
        PartitionKey: "YOUR_OID",
        Key:          "my-saved-query",
        Data: limacharlie.Dict{
            "query":  "event/FILE_PATH ends with .exe",
            "stream": "event",
        },
        Enabled: &enabled,
    }); err != nil {
        panic(err)
    }
}
limacharlie search saved-create \
  --name my-saved-query \
  --query "event/FILE_PATH ends with .exe" \
  --stream event

Run Saved Query

START=$(date -d '1 hour ago' +%s)
END=$(date +%s)

# First retrieve the saved query definition
curl -s -X GET \
  "https://api.limacharlie.io/v1/hive/query/YOUR_OID/my-saved-query/data" \
  -H "Authorization: Bearer $LC_JWT"

# Then execute with the query string from the saved definition
import time

from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.hive import Hive
from limacharlie.sdk.search import Search

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)

saved = Hive(org, "query").get("my-saved-query")
end = int(time.time())
start = end - 3600

for result in Search(org).execute(
    query=saved.data["query"],
    start_time=start,
    end_time=end,
    stream=saved.data.get("stream", "event"),
):
    print(result)
package main

import (
    "fmt"

    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    hive := limacharlie.NewHiveClient(org)
    saved, err := hive.Get(limacharlie.HiveArgs{
        HiveName:     "query",
        PartitionKey: "YOUR_OID",
        Key:          "my-saved-query",
    })
    if err != nil {
        panic(err)
    }

    resp, err := org.Query(limacharlie.QueryRequest{
        Query:  saved.Data["query"].(string),
        Stream: "event",
    })
    if err != nil {
        panic(err)
    }
    for _, r := range resp.Results {
        fmt.Println(r)
    }
}
limacharlie search saved-run --name my-saved-query

Delete Saved Query

curl -s -X DELETE \
  "https://api.limacharlie.io/v1/hive/query/YOUR_OID/my-saved-query" \
  -H "Authorization: Bearer $LC_JWT"
from limacharlie.client import Client
from limacharlie.sdk.organization import Organization
from limacharlie.sdk.hive import Hive

client = Client(oid="YOUR_OID", api_key="YOUR_API_KEY")
org = Organization(client)
Hive(org, "query").delete("my-saved-query")
package main

import (
    limacharlie "github.com/refractionPOINT/go-limacharlie/limacharlie"
)

func main() {
    org, err := limacharlie.NewOrganizationFromClientOptions(limacharlie.ClientOptions{
        OID:    "YOUR_OID",
        APIKey: "YOUR_API_KEY",
    }, nil)
    if err != nil {
        panic(err)
    }

    hive := limacharlie.NewHiveClient(org)
    if _, err := hive.Remove(limacharlie.HiveArgs{
        HiveName:     "query",
        PartitionKey: "YOUR_OID",
        Key:          "my-saved-query",
    }); err != nil {
        panic(err)
    }
}
limacharlie search saved-delete --name my-saved-query

See Also