Skip to content

Protocol query API

The protocol query API allows you to filter and sort protocol instances by their field values.

Endpoint

POST /npl/{package}/{ProtocolName}/-/query

For example, for a protocol IOU in package com.example.iou:

POST /npl/com/example/iou/IOU/-/query

The request body is a JSON object. All fields are optional -- an empty body {} returns all instances the caller has access to, subject to the default page size.

Request body

{
  "filter": { ... },
  "sort":   [ ... ],
  "page":         1,
  "pageSize":     20,
  "includeCount": false
}
Field Type Default Description
filter object Field filters; see Filters.
sort array Sort order; see Sorting.
page integer 1 1-indexed page number.
pageSize integer engine default Number of items per page.
includeCount boolean false When true, adds totalPages and totalItems to the response.

Making fields queryable

Fields must be explicitly marked with @query before they can be used in filters or sort expressions; using a non-annotated field will result in an error.

@api
protocol[seller, buyer] Order(
    @query var amount: Number,
    @query var label:  Text,
) {
    @query var status: Status = Status(0);
}

System fields

The following fields are always available without @query:

Field Type Description
@state Text Current state name of the protocol
createdAt DateTime When the protocol was created
modifiedAt DateTime When the protocol was last modified

Filters

The filter object maps field names to operator objects:

{
  "filter": {
    "amount": { "gt": 100 },
    "label":  { "eq": "urgent" }
  }
}

Multiple fields are combined with AND. All operators for a single field are also combined with AND.

Operators by type

Number, Symbol

Operator Description Value type
eq Equals number
gt Greater than number
gte Greater than or equal number
lt Less than number
lte Less than or equal number
in Matches any value in the array array of numbers
not Negates a nested operator operator object
{ "filter": { "amount": { "gte": 10, "lt": 100 } } }
{ "filter": { "amount": { "in": [10, 20, 30] } } }
{ "filter": { "amount": { "not": { "eq": 0 } } } }

Text, Enum, Identifier

Operator Description Value type
eq Equals string
in Matches any value in the array array of strings
not Negates a nested operator operator object
{ "filter": { "label": { "eq": "urgent" } } }
{ "filter": { "label": { "in": ["urgent", "normal"] } } }
{ "filter": { "priority": { "not": { "eq": "LOW" } } } }

DateTime, LocalDate

Values must be ISO-8601 strings and optionally include a timezone. DateTime requires a timezone offset, e.g. 2024-03-15T10:00:00Z or 2006-01-02T15:04:05.999+01:00[Europe/Zurich]; LocalDate requires a date-only string (e.g. 2024-03-15).

Operator Description Value type
eq Equals ISO-8601 string
gt After ISO-8601 string
gte At or after ISO-8601 string
lt Before ISO-8601 string
lte At or before ISO-8601 string
in Matches any value in the array array of strings
not Negates a nested operator operator object
{ "filter": { "dueDate": { "lt": "2024-12-31" } } }
{ "filter": { "eventTime": { "gte": "2006-01-02T15:04:05.999+01:00[Europe/Zurich]" } } }
{ "filter": { "createdAt": { "not": { "lt": "2006-01-02T15:04:05.999+01:00[Europe/Zurich]" } } } }

Boolean

Operator Description Value type
eq Equals boolean
not Negates a nested operator operator object
{ "filter": { "active": { "eq": true } } }
{ "filter": { "active": { "not": { "eq": false } } } }

Struct

When a @query-annotated field has a struct type, filter by its members using member names as keys. Each member you filter on must itself be annotated with @query on the struct definition.

struct Details {
    @query score: Number,
    @query label: Text
};
{ "filter": { "details": { "score": { "gte": 5 }, "label": { "eq": "gold" } } } }

List, Set

The contains operator checks whether the collection contains a matching element.

Scalar elements — wrap the value in a JSON array:

{ "filter": { "tags": { "contains": ["kotlin"] } } }
{ "filter": { "tags": { "not": { "contains": ["archived"] } } } }

Struct elements — pass an object with struct member filters; matches if any element satisfies all conditions:

struct Inner { @query value: Number, @query tag: Text };
{ "filter": { "items": { "contains": { "value": { "gt": 10 }, "tag": { "eq": "a" } } } } }
Operator Description
contains Collection contains a matching element
not Negates a nested operator

Map

