API Quickstart
Get up and running with the Twisp GraphQL API.
Let's start interacting with your ledger via the GraphQL API.
To keep things simple, we'll use the built-in GraphQL IDE in the console. Of course, you can make these same API calls from any authenticated client.
Insert sample data
Because the database is still empty, to start exploring the query capabilities we first need to seed it with sample data.
1. Create an account record for a customer
The first record we'll insert is a sample account in the accounts
table.
Paste the following GraphQL mutation into the query editor and execute the query with the "Execute Query" button (or press Ctrl-Enter
). If the query executed correctly, you should see a response that looks similar to the JSON document below.
mutation AddAccount {
accounts {
insert(input:{
document:{
account_id:"uuid('9dd984db-78d8-420f-9380-80e3cf36fe75')",
customer_name:"'Giovanni Medici'"
}
}) {
record {
account_id
balance
customer_name
}
}
}
}
{
"data": {
"accounts": {
"insert": {
"record": {
"account_id": "9dd984db-78d8-420f-9380-80e3cf36fe75",
"balance": 0,
"customer_name": "Giovanni Medici"
}
}
}
}
}
The values assigned to fields in the document
parameter of the insert
operation are of type Expression
, which means that they represent a Common Expression Language (CEL) expression wrapped in double quotes "
. This can be a bit confusing for first-time users, as they appear to be formatted as strings. You'll notice that the raw string Giovanni Medici
was wrapped in single quotes '
to indicate that it represents a raw UTF-8 string.
2. Insert sample transactions using variables
GraphQL operations also accept query variables, which you can define in JSON format in the "Query Variables" pane of the GraphQL interpreter. To expand the pane, click on the text "Query Variables".
The CEL evaluation engine is granted access to these variables inside of a context.vars
object, which means that you can utilize the variable values anywhere in a GraphQL query that accepts an Expression
type.
In this step, we'll create 3 transactions associated with the account created in step 1.
Define the query variables JSON object using the account_id
for the account created in step 1 and a posted_on
timestamp in RFC3339-compliant format.
{
"account_id":"9dd984db-78d8-420f-9380-80e3cf36fe75",
"posted_on":"2022-04-27T10:50:00.000Z"
}
Now you can write the mutation to insert a transaction
utilizing the values defined in the query variables. Note that the uuid()
and timestamp()
functions are used to coerce the values to the appropriate type.
mutation InsertTransaction {
transactions {
insert(input:{
document:{
transaction_id:"uuid.New()",
account_id:"uuid(context.vars.account_id)"
amount:"125",
posted_on:"timestamp(context.vars.posted_on)"
}
}) {
record {
transaction_id
account_id
amount
posted_on
}
}
}
}
{
"data": {
"transactions": {
"insert": {
"record": {
"account_id":"9dd984db-78d8-420f-9380-80e3cf36fe75",
"amount": 125,
"posted_on": "2022-04-27T10:50:00Z",
"transaction_id": "30f2283b-c44f-4781-93c8-a70c85bd5e16"
}
}
}
}
}
Execute this mutation and then insert two more transactions with amounts 50
and 75
.
While writing a GraphQL query or mutation in the query editor, you can use the autocomplete command (Ctrl-Space
) to ensure that you are always writing a syntactically correct query. Additionally, the built-in GraphQL docs (button at top right of GraphQL pane) provides an in-editor reference to the full GraphQL schema.
Execute a Query
Querying the data in your Twisp ledgers is done via GraphQL. Because Twisp generates a GraphQL schema custom-tailored to the ledger resources you declare, you get instant access to all the type-safety and developer ergonomics that comes with a GraphQL API.
In this step, we'll write some simple queries to read the data inserted in the previous page.
1. Query an account record using the ID index
The simplest query we can write is one that will fetch a single record from a ledger.
In the following example, we'll fetch the account record created in the previous page by querying the account_id
index and providing the UUID for Giovanni's account.
Copy the below query into your GraphQL editor, replace the UUID with the account ID from your database, and execute the query. You should see a response with the account information.
query GetAccount {
accounts(
index:account_id,
where:{ partition:["9dd984db-78d8-420f-9380-80e3cf36fe75"] }
) {
account_id
balance
customer_name
}
}
{
"data": {
"accounts": [
{
"account_id": "9dd984db-78d8-420f-9380-80e3cf36fe75",
"balance": 0,
"customer_name": "Giovanni Medici"
}
]
}
}
Every query must declare which index to query against. The name of the index must match one of the indices defined on the ledger database.
2. Expand the query to view customer's transactions
One of the most powerful features of GraphQL is its ability to combine related records into a single, nested document. Because we declared a one-to-many relationship between the accounts
and transactions
ledgers using the Join
property in the template file, within a query against accounts
we can also fetch the related transactions
as a nested document.
Edit your GetAccount
query to return the transactions
field for an account, returning the amount
and posted_on
fields.
When you execute your query, you should see a response with all 3 transactions associated with the account.
query GetAccount {
accounts(
index:account_id,
where:{ partition:["9dd984db-78d8-420f-9380-80e3cf36fe75"] }
) {
account_id
balance
customer_name
transactions {
amount
posted_on
}
}
}
{
"data": {
"accounts": [
{
"account_id": "9dd984db-78d8-420f-9380-80e3cf36fe75",
"balance": 0,
"customer_name": "Giovanni Medici",
"transactions": [
{
"amount": 50,
"posted_on": "2022-04-27T10:50:00Z"
},
{
"amount": 75,
"posted_on": "2022-04-27T10:50:00Z"
},
{
"amount": 125,
"posted_on": "2022-04-27T10:50:00Z"
}
]
}
]
}
}
Explore record history
Ledgers in Twisp are append-only, which means that any changes to a record do not overwrite existing data. The API allows for updates to existing records, but the previous values are stored as a change log.
This approach satisfies the immutability constraint required for accurate and trustworthy storage of financial data while still providing a full CRUD interface for ease of development.
Let's "update" a record and inspect the change sequence made available through the history
field available on every ledger record.
1. Change a transaction and query the change history
For the sake of this demonstration, let us assume that one of the transactions was initially inserted with the wrong posted_on
date.
Pick a transaction ID to use and update the timestamp using the following GraphQL mutation.
You can use the GetAccount
query above to fetch the transaction_id
for transactions associated with the singular account.
mutation UpdateTransaction {
transactions {
update(input:{
index:transaction_id,
where:{ partition:["<transaction ID>"] },
document:{
posted_on:"timestamp('2022-04-28T12:10:00.000Z')"
}
}) {
records {
transaction_id
amount
posted_on
}
}
}
}
{
"data": {
"transactions": {
"update": {
"records": [
{
"amount": 125,
"posted_on": "2022-04-28T12:10:00Z",
"transaction_id": "<transaction id>"
}
]
}
}
}
}
With that update made, you can see the history of changes by querying the history
field of the transaction.
query GetTransaction {
transactions(
index:transaction_id,
where:{ partition: ["<transaction ID>"] }
) {
transaction_id
posted_on
history {
seq
posted_on
}
}
}
{
"data": {
"transactions": [
{
"history": [
{
"posted_on": "2022-04-28T12:10:00Z",
"seq": 2
},
{
"posted_on": "2022-04-27T10:50:00Z",
"seq": 1
}
],
"posted_on": "2022-04-28T12:10:00Z",
"transaction_id": "<transaction ID>"
}
]
}
}
The fields available to query within history
are the same fields as the transaction
schema, with the addition of a seq
(sequence) field. The seq
field is an auto-incrementing integer showing the sequence of changes that have been applied to this record.
2. Query the change history of the account balance
The history
field is useful for querying changes made through GraphQL mutations as above. Because it tracks all changes to a record, we can also use it to inspect the changes made through automatic operations defined by Triggers
.
The trigger defined in the previous page declared a DO_UPDATE
operation against the accounts
table. With each new transaction inserted, the trigger updated the account balance.
This means that we can query the history
of the account record to see how the balance changed with each transaction added:
query GetAccount {
accounts(
index:account_id,
where:{ partition:["9dd984db-78d8-420f-9380-80e3cf36fe75"] }
) {
account_id
balance
transactions {
amount
}
history {
balance
seq
}
}
}
{
"data": {
"accounts": [
{
"account_id": "9dd984db-78d8-420f-9380-80e3cf36fe75",
"balance": 250,
"history": [
{
"balance": 250,
"seq": 4
},
{
"balance": 175,
"seq": 3
},
{
"balance": 125,
"seq": 2
},
{
"balance": 0,
"seq": 1
}
],
"transactions": [
{
"amount": 125
},
{
"amount": 50
},
{
"amount": 75
}
]
}
]
}
}
The history
shows changes in reverse chronological order, while transactions
are returned in the order in which they were inserted.
🎉 Congratulations! 🎉
You've completed all of the steps in the Quickstart. You are ready to go build with your Twisp leger.
We have only scratched the surface of what Twisp is capable of in these pages, however. To continue learning, consider reading the other pages in the docs.