Home / GraphQL API

Using GraphQL

Why GraphQL

The Highnote API uses GraphQL to enable modern integrations and experiences. GraphQL gives developers the flexibility to tailor requests to their use cases while providing type-safety over the network – all with a best-in-class tooling ecosystem.

GraphQL is not a query language for databases. It's an API pattern that cleanly expresses the behavior and modeling of a system. Using GraphQL, clients request data specific to their use case and receive predictable responses.

Documentation and developer experience is at the heart of GraphQL. In fact, documentation is built-in. GraphQL services are <a target=_blank href="https://graphql.org/learn/introspection/">introspectable</a> – this means they describe the operations and types available to developers in a way that allows for powerful tooling to streamline the development cycle. You can explore the Highnote API via the Highnote GraphQL Explorer or see clients & codegen for more.

While GraphQL has a rich ecosystem of tools, it does not require special libraries or frameworks to use. When integrating with the Highnote API, you will be sending and receiving JSON over HTTP.

The Using the Highnote API section covers Highnote API basics like request URLs, authentication, and more.

A basic query
query HelloHighnote {
  ping
}

GraphQL Concepts

GraphQL is a specification that provides a type system to ensure a contract between clients and servers.

Types

The GraphQL type system provides five scalar types or primitive values: Int, Float, String, Boolean, and ID. It also allows for custom scalar types. At this time, the Highnote API does not use custom scalars.

Introspection

GraphQL APIs are introspectable. This means that they publish information about their capabilities to clients. The Highnote API is fully introspectable (to authenticated users) in the Test environment.

Tools such as graphiql use introspection to render documentation, type hints, and client-side validations. You can try it out with the Highnote GraphQL Explorer.

Operations

GraphQL provides three operations – query, mutation, and subscription. These are actions you can take against the API. While there are nuances to each, for the most part, queries are used to fetch data, and mutations are used to write data. The Highnote API does not currently use subscriptions.

Node queries & Global IDs

The Highnote API uses Node queries and Global IDs as a means to look up individual objects.

In GraphQL, the node query is a pattern that allows clients to pass a global ID and not have to specify the entity it represents. Instead of fetching objects by resource, any object in the system that implements the Node interface can be retrieved with one query.

query NodeQuery {
  node(id: "PAYMENT_CARD_ID") {
    ... on CardProduct {
      __typename
      id
      name
    }

    ... on PaymentCard {
      __typename
      id
      last4
    }
  }
}

You must provide a fragment in your selection set (what data you ask to get back) for each type you may expect.

In the above example, if the ID resolved to a PaymentCard, the response would look like this:

{
  "data": {
    "node": {
      "__typename": "PaymentCard",
      "id": "PAYMENT_CARD_ID",
      "last4": "1234"
    }
  }
}

You can see which types in the Highnote API implement the Node interface by running this query:

{
  __type(name: "Node") {
    possibleTypes {
      name
      description
    }
  }
}

Pagination

When querying listed data, it is oftentimes useful to limit the number of records returned in a response. Doing so allows for faster response times from the server and smaller payloads on the client. The Highnote API automatically paginates data following the Relay Cursor Connections Specification. In this pagination specification, the edges field is used to obtain list of items where each item has a cursor specifying its position in the list and a node which contains the fields requested on the item.

To better understand how pagination works in the Highnote API, we'll take a look at a common use case using card products. Let's say you need a list of your organization's card products, and each card product in the list must include its id, name, usage, and vertical. Here is what the query would look like:

query ListCardProducts($first: Int, $after: String) {
  cardProducts(first: $first, after: $after) {
    edges {
      cursor
      node {
        __typename
        id
        name
        usage
        vertical
      }
    }
    pageInfo {
      startCursor
      endCursor
      hasNextPage
      hasPreviousPage
    }
  }
}

Notice the variables we provide to the query: $first and $after. These variables are optionally provided to the query in order to let the Highnote API know which records to return. For example, to return a list of the ten most recently created card products for an organization, provide the first variable in your query:

{
  "first": 10
}

Now that a first variable has been provided, the query returns the ten most recently created card products:

{
  "data": {
    "cardProducts": {
      "edges": [
        {
          "cursor": "dD0yMDIyLTEyLTIwVDE4JTNBMTUlM0E1NS4wOTUwMDAwMDBaJmk9cGRfY2YwNGNjYzc1YzlhNDNmYzhlOWViMTVlMmMxNGY5NzY",
          "node": {
            "id": "pd_JtxcCG3Y9Qx8gAfVBujHP7YEEQPGOGEW",
            "name": "Card 1",
            "usage": "MULTI_USE",
            "vertical": "COMMERCIAL_DEBIT"
          }
        },
        {
          "cursor": "dD0yMDIyLTEyLTA5VDE3JTNBMjAlM0EzOC41NzQwMDAwMDBaJmk9cGRfMThhZTE1YWJlZWMzNGMwN2E1ODdhMzVlNGIxNjA0Njk",
          "node": {
            "id": "pd_U5TkiuMjz1mu0haQdERQ7nwlI6Bka3OP",
            "name": "Card 2",
            "usage": "SINGLE_USE",
            "vertical": "AP_INVOICE_AUTOMATION"
          }
        },
        {
          "cursor": "dD0yMDIyLTEyLTA5VDE3JTNBMjAlM0EzMC43ODEwMDAwMDBaJmk9cGRfMzg3NTI3Y2ViMWY5NGYwNzg3NDE2ZTViNDAyZDU4NjM",
          "node": {
            "id": "pd_8ec9q3PzraRoeDa5UdlDBw2jHfP4Jsds",
            "name": "Card 3",
            "usage": "MULTI_USE",
            "vertical": "AP_INVOICE_AUTOMATION"
          }
        },
        {
          "cursor": "dD0yMDIyLTEyLTA5VDE3JTNBMTElM0EwNC4xNTAwMDAwMDBaJmk9cGRfMmM4N2JlNmViNGViNDMwYWFjZDU3Y2Y0NWI2ODI4NDE",
          "node": {
            "id": "pd_jsrG55BIaAIldWndPO5P3P61yHlZQpgS",
            "name": "Card 4",
            "usage": "MULTI_USE",
            "vertical": "AP_INVOICE_AUTOMATION"
          }
        },
        {
          "cursor": "dD0yMDIyLTEwLTMxVDIwJTNBMTUlM0E0Ni40NDUwMDAwMDBaJmk9cGRfNjI0NDc3YjcyZDljNGYzZWI5NWU1N2Q0ZDM2NzFhOWE",
          "node": {
            "id": "pd_KODNBgPrQqu4p3KBIx7F8r6dpkid0pc0",
            "name": "Card 5",
            "usage": "MULTI_USE",
            "vertical": "GENERAL_PURPOSE_RELOADABLE"
          }
        },
        {
          "cursor": "dD0yMDIyLTEwLTMxVDIwJTNBMTUlM0EyMC44MTEwMDAwMDBaJmk9cGRfY2NkMGQxZTE0NzBmNDkxMjlhZWI3YzY3NTQyODYwOWM",
          "node": {
            "id": "pd_yzp92A6EsH9tNjW6DGROiRryfHyeH4uj",
            "name": "Card 6",
            "usage": "MULTI_USE",
            "vertical": "GENERAL_PURPOSE_RELOADABLE"
          }
        },
        {
          "cursor": "dD0yMDIyLTEwLTMxVDE1JTNBNDMlM0EyNy45NjUwMDAwMDBaJmk9cGRfMDAyOTBmYzQxMWFlNDk0N2FmYTVhNjA5NDQ3ZTFiNzE",
          "node": {
            "id": "pd_Ojf2vtoyYne8ya2lEsykwPN4x4EoadSM",
            "name": "Card 7",
            "usage": "MULTI_USE",
            "vertical": "GENERAL_PURPOSE_RELOADABLE"
          }
        },
        {
          "cursor": "dD0yMDIyLTA5LTI5VDIwJTNBNDElM0ExOC45MTAwMDAwMDBaJmk9cGRfYmUyNTNjZDU2ZjAyNDkwMThjZTFjMjE2ODU5NmE4NmY",
          "node": {
            "id": "pd_W5tHx8DqYJbiFjAbRJghUB42Wt7tI2Ng",
            "name": "Card 8",
            "usage": "MULTI_USE",
            "vertical": "GENERAL_PURPOSE_RELOADABLE"
          }
        },
        {
          "cursor": "dD0yMDIyLTA5LTIzVDE0JTNBMzElM0ExMy43NTgwMDAwMDBaJmk9cGRfZDAzYmZlMjcxOTY3NDRlMmJhMGQxMjRkYzJiN2I3ZDM",
          "node": {
            "id": "pd_JTuTEFE7t4QvANQsVxvUexbEdlceNMxn",
            "name": "Card 9",
            "usage": "MULTI_USE",
            "vertical": "SECURED_COMMERCIAL_CREDIT"
          }
        },
        {
          "cursor": "dD0yMDIyLTA5LTIzVDE0JTNBMzAlM0E1OS43ODcwMDAwMDBaJmk9cGRfMTgzNTI4NDgwNjhkNGFhMDg5Nzg5NTVlM2NkNmQzZGI",
          "node": {
            "id": "pd_7LIs7e5TPjrt95MS6LedCve4Zr1p97Fk",
            "name": "Card 10",
            "usage": "MULTI_USE",
            "vertical": "SECURED_COMMERCIAL_CREDIT"
          }
        }
      ],
      "pageInfo": {
        "hasPreviousPage": false,
        "hasNextPage": true,
        "startCursor": "dD0yMDIyLTEyLTIwVDE4JTNBMTUlM0E1NS4wOTUwMDAwMDBaJmk9cGRfY2YwNGNjYzc1YzlhNDNmYzhlOWViMTVlMmMxNGY5NzY",
        "endCursor": "dD0yMDIyLTA5LTIzVDE0JTNBMzAlM0E1OS43ODcwMDAwMDBaJmk9cGRfMTgzNTI4NDgwNjhkNGFhMDg5Nzg5NTVlM2NkNmQzZGI"
      }
    }
  }
}

