GraphQL API Overview
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
Find more information about GraphQL basics here
The Fibery GraphQL API provides a way to integrate Fibery with your external systems and automate routine tasks. It’s capable of performing most of the tasks at hand:
- Read, create, update and delete entities in databases
- Execute a variety of additional actions
- Work with rich text fields
Non-ASCII or non-English characters in field or database names will be transliterated to English.
Getting started
Every Fibery space has its own GraphQL end-point which can be found at https://YOUR_ACCOUNT.fibery.io/api/graphql/space/YOUR_SPACE
or the list of all your space's end-points can be found at https://YOUR_ACCOUNT.fibery.io/api/graphql
By opening space's end-point in your browser you will find a graphical interactive in-browser GraphQL IDE. Here you can explorer space's GraphQL documentation and execute your GraphQL queries.
Both reading and editing data means sending a POST JSON request to https://YOUR_ACCOUNT.fibery.io/api/graphql/space/YOUR_SPACE
endpoint from your code.
Authentication
cURL:
# To authenticate set the Authorization header in this way:
curl -X POST "https://YOUR_ACCOUNT.fibery.io/api/graphql/space/YOUR_SPACE" \
-H "Authorization: Token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d "{me{email}}"
JavaScript:
fetch(YOUR_SPACE_ENDPOINT, {
method: 'POST',
body: JSON.stringify({query: "{me{email}}"}),
headers: {
'Content-Type': `application/json`,
'Authorization': `Token ${YOUR_TOKEN}`,
}
});
Make sure to replace your account name, space name and token with the actual values
Authentication is required for executing graphql queries from your code or GraphQL IDEs of your choice. Fibery API uses simple token-based authentication. That means you need to pass your API token with every request. This token could be the same for all requests, there is no need to generate a new one each time. Your API token carries the same privileges as your user, so be sure to keep it secret.
Generate a new token
The following endpoints are available to manage access tokens:
GET /api/tokens — lists all access tokens that were given to current user POST /api/tokens — creates new token for current user DELETE /api/tokens/:token_id — deletes token by id
When accessing those endpoints you need to be authenticated 🤷. At the moment, the only option is via a cookie. So the easiest way to acquire a token is to log in into your Fibery workspace and execute the following code in the browser console:
fetch(`https://${window.location.host}/api/tokens`, { method: 'POST' }) .then(res => res.json()) .then(obj => console.log("Your API token:", obj.value));
The browser will set the cookie automatically.
Manage existing tokens
To retrieve your API keys, send a GET request:
fetch(`https://${window.location.host}/api/tokens`, { method: 'GET' }) .then(res => res.json()) .then(apiKeys => console.log(apiKeys));
To delete an API key, send a DELETE request:
fetch(`https://${window.location.host}/api/tokens/token_id`, { method: 'DELETE' });
Queries
The list of entities can be retrieved from the database by using find
query which is defined for every
database for each space. For example findBugs
, findEmployees
.
Find more information about GraphQL queries here.
List of entities
curl -X POST https://my-space.fibery.io/api/graphql/space/Software_Development \ 2 ↵ alehmr@MacBook-Pro-Oleg-2
-H "Authorization: Token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{findBugs{id,name,state{name}}}"}'
import {config} from 'dotenv';
config();
import fetch from 'node-fetch';
const YOUR_SPACE_ENDPOINT = `https://my-space.fibery.io/api/graphql/space/Software_Development`;
const YOUR_TOKEN = process.env[`YOUR_TOKEN`];
(async () => {
const query = `{findBugs{id,name,state{name}}}`;
const response = await fetch(YOUR_SPACE_ENDPOINT, {
method: 'POST',
body: JSON.stringify({query}),
headers: {
'Content-Type': `application/json`,
'Authorization': `Token ${YOUR_TOKEN}`,
}
});
const result = await response.json();
console.log(JSON.stringify(result));
})();
Output
{
"data": {
"findBugs": [
{
"id": "b3814a20-e261-11e8-80ea-7f915d8486b5",
"name": "🐞 The first ever bug",
"state": {
"name": "Done"
}
},
{
"id": "fa39df10-912b-11eb-a0bf-cb515797cdf8",
"name": "Nasty bug from the trenches",
"state": {
"name": "To Do"
}
}
]
}
}
Use find without arguments to retrieve all records, but note that there is a limit of 100 by default, so use offset and limit to retrieve data page by page if it is required.
{ findBugs { id name state { name } } }
Use Docs->Query section to explore possible fields selection.
Filtering
Filtering by native fields
{
findBugs(
name: {contains: "disaster"}
state: {name: {in: ["Open", "Done"]}}
)
{
id
name
state {
name
}
}
}
Filtering by one-to-one fields
{
findBugs(
state: {name: {in: ["Open", "Done"]}}
)
{
id
name
state {
name
}
}
}
Filtering by many fields (AND statement is used)
{
findBugs(
release:{ startDate: {isNull: false}, name: {contains: "1.0"}},
state: {name: {in: ["Open", "Done"]}}
)
{
id
name
release{
startDate
name
}
state {
name
}
}
}
There is a variety of filtering capabilities for each database including filtering by one-to-one fields or inner lists content. Filters can be applied by providing filtering arguments for find queries.
String filtering operators
is: String isNot: String contains: String notContains: String greater: String greaterOrEquals: String less: String lessOrEquals: String in: [String] notIn: [String] isNull: Boolean
Int filtering operators
is: Int isNot: Int greater: Int greaterOrEquals: Int less: Int lessOrEquals: Int in: [Int] notIn: [Int] isNull: Boolean
Float filtering operators
is: Float isNot: Float greater: Float greaterOrEquals: Float less: Float lessOrEquals: Float in: [Float] notIn: [Float] isNull: Boolean
Boolean filtering operators
is: Boolean isNull: Boolean
ID filtering operators
is: ID isNot: ID in: [ID] notIn: [ID] isNull: Boolean
The available filter operators can be explored via autocomplete during creating query in GraphiQL.
Filtering by inner lists
Filtering by inner lists:
{
findReleases(
bugs:{
containsAny: [
{state:{name:{is:"Open"}}}
{effort:{greater:0}}
]
})
{
name
bugs{
name
state{
name
}
}
}
}
The database can be filtered by content of inner list, but it is a bit different from filtering by one-to-one or native fields. For example the query to the left allows to find releases which contains bugs in "Open" state or with effort greater than 0.
The following operators can be used for filtering database by inner list:
isEmpty: Boolean contains: [InnerListDbFilter] // AND statement containsAny: [InnerListDbFilter] // OR statement notContains: [InnerListDbFilter] // AND statement notContainsAny: [InnerListDbFilter] // OR statement
Filtering inner lists
Sample of filtering inner list
{
findReleases
{
name
bugs(state:{name:{is: "To Do"}}){
name
state{
name
}
}
}
}
The inner list of database can be filtered in the same way the database filtered. For example if you want to show only "To Do" bugs for releases:
Sorting
{
findReleases(
bugs: {isEmpty:false}
orderBy: {
releaseDate: DESC
}
)
{
name
releaseDate
bugs(
orderBy: {
name: ASC
createdBy: {email: ASC}
}
)
{
name
state {
name
}
}
}
}
The database or content of inner lists of the database can be sorted using orderBy
argument which can be applied
for native fields or one-to-one properties.
Rich fields and comments
{
findBugs{
name
stepsToReproduce{
text
}
comments{
md
}
}
}
Output
{
"data": {
"findBugs": [
{
"name": "🐞 The first ever bug",
"stepsToReproduce": {
"text": "Open up the Mark II\n\n\nCheck all the relays one-by-one\n\n\nFind a little naughty moth"
},
"comments": [
{
"md": "Please fix ASAP"
}
]
}
]
}
}
You can download content of rich fields or comments in four formats: jsonString
, text
, md
, html
.
Paging and limits
Retrieve first page (limit is 3)
{
findBugs(limit:3){
name
}
}
Retrieve second page (limit: 3, offset: 3). Retrieve only if first page size equals to 3
{
findBugs(limit:3,offset:3){
name
}
}
By default, find database query returns 100 records. The default can be changed by setting limit
argument.
Use offset
argument to retrieve next page if the current page contains 100 records (or equals to limit value).
Aliases
Using aliases:
{
todo: findBugs(state:{name:{is:"To Do"}}){
name
state{
name
}
}
done: findBugs(state:{name:{is:"Done"}}){
name
state{
name
}
}
}
Output:
{
"data": {
"todo": [
{
"name": "Nasty bug from the trenches",
"state": {
"name": "To Do"
}
}
],
"done": [
{
"name": "🐞 The first ever bug",
"state": {
"name": "Done"
}
}
]
}
}
GraphQL aliases can be used for find query if you would like to get separated results or as alternative to OR statement.
Mutations
Example of creating entity and appending content to its rich field
mutation{
bugs{
create(name: "New Bug"){message}
appendContentToStepsToReproduce(value: "TBD"){message}
}
}
Output:
{
"data": {
"bugs": {
"create": {
"message": "Create: 1 Bug added"
},
"appendContentToStepsToReproduce": {
"message": "Append content to Steps To Reproduce: Steps To Reproduce updated for 1 Bug"
}
}
}
}
Example of closing bugs with name "New Bug" and notifying assignees using text template
mutation{
bugs(name:{is:"New Bug"}){
moveToFinalState{message}
notifyAssignees(subject:"{{Name}} bug was closed"){message}
}
}
Output
{
"data": {
"bugs": {
"moveToFinalState": {
"message": "Move to final state executed"
},
"notifyAssignees": {
"message": "Notify Assignees: 0 notifications sent"
}
}
}
}
Mutations should be used to modify database content. We implemented a set of operations based on automation's actions. These operations can be performed one by one for created or filtered database's entities. In other words multiple actions can be executed for added entities or selected by filtering arguments.
Find below the syntax of mutation. Filter argument to select entities for modification is the same as defined for find
query. Every action has the result. It is a message
about action execution, affected entities
.
mutation { database(filter) { action1(args){message,entities{id,type}} action2(args){message,entities{id,type}} ... actionN(args){message,entities{id,type}} } }
The operations can not be duplicated inside mutation. Batch alternative of action can be used in case multiple arguments
supposed to
be performed for the same action. Batch action data
argument is an array of actions' arguments.
#Single command action(arguments){message} #Batch command actionBatch(data:[arguments]){message}
The available database actions or operations can be found in Docs -> Mutation.
Read more about GraphQL Mutations here.
Find below some of action's usage explained.
Create
Create one release and link all bugs in "To Do" state
mutation {
releases {
create(
name: "Urgent"
bugs: {
state:{ name: {is:"To Do"}}
}
)
{
entities {
id
type
}
}
}
}
Output
{
"data": {
"releases": {
"create": {
"entities": [
{
"id": "13114b4a-fa4b-400a-8da3-257cef0e22f5",
"type": "Software Development/Release"
}
]
}
}
}
}
Create several bugs in different states, assign to current user and add comment
mutation {
bugs{
createBatch(data:[
{name: "Login failure", state:{name:{is:"In Progress"}}}
{name: "Design is br0ken", state:{name:{is:"To Do"}}}
]){message}
assignToMe{message}
addComment(value:"I will fix this bug ASAP"){message}
}
}
Output
{
"data": {
"bugs": {
"createBatch": {
"message": "Create: 2 Bugs added"
},
"assignToMe": {
"message": "Assign to me: User(s) assigned to 2 Bugs"
},
"addComment": {
"message": "Add comment: Comments added to 2 Bugs"
}
}
}
}
New entities can be added to database using create
or createBatch
. The arguments of these actions native
database fields. One-to-one fields and inner list items also can be linked.
Update
Change effort and release of bugs in "To Do" with effort equals to 15
mutation {
bugs(effort:{is: 15}, state:{name:{is:"To Do"}}){
update(
release:{name:{is:"1.0"}}
effort: 10
){entities{id}}
countOfEntities #count of found bugs
}
}
Output
{
"data": {
"bugs": {
"update": {
"entities": [
{
"id": "fa39df10-912b-11eb-a0bf-cb515797cdf8"
},
{
"id": "f42d7db6-6c6f-429d-bf5c-c60b0d9d072d"
},
{
"id": "fb670021-4fe6-4489-86b4-8a17e60e9227"
}
]
},
"countOfEntities": 3
}
}
}
Use update action if it is required to modify some fields or relations.
The database mutation filter argument for selection entities should be provided for actions like update
.
Delete
Verify entities to be deleted using listEntities or countOfEntities
mutation {
bugs(state:{name:{is:"Done"}}){
listEntities{id}
countOfEntities
}
}
Proceed with delete
mutation {
bugs(state:{name:{is:"Done"}}){
delete{message}
}
}
Output
{
"data": {
"bugs": {
"delete": {
"message": "Delete: 4 Bugs deleted"
}
}
}
}
Use delete
action to remove bugs which satisfy provided criterion.
Be careful with this command and verify that only required entities are going to be deleted by using find command before
execute delete
.
Create and link relations
Mind using GraphQL aliases to have convenient names in output
mutation {
releases{
release: create(name: "3.0.1"){entities{id,type}}
tasks: addTasksItemBatch(data:[
{name:"Do design"}
{name:"Do development"}
]){entities{id,type}}
bugs: addBugsItemBatch(data:[
{name:"Fix design"}
{name:"Fix development"}
{name:"Remove code"}
]){entities{id,type}}
}
}
Output
{
"data": {
"releases": {
"release": {
"entities": [
{
"id": "c09b246b-3e47-49fa-9a28-94438dc640c3",
"type": "Software Development/Release"
}
]
},
"tasks": {
"entities": [
{
"id": "e9ddd110-e8ac-11ec-a77e-d74e2f66036c",
"type": "Software Development/Task"
},
{
"id": "e9f095c0-e8ac-11ec-a77e-d74e2f66036c",
"type": "Software Development/Task"
}
]
},
"bugs": {
"entities": [
{
"id": "ea19a190-e8ac-11ec-a77e-d74e2f66036c",
"type": "Software Development/bug"
},
{
"id": "ea290ae0-e8ac-11ec-a77e-d74e2f66036c",
"type": "Software Development/bug"
},
{
"id": "ea3a48f0-e8ac-11ec-a77e-d74e2f66036c",
"type": "Software Development/bug"
}
]
}
}
}
}
Related one-to-one, one-to-many or many-to-many entities can be created using AddRelation
or AddRelationItem
actions.
Rich fields
mutation {
bugs(assignees:{containsAny:{email:{contains:"oleg"}}}){
appendContentToStepsToReproduce(value:"Line for state: {{State.Name}}"){message}
attachPdfUsingTemplate(value:"{{CurrentUser.Email}} is an author"){message}
}
}
There are some additional actions which may be handy in modifying rich fields and generating PDFs.
Rich fields can be modified by actions started from appendContent
, overwriteContent
, prependContent
.
attachPdfUsingTemplate
can be used for generating PDF files.
The templating capabilities can be used in the same way it is available in automations. Find more information about templates here.
This is the result of the mutations from the right:
Unlink/Link relations
mutation {
bugs(release:{id:{isNull:false}}){
unlinkRelease{message}
}
}
Output
{
"data": {
"bugs": {
"unlinkRelease": {
"message": "Unlink Release: Release unlinked from 12 Bugs"
}
}
}
}
Link bugs with effort greater than 5 to empty release
mutation {
releases(name:{is:"3.0"}){
linkBugs(effort:{greater:5}){message}
}
}
Unlink bugs with effort greater than 15 from release
mutation {
releases(bugs:{isEmpty:false}){
unlinkBugs(effort:{greaterOrEquals:15}){message}
}
}
Relations can be linked or unlinked using link<Relation Name>
or unlink<Relation Name>
actions.
Delete relations
Delete release from bugs with release
mutation {
bugs(release:{id:{isNull:false}}){
deleteRelease{message}
}
}
Delete bug items in "Done" state from selected release
mutation {
releases(bugs: {isEmpty:false}){
deleteBugs(state:{name:{is: "Done"}}){message}
}
}
Relations can be removed from system by executing delete<Relation Name>
.
Again be careful with this operation since the data will be erased.
Send notifications
mutation {
bugs(release:{id:{isNull:false}}){
notifyCreatedBy(subject: "Please take a look"){message}
notifyAssignees(
subject: "Fix ASAP"
message: "Fix bug {{Name}} ASAP"
){message}
notifyUsers(
to:{email:{contains:"oleg"}}
message: "Waiting for fix..."
){message}
}
}
The build-in notifications can be sent using NotifyUsers
and other actions related to notifications
like it is done for automatons. Text templates are supported.
Add file from URL
mutation {
bugs(release:{id:{isNull:false}}){
addFileFromUrl(
name: "Super File"
url:"https://api.fibery.io/images/logo.svg"
){message}
}
}
The file can be attached to entities by using addFileFromUrl
action.
How to avoid timeouts
Execute mutation with limit for first time:
mutation{
stories(limit:10, sprint:{id:{isNull:true}}){
update(sprint:{limit:1, orderBy:{dates:{start:ASC}}}){message}
countOfEntities
}
}
Result contains 10 entities. So we will need to execute it again.
{
"data": {
"stories": {
"update": {
"message": "Update: 10 Stories updated"
},
"countOfEntities": 10
}
}
}
Execute the same mutation again with the same limit:
mutation{
stories(limit:10, sprint:{id:{isNull:true}}){
update(sprint:{limit:1, orderBy:{dates:{start:ASC}}}){message}
countOfEntities
}
}
Looks like no need to execute again since count of processed less than limit (10)
{
"data": {
"stories": {
"update": {
"message": "Update: 4 Stories updated"
},
"countOfEntities": 4
}
}
}
Use limit
, offset
and orderBy
in case of executing huge operational sets since it is required a lot of
processing time and timeouts may be a problem.
Please find an example on how to set sprint to stories by execution of the mutation with limit several times to avoid
facing timeout.
The main idea to update limited set of database until it has records which should be updated. In our case we are
updating stories until some of them don't have sprint.
Request limits
To ensure system stability and consistent user experience, our API is rate-limited.
Rate-limited requests will return a "Too Many Requests" error (HTTP response status 429). The rate limit for incoming requests is 3 requests per second per token.