Proxima Query Language (PQL)
PQL is the search language for querying your infrastructure in Proxima Console. It lets you find hosts by any combination of system properties, agent labels, user tags, services, containers, and packages.
Quick Start
The global search bar (Ctrl+K) has three modes:
- Simple mode (default) — type a hostname, IP, or OS to filter the hosts page inline
- Advanced mode — click the "PQL" toggle to write structured queries with autocomplete
- Plain-English mode — click the ✨ toggle to type in plain English and let Proxima translate it to PQL (see Plain-English Search below)
Switch to advanced mode and type:
host.os = "Ubuntu"
This returns all hosts running Ubuntu. In advanced mode, the search bar autocompletes fields, operators, and values as you type.
Plain-English Search
Don't know the PQL syntax yet? Click the ✨ toggle in the search bar to switch to plain-English mode, type your question the way you'd say it, and Proxima translates it into PQL for you:
ubuntu hosts that are offline
→ translates to, and runs:
host.os =~ "^ubuntu" AND host.agent_status = "offline"
The translated PQL is shown in the search bar and is fully editable — so plain-English search doubles as a way to learn PQL: read what your phrasing became, tweak it, and re-run. Cross-entity questions work the same way ("ubuntu hosts with a firing P1 alert" → a host.* AND alert.* query), with no extra syntax to remember.
How it works: the translation is proposed by an AI model and then validated by the same PQL engine that runs every query — so the result is always real, compilable PQL (the model can't invent a field that doesn't exist). If a phrasing can't be translated cleanly, you still get a best-effort starting query to refine rather than an error.
Notes for v1:
- The generated query is a starting point you refine, not a final answer — read it, edit it, re-run. The visible PQL is the source of truth for what actually runs.
- Cross-entity English works for free — phrasing that spans hosts, alerts, changes, compliance, and clusters translates into a cross-entity query.
- The query is RBAC-scoped at execution. Translation itself touches no tenant data; the usual multi-tenancy scoping applies only when the resulting PQL is run, exactly as if you'd typed it yourself.
Syntax
A PQL query is one or more expressions joined by logical operators.
Basic Expression
<field> <operator> <value>
Examples:
| Query | What it finds |
|---|---|
host.os = "Ubuntu" | Hosts running Ubuntu |
host.hostname ~ "web-*" | Hostnames starting with "web-" |
host.cpu_cores > 4 | Hosts with more than 4 CPU cores |
tag.env EXISTS | Hosts that have an "env" tag set |
Combining Expressions
Use AND, OR, NOT, and parentheses to build complex queries:
host.os = "Ubuntu" AND host.agent_status = "online"
(host.os = "Debian" OR host.os = "Ubuntu") AND tag.tier = "production"
NOT tag.decommissioned EXISTS
Free Text
A bare word or quoted phrase with no field or operator is free text — it matches the active entity's default text fields (hostname, IP, OS, … on the hosts tab) via case-insensitive substring search:
nginx
"web server"
Two rules:
- Quote multi-word phrases.
active hosts(two bare words) is a syntax error — write"active hosts"to search for the phrase, use field filters (host.agent_status = "online"), or use plain-English mode. The error message tells you exactly this. - Free text always searches the entity of the tab you're on —
nginxon the hosts tab searches host text fields; the same query on the alerts tab searches alert text fields.
Each entity searches a curated set of high-signal text columns (index-backed on the high-volume entities):
| Tab | Free text matches | For exact matches on other columns, use |
|---|---|---|
| Hosts | hostname, OS, IP address | host.* fields |
| Alerts | alert name | alert.severity, alert.status, alert.source |
| Changes | change summary | change.type, change.source, change.severity |
| Assets | name, asset type, status | asset.* fields |
| Runbooks | name, slug, description | runbook.* fields |
| Compliance | control code, framework name, status | compliance.* fields |
| Clusters | name, status, platform, version | cluster.* fields |
Machine-enum columns like change.type or alert.severity are deliberately not part of free text — they are low-cardinality values you filter exactly (alert.severity = "P1"), not text you search.
Free text combines with field predicates using AND:
nginx AND host.agent_status = "online"
Fields
Every field uses a prefix.name format. The prefix determines which data source is queried.
host.* — Host Properties
Direct columns on the host record.
| Field | Type | Description | Example |
|---|---|---|---|
host.hostname | string | Server hostname | host.hostname ~ "db-*" |
host.ip_address | string | IP address | host.ip_address = "10.0.1.5" |
host.os | string | Operating system | host.os = "Ubuntu 22.04.3 LTS" |
host.os_family | string | OS family | host.os_family = "debian" |
host.kernel_version | string | Kernel version | host.kernel_version ~ "6.8*" |
host.arch | string | CPU architecture | host.arch IN ("amd64", "arm64") |
host.cpu_cores | number | CPU core count | host.cpu_cores >= 8 |
host.memory_total_bytes | number | Total RAM in bytes | host.memory_total_bytes > 8000000000 |
host.disk_total_bytes | number | Total disk in bytes | host.disk_total_bytes > 100000000000 |
host.agent_status | string | Agent status | host.agent_status = "online" |
host.agent_version | string | Agent version | host.agent_version = "0.2.1" |
host.uptime_seconds | number | Uptime in seconds | host.uptime_seconds > 86400 |
host.cloud_provider | string | Cloud provider | host.cloud_provider = "aws" |
host.cloud_region | string | Cloud region | host.cloud_region ~ "eu-*" |
host.virtualization_type | string | Virtualization type | host.virtualization_type = "kvm" |
host.environment | string | Environment the host belongs to (matches the environment's display name, not its URL slug) | host.environment = "production" |
host.project | string | Project (client) slug the host belongs to | host.project = "ets" |
label.* — Agent Labels
Dynamic key-value pairs set in the agent configuration (labels in YAML or PROXIMA_AGENT_LABELS env var). Stored in the host's tags JSONB column.
| Query | What it finds |
|---|---|
label.env = "production" | Hosts with agent label env=production |
label.role ~ "web*" | Hosts with role labels starting with "web" |
label.team EXISTS | Hosts that have a "team" label defined |
tag.* — User Tags
User-defined tags added through the UI or API (PUT /hosts/{id}/tags). Stored in the entity_tags table.
| Query | What it finds |
|---|---|
tag.owner = "platform-team" | Hosts tagged with owner "platform-team" |
tag.tier = "production" | Hosts tagged as production tier |
tag.pci EXISTS | Hosts that have a "pci" tag (any value) |
NOT tag.decommissioned EXISTS | Hosts without a "decommissioned" tag |
service.* — Services
Systemd services discovered on the host.
| Field | Type | Description | Example |
|---|---|---|---|
service.name | string | Service name | service.name = "nginx" |
service.version | string | Service version | service.version ~ "1.24*" |
service.status | string | Service status | service.status = "running" |
service.type | string | Service type | service.type = "notify" |
Predicates on the same host-child prefix (service.*, container.*, package.*) that are
combined with AND at the top level must all match the same row — service.name = "nginx" AND service.status = "running" means one service that is both. When such predicates are split
across nesting levels (e.g. service.name = "nginx" AND (service.status = "running" OR …)),
each group is evaluated independently — different service rows may satisfy each part.
container.* — Docker Containers
Docker containers running on the host.
| Field | Type | Description | Example |
|---|---|---|---|
container.name | string | Container name | container.name ~ "nginx*" |
container.image | string | Container image | container.image ~ "postgres:*" |
container.status | string | Container status text | container.status ~ "Up*" |
container.state | string | Container state | container.state = "running" |
package.* — Installed Packages
Packages installed on the host (apt, yum, dnf, etc.).
| Field | Type | Description | Example |
|---|---|---|---|
package.name | string | Package name | package.name = "openssl" |
package.version | string | Package version | package.version ~ "3.0*" |
package.manager | string | Package manager | package.manager = "apt" |
asset.* — CMDB Asset Properties
Properties from the universal CMDB asset layer. These fields query the assets table joined to hosts.
| Field | Type | Description | Example |
|---|---|---|---|
asset.type | string | Asset type | asset.type = "host" |
asset.name | string | Asset display name | asset.name ~ "web-*" |
asset.status | string | Asset lifecycle status | asset.status = "active" |
asset.tags.* | dynamic | Asset tag values | asset.tags.env = "prod" |
Asset status values: active, inactive, degraded, unreachable, decommissioned, maintenance
Combine with host fields for powerful queries:
host.os = "Ubuntu" AND asset.status = "active" AND asset.tags.tier = "production"
See Assets & CMDB for full documentation on the asset system.
alert.* — Alert Groups
Active and historical alert groups.
| Field | Type | Description | Example |
|---|---|---|---|
alert.name | string | Alert rule name | alert.name ~ "CPU*" |
alert.severity | string | Alert severity | alert.severity = "P1" |
alert.status | string | Alert status | alert.status = "firing" |
alert.source | string | Alert source system | alert.source = "victoria-metrics" |
alert.host | string | Hostname the alert fired on | alert.host ~ "db-*" |
alert.environment | string | Environment name | alert.environment = "production" |
alert.fired_at | timestamp | When the alert fired | alert.fired_at > "2026-01-01" |
change.* — Change Events
File, package, and configuration change events recorded by the agent.
| Field | Type | Description | Example |
|---|---|---|---|
change.path | string | File or resource path | change.path ~ "/etc/*" |
change.type | string | Change type | change.type = "file_modified" |
change.severity | string | Change severity | change.severity = "critical" |
change.host | string | Hostname where change occurred | change.host ~ "web-*" |
change.source | string | Detection source | change.source = "fim" |
change.scanned_at | timestamp | When the change was detected | change.scanned_at > "2026-01-01" |
runbook.* — Runbook Definitions
Runbook catalog entries.
| Field | Type | Description | Example |
|---|---|---|---|
runbook.name | string | Runbook display name | runbook.name ~ "restart*" |
runbook.risk_tier | string | Risk classification | runbook.risk_tier = "destructive" |
runbook.description | string | Runbook description | runbook.description ~ "*postgres*" |
runbook.slug | string | URL-safe runbook identifier | runbook.slug = "restart-nginx" |
runbook.is_active | boolean | Whether runbook is active | runbook.is_active = "true" |
Risk tier values: safe, low, medium, high, destructive
compliance.* — Compliance Evaluations
Control evaluation results from compliance frameworks.
| Field | Type | Description | Example |
|---|---|---|---|
compliance.control | string | Control ID or name | compliance.control ~ "CIS-1.*" |
compliance.framework | string | Compliance framework | compliance.framework = "CIS" |
compliance.status | string | Evaluation result | compliance.status = "fail" |
compliance.severity | string | Control severity | compliance.severity = "high" |
compliance.host | string | Hostname evaluated | compliance.host ~ "db-*" |
compliance.score | number | Compliance score (0–100) | compliance.score < 80 |
Status values: pass, fail, warn, skip, error
Entity Types
PQL supports searching across multiple entity types. The entity type is auto-detected from the first field prefix in the query.
| First Prefix | Entity Type | Result Shape |
|---|---|---|
host, label, tag, service, container, package | hosts | Host records |
alert | alerts | Alert groups |
change | changes | Change events |
asset | assets | CMDB assets |
runbook | runbooks | Runbook definitions |
compliance | compliance | Control evaluations |
cluster | clusters | Kubernetes clusters |
A query may also combine prefixes from different entities — the leading entity is returned and the others become correlated filters. See Cross-Entity Queries below.
Result ordering. Results are deterministically ordered per entity: time-ordered entities return newest first (alerts by first-fired time, changes by creation time, assets by last-seen time, runbooks by last update, compliance by evaluation time), while hosts and clusters are ordered by name (hostname / cluster name, ascending). Pagination is stable across pages.
Cross-Entity Queries
A query led by one entity can filter by conditions on a related entity and still return the leading entity's rows. The leading entity is the one named by the first field prefix (or the entity query-parameter override); every predicate on a different entity is compiled into a correlated EXISTS subquery joined on the shared host. The result shape never changes — you always get rows of the leading entity, just narrowed to those that have a matching related record (no fan-out, no duplicated rows).
# Leading entity = hosts → returns HOSTS that have a P1 alert
host.os = "linux" AND alert.severity = "P1"
# Leading entity = alerts → returns ALERTS that fired on a Linux host
alert.severity = "P1" AND host.os = "linux"
Five entities can participate, all correlated on the host they share. Either side may lead; any of the others may appear as a correlated filter:
| Entity | Prefix | Correlation key |
|---|---|---|
| hosts | host.* | the host itself |
| alerts | alert.* | the alert's host |
| changes | change.* | the change's host |
| compliance | compliance.* | the evaluated host |
| clusters | cluster.* | the host's cluster (via the k8s node → cluster linkage) |
Worked Examples
host.os = "ubuntu" AND change.scanned_at > now-2h
Returns: Ubuntu hosts that recorded a change in the last 2 hours.
cluster.status = "healthy" AND alert.severity = "P1"
Returns: Healthy clusters that have a P1 alert on one of their nodes (clusters lead; the alert is the correlated filter).
host.agent_status = "offline" AND NOT compliance.status = "pass"
Returns: Offline hosts that are not passing compliance — the NOT compiles to a NOT EXISTS.
host.os ~ "Ubuntu*" AND alert.severity = "P1" AND change.severity = "critical"
Returns: Ubuntu hosts that have both a P1 alert and a critical change (each secondary entity becomes its own correlated EXISTS).
To explicitly override the detected leading entity, pass the entity query parameter:
GET /api/v1/search?q=host.os="Ubuntu"&entity=alerts
Negation Semantics
NOT on a related entity means two different things depending on whether it stands alone:
# Hosts with NO resolved alert at all (set-level: the NOT negates existence)
host.os = "ubuntu" AND NOT alert.status = "resolved"
# Hosts with SOME alert that is critical AND not resolved (row-level)
host.os = "ubuntu" AND alert.severity = "critical" AND NOT alert.status = "resolved"
A standalone NOT negates the existence of a matching related record — it compiles to NOT EXISTS, so the first query returns hosts that have no resolved alert whatsoever. When the NOT appears alongside other predicates on the same entity, all of those predicates describe the same related record, so the negation applies row-by-row inside a positive EXISTS — the second query returns hosts that have at least one alert which is both critical and unresolved. The two are not interchangeable: a host whose only critical alert is already resolved matches neither query above — the first because a resolved alert exists, the second because no single alert is critical and unresolved.
Permissions
A cross-entity query is authorized against every entity it references. Results are scoped to the intersection of the clients on which you hold each referenced entity's read permission. If you lack the read permission for any referenced entity, the query is rejected with an error before it runs — it never half-executes against data you cannot read. For example, a query touching host.* and alert.* requires both hosts:read and alerts:read.
v1 Limits
AND/NOTonly across entities. Cross-entity predicates must be combined withAND(and a whole secondary may be negated withNOT).ORacross different entities is not supported and returns an error — e.g.host.os = "linux" OR alert.severity = "P1"is rejected. (ORwithin a single entity is fine:(alert.severity = "P1" OR alert.severity = "P2") AND host.os = "linux".) The same rule applies to free text: it belongs to the leading entity, so it combines with cross-entity predicates only viaAND—nginx AND alert.severity = "P1"works,nginx OR alert.severity = "P1"is rejected.- No aggregation. The aggregate endpoint (group-by counts) is single-entity; a cross-entity query there returns a clean error.
- No time-windowed correlation. The
EXISTSjoin matches on host only; you cannot require the related record to fall within a time window relative to the leading row. You can still filter the related entity by an absolute or relative time of its own, e.g.change.scanned_at > now-2h. - Runbooks are excluded.
runbook.*has no host linkage, so it cannot appear in a cross-entity query. - Read permission required on every referenced entity (see Permissions above).
cluster.* — Kubernetes Cluster Properties
Properties from the Kubernetes cluster associated with a host via the node-host linkage. These fields use a two-table JOIN chain (k8s_nodes -> clusters), so only hosts that are linked as K8s nodes will match.
| Field | Type | Description | Example |
|---|---|---|---|
cluster.name | string | Cluster name | cluster.name = "k8s-prod-01" |
cluster.version | string | Kubernetes version | cluster.version ~ "v1.29*" |
cluster.platform | string | Cluster platform (rke2, eks, gke, aks, k3s, kubeadm) | cluster.platform = "rke2" |
cluster.status | string | Cluster health status | cluster.status = "healthy" |
Cluster status values: healthy, degraded, unreachable, unknown
Combine with host fields to find nodes in a specific cluster:
cluster.name = "k8s-prod-01" AND host.os = "Ubuntu"
See Kubernetes Inventory for full documentation on the cluster data model.
Operators
Comparison Operators
| Operator | Description | Types | Example |
|---|---|---|---|
= | Equals | string, number | host.os = "Ubuntu" |
!= | Not equals | string, number | host.agent_status != "offline" |
~ | Glob wildcard match | string | host.hostname ~ "web-*" |
> | Greater than | number | host.cpu_cores > 4 |
< | Less than | number | host.uptime_seconds < 3600 |
>= | Greater than or equal | number | host.memory_total_bytes >= 8000000000 |
<= | Less than or equal | number | host.disk_total_bytes <= 50000000000 |
Numeric values may be integers or decimals — compliance.score < 79.5 works.
Set Operators
| Operator | Description | Example |
|---|---|---|
IN | Value is in set | host.arch IN ("amd64", "arm64") |
NOT IN | Value is not in set | host.os_family NOT IN ("windows", "darwin") |
EXISTS | Field/key exists | tag.env EXISTS |
Logical Operators
| Operator | Description | Example |
|---|---|---|
AND | Both conditions must match | host.os = "Ubuntu" AND host.arch = "amd64" |
OR | Either condition can match | host.os = "Debian" OR host.os = "Ubuntu" |
NOT | Negate the next condition | NOT tag.decommissioned EXISTS |
(...) | Group conditions | (host.os = "Debian" OR host.os = "Ubuntu") AND tag.tier = "production" |
Glob Patterns
The ~ operator supports glob-style wildcard matching:
| Pattern | Matches |
|---|---|
"web-*" | Anything starting with "web-" |
"*-prod" | Anything ending with "-prod" |
"web-*-prod" | Anything starting with "web-" and ending with "-prod" |
"db-?" | "db-" followed by exactly one character |
Example Queries
Find All Online Ubuntu Hosts
host.os ~ "Ubuntu*" AND host.agent_status = "online"
Returns: All hosts running any Ubuntu version with an active agent connection.
Find Production Web Servers
service.name = "nginx" AND tag.tier = "production"
Returns: Hosts running nginx that are tagged as production tier.
Find High-Resource Hosts
host.cpu_cores >= 16 AND host.memory_total_bytes > 32000000000
Returns: Hosts with 16+ CPU cores and more than 32 GB RAM.
Find Hosts Missing Tags
NOT tag.owner EXISTS
Returns: Hosts that don't have an "owner" tag — useful for compliance auditing.
Find Hosts by Cloud Region
host.cloud_provider = "aws" AND host.cloud_region ~ "eu-*"
Returns: AWS hosts in any European region.
Find Hosts Running a Specific Package Version
package.name = "openssl" AND package.version ~ "3.0*"
Returns: Hosts with OpenSSL 3.0.x installed.
Complex Multi-Condition Query
(host.os = "Ubuntu" OR host.os = "Debian") AND host.agent_status = "online" AND service.name = "postgresql" AND tag.tier = "production"
Returns: Online Ubuntu or Debian hosts running PostgreSQL in the production tier.
Find Stale Hosts
host.agent_status = "stale"
Returns: Hosts whose agent hasn't sent a heartbeat within the stale timeout (default 5 minutes).
Find Hosts with Docker Running
container.state = "running"
Returns: Any host that has at least one running Docker container.
Find All Firing P1 Alerts
alert.severity = "P1" AND alert.status = "firing"
Returns: All active P1 alerts currently firing across any host.
Find Recent Critical Config Changes
change.type = "file_modified" AND change.severity = "critical"
Returns: File modification events classified as critical severity.
Find Destructive Runbooks
runbook.risk_tier = "destructive"
Returns: All runbooks classified as destructive — useful for access auditing.
Find Failing CIS Compliance Controls
compliance.framework = "CIS" AND compliance.status = "fail"
Returns: All CIS control evaluations that are currently failing.
Cross-Entity: Alerts on Ubuntu Hosts
alert.severity = "P1" AND host.os ~ "Ubuntu*"
Returns: P1 alerts that fired on Ubuntu hosts (alerts lead; the host predicate is the correlated filter). See Cross-Entity Queries for the full rules and limits.
API Endpoints
PQL queries can be used through the REST API:
| Endpoint | Description |
|---|---|
GET /api/v1/search?q={pql} | Search entities (paginated, supports format=csv, entity= override) |
GET /api/v1/search/count?q={pql} | Count matching entities |
GET /api/v1/search/fields | List all searchable fields and operators |
GET /api/v1/search/values?field={name} | Autocomplete values for a field |
POST /api/v1/search/validate | Validate query syntax |
GET /api/v1/search/aggregate?q={pql}&group_by={field} | Group results by field |
curl Examples
Search hosts:
curl -H "Authorization: Bearer $TOKEN" \
"https://api-console.prxm.uz/api/v1/search?q=host.os+%3D+%22Ubuntu%22"
Count results:
curl -H "Authorization: Bearer $TOKEN" \
"https://api-console.prxm.uz/api/v1/search/count?q=host.agent_status+%3D+%22online%22"
Export to CSV:
curl -H "Authorization: Bearer $TOKEN" \
"https://api-console.prxm.uz/api/v1/search?q=tag.tier+%3D+%22production%22&format=csv" \
-o hosts.csv
Get field value suggestions:
curl -H "Authorization: Bearer $TOKEN" \
"https://api-console.prxm.uz/api/v1/search/values?field=host.os&prefix=Ubu"
Response:
{
"data": ["Ubuntu", "Ubuntu 22.04.3 LTS", "Ubuntu 24.04 LTS"]
}
Saved Searches
Save frequently used queries for quick access:
# Save a search
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Production web servers", "query": "service.name = \"nginx\" AND tag.tier = \"production\"", "is_shared": true}' \
"https://api-console.prxm.uz/api/v1/saved-searches"
Saved searches appear in the search bar autocomplete when the input is empty.
Multi-Tenancy
Search results are automatically scoped to the authenticated user's accessible clients. Super admins see results across all clients. No additional filtering is needed — PQL handles authorization transparently.
Tips
- Simple vs Advanced: Use simple mode for quick hostname/IP lookups on the Hosts page. Switch to PQL (advanced) mode when you need structured queries with field operators.
- Use autocomplete: In advanced mode, the search bar suggests fields, operators, and values as you type. Press
TaborEnterto accept a suggestion. - Glob for flexibility: Use
~with wildcards instead of exact=when you want partial matches. - EXISTS for auditing:
tag.X EXISTS/NOT tag.X EXISTSis powerful for finding hosts that are missing required tags. - Parentheses for clarity: When mixing
ANDandOR, use parentheses to make operator precedence explicit. - CSV export: Add
&format=csvto any search API call to download results as a spreadsheet. - URL sharing: In simple mode on the Hosts page, the search term is stored in the URL (
?search=web), so you can share filtered views with teammates.