GraphQL

We build our GUI on top of a GraphQL API. If you want to integrate 21 Travel Rule™ with your own IT infrastructure, you can use the same GraphQL API.

The API is self-documenting. The documentation linked is running on a server of ours with fake data, and, as a consequence, you are unable to use that API for testing or production. You run our software on your IT infrastructure. Find the 'Docs' link in the upper right corner to view additional documentation.

Authentication

The GraphQL API is authenticated with a secure cookie.

Creating a user

Creating a user happens with an HTTP POST request to the /users/login path. The request must include a Content-Type: application/json header. The JSON payload looks like this:

{
  "username": "some-username",
  "password": "some-password"
}

This request returns a cookie that is used to authenticate all other requests.

Authenticating requests

Obtaining the cookie from the username/password combination happens in the same fashion as when creating a user. Just repeat the request used for creating a user. In order to authenticate GraphQL requests, include the returned cookie in the request. For example:

curl -H 'content-type: application/json' -b 'user=QgMwwh9+Q52CCuS0a6HAVr5BPSBY9nkgpRv5xU1DPYW1uhB1g2bd;' -d '{"query": "{trpd { vasps { name }}}"}' 'https://testing.21analytics.xyz/graphql'

Query

As the name suggests and as is the rule with GraphQL, use the operation type 'query' to return objects from the backend. There are several objects available.

autod

The autod query groups all the different protocols. With it, you can query the backend for transactions, and the list of returned transactions contain a chronologically ordered list of transactions.

This is the query we recommend using when getting transactions from 21 Travel Rule™.

txDetails

This query returns a page of transactions independent of the protocol. For example, our 'Transactions' tab in our GUI uses this object exclusively. The status argument can be INBOX, WAITING or ARCHIVE.

Example

To return the amount from the TRP and email transactions in the inbox, run this:

{
  autod {
    txDetails(status: INBOX, limit:10 ,offset: 0) {
      ... on TrpdTx {
        trpd {
          amount
        }
      }
      ... on EmailTx {
        email {
          amount
        }
      }
    }
  }
}

txDetailsCount

This query returns the number of transactions with a particular status. Having this number is useful for paging.

Example
{
  autod {
    txDetailsCount(status: INBOX)
  }
}

txsAsCsv

As the name suggests, this returns the transactions formatted as a CSV string. Please note that the returned data is not a CSV file but a GraphQL object where a field contains the CSV data.

Example
{
  autod {
    txsAsCsv(status: INBOX)
  }
}

trpd

This object contains all the TRP (Travel Rule Protocol) related data. All this data is already included in the autod query, and we suggest using that query instead.

txDetails

The txDetails object is the most important endpoint. It exposes the TRP transaction sent and received.

Example

The object has many fields, one of which is the asset field that contains a three-letter currency symbol. Find out what assets passed through the system via TRP by running this query:

{
  trpd {
    txDetails {
      asset
    }
  }
}

The txDetails object can be paginated with an offset and limit argument and filtered with the filter argument. For acceptable values for the filter argument, see the auto-generated API documentation.

vasps

The vasps object describes the registered counter-party, TRP enabled, VASPs. Only when a beneficiary LEI of an incoming TRP message matches the LEI of one of the registered VASPs does the txDetails object return the TRP message. This prevents spam.

Example

The following query returns the name and the optional public key for each configured counter-party VASP:

{
  trpd {
    vasps {
      name
      pubkey
    }
  }
}

See the live documentation for the entire list of queryable objects and fields.

configuration

As the name suggests, this object returns the current TRP configuration of your VASP.

Example

One of the configurable attributes is the LEI. Query the current value with:

{
  trpd {
    configuration {
      lei
    }
  }
}

lookupLnurl

Use this query to translate a Travel Address into a virtual asset, an associated address and the beneficiary VASP. The query will make an HTTP request to the resolved beneficiary VASP. As such, using this query only works if the resolved beneficiary VASP is also registered.

Example

With the Travel Address obtained from the originating user you can do:

{
  trpd {
    lookupLnurl(lnurl: "LNURL1DP68GURN8GHJ7AR9WD6XJMN89CERZCTWV9K8JARFVDEJUCMG9AEHWCTS9ASHXERXTPNJZL") {
      asset
      address
      vasp {
        name
      }
    }
  }
}

