QicsMilestones REST API



Users of QicsMilestones can create their own client applications that access QicsMilestones data programmatically via REST API.


General information about API can be found here.


API exists in several versions, so that older client applications remain functional, while new improvements and changes can be introduced to the latest version.
Supported versions are named at the root of the API documentation website. At the moment of this document writing there are two versions available:

Version 3 – is the latest version that is not changing anymore and is maintained to work the same way as it was working at the moment when it was frozen. This is the version that client applications should be addressing at the moment of this document writing.
Version 2 – obsolete

API Metadata

For each API version, the metadata describing the structures and data types can be found here:

For example, for the currently stable version 3.


OAuth authentication is used. When QicsMilestones API is used by a client, user account under which the API operations are performed, is identified by an access token instead of a user name and password.
Access token is issued to client aplication when user authorizes use of his account to the client application, in exchange of a one-time authorization code.
As soon as the client application receives an access token, it can perform any API operation under associated account as approved by owner of the account.

There are two API endpoints concerning OAuth2:

  • https://qicsmilestones.qics.nl/api/oauth2/oauth2/auth
  • https://qicsmilestones.qics.nl/api/oauth2/oauth2/token

The following picture shows overview of typical flow. Although OAuth2 (as per RFC6749) offers other authorization flows as well, QicsMilestones only supports authorization code flow.


Set-up of client ID and client secret is only performed once. Client secret and refresh token should be stored in a secure way by the client application since they represent actual credentials of the user who authorized it.

Step 1: Obtain Client ID and Client Secret

To get a Client ID and Client Secret, you need to create an OAuth application in QicsMilestones. You also must specify a Redirect Uri there. This Uri is used to pass authorization code to the client application. Make sure you configure the Redirect Uri as the endpoint your client application listens on.

Step 2: Set up authorization

When you have a Client ID, Client Secret and Redirect Uri, you can make an authorization request to obtain authorization code.


GET https://qicsmilestones.qics.nl/api/oauth2/oauth2/auth?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>

Note: Redirect Uri configured for the OAuth application in Step 1, must be part of redirect_uri passed in the GET request above. Additional query string parameters are allowed and are preserved in redirect after successful authorization.

  • QicsMilestones login form will be displayed. The user can log in with his QicsMilestones credentials.
  • After successful login, page is redirected to redirect_uri passed in the GET request. A query string parameter named 'code' is added to the Redirect Uri

Step 3: Get access token

The access token is used to authorize API requests. Authorization code from Step 2 is exchanged for first access token and refresh token.


POST https://qicsmilestones.qics.nl/api/oauth2/oauth2/token

Request body:

  • POST data should be url-encoded.
  • Authorization code may only be used once.

    access_token: "90f4068dfb914dd5aa9d46b1d08b3ef1",
    token_type: "bearer",
    expires_in: 3600,
    refresh_token: "792c136bce02493fa6b28788ae3de7a2"

The returned access_token can now be used to access QicsMilestones API for next 3600 seconds. Add authorization header to API requests, such as:

Authorization: Bearer 90f4068dfb914dd5aa9d46b1d08b3ef1

Step 4: Refresh access token

Since access token expires after some time, it must be periodically renewed. This is done by exchanging a refresh_token for new access_token and new refresh_token. The old access_token and refresh_token are invalidated and only new access_token can be used in API. Use the new refresh_token to renew both tokens again.


POST https://qicsmilestones.qics.nl/api/oauth2/oauth2/token

Request body:

  • POST data should be url-encoded.

    access_token: "d8a8a3aaf34b4af8b89ddb65000fc4e9",
    token_type: "bearer",
    expires_in: 3600,
    refresh_token: "f9284bc0c8184f91be0769c4cce89b33"

The returned access_token can now be used to access QicsMilestones API for next 3600 seconds.



Client applications are recognized by QicsMilestones using API keys. Each client application must have its own API key assigned by QICS. For new client applications API keys can be requested at the following email address: support@qics.nl

Provide a name and a short description of the client application when requesting new API key. Client application must identify itself with the assigned key using request header:

Header name



Key provided by QICS that uniquely identifies each web api client.

Tenant Number

Tenant is the term used for the environment within QicsMilestones into which the user has access. Each user can have access to multiple tenants, that’s why each request (made under a certain user) must also identify the tenant in the request header:

Header name



Tenant number into which the request is addressed.

Tenant ID can be seen in the address field of the web browser when user is logged into QicsMilestones website:

Here the Tenant number is 3.

Detail Entities

Most objects are exposed in the REST-ful way, identified by the object name in the URL. For example, to work with a Customer object, the following HTTP verbs are supported:

Here, the client must know the internal ID (unique identifier) of each customer to get it from the API and subsequently manipulate it. To find out the ID of each object that needs to be manipulated, client must make API query that returns one or more objects from which the client can get the IDs.

Documentation for all entities can be found here.

API Queries with Overview Entities

API queries can be used to obtain a set of objects such as customers, that satisfy certain query criteria. API has an “Overview” version of endpoint for most objects. This endpoint can be used to make complex queries using the properties exposed by the given object in ODATA format. Objects that do not have their “Overview” counterpart can be queried directly using the HTTP GET verb.

For example, for the Customer object there is OverviewCustomer endpoint:

Summary of the differences between detail and overview entities are:

1. Detail version of the object
    • Exposes only properties owned by the object
    • Allows selection only using primary key property
    • Supports Selects, Inserts, Updates and Deletes
2. Overview version of the object
    • Object name prefixed with Overview
    • Exposes limited set of object’s properties together with number of properties of related objects.
    • Allows querying based on all exposed properties
    • Supports only selection of objects.
    • Insert, Update and Delete is not possible.

Usually objects are selected first by querying the overview endpoint, using a more or less complex query. Then iterate through the set to display the objects and select single object based on given ID from the detail endpoint to modify the object if necessary.


When using API Queries (“Overview” endpoints) client must apply paging to each request. That puts a maximum on the number of objects that can be obtained in one call. If the client does not provide the paging information with a request, only the first 50 objects from the entire set are returned.

Paging information must be provided by the client in ODATA format.

EntityState Endpoint

QicsMilestones objects such as Customers, Employee, Items, etc. are often synchronized to/from external systems, such as CRM, or bookkeeping software. In this scenario, it is important to keep track of the synchronization state of the given object (e.g. Customer) for each external system.

External clients should use the EntityState endpoint to report the synchronization state for each object that is a subject of the synchronization. Because each QicsMilestones object can be synchronized from/to different external systems, multiple EntityState records can be linked with each QicsMilestones object. For example:

Synchronization client should after each synchronization step create, or update EntityState record with the latest results of a synchronization attempt (success, or failure).

Object Properties:

Property name





Primary key



Identifies the company within which the object was synchronized



Custom name identifying a single data source



One of the predefined entity types: General | CustomerCategory | Company | CostCenter | Currency | Customer | Department | Employee | Function | InvoiceLayout | InvoiceMutationReason | IrregularityPercentage | Item | ItemGroup | ItemMainGroup | Material | Project | ProjectBudget | ProjectContract | ProjectMember | ProjectTask | TimesheetEntry | User | VatCode | Rate | ProjectCategory | ProjectInstallment | ProjectItem | MaterialCompany | EmployeeCompany | ItemCompany | CustomerCompany | Role | UserPreferences | ExtendedPropertyGroup | ExtendedProperty | InvoiceHeader | Competence | ProjectState | ProjectTaskBreakdown | EmailTemplate | ReportRole | InvoiceLine | PaymentCondition | ItemCompanyCustomerCompany | EmployeeCompanyItemCompany | EntityState.

Messages that are not bound to a specific existing entity type, should use type: General



Id of the entity in QicsMilestones



Key uniquely identifying the synchronized object in the data source



Client’s date and time of the synchronization attempt



True if the synchronization attempt failed



Description of the synchronization failure

EntityState keeps only a single record for unique combination of following properties:

- CompanyId
- DataSource
- EntityType
- EntityId

An object in one company can thus only hold one state information for each external system (data source).

EntityId can be set to 0, for situations when object does not exist yet in QicsMilestones and insert was not successful. 

Synchronization client can record general synchronization problem (e.g. authentication to external system) by setting EntityType to General and setting EntityId to 0.

When multiple error messages are to be recorded for single QM object, the web API client has to merge them into one message and post to EntityState object.

Example in Visual Studio 2017 and C# .Net core 2.0

.NET Core 2.0 Console application

In Visual Studio 2017, create a new Console App (.NET Core):


You can download the sample project here.

De main code:




Hebt u meer vragen? Een aanvraag indienen


Mogelijk gemaakt door Zendesk