Filterable API Collections

Introduction

Numerous GraphQL queries that return collections of objects (such as self-hosted wallet proofs or VASP-to-VASP transactions) accept identically structured filtering parameters that can be used to select a sub-set of items. This section explains how those filter parameters are structured, and walks through some examples.

It may also be instructive to see how the 21 Compliance Dashboard leverages these filter APIs to present numerous filtering options to the user. This is explained in the corresponding GUI section.

Recursive filter structure

In general, any query for filtering results can be expressed as a nested combination of AND, OR and NOT formulas. The root filter parameter accepted by filterable APIs allows you to specify a filter condition that can represent such an arbitrarily nested formula. Taking the AopdProofFilterInput input as an example, it is defined as:

input AopdProofFilterInput @oneOf {
	and: [AopdProofFilterInput!]
	or: [AopdProofFilterInput!]
	not: AopdProofFilterInput
	is: AopdProofConditionInput
}

The union type has three recursive and, or and not variants that start a new boolean formula, and one terminating variant is, where an actual condition can be specified. Abbreviating the terminating objects of the AopdProofConditionInput type as C1, C2, etc., a valid filter object could thus look like:

filter: {
  and: [
    { is: C1 },
    { not: { is: C2 } },
    { or: [ { is: C3 }, { and: [ { is: C4 }, { not: { is: C5 } } ] } ] }
  ]
}

In the next section, we will look at how those Cx filter conditions can be constructed.

Filtering Heterogeneous vs. Homogeneous Collections

Again taking the filter for self-hosted wallet proofs as an example, let's look at the AopdProofConditionInput type, which terminates the recursive filter formulas for AopdProofFilterInput, as an example. Since self-hosted wallet proofs can be of three different types, this is again a union with variants corresponding to the different proof types:

input AopdProofConditionInput @oneOf {
	signature: AopdSignatureProofConditionInput
	media: AopdMediaProofConditionInput
	satoshi: AopdSatoshiProofConditionInput
	any: AopdAnyProofConditionInput
}

Choosing one of the first three variants will automatically restrict filter results for this condition to the chosen proof type. In addition, there is an any variant which collects filtering criteria applicable to all proof types, and which doesn't restrict the type of returned proofs.

Some filter APIs only return objects of one particular type, such as the filter for counterparty VASPs. In this case, this intermediate union is skipped, and the recursive filter is terminated by an object type where conditions can be specified.

Next, we will look at the actual filtering condition types, e.g. the kinds of types pointed at by the AopdProofConditionInput variants above.

Specifying Filter Conditions

Again taking the filter for self-hosted wallet proofs as an example, let's look at the abbreviated definition of AopdSatoshiProofConditionInput:

input AopdSatoshiProofConditionInput {
	asset: TravelStringConditionInput
	amount: TravelAmountConditionInput
	status: [AopdProofStatus!]
	createdAt: TravelTimestampConditionInput
	...
}

The first thing to notice is that all fields are optional, representing optional restrictions on some attribute of a Satoshi proof. In fact, using the empty object as:

filter: { is: { satoshi: {} } }

has the meaningful effect of restricting the results to Satoshi proofs.

More likely though, you will want to use a subset of the fields to specify some conditions on a Satoshi proof. The types used for this fall into two categories, detailed next.

Condition Types

For numerous types, there exist union Condition types which are shared across the whole schema and whose variants express a constraint. For the above example:

  • For strings: TravelStringConditionInput, whose Ilike variant allows you to filter for sub-strings (see the type's GraphQL documentation for more information).

  • For amounts: TravelAmountConditionInput, which has variants to express 'less than X', 'equal to X' and 'larger than X'.

  • For timestamps: TravelTimestampConditionInput, where a 'before X' or 'after X' constraint can be expressed.

Filter for Enumerable Fields

In the above example, every Satoshi proof has a status of union type AopdProofStatus. The available filter for this property allows you to specify a list of statuses, where the Satoshi proof status must match one of them.

A Complete Example

As a concluding example, the following query will return all proof IDs for proofs that are:

  • Of any type and verified, or

  • Signature proofs created after 2024 whose address start with "0x", or

  • Media proofs whose comment contains "OK" and whose customer ID does not contain "LEGACY".

query {
  aopd {
    proofs(
      filter: {
        or: [
          { is: { any: { status: VERIFIED } } }
          {
            is: {
              signature: {
                address: { ilike: "0x%" }
                createdAt: { after: "2025-01-01T00:00:00" }
              }
            }
          }
          {
            and: [
              { is: { media: { comment: { ilike: "%OK%" } } } }
              { not: { is: { media: { customerId: { ilike: "%LEGACY%" } } } } }
            ]
          }
        ]
      }
    ) {
      __typename
      ... on AopdMediaProof {
        id
      }
      ... on AopdSatoshiProof {
        id
      }
      ... on AopdSignatureProof {
        id
      }
    }
  }
}