Skip to main content
Version: 2022


The Thinkwise Indicium application tier offers an open API that can be used to perform CRUD-operations and execute procedures on the applications that have been made with the Thinkwise Platform. Through Indicium, a user will only have access to those tables, views and procedures for which the user has been authorized in IAM.

Indicium implements version 4.0 of the OData standard. The OData standard is well documented and as such this manual will not explain all of its features in detail. The OData documentation can be found using the link

Base URL

To access the API of a specific application, the URL of the request must always start with:


In this template, <web_app_root_url> refers to the root URL of Indicium in IIS and <appl> refers to the ID or the alias of the application in question in IAM. Below, you will find two examples of valid base-URLs:

  • <https://server/indicium/iam/123/>
  • <https://server/indicium/iam/insights/>

All requests that start with this base-URL will be subjected to authentication. The type of authentication used depends on the configuration of the user in question in IAM. All authentication follows the HTTP Basic authentication scheme, meaning that Indicium should only be exposed to the Internet over HTTPS.


Indicium running on a 2018.2 and earlier IAM must use <web_app_root_url>/api/<appl>/ instead.

Software Factory

The Indicium API provides access to project versions in the Software Factory development environment, as long as the Software Factory itself is available through the Indicium IAM.


The segment <sf_appl> refers to the application ID or application alias which can be found in the Software Factory at the desired runtime configuration. The runtime configuration must be activated for the user in the Software Factory to obtain access via Indicium.

For example:

  • <https://server/indicium/sf/144/>
  • <https://server/indicium/sf/insights/>

Or, if you are using Universal GUI:

  • https://server/indicium/iam/sf/

On the off-chance that there are multiple Software Factory applications in IAM, the segment /sf/ can be substituted with the alias of another Software Factory application.

Standard operations

Indicium offers several standard operations that can be performed on every application.


With the standard operation $metadata, you can retrieve the metadata document for an application. To do this, add $metadata behind the base URL of the application.

The metadata document contains an extensive description of all entities and operations available for that particular user in that particular application. You can use it to validate whether a user's permissions in IAM have been configured properly. It can also serve as a guideline for writing your own Indicium client.

Open API

With the standard operation /openapi, you can retrieve the API specification for an application in JSON format. To do this, add /openapi behind the base URL of the application.

The API specification can be used by third-party tools to generate code or documentation. Copy-paste the JSON content into the editor to provide a web-based UI with information about your API. Examples include:

  • NSwagStudio for code generation.
  • Swagger for documentation generation.
  • Postman for API testing. Postman will use the schemas defined in the API specification to create a new collection and automatically generate request and response bodies.

Refresh model

Windows GUI Web GUI Indicium

The standard operation Refresh model can be accessed from the ribbon tab Developer in the Windows or Web GUI. It is also available as the /refresh_model API endpoint in the Indicium application tier. To reload (refresh) the model, send a POST request to one of the following URLs:



OData Service Document

With the standard operation /application.svc, you can retrieve the OData Service Document. This is a metadata document that contains all of the top-level feeds that are exposed for an application model. To do this, add /application.svc behind the base URL of the application, using the format <application_root>/application.svc. For example, https://my_server/indicium/iam/appl/application.svc.

The OData Service Document can be used by clients to discover the feeds that a service offers. Some clients rely on this Service Document, such as OData Feed, which can be used in PowerBI queries.

Performing CRUD-operations, functions and procedures

Aside from the standard operations, the Indicium API offers the ability to perform CRUD-operations on entities and to execute tasks, reports, functions and procedures on the database.

Note that every insert, update, task and report action is done by Indicium via a process called resource staging. For a more detailed explanation of this process and a description of the available endpoints and operations on each endpoint, see Resource Staging.

API actionHTTP VerbExample request
Selecting an entityGETRequest URL to retrieve all records from the project entity:

