Skip to main content
This feature is under active development. Behavior, supported fields, and limits may change before General Availability (GA). This guide is provided as forward-looking context for evaluation and feedback.

API Filtering is now available in Early Access.
When using ngrok’s API, you can add the filter query parameter to GET requests to return only those results which match a provided criteria. This makes automated management of resources easier while eliminating the need to download large collections and filter client-side. To use API Filtering, you pass a subset of CEL expressions to the filter query parameter, as demonstrated in the following example. This example request fetches a list of all your Cloud and Agent endpoints.
GET /endpoints?filter=obj.type == "cloud" || obj.type == "agent"

Request shape

GET /{resource}?filter={CEL_EXPRESSION}

cURL usage

curl --location 'https://api.ngrok.com/endpoints' \
  --get \
  --data-urlencode '<filter>' \
  --header 'Ngrok-Version: 2' \
  --header 'Authorization: Bearer <token>'

Supported CEL (subset)

These core operators and helpers are supported:
  • Logical operators: !, &&, ||
  • Comparative operators: <, <=, ==, !=, >=, >
  • Parentheses for grouping
  • List membership using the in keyword
  • String substring checks: startsWith(), contains(), endsWith()
  • Length / emptiness checks: size(), == "", == null
  • Date and time helpers: timestamp(RFC-3339), timestamp(time.now), timestamp(time.now).subtract(<duration>), timestamp(time.now).add(<duration>)

Instance inspection (versus list comprehension)

Expressions are evaluated against a single resource instance exposed as obj. Compare fields on the instance rather than attempting list-wise checks on fields. ✅ Valid
GET /endpoints?filter=obj.type == "cloud" || obj.type == "agent"
GET /endpoints?filter=obj.type in ["agent", "cloud"]
❌ Not valid
GET /endpoints?filter=["agent","cloud"] in obj.types

Dates and time helpers

  • Treat timestamps as numerics by using <, <=, ==, >=, > directly on timestamp() fields, for example:
    GET /vaults?filter=obj.created_at < timestamp("2025-10-31T09:23:45-07:00")
    
  • Relative helpers based on the current time: Use timestamp(time.now) for the current time, then chain .subtract(<duration>) or .add(<duration>) with a duration string such as "7d", "24h", or "15m". For example:
    # resources created in the last 7 days
    GET /endpoints?filter=obj.created_at >= timestamp(time.now).subtract("7d")
    
    # resources that will expire within the next 24 hours
    GET /tls_certificates?filter=obj.not_after <= timestamp(time.now).add("24h")
    

Query restrictions and limitations

Unsupported CEL features

To keep filter evaluation small and predictable, the following CEL features are not supported.
  • No index access (for example, a[0])
  • No arithmetic (for example, a + b)
  • No ternary (for example, cond ? x : y)
  • No type checks (for example, type(a) == string)
  • No regexes
  • No fuzzy matching
These exclusions intentionally keep evaluation small and predictable.

High-entropy fields and substring checks

High entropy fields are fields with values that are effectively random, usually because they’re generated. The id field on a response object, such as obj.id, is a common example. Substring functions, such as startsWith(), contains(), and endsWith(), are disallowed on high entropy fields. Check for equality on these fields instead. For example:
obj.id == "ep_123"

Query complexity (budgeting/limits)

Very large expressions can stress the query engine. The service may enforce limits on the number of conditions per query or similar throttles in the future.

Filterable resources and fields

The initial release prioritizes the resource types and fields below. CEL filtering is not supported on deprecated endpoints. Field coverage is evolving and may change before GA.
Resource TypeFilterable FieldsAvailable for Early Access Testing
Endpoints- id
- created_at
- description
- metadata
- principal.id
- type
- binding
- url
- pooling_enabled
- scheme
- region
Coming soon
Reserved Addresses- id
- created_at
- description
- metadata
- addr
- region
Coming soon
Reserved Domains- id
- created_at
- description
- metadata
- domain
- region
- cname_target
- certificate.id
- acme_challenge_cname_target
Coming soon
TLS Certificates- id
- created_at
- description
- metadata
- subject_common_name
- not_after
- not_before
- serial_number
Coming soon
Certificate Authorities- id
- created_at
- description
- metadata
- subject_common_name
- not_before
- not_after
Coming soon
IP Policies- id
- created_at
- description
- metadata
Coming soon
IP Policy Rules- id
- created_at
- description
- metadata
- ip_policy
- cidr
- action
Coming soon
Agent Ingress- id
- created_at
- description
- metadata
- domain
Coming soon
Tunnel Sessions- id
- metadata
- agent_version
- ip
- os
- region
- started_at
- credential
Coming soon
Event Destinations- id
- created_at
- description
- metadata
Coming soon
Event Subscriptions- id
- created_at
- description
- metadata
Coming soon
IP Restrictions- id
- created_at
- description
- metadata
Coming soon
API Keys- id
- created_at
- description
- metadata
- owner_id
Coming soon
SSH Credentials- id
- created_at
- description
- metadata
- owner_id
- acl
Coming soon
Credentials- id
- created_at
- description
- metadata
- owner_id
- acl
Coming soon
Service Users- id
- created_at
Coming soon
SSH Certificate Authorities- id
- created_at
- description
- metadata
Coming soon
Vaults- id
- created_at
- description
- metadata
- name
Coming soon
Secrets- id
- created_at
- description
- metadata
- name
Coming soon

Usage examples

Filter endpoints by type and creation time

GET /endpoints?filter=obj.type == "cloud" && obj.created_at < timestamp("2025-10-31T09:23:45-07:00")
# or using helpers
GET /endpoints?filter=obj.type == "cloud" && obj.created_at >= timestamp(time.now).subtract("6d")
Reference:

Reserved domains by prefix

GET /reserved_domains?filter=obj.domain.startsWith("myapi.ngrok")
Reference:

IP policy rules by CIDR and action

GET /ip_policy_rules?filter=obj.cidr.contains("1.1.0.0/16") && obj.action == "deny"
Reference:

Credentials by owner with optional empty ACL

GET /credentials?filter=obj.owner_id == "usr_2tEpN0yrxDI4j8jVnhVRoTNN2Tx" && (obj.acl == null || obj.acl == "")
Reference:

Complex nesting

GET /agent_ingresses?filter=obj.domain in ["foo.com","bar.com","baz.com"] || (obj.created_at < timestamp("2025-05-10Z") && obj.description.contains("cowbell"))
Reference:

Error handling

Invalid filters return HTTP 400 with a structured error body (category, status_code, message, details). Example:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Cache-Control: no-store

{
  "error_code": "invalid_cel_expression",
  "status_code": 400,
  "msg": "Invalid CEL query: unsupported field: endpoint.idk (must be endpoint.url, endpoint.id, endpoint.type, or endpoint.bindings).",
  "details": {
    "operation_id": "op_k23j45n134jkasdfk34jkjnlkjuhasdf"
  }
}