Note: Most queries in the Highnote API will assume a default value of 20 for the first variable if one is not provided by the user.

To request the next ten card products, you must additionally provide the after variable to your query. In this case, the after variable will be the cursor of the last item in the previously returned list. This value is also conveniently available as the endCursor value in pageInfo on the previous response. Updating the query variables, we now have:

{
  "first": 10,
  "after": "dD0yMDIyLTA5LTIzVDE0JTNBMzAlM0E1OS43ODcwMDAwMDBaJmk9cGRfMTgzNTI4NDgwNjhkNGFhMDg5Nzg5NTVlM2NkNmQzZGI"
}

Performing the query again with the updated variables will return the next ten items in the list after the item with a cursor value specified in after.

Making requests

To learn more about making Highnote specific calls, see Using the API.

GraphQL itself is transport agnostic, but the Highnote API is served as JSON over HTTP(s).

If you are familiar with making HTTP requests that send and receive JSON, a GraphQL call should feel very familiar. The key differences with GraphQL are that you do not rely on HTTP verbs (GET, PUT, POST, PATCH, DELETE, etc.) or multiple endpoints per resource. Instead, you make POST requests to a single endpoint (/graphql) and call specific queries or mutations to accomplish your task.

Note: While both GET and POST requests are valid, it is recommended to use POST requests when interacting with the API.

Your request should contain a JSON body containing: query, variables (optional), and operationName (optional). The query is the actual operation you want to run. In the following example, we want to call the ping query. (this is just the Hello World call in our API). The variables key is used to pass in dynamic data on each request and operationName is used to specify an action in the case multiple queries are sent (read more here).

A call to the Highnote API will look like this:

curl -X POST \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic <API_KEY>' \
--data '{"query":"query Ping {\n  ping\n}"}' \
https://api.us.test.highnoteplatform.com/graphql

and will return a JSON payload with the following format:

{
  "data": {
    "ping": "pong"
  },
  "extensions": {
    "requestId": "REQUEST_ID"
  }
}

Clients & Codegen

The GraphQL ecosystem is incredibly vibrant and naturally focuses on developer and integrator experience.

Desktop clients

When developing against GraphQL APIs, it is helpful to use a GUI tool that leverages introspection to provide documentation and type hints. Some of the tools we use include:

Editor plugins & Browser Extensions

There are plugins available for various IDEs to make your development experience more seamless.

Codegen tools

There are a number of tools to generate code from GraphQL APIs. At Highnote, we make heavy use of GraphQL Code Generator to generate TypeScript definitions and SDKs for use with GraphQL.

Provide Feedback

Was this content helpful?