Request URL to retrieve a specific record from the project entity given that it only has one numerical primary key column: <https://server/indicium/iam/appl/project(123)>

Request URL to retrieve a specific record from the sub_project entity which has a primary key of project_id and sub_project_id: <https://server/indicium/iam/appl/sub_project(project_id=123,sub_project_id=321)>

Request URL to retrieve the first 100 records from the project entity where the project_id is greater than 100, ordered by the project ID in ascending order: https://server/indicium/iam/appl/project?$top=100&$select=project_id, description&$filter=project_id gt 100&$orderby=project_id asc
Inserting an entityPOSTRequest URL to insert a new record into the project entity:

Request body assuming that project_id is not an identity:
{"project_id": 123, "description": "a new project", "deadline": "2017-01-01T12:00:00.000"}

Only one record can be inserted per request. All query string parameters will be ignored.

See also Resource Staging.
Inserting a detail entityPOSTRequest URL to insert a new subproject record in the context of the project entity:

Request body:
{"subproject_name": "Documentation"}

Only one record can be inserted per request. All query string parameters will be ignored. The last part of the url is the name of the reference prefixed by detail_.
The context of the parent record is automatically filled in by Indicium.
Updating an entityPATCHRequest URL to update the description of an existing record of the project entity:

Request body:
{"description": "a changed project"}

Records can only be updated by specifying their entire primary key between the parentheses. All query string parameters will be ignored.

See also Resource Staging.
Deleting an entityDELETETo delete an existing record of the project entity:

Records can only be deleted by specifying their entire primary key between the parentheses. All query string parameters will be ignored.
Executing a taskPOSTRequest URL to call the task my_task with parameters project_id and description: <https://server/indicium/iam/appl/my_task>

Request body:
{"project_id": 123, "description": "my project"}

All query string parameters will be ignored.

See also Resource Staging.
Executing a task on a recordPOSTRequest URL to call the task generate_invoice with parameters in the context of a project. The table task parameters are automatically filled in by Indicium.

Request body:
{"month": 1}
Executing a task on an empty detailPOSTRequest URL to call the task create_default_subprojects on the subproject subject with parameters. The task is executed within the context of a project.
<https://server/indicium/iam/appl/project(1)/ empty_detail_ref_project_subproject/task_create_default_subprojects>

Request body:
{"create_default_activities": true}

The context of the parent record is automatically filled in by Indicium.
Exporting a reportPOSTRequest URL to call the report my_report with parameters project_id and description:

Request body:
{"project_id": 123, "description": "my project"}

All query string parameters will be ignored.

See also Resource Staging.
Calling a functionGETRequest URL to call the function my_function with parameters project_id and description:
https://server/indicium/iam/appl/my_function(project_id=123,description='my project')

All query string parameters will be ignored.
Calling a procedurePOSTRequest URL to call the procedure my_procedure with parameters project_id and description:

Request body:
{"project_id": 123, "description": "my project"}

All query string parameters will be ignored.

It is important to apply URL encoding (also known as Percent-encoding) to all value literals in the request URL. Value literals can only occur between the parentheses ( ) after the name of an entity and in the value of the $filter query string parameter.


For security reasons, subroutines are not exposed by the Indicium OData API by default. To expose a subroutine using Indicium, enable the API option of the subroutine in the Software Factory. The service name of a subroutine and its parameters can be changed using the Alias settings.

Supported OData operations

Indicium supports the following OData operations and query string options in the URL:

  • Use OData query options like $filter, $search or $orderby to filter and/or order a set of records.
  • Use $seek to search within this filtered and sorted set.


To get the number of records in a table without getting actual data, use a $top=0&$count=true query.

Template: http://server/indicium/iam/123/table?$top=0&$count=true


Response: {"@odata.context":"$metadata#uur","@odata.count":598325,"value":[]}


Indicium supports filtering on and ordering by the display value of a lookup. This can be achieved by specifying reference_id/display_column_id in the request URL, rather than simply the ID of the column. For example:


In addition to the standard OData operations, Indicium also supports a custom $prefilter operation which can be used to add prefilters to requests in order to select records from entities. For example:


The $search query option can be used to retrieve all records that match a free-text search expression. For example:

<http://server/indicium/iam/123/project?$search="my project">

You can only search in columns included in the combined filter.


When the custom $seek query option is used, Indicium will return the row index of the records that match the given filter condition. For example:

<http://server/indicium/iam/123/project?$seek=contains(name,'my project')>

The response of a $seek request will look like this:

"row_index@odata.type": "#Int64",
"row_index": 1,
"name":"my project"
"row_index@odata.type": "#Int64",
"row_index": 2,
"name":"this is also my project"


The $expand operation allows you to select any properties from the entity on the other side of the navigation property. The navigation properties for lookups will always lead to the entity that contains the display property of the lookup, which is not necessarily the same entity as the source_tab of the corresponding lookup reference.


  • <http://server/indicium/api/123/sub_project?$expand=transl_project_id>
    To select all columns from sub_project and project.
  • <http://server/indicium/api/123/sub_project?$select=project_id&$expand=transl_project_id($select=description)> To select project_id from sub_project and description from project.

When using $expand, you can use all of the supported operations such as $select, $filter, $orderby, $prefilter on the target entity, but for now only $select is supported on the source entity in the $expand clause.

The response of an $expand request will look like this:

"name":"Board of supervisors",
"sub_project_name":"Board of supervisors",
"start_date_and_end_date":"2006-08-18 | 2007-08-19",

You can use the $expand operation also for one-to-many (1:N) relationships. For example, to retrieve a parent record and its detail records in a single API call. To expand a detail within the context of a parent, the request should look like this:


For example: https://server/indicium/iam/appl/sales_order?$expand=detail_ref_sales_order_sales_order_line

Most of the supported operations such as $select and $filter can also be applied to the expanded detail records. They can be added as a semicolon-separated list between parentheses.

For example, to retrieve all sales orders including the order_id and order_line_id of their order lines with a cost greater than 10:

https://server/indicium/iam/appl/sales_order?$expand=detail_ref_sales_order_sales_order_line($select=sales_order_id,sales_order_line_id;$filter=sales_order_line_cost gt 10)

Apply with Groupby

You can also use the $apply=groupby clause to select lookup values, but this method should typically only be used when selecting properties that are part of the lookup reference or the display property, otherwise you're better off using $expand. All columns specified in the $apply=groupby clause will in fact be added to a GROUP BY clause in the resulting SQL statement.


  • <http://server/indicium/api/123/sub_project?$apply=groupby(project_id,transl_project_id/description)>
    To select _project_id_ from _sub_project_ and _description_ from _project_ and group the result by both values.

The response of an $apply=groupby request will look the same as the response of an $expand request, depending on the properties you select or group by:


It's also possible to apply an aggregate function to the display property, when a key can result in multiple display values or if you want to provide an alias to the display value.


<http://server/indicium/api/123/sub_project?$apply=groupby((project_id),aggregate(transl_project_id/description with min as display_field)>

Apply with standard computations

Standard computations are a great way to extract data from a column without the need to store them in a different column with a default expression. For example, if the computation Year is called, the computation will return the year of the supplied dates. Or, if you want to get a part of a column value, you can use Substring to retrieve it.

Indicium supports the following standard computations:

DateDate, DateTimeDate$apply=Compute(Date(columnname) as Alias))
TimeTime, DateTimeTime$apply=Compute(Time(columnname) as Alias))
IsoweekDate, DateTimeInt$apply=Compute(Isoweek(columnname) as Alias))
WeekdayDate, DateTimeInt$apply=Compute(Weekday(columnname) as Alias))
YearDate, DateTimeInt$apply=Compute(Year(columnname) as Alias))
QuarterDate, DateTimeInt$apply=Compute(Quarter(columnname) as Alias))
MonthDate, DateTimeInt$apply=Compute(Month(columnname) as Alias))
DayDate, DateTimeInt$apply=Compute(Day(columnname) as Alias))
HourTime, DateTimeInt$apply=Compute(Hour(columnname) as Alias))
MinuteTime, DateTimeInt$apply=Compute(Minute(columnname) as Alias))
SecondTime, DateTimeInt$apply=Compute(Second(columnname) as Alias))
CeilingInt, Numeric, DecimalInt$apply=Compute(Ceiling(columnname) as Alias))
SubstringVarchar, Nvarchar, Char, NcharVarchar$apply=Compute(Substring(columnname, Startindex, Length) as Alias))
ConcatVarchar, Nvarchar, Char, NcharVarchar$apply=Compute(Substring(FirstColumn, SecondColumn) as Alias))

If $compute is used in the URL, the alias must be included in $select in order to be selected. The alias is also available in $filter, $orderby, and $groupby.


compute(year(column) as alias)


select datepart(year, column) as alias

Chain transformations in the $apply clause

Indicium Universal

In the $apply clause, you can do a single compute, groupby, aggregate, groupby+aggregate, or filter transformation. Added to this, you can chain the transformations indefinitely, with each transformation adding to the result set of the previous transformation.

To illustrate this, the request below selects all customers with more than five projects in the year 2021:

/project?$apply=compute(year(project_date) as project_year) 
/filter(project_year eq 2021)
/groupby((customer_id), aggregate(project_id with count as count))
/filter(count gt 5)&$select=customer_id

API Validation

Indicium validates that the user has access to the API and that the operations are allowed. For example:

  • When calling a task on a record, Indicium validates that this task is available by executing the context procedure.
  • When a request body contains parameters, the layout procedure is executed to ensure that the parameters can be edited by the user executing the API.
  • The same applies to inserting or patching a record. For each parameter in the request body, Indicium will execute a layout procedure to detect if the column is editable. If a column is not editable, the action aborts, and a status code "Forbidden" is returned. After patching the value, a default is executed that can change the column value. For the layout and the default procedure, the cursor_from_col_id will be filled in for each patched parameter.
  • If the column is a lookup control, the value is validated to ensure it is a valid option for the user.

TSFMessages header

In some cases, Indicium returns a "TSFMessages" header. This is a base64 encoded header and contains the information why the request failed. This can be a message that has been added by Indicium, for example, because not all mandatory parameters are filled in. It can also be a message from the database sent via the tsf_send_message procedure.


Base64 encoded messages can be decoded on this website:
Click the DECODE button to show the message in JSON format.

Example of a TSFMessages header:


Decoded result:

"TranslatedMessage":"Cannot save this record because not all of the required fields were entered. Required field: 'Name'."


To avoid exceeding the maximum URL length, it is possible to pass the query options part of the OData URL in the request body and append /$query to the resource path of the request URL.

When $query is used, the request has to be a POST request instead of a GET, and the request body has to use the content-type text/plain. For example:

POST http://server/indicium/iam/123/project/$query
Content-Type: text/plain

Request body: $filter=contains(name,'my project')&$select=project_id

The response will be the same as the response of a GET request with the query options specified in the request URL.

Query options specified in the request body are processed together with the query options specified in the request URL.

Extended Api

In addition to the standard OData Api Indicium offers several extended operations.

Query ParameterDescription
$deselectA comma-separated list of column id’s which are not requested. Use $deselect instead of $select if you want to exclude only a few columns and keep the rest. So, remove $select (to make all columns available) and add the exceptions with $deselect. This can help reduce the response size when, for example, binary data is stored in columns, i.e. $deselect=column_one, column_two.
$prefilterA comma-separated list. It can be used for adding prefilters to requests so you can select records from entities.
$seekReturns the row-index of the records that match the $seek expression.