Operator Description Value
exists Map has the given key scalar key value
contains Map has a matching entry; accepts key, value, or both object with key and/or value fields
not Negates a nested operator operator object
{ "filter": { "metadata": { "exists": "region" } } }
{ "filter": { "metadata": { "contains": { "key": "env", "value": "prod" } } } }
{ "filter": { "metadata": { "not": { "exists": "deprecated" } } } }

Union

Use type to filter by the variant type name and value to filter by the variant's content.

Type names for type filters:

  • Primitive NPL types use the short name: "Text", "Number", "Boolean", "LocalDate", "DateTime"
  • Struct variants use the fully-qualified package path: "/com/example/MyStruct"
  • The absent Optional variant uses /lang/core/None
Operator Description
type Filter by variant type; uses Text operators (eq, in, not)
value Filter by variant content; uses scalar operators or struct field names
not Negates a nested operator
{ "filter": { "status": { "type": { "eq": "Number" } } } }
{ "filter": { "status": { "type": { "in": ["Text", "Number"] } } } }
{ "filter": { "status": { "type": { "not": { "eq": "/com/example/StatusInfo" } } } } }

{ "filter": { "status": { "value": { "gt": 42 } } } }
{ "filter": { "status": { "value": { "code": { "gte": 100 } } } } }

{ "filter": { "optionalAmount": { "type": { "eq": "/lang/core/None" } } } }
{ "filter": { "optionalAmount": { "type": { "not": { "eq": "/lang/core/None" } } } } }

type and value can be combined in one filter — both conditions must match:

{ "filter": { "status": { "type": { "eq": "Number" }, "value": { "lt": 100 } } } }

The not operator

not wraps any single operator object and negates it. It cannot wrap another not directly (doing so returns 400 Bad Request).

{ "filter": { "amount": { "not": { "in": [0, -1] } } } }
{ "filter": { "label":  { "not": { "eq": "archived" } } } }

Sorting

The sort field accepts an array of sort entries, applied left to right:

{
  "sort": [
    { "field": "amount", "dir": "ASC"  },
    { "field": "label",  "dir": "DESC" }
  ]
}
Field Values Description
field field name Must be @query-annotated or a system field
dir ASC, DESC Sort direction

Sortable types: Number, Text, DateTime, LocalDate, Boolean, Enum, Identifier, Union. Collection types (List, Set, Map) and Struct fields cannot be sorted.

System fields @state, createdAt, and modifiedAt are always sortable.

!!! note "Union sort order" Union fields are sorted by their raw JSON text representation. This means sort order across mixed variant types (e.g. numbers and strings in the same union) follows JSON string collation and may not reflect the semantic ordering of the values.

Response

The response follows the same structure as the standard list endpoint:

{
  "items": [ ... ],
  "page": 1,
  "totalPages": 5,
  "totalItems": 42
}

totalPages and totalItems are only present when includeCount: true was set in the request.

Errors

All validation errors return 400 Bad Request with a description of the problem.

Cause Example
Filtering on a field that does not exist {"filter": {"nosuchfield": {"eq": 1}}}
Filtering on a field not annotated with @query {"filter": {"privateField": {"eq": 1}}}
Operator not allowed for the field's type {"filter": {"amount": {"contains": "x"}}} (Number)
Filter value is not an operator object {"filter": {"amount": 42}}
in value is not an array {"filter": {"amount": {"in": 10}}}
not wrapping another not {"filter": {"amount": {"not": {"not": {"eq": 0}}}}}
sort is not an array {"sort": "amount"}
Sort entry missing field or dir {"sort": [{"field": "amount"}]}
dir is not ASC or DESC {"sort": [{"field": "amount", "dir": "SIDEWAYS"}]}
Sorting on a non-existent or non-@query field {"sort": [{"field": "nosuchfield", "dir": "ASC"}]}
Sorting on an unsortable type (List, Set, Map, Struct) {"sort": [{"field": "tags", "dir": "ASC"}]}
URL query parameters used instead of request body POST /-/query?page=1
page or pageSize is not a valid integer {"page": "first"}
includeCount is not a boolean {"includeCount": "yes"}
Struct filter references a non-existent member {"filter": {"details": {"nosuchfield": {"eq": 1}}}}
Struct filter member is not annotated with @query {"filter": {"details": {"privateField": {"eq": 1}}}}