NAV Navbar

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:

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

Endpoints List

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.

GraphiQL

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 List

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.

img.png

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.

Autocomplete

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.

Mutations

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.

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:

UI

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.