Introduction
Welcome to the Factsys API! You can use our API to include Factsys features in your own software. This is API is a pure REST api so you can use this in whatever language you want to.
The two dots in the beginning of every URL refer to https://factsys.be
@POST("/rest/v1/ping")
Call<Ping> ping();
For every API call examples will be available in Java (we use Retrofit in our examples) and CURL (CLI) and we'll also try to include the JSON request and/or response if applicable. You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.
The REST API is available at https://factsys.be/rest/v1/xxx. The base URL is simply https://factsys.be. This one will never be repeated.
Errors
Error Codes
Error response body
{
"message": "A message with some more info on what exactly went wrong",
"code": xxx,
"errorCode": xxxx(x)
}
| Code | Error Code | Meaning |
|---|---|---|
| 400 | 1004 | Bad Request -- The action you are trying to perform is not allowed |
| 400 | 10004 | Bad Request for OAuth -- The client ID or client secret are not correct |
| 400 | 10005 | Bad Request for OAuth -- The grant type provided in the OAuth request is not valid (should be either password or refresh_token) |
| 400 | 99999 | Bad Request -- The form you submitted is not valid, see body content for details |
| 400 | na | Bad Request -- The server could not correctly interprete your request |
| 401 | na | The bearer token provided in the request is no longer valid. Request a new one with your refresh_token |
| 403 | 1002 | Forbidden -- The resource you are trying to load is not visible to the currently authenticated user |
| 404 | 1001 | Not Found -- The specified endpoint is not available. |
| 404 | 1003 | Not Found -- The resource you are trying to load is cannot be found |
| 404 | 10001 | Not Found -- Either the username or password (or combination) you provided to the OAuth are not available |
| 404 | 10003 | Not Found -- The refresh token you provided to the OAuth request cannot be found or has expired |
| 405 | na | Method Not Allowed -- You tried to access an endpoint method with an HTTP method that is not supported |
| 406 | na | Not Acceptable -- You requested a format that isn't json. |
| 500 | 1000 | Internal Server Error -- We had a problem with our server. Try again later. |
| 500 | na | Internal Server Error -- We had a problem with our server. Try again later. |
| 503 | na | Service Unavailable -- We're temporarily offline for maintenance. Please try again later. |
Form Validation
Form validation error response body
{
"message": "OAuth validation failed due to field validation issues",
"code": 400,
"errorCode": 99999,
"errors": [
{
"field": "username",
"type": "MISSING",
"reason": "The username is missing"
},
{
"field": "password",
"type": "MISSING",
"reason": "The password is missing"
}
]
}
Whenever submitting forms to the API (for creation (PUT), update (PATCH) or whatever else (POST)) all fields of the form will be validated. When the form is invalid for whatever reason the API will return HTTP status code 400 with an internal error code 99999. Together with this you'll receive some extra information on which forms that caused the 400 and the reason why the validation of these individual fields failed. The reason is provided in a textual explanation of what is not ok but also in a structural way using the type field.
| Type | Meaning |
|---|---|
| MISSING | The specific field is required and cannot be empty |
| INVALID | The value provided in the field is not valid, check the reason for more details |
| NOT_ALLOWED | The field you are trying to change is not editable (like identifiers) |
Formatting
Timestamps
Timestamps will always be formatted like this: 2020-02-14T12:13:00.164Z. The format is yyyy-MM-dd'T'hh:mm:ss.SSS'Z'. Depending on your programming language the format might be a little different. Times are always in UTC time! To format times for the user you should use the timezone and dateformat specified in the user's profile!
Authentication
Factsys makes use of two types of authentication. Both are industry standards: OAUTH2 and Basic authentication. Both however serve a different purpose. Which authentication type to use depends if you tend to create a public or private software package.
Public apps
When you develop a public application that you will publish to an application store where it can be downloaded then you have no control over what user comes to your app and wants to connect to Factsys. Therefore you choose for OAuth2. Using this authentication method you will have to ask the user for his/her username and password at Factsys, submit it to us and we will provide you with a unique but temporary access token.
Private apps
When developing a private app that should make use of or integrate with Factsys you can go for basic authentication. This way you will keep two technical values in you client app that will allow you to communicate with our backend. These values are the client-id and client-secret.
Authentication - OAUTH2
Accessing resources without authentication
{
"code": 401,
"message": "Full authentication is required to access this resource"
}
Factsys uses OAuth 2.0 as authentication. That means that initial authentication will go in a few steps, you will have keep track of the access_token and refresh_token on your clients and you have to manage reauthentication (401).
Prerequisites
Provide your clientId and clientSecret for authenticating users
OkHttpClient oauthOkHttpClient() {
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(chain -> {
Request.Builder requestBuilder = chain.request().newBuilder();
String clientId = "YOUR_CLIENT_ID";
String clientSecret = "YOUR_CLIENT_SECRET";
requestBuilder.addHeader("Authorization", "Basic " + Base64.encodeToString((clientId + ":" + clientSecret).getBytes("UTF-8"), Base64.NO_WRAP));
return chain.proceed(requestBuilder.build());
});
enableOkHttpLogging(builder);
OkHttpClient httpClient = builder.build();
return httpClient;
}
curl -X POST \
'/rest/v1/ping' \
-H 'Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU='
To be able to authenticate a user with OAuth 2.0 you first of all need a clientId and clientSecret. You cannot currently get one from within your factsys account so you will have to request one at api@factsys.be. However in the future it will be possible generate both for your company. For all of your authentaication requests both need to be provided to the API using Basic authentication. All of the OAuth API is availalbe at /rest/v1/oauth.
Retrieve an access_token
Authenticate a Factsys user with username and password
@POST("/rest/v1/oauth/token?grant_type=password")
Observable<Authentication> getAccessToken(@Query("username") String username, @Query("password") String password);
curl -X POST \
'/rest/v1/oauth/token?grant_type=password&username=user@domain.com&password=ahardtorememberpassword' \
-H 'Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU='
Response
{
"token_type": "bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1NDk2MzQ2Njl9.kUePqM4_IWpAYpBeI7DySdlTyMTFDueG9WtGSxVa-Ww",
"refresh_token": "QNlpQ0qH4zRMPzM3i7tvqhEubrL0nu4qkjAdKFB7Yi4bBgkznVsr886Qtkgnd1X6",
"expires_in": 180
}
So we are ready to start authenticating users now. In Factsys users authenticate with a username (= email) and password. According to the OAuth 2.0 specs the password grant type will be used for this purpose in a POST request. Although the HTTP method is POST we are not submitting a body here, only query parameters:
* grant_type=password
* username with the email adress of the authenticating user
* password with the password of the authenticating user
And off course don't forget the Authorization: Basic xyz header!
In the result you will find the access_token and refresh_token. Save them somewhere safe because you will need them later for access resources on the server or to get a new access_token.
During authentication following errors might occur:
| Code | Error Code | Meaning |
|---|---|---|
| 404 | 10001 | Either the username or password (or combination) you provided to the OAuth are not available |
| 400 | 10004 | The client ID or client secret are not correct |
| 400 | 10005 | The grant type provided in the OAuth request is not valid (should password) |
| 400 | 99999 | The grant_type, username or password fields are missing or the authorization header is not provided |
401 - Please reauthenticate
Response with expired access_token
{
"code": 401,
"message": "The Token has expired on Tue Feb 12 06:17:59 UTC 2019."
}
Issue a new access_token
@POST("/rest/v1/oauth/token?grant_type=refresh_token")
Observable<Authentication> getAccessToken(@Query("refresh_token") String refreshToken);
curl -X POST \
'/rest/v1/oauth/token?grant_type=refresh_token&refresh_token=4tp61TGNRbIXqwWZFb0W6gyOS9FREQliQraaEQYtRGuXjOc8Vu3vTQ6CBOYqd33l' \
-H 'Authorization: Basic ZmFjdHN5c19tb2JpbGVfYXBwOlImNyFCend1YHNDeFA0NT4='
Response
{
"token_type": "bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1NDk2MzU1NDJ9._REkE3kgHQAvgAoIPHoMuwNqzwfjoGJDNOFvhFlekOY",
"refresh_token": "43ZzpML5Z1id0o29mEVTKS0JEQZDyJxtQ40JgXCCMyQhw4uAIHoZSEi8p7VYTudm",
"expires_in": 180
}
The access tokens issued by Factsys will expire every 30 minutes. That's not much time for you to do your work. And that's why you will need the refresh token as well. Whenever the access token has expired every API call will result in a 401 status. You will have to issue a new access token that will be valid for another 30 minutes. Issuing a new access token is done with a refresh token and the server will return you a new access_token off course BUT also a new refresh_token. The access tokesn are reusable for 30 minutes, the refresh tokens can only be used once. According to the OAuth 2.0 specification the grant_type for getting a new access token is refresh. The only other query parameter you need is the refresh_token itself.
Make sure to update the newly retrieved access_token and refresh_token from the response of the call to use in any future requests!
Error response
{
"message": "Trying to refresh access token with an invalid/blacklisted refresh-token: HXmpwmcA4srFzWo7WzHDCCpM49t7jcuzCeSZztWpXG7LZmf0LMfPM32n1o28RkcS",
"code": 404,
"errorCode": 10003
}
While reauthenticating following errors might occur:
| HTTP Code | Internal error code | Meaning |
|---|---|---|
| 404 | 10003 | The refresh token you provided to the OAuth request cannot be found or has expired |
| 400 | 10004 | The client ID or client secret are not correct |
| 400 | 10005 | The grant type provided in the OAuth request is not valid (should refresh_token) |
| 400 | 99999 | The grant_type or refresh_token fields are missing or the authorization header is not provided |
Authentication - Basic
Accessing resources without authentication
{
"code": 401,
"message": "Full authentication is required to access this resource"
}
Basic authentication is much simpler than OAuth2 on the client side to implement. The only thing you need to do is to pass on a certain header and you are good to go. No token refresh is needed.
However you still need to handle 401 status codes in case the client secret gets reject on the server because of some kind of security breach!
Every request should have an Authorization header with a base64 encoded string. The header will look something like this: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ==. The base64 encoded string is the combination of the client-id and client-secret separated with a colon (:). If you base64 encode that string you will have something similar as our example. Then you put Basic (mind the space at the end) in front of it and you have your Authorization header.
Basic authentication is only availble for users with company admin rights. This means that when using this authentication method that you may safly assume the user will always have the COMPANY_ADMIN which is explained in the next section. However which features that are available can still change from user to user or company to company configuration. So before using any feature you should check if the feature is available in the user profile (which will be linked to the client-id and client-secret).
ACL and Security
Introduction
Accessing a non-existing or non-authorized resource
{
"message": "Some more info on the resource that cannot be accessed",
"code": 403,
"errorCode": 1002
}
All Factsys users can authenticate against either the REST API or the web interface (at https://factsys.be/login) but the data they will see or have access to will be limited based on the Access Control Layer we have put in place. Whenever you try to access a resource that cannot be accessed by this user either because it simply does not exist or because the user is not allowed to we will return an HTTP status code 403 (Forbidden) with an internal error code 1002.
The Access Control Layer works with with Roles and Features.
Roles and Features
There are 2 roles available:
| Role | Meaning |
|---|---|
| COMPANY_ADMIN | The company admin (the default role you will get when you first register a company on Factsys) has access to all resources of his company. |
| COMPANY_USER | The company users are managed by the company admins and the resources they have access to can be limited and can change over time. |
Based on these roles and the configuration of the user Factsys will determine which Features the user has access to. For most features there is a VIEW feature and a MANAGE feature. Easy as it is a user with the VIEW feature will be able to view a certain resource, if he also has the MANAGE feature he will be able to change, update or create resources in that category. A company admin will always have all VIEW features, that is for sure. However weather he has the MANAGE features depends on a valid subscription on the platform (not managable through the API's).
For company users it's more complex. Whenever an admin creates a user different configuration options are available for that user. For instance it is possible to give him access on the invoices of the company. That means that the user will have the FEATURE_VIEW_INVOICE feature, and if the subscription for the company is still valid he will also have FEATURE_MANAGE_INVOICE. But that does not mean he will be able to access all invoices. The company admin can limit the resources the user has access to. Whenever retrieving a list of resources (invoices for instance) that list will be filtered based on the access rights of the user, and only that subset of resources will be accessible to the user. If at any time the user tries to access a resource only available to the company admins (or other users but not to him) the API will return the 403 status code.
An overview of all features available today:
| Feature | Meaning |
|---|---|
| FEATURE_VIEW_CLIENTS | View the company clients/customers |
| FEATURE_MANAGE_CLIENT | Manage (create, update) company clients/customers |
| FEATURE_VIEW_PRODUCTS | View the products |
| FEATURE_MANAGE_PRODUCT | Manage (create, update) products |
| FEATURE_VIEW_INVOICE | View the invoices |
| FEATURE_MANAGE_INVOICE | Manage (create, update, delete, send) invoices |
| FEATURE_MANAGE_RECURRING_INVOICE | Manage (create, update, delete) recurring invoice configurations. There is no VIEW feature available for this! |
| FEATURE_VIEW_QUOTATION | View the quoations |
| FEATURE_MANAGE_QUOTATION | Manage (create, update, delete, send) quoations |
| FEATURE_VIEW_CREDIT_NOTE | View the credit notes |
| FEATURE_MANAGE_CREDIT_NOTE | Manage (create, update, delete, send) credit notes |
| FEATURE_VIEW_WORK_ORDER | View the work orders |
| FEATURE_MANAGE_WORK_ORDER | Manage (create, update, delete, send) work orders |
| FEATURE_VIEW_PAYMENTS | View payments on invoices |
| FEATURE_MANAGE_PAYMENT | Manage (create, update, delete) payments |
| FEATURE_VIEW_COMPANIES | View the companies you are active in |
| FEATURE_MANAGE_COMPANY | Manage the company details (update) |
| FEATURE_MANAGE_COMPANY_USERS | Manage the users of the company. There is no VIEW feature available for this! |
| FEATURE_MANAGE_COMPANY_TASKS | Manage the company internal tasks. There is no VIEW feature available for this! |
| FEATURE_VIEW_TIMESHEET | View and manage your own time sheet |
| FEATURE_MANAGE_TIMESHEETS | Manage the timesheet (time entries) for all users of the company |
Working with Roles and Features
Since there are 2 roles to handle, the less restrictive role is the COMPANY_ADMIN one. This one will be allowed to do anything and cannot be limited in features or resources. On the other hand if the role of the user COMPANY_USER it will be limited to a set of so-called features that he will have access to. Also the resources may be limited, but that will be handled by the API internally, no need for you to worry about!
There is however a 'rare' case in which a COMPANY_ADMIN does not have access to all of the features. The only case in which this may happen is when the license for the company has expired. As soon as this happens the entire company will fall back to only have the VIEW, and no longer the MANAGE features. So ideally you should always check that a user has the according MANAGE action before executing an action (such as entering creating or finalizing invoices). However if you don't you might end up with some bad UX, but the API's will internally still handle this and disallow the action performed.
User Profile
All of the user endpoints are available at /rest/v1/users.
The Profile object
{
"email": "user@domain.com",
"firstName": "John",
"lastName": "Doe",
"language": "nl-BE",
"timezone": "CET",
"dateFormat": "dd/MM/yyyy",
"favouriteCompany": {
"id": 1,
"name": "Hooli",
"logo": "https://botw-pd.s3.amazonaws.com/styles/logo-thumbnail/s3/032018/untitled-1_402.png?9B3JAUEkoVDD_uwVlOJaQ57wMuBKWul_&itok=eo3I5hnx",
"address": {
"street": "Newell Road 5230",
"postal": "94020",
"city": "Palo Alto",
"country": "US"
},
"vatNumber": "ATU37675002",
"language": "nl",
"currency": {
"sign": "€",
"shortName": "EUR",
"name": "Euros"
},
"defaultVAT": {
"products": 0.21,
"services": 0.21
}
},
"availableCompanies": [
{
"id": 1,
"name": "Hooli",
"logo": "https://botw-pd.s3.amazonaws.com/styles/logo-thumbnail/s3/032018/untitled-1_402.png?9B3JAUEkoVDD_uwVlOJaQ57wMuBKWul_&itok=eo3I5hnx",
"vatNumber": "ATU37675002",
}
],
"accessibleFeatures": [
"ROLE_COMPANY_ADMIN",
"FEATURE_VIEW_CLIENTS",
"FEATURE_MANAGE_CLIENT",
"FEATURE_VIEW_PRODUCTS",
"FEATURE_MANAGE_PRODUCT",
"FEATURE_VIEW_INVOICE",
"FEATURE_MANAGE_INVOICE",
"FEATURE_MANAGE_RECURRING_INVOICE",
"FEATURE_VIEW_QUOTATION",
"FEATURE_MANAGE_QUOTATION",
"FEATURE_VIEW_CREDIT_NOTE",
"FEATURE_MANAGE_CREDIT_NOTE",
"FEATURE_VIEW_PAYMENTS",
"FEATURE_MANAGE_PAYMENT",
"FEATURE_VIEW_COMPANIES",
"FEATURE_MANAGE_COMPANY_USERS",
"FEATURE_MANAGE_COMPANY",
"FEATURE_MANAGE_COMPANY_TASKS",
"FEATURE_VIEW_TIMESHEET",
"FEATURE_MANAGE_TIMESHEETS"
]
}
| Field | Status | Info |
|---|---|---|
| Required | ||
| firstName | Required | |
| lastName | Required | |
| language | Required | |
| timezone | Required | |
| dateFormat | Required | |
| favouriteCompany | Available | The favourite company is a company from the available companies on which all actions for the user will be performed. The favouriteCompany property contains a Company object. Submission of this object on edit will be ignored. |
| availableCompanies | Available | This list can never be edited, submission on edit will be ignored. |
| availableCompanies.id | Available | |
| availableCompanies.name | Available | |
| availableCompanies.vatNumber | Available | |
| availableCompanies.logo | Optional | This field cannot be set in an update of the profile. A separate endpoint is available for this. |
| accessibleFeatures | Available | This list can never be edited, submission on edit will be ignored. This is the list you have to use to limit which features the user will have access to. |
Get the profile (GET)
Get the authenticated user's profile
curl -X GET \
/rest/v1/users/profile \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA'
@GET("/rest/v1/users/profile")
Call<UserProfile> getUserProfile();
Retrieval of the user's profile will result in the Profile object. All required and available fields will be filled in a GET request. The optional fields will be missing when not available.
Update the profile (PATCH)
Update the authenticated users's first name
curl -X PATCH \
http://localhost:8082/rest/v1/users/profile \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA' \
-H 'Content-Type: application/json'
-d '{
"firstName": "Jane"
}'
// RestService.java
@PATCH("/rest/v1/users/profile")
Call<ResponseBody> patchUserProfile(@Body UserProfilePatchBody);
// Rest consuming class
UserProfilePatchBody patchBody = new UserProfilePatchBody();
patchBody.setFirstName("Jane");
Response<ResponseBody> response = restService.patchUserProfile(patchBody).execute();
if (response.isSuccessfull()) {
// TODO the success case
} else {
// TODO the error case
}
Error response
{
"message": "Following fields are missing in the OAuth token request: username, password",
"code": 400,
"errorCode": 1003,
"items": [
"username",
"password"
]
}
For updating the user profile only the main Profile fields can be updated. The ID field and relational and calculated fields cannot be updated through the update profile. For instance profile.favouriteCompany.name cannot be updated. If you would like to do so you will need to use the Company REST API calls for this purpose. Here is a quick overview of what can be updated on the userprofile and what not:
| Field | Updatable |
|---|---|
| No (identifier) | |
| firstName | Yes |
| lastName | Yes |
| language | Yes |
| timezone | Yes |
| dateFormat | Yes |
| favouriteCompany | No (relational field) |
| availableCompanies | No (relational field) |
| accessibleFeatures | No (calculated field) |
The update of a profile should use the HTTP PATCH method and should only provide the fields that have changed. A succesfull PATCH request will result in an HTTP status code 200 and will not have a response-body.
Switch favourite company (POST)
CRM
Clients
The client object
{
"id": 432,
"name": "ABCompany BVBA",
"number": 3,
"vatNumber": "BE000000001",
"netInvoice": "14",
"address": {
"street": "Grote markt",
"postal": "1000",
"city": "Brussel",
"country": "BE"
},
"currency": {
"sign": "€",
"shortName": "EUR",
"name": "Euros"
},
"language": "English",
"languageCode": "en"
}
All of the CRM Client endpoints are available at /rest/v1/crm/clients.
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✗ | The identifier of the client will always be available, after retrieving a client this is the key data to be used for further operations on the item. |
| name | ✓ | The name of this client |
| number | ✗ | This is the internal client's number, auto numbered starting from 1 if not manually modifiable |
| vatNumber | ? | A EU compliant VAT number for the company if available, some companies are VAT eligible so this field can be empty! |
| netInvoice | ? | The default amount of days after which the client will have to pay his invoices. We suggest you provide this field, however you can leave this one out and we will default it to 14 (days). |
| address | ✓ | The address entity (street, postal, city and country). This should be the invoice address, not the delivery address if different. The country is the ISO country code! |
| currency | ✓ | The currency the client will by default be invoiced in. This can be overriden when creating invoices. For setting the currency you only need to provide the field currency.shortName which is the currencies ISO representation. |
| language | ✗ | The language as can be displayed to the user |
| languageCode | ✓ | The iso language code (supported values: en, fr, nl) |
Get all the clients (GET)
Get the list of all available clients
curl -X GET \
/rest/v1/crm/clients \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA'
@GET("/rest/v1/crm/clients")
Call<List<CrmClient>> getCrmClients();
Retrieval of all the clients available in the user's company mapped in a json array.
Get one client (GET)
Get one single client by ID
curl -X GET \
/rest/v1/crm/clients/487 \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA'
@GET("/rest/v1/crm/clients/{id}")
Call<CrmClient> getCrmClient(@Path("id") int id);
Search clients by property (GET)
You can search for clients by name, email or vat. The endpoints available are:
/rest/v1/crm/clients/by/name?name={name}/rest/v1/crm/clients/by/email?email={email}/rest/v1/crm/clients/by/vat?vat={flatVat}
They all result in an array of clients.
Create client (POST)
All the fields marked as required in the client object table should be provided when creating a new one.
POST /rest/v1/crm/clients
A client POST/PUT request object
{
"name": "XYZ Company",
"vatNumber": "BE000000001",
"netInvoice": "14",
"address": {
"street": "Grote markt",
"postal": "1000",
"city": "Brussel",
"country": "BE"
},
"currency": {
"shortName": "EUR"
},
"languageCode": "en"
}
Edit client (PUT)
PUT /rest/v1/crm/clients/{id}
The id is the one you retieve from the list of clients or that you got after creating a client.
By not specifying a field from the client that has been set before (and that is not required) you will reset that field!
Invoices
Invoices are complex documents with quite complex states. However in Factsys we like to keep this as simple as possible. In Factsys an invoice can have 2 states: draft or ready.
The draft state means that the invoice is being created. As long as it is in this state you can keep modifying the invoice. It is not possible to download the PDF version of the invoice as long as it's in this state!
The ready state means that the invoice is ready to be sent to the customers and more advanced features are available such as sending the invoice to the accountant, getting the PDF version of the invoice,...
Invoices can transitation between these two states. The transition from draft to ready is called finalize, the transition back is called recall. In some cases it's not allowed for an invoice to transition back from ready to draft. One of these cases is when the invoice is already at the accountant.
Invoice Object
All of the invoice endpoints are available at /rest/v1/invoices.
The invoice object
{
"id": 432,
"subject": "Invoice for March 2020",
"year": 2020,
"number": 10,
"currency": {
"sign": "€",
"shortName": "EUR",
"name": "Euros"
},
"reverseVat": false,
"status": "READY",
"sentToCustomer": false,
"date": "TBD",
"expirationDate": "TBD",
"language": "English",
"languageCode": "en",
"totalExclVat": 200.00,
"totalVat": 42.00,
"total": 242.00,
"paymentDetails": {
"paymentProvider": "epc_free",
"communication": {
"type": "free",
"description" "TEST123"
},
"banking": {
"iban": "BE000000001",
"bic": "GEBABEBB"
},
"netInvoice": 14,
"conditions": "The full payment conditions text"
},
"companyDetails": {
},
"clientDetails": {
},
"client": {
"id": 432,
"name": "ABCompany BVBA",
"number": 3,
"vatNumber": "BE000000001",
"netInvoice": "14",
"address": {
"street": "Grote markt",
"postal": "1000",
"city": "Brussel",
"country": "BE"
},
"currency": {
"sign": "€",
"shortName": "EUR",
"name": "Euros"
},
"language": "English",
"languageCode": "en"
},
"creator": {
"email": "test@domain.com",
"firstName": "Jane",
"lastName": "Doe"
},
"paymentDetails": {
},
"deliveredToAccountant": true,
"deliveredToAccountantDate": "TBD",
"lines": [
{
"id": 1234,
"order": 0,
"type": "line",
"description": "Cake",
"quantity": 3.0,
"unit": "pieces",
"price": 10.45,
"reduction": 0.00,
"reductionPercentage": 0.00,
"vat": 0.21,
},
{
"id": 1235,
"order": 1,
"type": "comment",
"description": "As you asked, non burned cake!"
},,
{
"id": 1236,
"order": 2,
"type": "group",
"description": "All of the pie:",
"lines": [
{
"id": 1237,
"order": 0,
"type": "line",
"description": "Cherry pie",
"quantity": 2.0,
"price": 10.45,
"reduction": 0.00,
"reductionPercentage": 0.00,
"vat": 0.11,
},
{
"id": 1238,
"order": 1,
"type": "line",
"description": "Chees pie",
"quantity": 1.0,
"price": 15.99,
"reduction": 0.00,
"reductionPercentage": 0.00,
"vat": 0.11,
}
]
},
],
"payments": [
{
"id": 1,
"amount": 100.10,
"date": "TBD",
"communication": "Yes we paid for invoice xxxx/zzzz!",
"source": "pontno"
},
{
"id": 2,
"amount": 10.59,
"date": "TBD",
"communication": "+++642/4687/20064+++",
"source": "codabox"
}
]
}
Invoice
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✗ | The identifier of the invoice will always be available, after retrieving an invoice this is the key data to be used for further operations on the item. |
| subject | ✓ | The subject of the invoice. This field might be empty on retrieval for legacy invoices |
| year | ✓ | The (financial) year for this invoice |
| number | ✗ | The number of this invoice, this cannot be manually set. |
| invoiceNumber | ✗ | The fully formated invoice, most of the times a combination fof the year and number. This cannot be manually set. |
| currency | ✓ | The currency for this invoice. For setting the currency you only need to provide the field currency.shortName which is the currencies ISO representation. Leaving this empty on creation will take the default from the client. |
| reverseVat | ? | Only needs to be set if reverse VAT is applicable. If you don't set this field and reverse VAT is set through client configuration then it will be set to true afterwards. |
| status | ✗ | The status of the invoice being either DRAFT or READY. To change the status you need to finalize or recall the invoice through the API calls. |
| sentToCustomer | ✗ | An indication if this invoice has already been sent to the customer or not. |
| date | ✓ | The date for which the invoice is applicable (normally you should put this to today or the day a customer made an order). |
| expirationDate | ✗ | This is a calculated date based on the invoice date and the net invoice value in the payment details. |
| language | ✗ | The language as can be displayed to the user |
| languageCode | ✓ | The iso language code (supported values: en, fr, nl) |
| totalExclVat | ✗ | The total excluding VAT for this invoice. Calculated based on the lines. |
| totalVat | ✗ | The total VAT amount for this invoice. Calculated based on the lines. |
| total | ✗ | The total including VAT for this invoice. Calculated based on the lines. |
| creator | ✗ | The name and email of the user account used for creating this invoice. |
| client | ✗ | On invoice lookup the client object will be provided as described in the client API documentation. |
| deliveredToAccountant | ✗ | This is only available if an invoice to accountant integration (such as accounting software or Codabox) is enabled and configured. true if already sent to the accountant, false otherwise. As soon as invoice is with the accountant you can no longer recall and edit the invoice! |
| deliveredToAccountantDate | ✗ | This is only available if an invoice to accountant integration (such as accounting software or Codabox) is enabled and configured. |
Payment details
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| paymentProvider | ? | Optionally you can set a payment provider. Currently supported values are: epc_free. |
| communication | ✓ | The communication information needs to be provided. Two types are available: free or belgian_structured. The free communication can be set through the API with the description field, the Belgium structured communication cannot be set but will be generated by the system. |
| banking | ✓ | Both the IBAN and BIC number of the account on which you want to receive the invoice payment needs to be provided. |
| netInvoce | ✓ | The amount of days after the invoice date by which the invoice needs to be paid |
| conditions | ? | The payment conditions. These are pre-configured on company level but can be overriden (or set empty). |
Company details
TODO!
Client details
TODO!
Line
Factsys supports nesting of invoice lines to create really complex invoices in a structured way. To do so we have 3 types of invoice lines:
line: The basic line in which you will put invoicing information such as quantity, price and vat.comment: A line to add an extra comment to the invoicegroup: The most important to build complex invoices is the group. A group itself can contain lines, comments and again groups. Nesting is currently unlimited in depth, however we advice to not nest for more than 2 levels (so the root level and one level deep).
Important to now is that a line of the invoice from one type cannot be changed to another type! If you want to change the comment line in a regular line then you need to remove the comment line and add a new regular line!
Regular line
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✗ | The identifier of the line will always be available for further operations on the item. |
| order | ? | The order of the invoice lines. If you provide 0 for all of the lines we will do the ordering for you based on the order in which you provide the lines to the API. Don't mind if there are gaps in the order numbers you provide, that will not pose an issue! |
| type | ✓ | Always provide the type of the line (and don't change it!), allowed values are line, group and comment. In this case it must always be line. |
| description | ✓ | The description of what you are selling (service, product,...) |
| quantity | ✓ | The number of items, hours,... you sell |
| unit | ? | The unit is optional, you can leave this empty. Allowed values are (but not limited to this list!): none (same as empty), items, pieces, visits, people, packets, crates, boxes, pallets, rolls, bags, minutes, hours, days, weeks, months, kilometer, meter, decimeter, centimeter, millimeter, kilogram, gram, milligram, liter, deciliter, centiliter, milliliter, square_meter, cubic_meter, cubic_centimeter |
| price | ✓ | The price per item at which you sell |
| vat | ✓ | The amount of vat to be applied (eg: 0.21) |
| reduction | ? | The total reduction you want to give the user (not per quantity but for the total). This is not required. |
| reductionPercentage | ? | If you don't provide the total reduction you can apply a reduction percentage |
| totalVat | ✗ | Readonly: The total amount of VAT |
| totalReduction | ✗ | Readonly: The total reduction (if specified in percentage this is the calculated field) |
| totalExcludingVat | ✗ | Readonly: The total excluding VAT |
| totalExcludingVatAndReduction | ✗ | Readonly: The total excluding VAT and excluding reduction (is the same as quantity * price) |
| total | ✗ | Readonly: The total of this line (including VAT and with reduction applied) |
Comment line
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✗ | The identifier of the line will always be available for further operations on the item. |
| order | ? | The order of the invoice lines. If you provide 0 for all of the lines we will do the ordering for you based on the order in which you provide the lines to the API. Don't mind if there are gaps in the order numbers you provide, that will not pose an issue! |
| type | ✓ | Always provide the type of the line (and don't change it!), allowed values are line, group and comment. In this case it must always be line. |
| description | ✓ | The description is the comment. |
Group line
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✗ | The identifier of the line will always be available for further operations on the item. |
| order | ? | The order of the invoice lines. If you provide 0 for all of the lines we will do the ordering for you based on the order in which you provide the lines to the API. Don't mind if there are gaps in the order numbers you provide, that will not pose an issue! |
| type | ✓ | Always provide the type of the line (and don't change it!), allowed values are line, group and comment. In this case it must always be line. |
| description | ✓ | The description is the name of the group, this is requried! |
| totalVat | ✗ | Readonly: The total amount of VAT for all sub lines |
| totalReduction | ✗ | Readonly: The total reduction for all sub lines |
| totalExcludingVat | ✗ | Readonly: The total excluding VAT for all sub lines |
| totalExcludingVatAndReduction | ✗ | Readonly: The total excluding VAT and excluding reduction for all sub lines |
| total | ✗ | Readonly: The grand total of this group (including VAT and with reduction applied) |
Payment
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✗ | The identifier of the payment will always be available for further operations on the item. |
| amount | ✓ | The amount the user paid |
| date | ? | The date of the payment. If not provided in a payment creation it will be set to now ( = today) |
| communication | ✗ | The communication the user specified. In case of an automated payment with a payment provider this info could be put here. For payments we automatically process from an external resource (such as Ponto or Codabox) it will be the real communication that will be on the company accounts transaction. In case of a structured communication it will be formatted like this: +++642/4687/20064+++. |
| source | ✗ | You cannot specify this field yourselve. It will either contain codabox or ponto. The two automated payment providers we support today. Keep in mind that in the future any value could be in this field. |
Retrieving all invoices
curl --location --request GET '/rest/v1/invoices' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ=='
GET /rest/v1/invoices
Retrieving a single invoice by ID
curl --location --request GET '/rest/v1/invoices/{invoiceId}' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ=='
GET /rest/v1/invoices/{invoiceId}
Search invoice by year and number
curl --location --request GET '/rest/v1/invoices/by/{year}/{number}' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ=='
GET /rest/v1/invoices/by/{year}/{number}
In case the year and number combination cannot be found for the authenticated user this endpoints throws a 404 with error code 1003 indicating the requested resource is not available at the moment.
Download the invoice PDF
curl --location --request GET '/rest/v1/invoices/{invoiceId}/pdf' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ=='
You can download the PDF version of an invoice as soon as it has transitioned to the ready state. The invoice PDF will remain available for download as long as the invoice remains in this state! If the invoice in state draft it will return an error!
GET /rest/v1/invoices/{invoiceId}/pdf
Creating an invoice
Simples invoice creation call
{
"subject": "My invoice created with the Factsys API",
"year": 2020,
"date": "2020-01-31T12:13:00.164Z",
"lines": []
}
Invoice creation call with lines included
{
"subject": "Test invoice",
"year": 2020,
"date": "2020-01-31T12:13:00.164Z",
"lines": [
{
"type": "line",
"description": "Cake",
"quantity": 3.0,
"unit": "pieces",
"price": 10.45,
"vat": 0.21
},
{
"type": "line",
"description": "Flower",
"quantity": 3.0,
"unit": "kilogram",
"price": 1.50,
"vat": 0.21
}
]
}
The invoice creation is an easy process and taking into account all of the default values for payment, company and client information you can create an invoice in one single call. The invoice creation API is available at:
POST /rest/v1/invoices/create/{clientId}
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| subject | ✓ | The subject of the invoice |
| year | ✓ | The (financial) year of the invoice |
| date | ✓ | The date for which the invoice is, technically this could be in the future, however we don't recommend this. Normally you would put the current timestamp here |
| lines | ? | The lines for this invoice. If you don't specify them now you could specify them later |
Creating an invoice and finalize
Creation and finalisation in one call
curl --location --request POST '/rest/v1/invoices/create/{invoiceId}/finalize' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ==' \
--data-raw '{
"subject": "Test invoice",
"year": 2020,
"date": "2020-01-31T12:13:00.164Z",
"lines": [
{
"type": "line",
"description": "Cake",
"quantity": 3.0,
"unit": "pieces",
"price": 10.45,
"vat": 0.21
},
{
"type": "line",
"description": "Flower",
"quantity": 3.0,
"unit": "kilogram",
"price": 1.50,
"vat": 0.21
}
]
}'
Creating the invoice and finalizing it at once it exactly the same as for simply creating an invoice. The only difference is the endpoint:
POST /rest/v1/invoices/create/{clientId}/finalize
Finalize an invoice
curl --location --request PUT '/rest/v1/invoices/{invoiceId}/finalize' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ=='
In order to be able to download the PDF, send it to a customer, send it to an accountant,... the invoice needs to be finalized first.
PUT /rest/v1/invoices/{invoiceId}/finalize
Every invoice that has been created can be finalised.
Recall an invoice
curl --location --request PUT '/rest/v1/invoices/{invoiceId}/recall' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ=='
In order edit an invoice it needs to be recalled (meaning that is needs to be in the status draft).
PUT /rest/v1/invoices/{invoiceId}/recall
Recalling an invoice is not available in some cases. However the endpoint will result in a 200 and just return the invoice with an unchanged state. You should check yourself if the state has transitioned or not.
Mark invoice sent to customer
curl --location --request PUT '/rest/v1/invoices/{invoiceId}/sent' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ==' \
--data-raw ''
By default when finalizing an invoice it is not sent to the customer. The user could eventually choose to do so in Factsys or you could provide this as a feature as well by downloading the PDF version of the invoice and handling the emailing yourself. In order to keep track of which invoices have been sent to the customer you can invoke this endpoint to mark is as succesfully sent.
PUT /rest/v1/invoices/{invoiceId}/sent
Payments handling
Add a payment to an invoice
curl --location --request POST 'http://localhost:8082/rest/v1/invoices/12/payments' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ==' \
--data-raw '{
"amount": 100.00,
"date": "2020-04-13T12:13:00.164Z"
}'
Registering payments on an invoice is really simple. The only required field is the amount that has been paid. The date will by default set to now if not specified. Future dates are not allowed!
POST /rest/v1/invoices/{invoiceId}/payments
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| amount | ✓ | The amount the user paid |
| date | ? | The date of the payment. If not provided it will be set to now. |
| communication | ? | The communication the payer has specified on the wire transfer. For online payments you can leave this field empty. |
| sendEmails | ? | This field is not mandatory. If not specified emails will be sent out within user's company to all administrtors that a new payment is received. If you specify this flag and put it false no emails will be sent out. Most often for direct payments in a shop order you want to put this flag on false to avoid spamming of the inboxes. |
Edit a invoice payment
Editing the invoice payment is similar to the payment creation. Exactly the same fields are allowed for editation as the ones in the creation. You can also skip update emails being sent by setting the sendEmails flag to false.
PUT /rest/v1/invoices/{invoiceId}/payments/{paymentId}
Delete invoice payment
curl --location --request DELETE 'http://localhost:8082/rest/v1/invoices/12/payments/1' \
--header 'Authorization: Basic YjE0NmJlZWYtNTA1NS00YjA5LTk5NDQtMTkzYzEzNWU3ODczOmQyZWE2YzgyLWU2NTktNDY0OS04NjIxLTQwYmI1NGZhZDkxYQ==' \
--header 'Content-Type: application/json'
DELETE /rest/v1/invoices/{invoiceId}/payments/{paymentId}
Time Registrations
All of the time registration endpoints are available at /rest/v1/timeentries.
The time-entry object
{
"id": 432,
"note": "This note gives me some more info about the time entry, however this is optional for the user to provide this!",
"time": 9000000,
"date": "2019-12-18T10:03:44",
"approved": true,
"invoiced": false,
"task": {
"id": 543,
"name": "Some work needed to be done",
"internal": false,
"invoicable": true,
"invoiceRate": 534.23,
"invoiceVat": 0.21,
"client": {
"id": 532,
"name": "My First Client",
"number": "3",
"vatNumber": "BE0000000000",
"netInvoice": "30",
"address": {
"street": "Grote Markt 1",
"postal": "1000",
"city": "Brussels",
"country": "BE"
},
"email": "test@testcompany.com",
"contactPerson": "John Doe",
"phone": "+32477001122",
"currency": {
"sign": "€",
"shortName": "EUR",
"name": "EURO"
},
"language": "nl",
"defaultRate": 112.00,
"company": {
"id": 8891,
"name": "Test Company",
"logo": "https://botw-pd.s3.amazonaws.com/styles/logo-thumbnail/s3/032018/untitled-1_402.png?9B3JAUEkoVDD_uwVlOJaQ57wMuBKWul_&itok=eo3I5hnx",
"address": {
"street": "Reyerslaan 32",
"postal": "1000",
"city": "Brussels",
"country": "BE",
"vatNumber": "BE0000000001",
"language": "nl",
"currency": {
"sign": "€",
"shortName": "EUR",
"name": "EURO"
}
}
}
}
}
}
| Field | Required(✓)/Optional(?) | Info |
|---|---|---|
| id | ✓ | The identifier of the time entry will always be available, after retrieving a time entry this is the key data to be used for further operations on the item. |
| date | ✓ | The date to which this time entry is linked |
| note | ? | The note is a way for the user to declare a bit more (for himself) on what work has been performed. However the note is optional. |
| time | ✓ | The time is always in milliseconds, this is the time a user claims to have spent on a certain task. |
| approved | ✓ | If the entry has been approved by an admin or not. Entries created by an admin will always be approved automatically. Approved time entries can still be edited by the user, however they will automatically go back to approved=false. This flag cannot be set in creation or update of the time entry! See the time registration management API's for approving a time entry. |
| invoiced | ✓ | If the entry has been invoiced or not. Invoiced time entries can no longer be edited by any user (not even admins). This flag cannot be set in creation or update of the time entry! See the time registration management API's for approving a time entry. |
| task | ✓ | The task that is linked to this time entry. |
| task.internal | ✓ | If the internal flag is set to true the task will not have a client relation. |
| task.invoicable | ? | If the task can be invoiced, for internal tasks info won't be available. |
| task.invoiceRate | ? | The rate against which can be invoiced. For both internal tasks and tasks that cannot be invoiced this info won't be available. |
| task.invoiceVat | ? | The VAT percentage to be applied on the rate when invoiced. For both internal tasks and tasks that cannot be invoiced this info won't be available. |
| task.client | ? | Only available if the task is not internal. More details on which client info if always available and which only sometimes (optional) is explained in the Client section! |
Retrieving Time Entries
By day
Get all the time entries for one day
curl -X GET \
/rest/v1/timeentries/2019/12/22 \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA'
@GET("/rest/v1/timeentries/{year}/{month}/{day}")
Call<List<TimeEntry>> getTimeEntriesForDay(@Path("year") int year, @Path("month") int month, @Path("day") int day);
The endpoint for the day-by-day retrieval is [GET] /rest/v1/timeentries/{year}/{month}/{day}.
This call will result in a list of TimeEntry objects. All required and optional fields will be filled in a GET request. The optional fields will be missing when not available.
By period (aka search)
Search all the time entries in a certain period
curl -X GET \
/rest/v1/timeentries/search?from=1564624800000&until=1566655200000 \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA'
@GET("/rest/v1/timeentries/search")
Call<List<TimeEntry>> seachTimeEntries(@Query("from") long from, @Query("until") long until, @Query("taskId") Integer taskId);
| Query Parameter | Type | Required(✓)/Optional(?) | Info |
|---|---|---|---|
| from | Long/Bigint | ✓ | The search starting time (oldest time value) in milliseconds |
| until | Long/Bigint | ✓ | The search ending (including) time (newest time value) in milliseconds |
| taskId | Integer | ? | To only search for time entries that are created for a certain task. |
The endpoint for the day-by-day retrieval is [GET] /rest/v1/timeentries/search.
This call will result in a list of TimeEntry objects. All required and available fields will be filled in a GET request. The optional fields will be missing when not available.
Create
| Form Parameter | Type | Required(✓)/Optional(?) | Info |
|---|---|---|---|
| date | Long/Bigint | ✓ | The date in milliseconds since 1970 on which the time entry took place/started |
| note | String | ? | |
| time | Long/Bigint | ✓ | The time in milliseconds, this is the duration of the time entry |
| task.id | Integer | ✓ | The task to which this time entrywill be linked |
The endpoint for the creating a time entry is [POST] /rest/v1/timeentries.
Update
| Form Parameter | Type | Required(✓)/Optional(?) | Info |
|---|---|---|---|
| date | Long/Bigint | ✓ | The date in milliseconds since 1970 on which the time entry took place/started |
| note | String | ? | |
| time | Long/Bigint | ✓ | The time in milliseconds, this is the duration of the time entry |
| task.id | Integer | ✓ | The task to which this time entrywill be linked |
The endpoint for the creating a time entry is [PATCH] /rest/v1/timeentries/{id}.
Delete
curl -X DELETE \
/rest/v1/timeentries/543 \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYWN0c3lzX21vYmlsZV9hcHAiLCJpc3MiOiJkaXJrQGFwcDMuYmUiLCJleHAiOjE1MzgzMzYzNjh9.dNkyBm2F70FYyiJUntf-ftTAU3cpf1f_pCrDyWXY0NA'
@GET("/rest/v1/timeentries/{id}")
Call<ResponseBody> getTimeEntriesForDay(@Path("id") int id);
The endpoint for the deletion is [DELETE] /rest/v1/timeentries/{id}.