Note that the vasp object has more than just a name field.

aopd

Querying AOPP related objects happens here.

proofs

When an end-user uses AOPP to create a verified withdrawal address, it creates a proof. Once a proof is submitted, it is verified. Listing verified proofs happen with this object. Pagination is supported.

Example

Get the first ten proofs with:

{
  aopd {
    proofs(offset: 0, limit: 10) {
      address
      id
    }
  }
}

configuration

AOPD allows you to configure the callback URL the user's wallet should call with the generated proof and address. Query that URL with this object.

{
  aopd {
    configuration {
      callbackUrl
    }
  }
}

proofsCount

A supporting query that returns the number of verified proofs.

Example
{
  aopd {
    proofsCount
  }
}

proofAsCsv

As the name suggests, this returns the proofs formatted as a CSV string. Please note that the returned data is not a CSV file but a JSON document in which one field contains the CSV data.

Example
{
  aopd {
    proofsAsCsv
  }
}

email

Email is the fallback mechanism for staying compliant with the FATF Travel Rule.

configuration

Calling this query returns the email configuration of 21 Travel Rule™.

Example
{
  email {
    configuration {
      smtpUsername
      smtpPassword
    }
  }
}

vasps

As with the trpd vasps query, this returns a list of registered VASPs to whom you can email.

Example

In essence, the same as the previously mentioned trpd vasps query.

{
  email {
    vasps {
      name
    }
  }
}

version

The version object returns the current version of the software.

Example

Possibly the simplest query ever:

{
  version
}

Mutation

As the name suggests and as is the rule with GraphQL, the operation type 'mutation' can be used to affect changes in the backend. Notice that the object type mutation is explicit in the examples. GraphQL allows omitting the query type but not the mutation type. There would be no way to distinguish them otherwise. There are several objects available.

trpd

Everything related to the Travel Rule Protocol.

registerTx

Performing this mutation registers a TRP transaction in the backend.

Note that either addressLine OR street and number are required.

Also, note that the assets Bitcoin, Grin and Ethereum have their own decimal accuracy of 8, 9 and 18 decimal places, respectively. All others default to 8.

Example

This example registers a transaction with an amount of 1 Tether.

mutation {
  trpd {
    registerTx(txDetails: {
      asset: USDT 
      amount: "100000000"
      originator: {
        name: {
          firstName: "some name"
          lastName: "some last name"
        }
        address: {
          postalCode: "1234"
          town: "town"
          country: "ch"
          street: "Dorpstraat"
          number: "1"
        }, 
        walletAddress: "123"
        customerIdentification: "123"
      }
      beneficiary: {
        name: {
          firstName: "some other name"
          lastName: "some last name"
        }, 
        walletAddress: "123"
      }
      beneficiaryVaspLei: "OHRD00TW7IQEDT2YCY76"
    })
  }
}

registerOrUpdateVasp

Before transactions are turned by the txDetails query, a VASP needs to be registered.

Example
mutation {
  trpd {
    registerOrUpdateVasp(vasp: {
        url: "https://demo.21analytics.ch",
        name: "Harm's Emporium",
        lei: "RILFO74KP1CM8P6PCT96",
        autoApproved: false
    }) {
      lei
    }
  }
}

distrustVasp

This removes a VASP. Transactions received from this VASP are no longer shown, but they remain in the database.

Example
mutation {
  trpd {
    distrustVasp(lei: "RILFO74KP1CM8P6PCT96")
  }
}

updateConfiguration

In order to receive TRP messages your VASP needs to be configured. That is done with this query. Creating and updating are the same things. All arguments need to be passed always.

The lnurlBaseUrl is the external facing host to be used when exchanging messages in the advanced flow.

Example
mutation {
  trpd {
    updateConfiguration(vasp: {
      name: "Harm's outfit",
      lei: "RILFO74KP1CM8P6PCT96",
      lnurlBaseUrl: "https://some.com"
    }) {
      name
    }
  }
}

obtainLnurl

When a user requests to send funds to a VASP which supports the advanced flow of TRP, a LNURL or Travel Address needs to be generated. The resulting LNURL is given to the beneficiary user, which in turn gives it to the originating user.

