Making calls with GraphQL
Learn how to authenticate to the GraphQL API, then learn how to create and run queries and mutations.
Authenticating with API access key
You can authenticate the GraphQL API using your personal API access key.
To optain your personal API access key, follow the steps in Authentication.
The data that you are requesting will dictate which scopes or permissions you will need.
If your token does not have the required scopes or permissions to access a resource, the API will return an error message that states the scopes or permissions your API access key needs.
The GraphQL endpoint
The REST API has numerous endpoints; the public GraphQL API has a single endpoint:
https://sensaai.com/graphiql
The endpoint remains constant no matter what operation you perform.
Communicating with GraphQL
Because GraphQL operations consist of multiline JSON, SensaAI recommends using the Explorer to make GraphQL calls. You can also use curl
or any other HTTP-speaking library.
In REST, HTTP verbs determine the operation performed. In GraphQL, you'll provide a JSON-encoded body whether you're performing a query or a mutation, so the HTTP verb is POST
. The exception is an introspection query, which is a simple GET
to the endpoint.
To query GraphQL in a curl
command, make a POST
request with a JSON payload. The payload must contain a string called query
:
curl -H "Content-Type: application/json" -H "api_key: YOUR_API_KEY" -X POST -d \
'{
"query": "query { \
profile { \
email \
firstName \
lastName \
phoneNumber \
} \
}"
}' https://sensaai.com/graphiql/
"query"
must escape newline characters or the schema will not parse it correctly. For the POST
body, use outer double quotes and escaped inner double quotes.About query and mutation operations
The two types of allowed operations in SensaAI's GraphQL API are queries and mutations. Comparing GraphQL to REST, queries operate like GET
requests, while mutations operate like POST
/PATCH
/DELETE
. The mutation name determines which modification is executed.
Queries and mutations share similar forms, with some important differences.
About queries
GraphQL queries return only the data you specify. To form a query, you must specify fields within fields (also known as nested subfields) until you return only scalars.
Queries are structured like this:
query {
JSON-OBJECT-TO-RETURN
}
In the queries reference, the listed fields are what will be contained in the reponse object.
For a real-world example, see "Example query."
About mutations
To form a mutation, you must specify three things:
- Mutation name. The type of modification you want to perform.
- Input object. The data you want to send to the server, composed of input fields. Pass it as an argument to the mutation name.
- Payload object. The data you want to return from the server, composed of return fields. Pass it as the body of the mutation name.
Mutations are structured like this:
mutation {
MUTATION-NAME(input: {MUTATION-NAME-INPUT!}) {
MUTATION-NAME-PAYLOAD
}
}
The input object in this example is MutationNameInput
, and the payload object is MutationNamePayload
.
In the mutations reference, the listed input fields are what you pass as the input object. The listed return fields are what you pass as the payload object.
For a real-world example, see "Example mutation."
Working with variables
Variables can make queries more dynamic and powerful, and they can reduce complexity when passing mutation input objects.
variables
before the JSON object.Here's an example query with a single variable:
query($number_of_stocks: Int) {
stockRatings(first: $number_of_stocks
) {
edges {
node {
date
totalRating
stock {
id
name
}
}
}
}
}
variables {
"number_of_stocks": 10
}
There are three steps to using variables:
- Define the variable outside the operation in a
variables
object:variables { "number_of_stocks": 10 }
The object must be valid JSON. This example shows a simpleInt
variable type, but it's possible to define more complex variable types, such as input objects. You can also define multiple variables here. - Pass the variable to the operation as an argument:
query($number_of_stocks: Int) {
The argument is a key-value pair, where the key is the name starting with$
(e.g.,$number_of_stocks
), and the value is the type (e.g.,Int
). Add a!
to indicate whether the type is required. If you've defined multiple variables, include them here as multiple arguments. - Use the variable within the operation:
stockRatings(first: $number_of_stocks
In this example, we substitute the variable for the number of stocks to retrieve. We specify a type in step 2 because GraphQL enforces strong typing.
This process makes the query argument dynamic. We can now simply change the value in the variables
object and keep the rest of the query the same.
Using variables as arguments lets you dynamically update values in the variables
object without changing the query.
Example query
Let's walk through the query from above with some more inputs and put this information in further context.
The query looks up the stockRatings
in the database, finds the 20 highest rated stocks based on total_rating on the 1 June 2020:
query {
stockRatings(first: 20, orderBy: "-total_rating", date: "2020-06-01") {
edges {
node {
stock {
name
sector
industry
}
date
totalRating
growthRating
returnRating
riskRating
valuationRating
technicalRating
}
}
}issues
}
Looking at the composition line by line:
query {
Because we want to read data from the server, not modify it,query
is the root operation. (If you don't specify an operation,query
is also the default.)stockRatings(first: 20, orderBy: "-total_rating", date: "2020-06-01")
We call thestockRatings
object and ask for it to return 20 stockRatings, ordered in descending order by total_rating on the 1 of June 2020.- The stockRatings docs tell us that the
stockRatings
object has the typeStockRatingNodeConnection
. edges {
We knowstockRatings
is a connection because it has theStockRatingNodeConnection
type. To retrieve data about individual stockRatings, we have to access the node viaedges
.node {
Here we retrieve the node at the end of the edge. The StockRatingNodeConnection docs indicate the node at the end of theStockRatingNodeConnection
type is aStockRatingNode
object.- Now that we know we're retrieving a
StockRatingNode
object, we can look at the StockRatingNode docs and specify the fields we want to return:stock { name sector industry } date totalRating growthRating returnRating riskRating valuationRating technicalRating
Here we specify thestock
,date
, andratings
fields of theStockRatingNode
object.
Thestock
field has the type StockType and we specify the subfields we would like to return in this casename
,sector
andindustry
.
Example mutation
Mutations often require information that you can only find out by performing a query first. This example shows two operations:
- A query to get a watchlist ID.
- A mutation to add some stocks to the watchlist.
query getWatchlistsForUser {
watchlistsForUser {
id
name
}
}
mutation addToWatchlist {
addToWatchlist(watchlistId: 69, stockTickers: ["MSFT", "MMM", "X"]) {
success
errors
}
}
getWatchlistsForUser
and addToWatchlist
in this example), the operations will be executed as separate calls to the GraphQL endpoint. It's not possible to perform a query at the same time as a mutation, or vice versa.Let's walk through the example. The task sounds simple: add one or more stocks to a watchlist.
So how do we know to begin with a query? We don't, yet.
Because we want to modify data on the server (add stocks to a watchlist), we begin by searching the schema for a helpful mutation. The reference docs show the addToWatchlist
mutation, with this description: Add one or more stocks to a watchlist.
Perfect!
The docs for the mutation list three input fields:
watchlistId
(ID!
)stockTickers
([String]
)stockIds
([String]
)
While neither stockTickers
or stockIds
are strictly required we will need to include at least one of them or nothing will be added to the watchlist.
The !
s indicate that watchlistId
is a required field as we will need to specify what watchlist we wish to add the stock/s to.
This is why we start this example with a query: to get a list of watchlists for the user to then select the ID
for the watchlist we would like to add the stock/s to.
Let's examine the query line by line:
query getWatchlistsForUser {
Here we're performing a query, and we name itgetWatchlistsForUser
. Note that naming a query is optional; we give it a name here so that we can include it in same Explorer window as the mutation.
We retrieve watchlists for the user to react to by querying thewatchlistsForUser
array.id
name
This is where we retrieve theid
andname
to pass as thewatchlistId
.
When we run the query, we get an array of all watchlists for the user and we can then select the id
corresponding to the name
of the watchlist we would like to add stock's to.
id
returned in the query is the value we'll pass as the watchlistId
in the mutation. Neither the docs nor schema introspection will indicate this relationship; you'll need to understand the concepts behind the names to figure this out.With the ID known, we can proceed with the mutation:
mutation addToWatchlist {
Here we're performing a mutation, and we name itaddToWatchlist
. As with queries, naming a mutation is optional; we give it a name here so we can include it in the same Explorer window as the query.addToWatchlist(watchlistId: "69", stockTickers: ["MSFT", "MMM", "X"]) {
Let's examine this line:addToWatchlist
is the name of the mutation.(watchlistId: "69"
is the requiredid
of the watchlist.stockTickers: ["MSFT", "MMM", "X"])
is a list of the tickers of the stocks we are adding to the watchlist.
How do we know which value to use for thestockTickers
? The docs for the mutation field has the type[String]
which is a list of one or more strings hence we provide the input as["MSFT", "MMM", "X"]
.- The rest of the call is composed of the payload object. This is where we specify the data we want the server to return after we've performed the mutation. These lines come from the
addToWatchlist
docs type, which two possible return fields:"success"
true
errors
"abc123"
In this example, we return the two fields (success
anderrors
).
When we run the mutation, this is the response:
{
"data": {
"addToWatchlist": {
"success": true,
"errors": null
}
}
}
That's it! The stocks have now been added to the watchlist.
One final note: when you pass multiple fields in an input object, the syntax can get unwieldy. Moving the fields into a variable can help. Here's how you could rewrite the original mutation using a variable:
mutation($watchlistId: ID!, $stockTickers: [String]) {
addToWatchlist(watchlistId: $watchlistId, stockTickers: $stockTickers) {
success
errors
}
}
variables {
"watchlistId": "69",
"stockTickers": ["MSFT", "MMM", "X"]
}
Further reading
There is a lot more you can do when forming GraphQL calls. Here are some places to look next: