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
- Our API is not a REST API, instead we use the GraphQL specification.
- For connecting to the API you need a token. Tokens can be created manually, or through OpenID Connect.
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 — It's possible to create a token manually using the web application.
- Open Connect Id — This allows users to grant your application access to our API using an online flow integrated into your web application.
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:
- Log in as an accountant user in the accountant module.
- In the top right corner, click on your profile and select "integrations".
- Click on the button "New token".
- Give your token a descriptive name.
- Select the scopes you'd like to grant your token.
- Click the "save" button.
- 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:
- An intro on OpenID Connect - http://openid.net/connect/
- A list of client libraries - http://openid.net/developers/libraries/
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:
- Client prepares an Authentication Request containing the desired request parameters.
- Client sends the request to the Authorization Server.
- Authorization Server Authenticates the End-User.
- Authorization Server obtains End-User Consent/Authorization.
- Authorization Server sends the End-User back to the Client with an Authorization Code.
- Client requests a response using the Authorization Code at the Token Endpoint.
- Client receives a response that contains an ID Token and Access Token in the response body.
- 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:
Authenticate users and get additional information about them such as their email or name (Type in the table below: OpenID)
Request granular access control to the API. (Type: API)
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 | 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
- scalars
- enums
- interfaces
- objects unions
- input objects.
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.
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:
- AIR:
- startPeriod
- endPeriod
- companyNumber
- Processing:
- startPeriod
- endPeriod
- companyNumber
- invoiceType
- worklist
Parameters:
- startPeriod: An RFC-3339 encoded date string. This field is required.
- endPeriod: An RFC-3339 encoded date string. This field is required.
- companyNumber: Company number of the administration whose statistics you want to query. This field is optional. (e.g. BE0123456789)
- invoicetype: Filter statistics based on invoicetype. The invoicetype has to be one of the following: "SALE" or "PURCHASE". This field is optional.
- worklist: If true, query statistics for documents processed through worklist. If false, query statistics for documents not processed through worklist. This field is optional. By default, statistics for all documents are returned.
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}).