Example
mutation {
  trpd {
    obtainLnurl(user: {
      firstName: "Harm",
      lastName: "Aarts",
      asset: BTC,
      walletAddress: "abc"
    })
  }
}

aopd

Changing AOPP related server state happens under this object type.

updateConfiguration

The AOPP functionality of 21 Travel Rule™ knows only one configuration parameter. This parameter is called callbackUrl and governs where a user wallet should POST the proof when completing an AOPP request.

Example
mutation {
  aopd {
    updateConfiguration(config: {callbackUrl: "https://some.com"})
  }
}

registerProof

Proofs obtained via wallets that do not support AOPP can be stored with this query. For example, the end-user has manually submitted the signature for a signing challenge.

Example
mutation {
  aopd {
    registerProof(proof: {
      message: "hello", 
      asset: BTC, 
      address: "bc1qnshsvhrfl28g03k0vxdez6vua56r0c72xy9e93", 
      signature: "H1oYVmDaWxZBPEk2ou4myn1SRC20ycBUPPD5fLS+SmQ1e04Bi1J9mIJ5fNhe3khDhJRUX2fU+VHGKlJdAjYIvBU="
    })
  }
}

email

Email is the fallback mechanism. When all else fails, email makes sure you are still compliant.

updateConfiguration

Here you can configure your VASPS email server to send outgoing emails.

Example
mutation {
  email {
    updateConfiguration(config:{
      smtpHost: "some host"
      smtpPort: 25
      smtpUsername: "some smtp user"
      smtpPassword: "some smtp password"
      smtpTlsSec: TLS
      fromEmailAddress: "from@my-vasp.com"
      notificationEmailAddress: "notification@my-vasp.com"
    }) {
      smtpHost      
    }
  }
}

registerVasp

Sending emails can only be done to previously registered VASPs. Use this query to register email VASPs.

Example
mutation {
  email {
    registerVasp(vasp: {
      name: "some name"
      email: "compliance@other-vasp.com"
    })
  }
}

distrustVasp

When an email VASP is no longer required, use this query to remove it from the list. Previously sent transactions are retained.

Example
mutation {
  email {
    distrustVasp(id:"some id")
  }
}

registerTx

The same consideration hold here as with the registerTx mutation under the trpd object. Again, either addressLine OR street and number are required.

Example
mutation {
  email {
    registerTx(txDetails: {
      asset:BTC
      amount:"1230"
      txId:"68cf8b54b8af3e81c7d6186d20aada6a8896de4c9329b79427950b84334262ff"
      originator:{
        name: {
          firstName:"some first name"
          lastName:"some last name"
        }
        address:{
          country:"ch"
          town:"zug"
          postalCode: "1111"
          addressLine:"Dorpstraat 1"
        }
        walletAddress:"123"
        customerIdentification:"some id"
      }
      beneficiary: {
        name: {
          firstName:"some first name"
          lastName:"some last name"
        }
        walletAddress:"123"
      }
      beneficiaryVaspId:"123"
    })
  }
}

offline

Sometimes Travel Rule information is received out of band. 21 Travel Rule™ allows entering that information in the system nonetheless. Doing so makes sure that travel rule compliance data is held at a single source of truth.

registerTx

The mutation is very similar to the other registerTx mutations. The notable difference is that there's a field called originatorVasp. Because this Travel Rule data was received out of band, it is required to specify which VASP originated the request. To this end, the name field is required and the lei field is optional.

Example
mutation {
  offline {
    registerTx(txDetails: {
      asset:BTC
      amount:"1230"
      txId:"68cf8b54b8af3e81c7d6186d20aada6a8896de4c9329b79427950b84334262ff"
      originator:{
        name: {
          firstName:"some first name"
          lastName:"some last name"
        }
        address:{
          country:"ch"
          town:"zug"
          postalCode: "1111"
          addressLine:"Dorpstraat 1"
        }
        walletAddress:"123"
        customerIdentification:"some id"
      }
      beneficiary: {
        name: {
          firstName:"some first name"
          lastName:"some last name"
        }
        walletAddress:"123"
      }
      originatorVasp: {
        name: "some name"
      }
    })
  }
}