NAV Navbar
shell php
  • Introduction
  • Authentication
  • GraphQL
  • Recipes
  • Introduction

    Welcome to the ClearFacts Developer Documentation. We offer an open API available to all our users, partners and app developers.

    In this documentation you'll find information on how to authenticate and connect to our API as well as in depth information on the functionality it exposes.

    Technology

    Getting help

    If you have any questions regarding our api please contact our developer support via email at dev-support at clearfacts dot be.

    Using this documentation

    You can view code examples in the area to the right, and you can switch the programming language of the examples with the tabs in the top right.

    Shortcuts

    The quick links below might help you get started:

    Authentication

    To use our API you need to authenticate using a valid API token that has the right scopes.

    When using such a token you will be able to act on behalf of the ClearFacts user that created the token, or allowed for it be created.

    A token can be acquired in two ways, depending on your use case:

    Personal Access Token

    A personal access token is created manually and is typically used by offline or desktop applications.

    This means it's an easy way to get a token for talking to the API using command line tools, eg while exploring the API's capabilities.

    Personal access tokens are always exactly 80 ASCII characters (or bytes) long.

    Creating an personal access token

    To create a personal access token, you need to follow these steps:

    1. Log in as an accountant user in the accountant module.
    2. In the top right corner, click on your profile and select "integrations".
    3. Click on the button "New token".
    4. Give your token a descriptive name.
    5. Select the scopes you'd like to grant your token.
    6. Click the "save" button.
    7. The newly created token is displayed in a message box. Make sure to copy it, as it is only displayed once.

    Testing your token on the command line

    To get information on the user of the provided token

    curl -H "Authorization: Bearer <token>" -X GET https://login.clearfacts.be/oauth2-server/userinfo
    
    // todo
    

    You can use a simple CURL request to test your token and information about the user for whom the token was created.

    This will return:

    Each entry in the returned JSON structure is called a claim.
    You'll find more information about this in the chapter on Scopes and Claims.

    {
      "sub":"user@domain.com",
      "username":"user@domain.com",
      "email":"user@domain.com"
    }
    

    OpenId Connect

    If you're an app developer and want to enable your users to grant your application access to the API, implementing OpenId Connect (OIDC) is the way to go.

    OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the user based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the user in an interoperable and REST-like manner.

    Building a secure OpenID Connect client is difficult, so you should use an existing and proven implementation in the language of your choice.

    For more information check these links:

    Application registration

    In order to use OpenID Connect, your app needs to be registered with ClearFacts. Currently this is a manual process, so please contact us via email or phone to set things up.

    You will need to supply us with your OIDC redirect URI and receive a client identifier and secret after your registration has been completed.

    Auto Discovery

    To get the OpenID Connect configuration:

    curl -X GET https://login.clearfacts.be/.well-known/openid-configuration
    

    OIDC has a way to configure clients automatically through a Discovery Document URL.
    The URL for this is defined by the OIDC spec and can be inspected manually if you'd like to see which features are supported in our implementation.

    {
        "issuer": "https://login.clearfacts.be",
        "authorization_endpoint": "https://login.clearfacts.be/oauth2-server/authorize",
        "token_endpoint": "https://login.clearfacts.be/oauth2-server/token",
        "userinfo_endpoint": "https://login.clearfacts.be/oauth2-server/userinfo",
        "jwks_uri": "https://login.clearfacts.be/oauth2-server/jwks.json",
        "response_types_supported": ["code", "token"],
        "subject_types_supported": ["public"],
        "id_token_signing_alg_values_supported": ["RS256"],
        "scopes_supported": ["openid", "profile", "email"]
    }
    

    https://login.clearfacts.be/.well-known/openid-configuration

    Authorisation code flow

    OpenID connect (or OAuth2) supports several flows to obtain an access token.

    Currently we only support the "Authorization Code Flow". This is the most commonly used and most secure flow, but it cannot be used by client-side only apps like javascript apps without a back-end.

    The Authorization Code Flow goes through the following steps:

    1. Client prepares an Authentication Request containing the desired request parameters.
    2. Client sends the request to the Authorization Server.
    3. Authorization Server Authenticates the End-User.
    4. Authorization Server obtains End-User Consent/Authorization.
    5. Authorization Server sends the End-User back to the Client with an Authorization Code.
    6. Client requests a response using the Authorization Code at the Token Endpoint.
    7. Client receives a response that contains an ID Token and Access Token in the response body.
    8. Client validates the ID token and retrieves the End-User's Subject Identifier.

    It is beyond the scope of this document to get into more detail, but you can easily test this flow on openidconnect.net.

    OIDC Token

    The token that you'll receive using OIDC is not a random byte string, but a signed JWT token that can be decoded (see: jwt.io). The JWT token contains the basic claims (sub, username, email) as well as the 80 character access token like you would get when creating a personal access token.

    Both the longer JWT token and the 80 character token can be used in the Authorization: Bearer <token> header.
    This means you can store either one depending on your use case.

    Creating multiple tokens

    You can create multiple tokens for a user/client_identifier combination.

    This is useful if your applications supports one workflow for sign-in and only requires basic user information. Another use case may require access to a user's archive. Using multiple tokens, your application can perform the authorization flow for each use case, requesting only the scopes needed.

    Additionally, you can create multiple tokens per unique user/client_identifier/requested_scopes combination, but the number of active tokens is limited to 10. If your application requests enough tokens to go over the limit, least recently used tokens with the same scopes being requested will stop working.

    Scopes and claims

    The OAuth2 protocol is a delegated authorization mechanism, where a client application requests access to resources controlled by the user (the resource owner) and hosted by an API (the resource server) and the authorization server typically issues the client application a more restricted set of credentials than those of the user.

    You can use scopes to:

    Scopes

    This is a list of all the supported scopes in our API:

    Scope Type Description
    openid OpenID This is a technical scope, it should always be requested when using the OpenID Connect flow, it also allows access to a unique identification of the user (the sub claim)
    email OpenID Access to a users emailaddress (via the /userinfo endpoint)
    profile OpenID Access to a users name, locale
    read_administrations API Allows the client to query the list of administrations the user has access to
    upload_document API Allows the client to upload documents and invoices for the administrations the user has access to
    archive API A complete archive scope that grants access to both archive_read and archive_actions
    archive_read API Allows the client to query the list of available archive categories
    archive_actions API Allows the client to upload files to a specific administration's archive category
    associate_read API Allows the client to query all information associated with associates and groups
    associate_actions API Allows the client to modify the information of associates, as well as creating new ones
    statistics API Allows the client to query office statistics
    contact_read API Allows the client to query customers for the administration

    Each token, be it a personal access token or a token acquired through OpenID Connect, should have been granted one or more scopes for it to be of any use.

    If you are using OpenID Connect, you are required to request the openid scope.

    Claims

    OpenID Connect specifies a set of standard claims. These claims are user attributes and are intended to provide the client with user details such as email, name and locale.

    The basic claim returned for the openid scope is the sub claim, which uniquely identifies the user (iss, aud, exp, iat and at_hash claims will also be present in the id_token).

    Requesting individual claims is not supported in our OIDC implementation. Access to claims is regulated through scopes. Applications can ask for additional scopes, separated by spaces, to request more information about the user.

    The /userinfo endpoint will return a json object with a few "OpenID Claims", depending on the scopes that have been granted to your token.

    Claim Type Required scope Description
    username string openid The username as used in ClearFacts.
    sub string openid Subject-identifier for the user in the ClearFacts. It contains the same value as the 'sub' claim.
    name string profile User's full name in displayable form including all name parts, possibly including titles and suffixes, ordered according to the user's locale and preferences.
    given_name string profile Given name(s) or first name(s) of the user.
    family_name string profile Surname(s) or last name(s) of the user.
    preferred_username string profile Shorthand name by which the user wishes to be referred to.
    email string email The user's e-mail address. Note: in most cases the username equals the email address.
    locale string profile The user's locale, represented as a BCP47 [RFC5646] language tag. This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. For example, nl-BE.

    Although claims are a part of the OpenID Connect specification, it's still possible to request the /userinfo with a personal access token with the right scopes.

    Granting and revoking scopes

    Personal Access Token In case of a personal access token, the user can select which scopes to grant. It's possible to grant or revoke scopes later on by editing or deleting the personal access token in the application.

    OpenID Connect Token When using OpenID Connect, tokens are issued to the client application any time the client application requests a token and the users approves. This process is typically hidden from the user and this it's not possibe to change the scopes for any existing token.

    If a user wants to revoke rights from a client application he will need to remove the client application from within ClearFacts. At that point all tokens for this client application will be revoked at once.

    GraphQL

    What is GraphQL ?

    GraphQL is a data query language developed by Facebook in 2012.

    It was published in 2015 and has since been adopted by major tech products like GitHub, YouTube or Pinterest to build the most recent versions of their APIs.

    GraphQL provides an alternative to REST and allows clients to define the structure of the data required, and exactly the same structure of the data is returned from the server. It is a strongly typed runtime which allows clients to dictate what data is needed. This avoids both the problems of over-fetching as well as under-fetching of data.

    More information on GraphQL can be found here (amongst plenty of others):

    The GraphQL Endpoint

    Where REST has several endpoints, graphQL has just one:

    https://api.clearfacts.be/graphql

    The schema

    We generate documentation from our GraphQL schema. All calls are validated and executed against the schema. Use these docs to find out what operations (mutations in GraphQL lingo) you can execute and which data you can query.

    This is the link to our schema documentation: https://assets-prod.cdn.clearfacts.be/doc/index.html

    Allowed operations

    Types

    Recipes

    In this section we provide you with a variety of examples using GraphQL queries and mutations. That way you'll quickly be familiar with the usage and common use cases of our GraphQL schema and endpoint.

    If you'd like to run the examples, you can use the GraphQL Playground on postman.com.

    https://www.postman.com/clearfacts/workspace/clearfacts-public-api/collection/27143459-ef532f3b-5631-478d-a4d6-a81779dcaacb

    Query the list of accessible administrations

    query admins {
      administrations {
        name, 
        companyNumber, 
        emails {
          type,
          emailAddress
        }
        companyType,
        address {
          streetAddress, 
          country {
            iso2,
            name
          }
        },
        accountManager
      }
    }
    

    this will return a list of administrations:

    {
      "data": {
        "administrations": [
          {
            "name": "Acme Company",
            "companyNumber": "0123123123",
            "emails": [
              {
                "type": "purchase",
                "emailAddress": "aankoop-0123123123@acme.clearfacts.be"
              },
              {
                "type": "purchase",
                "emailAddress": "achat-0123123123@acme.clearfacts.be"
              }          
            ],
            "companyType": null,
            "address": {
              "streetAddress": "Street 16",
              "country": {
                "iso2": "BE",
                "name": "Belgium"
              }
            },
            "accountManager": "account.manager@acme.be"
          }
        ]
      }
    }
    

    This first example queries all the administrations a user has access to.

    For each administration we'll fetch basic information such as name, company number and address, as well as the account manager of that administration at the accountants office. Finally we'll add the email addresses that can be used to send documents to.

    The result is a JSON structure with an array of all the administrations and their requested properties. (Please note that the result has been trimmed for brevity.)

    If you'd like to test the example above, you can use this link to the GraphQL Playground: https://www.postman.com/clearfacts/workspace/clearfacts-public-api/request/27143459-c579be61-8508-4e3d-b00b-ff6678e66e28

    Query the list of available categories for a company using arguments

    query categories {
      archiveCategories(vatnumber: "0123456789") {
        various {
          id,
          name  
        },
        permanent {
          id,
          name
        }
      }
    }
    

    will result in:

    {
      "data": {
        "archiveCategories": {
          "various": [
            {
              "id": "cb29ce32-2dca-11e8-9a15-000c2985a2fd",
              "name": "Other"
            },
            {
              "id": "cb29ccf9-2dca-11e8-9a15-000c2985a2fd",
              "name": "Credits"
            }
          ],
          "permanent": [
            {
              "id": "cb641aea-2dca-11e8-9a15-000c2985a2fd",
              "name": "Other"
            },
            {
              "id": "cb641a7b-2dca-11e8-9a15-000c2985a2fd",
              "name": "Financial"
            }
          ]
        }
      }
    }
    

    This example illustrates the use of inline query arguments.

    Let's query all the archiveCategories for an administration. To indicate which administration, we'll pass the VAT number as an argument to our query, after which we'll ask for both types of categories, with their respective members.

    Each member has an ID you can use to upload a file to the archive using the uploadArchiveFile mutation.

    If you'd like to test the example above, you can use this link to the GraphQL Playground: https://www.postman.com/clearfacts/workspace/clearfacts-public-api/request/27143459-3963daa5-639b-4ecc-abc3-624499491e6a

    Upload a sales invoice through a mutation

    The mutation:

    mutation upload($vatnumber: String!, $filename: String!, $invoicetype: InvoiceTypeArgument!) {
     uploadFile(vatnumber: $vatnumber, filename: $filename, invoicetype: $invoicetype) { 
       uuid,
       amountOfPages
     } 
    }
    

    The variables:

    {
      "vatnumber": "0123123123", 
      "filename": "test_upload.pdf", 
      "invoicetype": "SALE"
    }
    

    In this example we call a mutation with the goal of uploading a file for a specific administration. Just like our previous example, we will provide arguments to determine the administration, as well as the name and the type of the file. The uploaded file will in turn provide us with an ID, the filename and the detected amount of pages.

    The way in which we send this mutation differs a bit from regular queries. Since we have to provide a file as data, we will send our information as form-data instead of a text-based content-type like application/json or application/graphql.

    The file must be present as a parameter named "file", while the mutation and arguments can be provided as regular text-based content in the form, respectively called "query" and "variables".

    Unfortunately due to the nature of the GraphQL Playground, we can not provide you with an example, as they do not allow or provide an option for a user to enter form-data or upload files. Instead we'll give you a cURL example you can use on the command line.

    Uploading a file using cURL:

    curl -X POST \
      https://api.clearfacts.be/graphql \
      -H 'Authorization: Bearer <token>' \
      -F 'query=mutation upload($vatnumber: String!, $filename: String!, $invoicetype: InvoiceTypeArgument!) {
      uploadFile(vatnumber: $vatnumber, filename: $filename, invoicetype: $invoicetype) { 
          uuid,
          amountOfPages
        } 
      }' \
      -F 'variables={ "vatnumber": "0123123123", "filename": "test_upload.pdf", "invoicetype": "SALE"}' \
      -F file=@/home/user/Documents/example.pdf
    

    Creating an associate with associate groups

    query {
        associateGroups { 
            id, 
            name, 
            email 
        } 
    }
    

    returns the following list:

    {
        "data": {
            "associateGroups": [
                {
                    "id": "16d5379f-30cb-11e8-987b-000c2985a2fd",
                    "name": "Group 1",
                    "email": "group1@clearfacts.be"
                }
            ]
        }
    }
    

    In this example we create an associate and assign him to an associate group. The first step of the process is to get a list available associate groups so we can figure out the ID of the group the associate to add to.

    In the example we'll use the first group returned.

    mutation add($associate: AddAssociateArgument!) {
        addAssociate(associate: $associate) {
            id,
            firstName,
            lastName,
            email,
            associateGroups {
                id,
                name,
                email
            },
            plainPassword
        }
    }
    
    {
        "associate": {
            "firstName": "Test",
            "lastName": "Client",
            "email": "test@clearfacts.be",
            "type": "ADMIN",
            "associateGroups": [
                {
                    "id": "16d5379f-30cb-11e8-987b-000c2985a2fd"
                }
            ],
            "active": true,
            "language": "nl_BE",
            "sendActivationMail": false
        }
    }
    

    will return the properties of the new associate together with a generated password

    {
        "data": {
            "addAssociate": {
                "id": "198772ef-d095-4859-9328-dc47950853b9",
                "firstName": "Test",
                "lastName": "Client",
                "email": "test@clearfacts.be",
                "associateGroups": [
                    {
                      "id": "16d5379f-30cb-11e8-987b-000c2985a2fd",
                      "name": "Group 1",
                      "email": "group1@clearfacts.be"
                    }
                ],
                "plainPassword": "ZE3qa2vqizu4"
            }
        }
    }
    

    Upon completion of the query, the associate has been created and he will be able to access the system with the generated password.

    Querying an associate and editing it

    query {
        associates {
            id,
            firstName,
            lastName,
            email
        }
    }
    

    will result in

    {
        "data": {
            "associates": [
                {
                    "id": "198772ef-d095-4859-9328-dc47950853b9",
                    "firstName": "Test",
                    "lastName": "Client",
                    "email": "test@clearfacts.be"
                }
            ]
        }
    }
    

    In this use case we modify an existing associate. First, we query the existing associates to find out their ID. Upon executing this query in a real scenario you'll end up with a result set of all the associates for your accountant. For the sake of simplicity we've kept the result set for this example short and only show the one that will be of use to us.

    mutation edit($id: ID!, $associate: EditAssociateArgument!){
      editAssociate(id: $id, associate: $associate) {
        id,
        firstName,
        lastName,
        email
      }
    }
    
    {
        "id": "198772ef-d095-4859-9328-dc47950853b9",
        "associate": {
            "firstName": "Updated"
        }
    }
    

    will result in

    {
        "data": {
            "editAssociate": {
                "id": "198772ef-d095-4859-9328-dc47950853b9",
                "firstName": "Updated",
                "lastName": "Client",
                "email": "test@clearfacts.be"
            }
        }
    }
    

    Use the ID of the associate in the editAssociate mutation. It's not required to pass the complete associate object when editing. Only the parameters that you provide will get updated, the others will retain their value. Consider it 'patching' of the associate object.

    Creating or updating the customization of an app

    mutation UpdateAppInfo ($vatnumber: String!, $imageUrl : String, $emailAddress: String,$badgeText: String, $badgeTextColor: String, $badgeColor: String, $iconType: String, $iconColor: String) {
        updateAppInfo (vatnumber: $vatnumber, imageUrl: $imageUrl, emailaddress: $emailAddress, badge: {text: $badgeText, textColor: $badgeTextColor, color: $badgeColor}, icon: {type: $iconType, color: $iconColor}) {
            vatnumber,
            imageUrl,
            emailaddress,
            icon { type, color },
            badge{ text, textColor, color }
        }
    }
    
    
    
    { 
        "vatnumber": "0123123123",
        "imageUrl": "XXX", 
        "emailAddress": "test@clearfacts.be", 
        "badgeText": "NEW", 
        "badgeTextColor": "#d5a855", 
        "badgeColor": "red", 
        "iconType": "trophy", 
        "iconColor":"yellow"}
    

    will result in

    {
        "data": {
            "updateAppInfo": {
                "vatnumber": "0123123123",
                "imageUrl" : "XXX",
                "emailaddress": "test@clearfacts.be",
                "icon": {
                    "type": "trophy",
                    "color": "yellow"
                },
                "badge": {
                    "text": "NEW",
                    "textColor": "#d5a855",
                    "color": "red"
                }
            }
        }
    }
    

    You can create or update customization for the app connected to your client id. If you want an app connected to your client id, please contact support@clearfacts.be.

    If you'd like to test the example above, you can use this link to the GraphQL Playground: https://www.postman.com/clearfacts/workspace/clearfacts-public-api/request/27143459-deb675cd-896b-4b9a-9d2d-61f23b9bf03f

    Query the statistics of a company

    query statistics ($type: String!, $startPeriod: Date!, $endPeriod: Date!) {
      getCompanyStatistics(type: $type, startPeriod: $startPeriod, endPeriod: $endPeriod) {
        companyNumber
        items {
          period
          value
        }
      }
    }
    

    The variables:

    {
      "type": "AIR",
      "startPeriod": "2015-01-18",
      "endPeriod": "2018-07-18"
    }
    

    this will fetch the statistics of AIR usage:

    {
      "data": {
        "getCompanyStatistics": [
          {
            "companyNumber": "BE0123123123",
            "items": [
              {
                "period": "06/2018",
                "value": 3
              }
            ]
          }
        ]
      }
    }
    

    This example queries the statistics of AIR usage.

    The getCompanyStatistics supports multiple types, each will have a set of usable parameters.

    Types:
    Parameters:

    If you'd like to test the example above, you can use this link to the GraphQL Playground: https://www.postman.com/clearfacts/workspace/clearfacts-public-api/request/27143459-023fdc83-f192-4fc5-ab5f-17c761e0dc09

    Query an invoice document

    query doc {
      document(id: $documentId) {
        date, 
        ... on InvoiceDocument {
          type, 
          paymentState
        }
      }
    }
    

    The variables:

    {
      "documentId": "6bf58d1c-0952-44bd-8d95-1dd0307fad25", 
    }
    

    this will return details of the document:

    
    {
     "data": {
        "document": {
          "date": "2016-10-06",
          "type": "PURCHASE",
          "paymentState": "UNPAID"
        }
      }
    }
    
    

    You can get more information about a document using this query. In this case we would like to know the date, type and payment state of an invoice.

    The date field is part of the Document interface, so you can query the field like you normally would. The type and paymentState however are fields of the implementation InvoiceDocument. To query these fields we need to use inline fragments (... on InvoiceDocument {type, paymentState}).