BLG API

Overview

This document

This document aims at providing comprehensive and exhaustive instructions in order to use the BLG API.

Main characteristics

The BLG is a web service. This means that all of its capabilities can be used directly through a network connection, via the HTTP protocol.

The API protocol follows the REST standard. It has the following characteristics:

  • All calls are stateless. There is no established connection, and every request is completely independent.
  • The main goal of the BLG API is to manage and save data. Data are always shown as objects to the user, and can be modified via HTTP requests using the right HTTP verb.
  • Unless binary data must be transferred (file uploads for example), send and received data are always transferred as strict JSON.
  • Changes are always performed during the request: when you receive a successful response from your request, it means that changes were successfully performed, and are accessible right away from the other users.
  • Every data must match a schema. The BLG API is very strict, in order to be stored, data must match the schema of the object you want to send.
  • The API only accepts data that can be saved. If you try to send more data or more precise data (for example, an unknown field, a string too long or a date with time instead of a date), then your request won't be accepted.
  • Any access must be authenticated and the transmission must be secured via the HTTPS protocol.

Communication

Data access

Aggregation

Schema based objects

Searching in objects

File uploads


Get the right tools

Development tools

Before starting to code a program accessing the BLG API, you may want to take some time to familiarize with the API by sending some test requests.

A great tool to do that is DHC, a Chrome extension. DHC allows you to send any HTTP requests and get the pretty formatted JSON response.

You can also do tests with a command line tool like CURL.

As all GET request can be made directly via your web browser, you could also consider it a tool. However, the API won't return a nicely formatted JSON string. You can install an extension to automatically parse and format JSON.

Production tools

To use the API in your own programs, it is recommended to use some helper libraries. You can opt for different libraries to make the raw http requests, build the query string and decode JSON, or take a higher level REST client library to fulfill all those roles.

We don't provide any recommendation about this choice, as most high level languages come with a set of tools to make HTTP requests.


Send your first GET request

Introduction

This page shows you how to communicate with the BLG API.

GET Requests

Get requests are used to retrieve data from the API.
When performing a GET request, no changes will be made to the stored data.

Request data

Unlike some other requests, GET requests do not contain a body.
A GET request contains the following elements:

  • An URL: as every HTTP request, the url indicates to the BLG API which object should be returned. This field is mandatory
  • Query parameters: An URL is the path to the object you want to get, but sometimes, you may need to make a more precise request. For example, you may want to ask the API to filter out some results, to limit the number of results or to join more data.
  • Headers: An HTTP header is an environmental information that should be sent with every request. It contains information about your program, and can change the way the API will respond the request.
    Your spoken language and your credentials should be sent via HTTP headers.

Simple request example

As a first example, we will do a very simple GET request to the BLG API.
You can perform this GET request directly via your web browser or via a dedicated tool (like postman or curl).

For you're first request, we will simply ask the API for all the available namespaces. Here is the request:

{ "request": { "path": "" }, "response": { "content": [ "apiView", "batch", "common", "company", "dataRepository", "element", "security", "test", "user" ] } }

Authentication headers

As you can see, the request contains an Authentication header. This header contains the username and password used as credentials for the request.

Any non authenticated request will be refused by the API.

This header follows the HTTP Basic authentication standard, witch allows the API to be compatible with any standard REST client.

To create the authentication header, you simply have to concatenate your username, the string ':' and your password. Then you should use the base64 function on the resulting string and prepend 'Basic '.

Here is an example with jack / myPassword credentials:

user = "jack"
password = "myPassword"

result = "Basic " + base64(user + ":" + password)
# Here, result is "Basic amFjazpteVBhc3N3b3Jk"

Note that this "encryption" is not secure at all and is not meant to be. The HTTPS encryption layer and certificate check make the credentials transmission secure.

Here is an example request with bad credentials:

Here is an example with jack / myPassword credentials:

{ "request": { "path": "", "username": "tom", "password": "badpw" }, "response": { "headers": { "WWW-Authenticate": "Basic realm=\"BLG API\"" }, "statusCode": 401 } }

Query parameters

An important part of GET requests are query parameters. The BLG API uses its own way to serialize objects into query parameters. This will be the subject of the next section.


Sending complex data via query parameters

Introduction

Query parameters are just a key-value list, with both key and value being a string.

This is not sufficient for the BLG API needs. Indeed, some GET request can be performed with very complex filters, which would be very complex to describe with a simple key value data structure.

To solve this problem, we are transforming a JSON like data structure to a key value structure, compatible with query parameters. A tool to perform this operation is available here.

Simple example

First, let's start with an example. We want to serialize the following object:

{ "filter": { "age": { "$gt": 20 }, "name": { "$eq": "Colin" }, "enabled": { "$eq": true } }, "limit": 10 }

The serialization process is quite simple. We just take all the leaf values in this json object (string, number, boolean or null). Then, for every value, we get the path leading to it, separated by points.

For example, the path of the value 20 is filter.age.$gt.

We now have the following key-value map:

filter.age.$gt=20
filter.name.$eq=Colin
filter.enabled.$eq=true
limit=10

Then we can simply transform it to query string:

?filter.name.$eq=Colin&filter.age.$gt=20&filter.enabled.$eq=true&limit=10

Don't forget to url encode all the values, to escape '=' and '&' and '?' signs.

As you can see, the resulting query string remains human readable.

Indexed array

In order to make the query string more readable and briefer, you can simplify the indexed array containing only leaf values with the following notation: [value 1, value 2, value 3].

Values are coma separated. If the value is a string and contains a coma, you can't use this short notation.

Let's take the following example:

{ "filter": { "job": { "$in": ["developper", "journalist", "painter"] } } }

A possible serialization can be:

filter.job.$in.0=developper
filter.job.$in.1=journalist
filter.job.$in.2=painter

But it is also possible to use this representation:

filter.job.$in=[developper,journalist,painter]

And the corresponding query string is:

?filter.job.$in=[developper,journalist,painter]

This notation can only contain leaf values, not an array or a map.

We recommend using this notation when possible to make the query string more easy to read and debug, but using the standard notation will also work.

String escaping

Query parameters only allow strings to be passed.
This could lead to ambiguous situation. For example, "true" can mean the string "true" or the boolean value true. A number could also be a number or a string.

To solve this problem, it is possible to escape a string. Doing that is quite simple: you just need to add a simple quote at the begining your string.

It is not necessary to escape any other quote contained in the string.

For example, true string would be escaped as 'true, 10.5 as '10.5 and 'Hello as ''Hello.

Strings that needs to be esacped

You need to escape a string in the following situations:

  • The string could be read as a number (if it matches the regex ^-?[0-9]+\.?[0-9]*$).
  • The string is the keyword true, false or null.
  • The string could be interpreted as a quoted string (begin with a simple quote).
  • The string could be interpreted as an indexed array (begin and end with a "[" and "]" character).

Usage

Quoting all the strings would work, but we recommend quoting strings only if needed, to improve the readability of the query string.

Example

{ "filter": { "phone": { "$contains": "0323" }, "text": { "$contains": "'s" } } }

The right serialization is:

filter.phone.$contains='0323
filter.text.$contains=''s

And the corresponding query sting (unencoded):

?filter.phone.$contains='0323&filter.text.$contains=''s

Implementation

Tested implementations of this algorithm, using indexed array notation and quoting string only when required are available on demand in different programming language.

In situ example

Let's make a simple query exemple involving query parameters.

Here is the request without any parameters. We simply get the list of user we have access to (us):

{ "request": { "path": "/user/user" }, "response": { "content": [ { "id": 1, "username": "test", "password": "***", "email": "[email protected]", "firstname": "John", "lastname": "Smith", "address": { "@id": 1 }, "birthday": "2014-12-06", "active": true, "elements": null, "elementEvents": null, "sentMessages": null, "receivedMessages": null, "shares": null, "userGroups": null } ] } }

And with some query parameters. We ask the API to return all the available languages and join the user's group.

{ "request": { "path": "/user/user", "queryParams": { "join": [ "userGroups.group" ], "allLang": true } }, "response": { "content": [ { "id": 1, "username": "testUser", "password": "***", "email": "[email protected]", "firstname": "John", "lastname": "Smith", "address": { "@id": 1 }, "birthday": "2014-12-06", "active": true, "elements": null, "elementEvents": null, "sentMessages": null, "receivedMessages": null, "shares": null, "userGroups": [ { "group": { "id": 1, "name": "admin", "rights": null, "title": { "fr_FR": "Groupe super administrateur", "en_GB": "Super admin's group" } } } ] } ] } }

As you can see, the query now has some query parameters. You can click on the query string to show the original JSON object used to build the request.

Please note that the query strings are not url-encoded in this document to improve readability.


Error handling

Before starting to really use the API, there is one last thing we need to see: error handling.

Error format

In order to respect the REST standard, any error returned by the API will have a 4xx or 5xx error code. The following information is returned in case of error:

Status code
The HTTP status code is part of the HTTP protocol. The code that match the best the error is returned. For example, if you try to access a resource that doesn't exist, a 404 status code will be returned.
Identifier
The identifier is a short string identifying the error. You should base your error handling code on this identifier. Example: "unknowField". This field is part of the JSON response.
Context
the context is a key - value map containing more information about's the error context. For example, it can show with field is badly formatted. This field is part of the JSON response.
Message
the message is an English text describing the error. This text may change as the API evolves, so you should not parse it. This field is part of the JSON response.

Example

Some example will show how error are represented. Let's start with a simple error: we fetch an object from a namespace that doesn't exist.

{ "request": { "path": "/fakeNamespace/object" }, "response": { "statusCode": 404, "content": { "identifier": "unknownNamespace", "context": { "namespace": "fakeNamespace" }, "message": "Can't find object type named \"fakeNamespace\"." } } }

Now, let's try to add a condition on a field that doesn't exist

{ "request": { "path": "/user/user", "queryParams": { "filter": { "age": 30 } } }, "response": { "statusCode": 400, "content": { "identifier": "unknowField", "context": { "resourceType": "static", "namespace": "user", "objectName": "user", "fieldName": "age" }, "message": "Error with request parameter: field \"age\" doesn't exist in object \"user/user\"" } } }

API structure, object definitions

We have seen some request in the previous chapter, but it was mainly to understand the API's basis. Now, we are going to make real requests to get data from the API.

Structure

Every object stored in the API are organised into namespaces. A namespace is just a "folder" of objects, to keep things organised.

The main advantage of the API is that it is self-documented. You won't need to read this documentation to know which field is requiered for a specific type, you just need to ask the API.

Listing namespaces

We can start by requesting all the available namespaces. To do that, we just need to make a request on the API's root path. Here is the request:

{ "request": { "path": "" }, "response": { "content": [ "apiView", "batch", "client", "common", "company", "dataRepository", "element", "product", "reference", "security", "test", "user" ] } }

Listing objects in a namespace

Now that we can list namespaces, let's list objects available in this namespace. For example, we can list all the objects present in the dataRepository namespace:

{ "request": { "path": "/dataRepository" }, "response": { "content": [ "field", "fieldChoice", "fieldConstraint", "level", "make", "model", "physicalQuantity", "range", "rangeField", "repositoryContext", "repositoryContextField", "unit", "version" ] } }

As you can see, this is pretty straightforward.

You can also list all the namespaces and their objects as a tree with the tree flag. { "request": { "path": "/", "queryParams": {"tree": true} }, "response": { "content": { "activity":[ "equipmentRepair", "global", "lead", "meeting", "message", "opportunity", "phoneCall", "recommendation", "reminder" ], "article":[ "family", "part", "partStock", "subfamily", "warehouse" ], "batch":[ "export", "ftpAccount", "marketplace", "report", "reportContact" ], "cms":[ "catalog", "catalogCollection", "contentBlock", "dynamicPage", "makePage", "news", "picture", "rangePage", "rangePageMake", "recruitment", "redirect", "repositoryLevel", "repositoryLevelRange", "setting", "slide", "slideShow", "storePage", "video", "websiteContact" ], "common":[ "attachment", "filesCollection", "linkAttachment" ], "communication":[ "message" ], "core":[ "configurableEnum", "counter", "customObject", "history", "partitionLink", "tag", "tagGroup" ], "crm":[ "coOwnership", "company", "contact", "global", "zone" ], "dashboard":[ "widget", "widgetTemplate" ], "dataRepository":[ "contextField", "contextGroup", "contextType", "field", "fieldChoice", "make", "model", "physicalQuantity", "range", "rangeField", "repository", "unit" ], "document":[ "document", "documentType", "generableDocument", "importedDocument", "template", "templateField" ], "equipmentTracking":[ "device", "measurement", "position", "shock" ], "mail":[ "mail", "model", "smtpAccount" ], "movement":[ "deliveryOrder", "logisticsOrder", "movement", "movementLine", "movementType" ], "preference":[ "broadcastConfiguration", "dashboard", "fieldDisplay", "filterBar", "filterBarWidget", "filterConfiguration", "preferredSearch", "searchEngineDisplay" ], "purchase":[ "order", "orderPart" ], "repository":[ "equipment" ], "sale":[ "productServiceProfile", "quote", "quoteLine", "quotePayment", "saleDocument" ], "security":[ "entrepriseSecurity", "externalCredential", "group", "right", "userGroup" ], "shop":[ "store" ], "system":[ "jsCode", "jsTrigger", "multiAction", "parameters", "trigger" ], "user":[ "cart", "cartItem", "user", "userSettings" ], "workflow":[ "state", "target", "transition", "transitionSecurity", "workflow" ] } } }

By combining the tree and ref flag, you can get a full namespace / object name tree and the object references.

Requesting object's definition

Let's select an object in this namespace, for example, we can take the unit object type, which contains all the units known by the API.

We will request the definition of this object. To do that, you should request /api/ref/dataRepository/unit.

{ "request": { "path": "/ref/dataRepository/unit" }, "response": { "content": { "fields": { "physicalQuantity": { "primaryKey": true, "relation": "agregation", "target": { "namespace": "dataRepository", "objectName": "physicalQuantity" }, "inversedBy": "units", "type": "manyToOne" }, "name": { "primaryKey": true, "length": 64, "minLength": 1, "type": "string" }, "title": { "type": "lang" }, "abbreviation": { "type": "lang" }, "coefficient": { "type": "float" } } } } }

The attribute object contains all the fields defining a unit.

physicalQuantity
This field is a relation field. It means it connects the object with another object. Here, unit is connected to dataRepository/physicalQuantity with a manyToOne relation (there are multiple units corresponding to one physicalQuantity). For example, a unit can be connected to the "volume" physical quantity.
name
The unit has a name, which is a string with a size within 1 and 64 characters. For example, it could be "cubicMeter". The name is meant to be a unique identifier, without spaces.
title
The unit also contains a title, which is a more human readable version fo the name. Here, we can go for "cubic meter" as a name. This field is a lang field, which mean it can be translated.
abbreviation
Every unit has an abbreviation. This string can be "m³".
coefficient
this last field has the type float, which means it can contain a floating point number. In this example, the coefficient should be 1 as the cubic meter is the standard volume unit.

More example: { "request": { "path": "/ref/dataRepository/physicalQuantity" }, "response": { "content": { "fields": { "id": { "readonly": true, "primaryKey": true, "autoCount": true, "type": "integer" }, "name": { "length": 64, "minLength": 1, "unique": true, "type": "string" }, "title": { "type": "lang" }, "units": { "relation": "composition", "target": { "namespace": "dataRepository", "objectName": "unit" }, "inversedBy": "physicalQuantity", "type": "oneToMany" }, "fields": { "relation": "agregation", "target": { "namespace": "dataRepository", "objectName": "field" }, "inversedBy": "physicalQuantity", "default": [ ], "type": "oneToMany" }, "SI": { "length": 64, "minLength": 1, "type": "string" } } } } }


API Version

You can query the API version via the /info path. This could be usefull to have a script running on multiple major API versions.

{ "request": { "path": "/info" }, "response": { "content": { "version": { "major": 4, "minor": 0, "rev": "ad33b5b" } } } }

Get data from the API

Get units

To request all objects having the object name "object" in the namespace "namespace", you just need to make a GET request on /api/namespace/object. We will apply that to units:

{ "request": { "path": "/dataRepository/unit" }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicCentimeter", "title": "cubic centimeter", "abbreviation": "cm³", "coefficient": 0.000001 }, { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 }, { "physicalQuantity": { "@id": 1 }, "name": "gallon", "title": "gallon", "abbreviation": "gal", "coefficient": 0.003785411784 } ] } }

This result is actually a subset of the actual data stored in the API.
As you can see, the content is an array of objects, and the objects are the units' information.

As you can see, the returned data follow strictly the definition we fetched in the previous part.

More example: { "request": { "path": "/dataRepository/physicalQuantity", "queryParams": {"limit": 3} }, "response": { "content": [ { "id": 1, "name": "volume", "title": "volume", "units": null, "fields": null, "SI": "m³" }, { "id": 2, "name": "length", "title": "length", "units": null, "fields": null, "SI": "m" }, { "id": 3, "name": "mass", "title": "mass", "units": null, "fields": null, "SI": "kg" } ] } }

Filtering

Actually, there are more than 20 units in the API. That's not a lot, but we may just want a subset of them. The API allows us to filter the returned result: we can add conditions to the returned objects.

Equal operator

Let's take a simple example, we want a unit witch name is... cubicMeter. To do that, we will pass the following object as a query parameter:

As you can see, we apply the filter "$eq", which stands for equals on the name field.

{ "filter": { "name": { "$eq": "cubicMeter" } } }

Please note that the "$eq" filter is the default one, so the following syntax also works:

{ "filter": { "name": "cubicMeter" } }

Now, let's make an example:

{ "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "name": "cubicMeter" } } }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 } ] } }

Operators

Equal is the most simple and used operator: we want a value equal to the passed value. More operators are available to build more complex conditions.

Name Description Comments
$eq Check that the field's value is equal to the passed value Can apply to almost all fields, expect relations
$ne Not equals / different Same as equals
$lt, $lte, $gt, $gte Comparison operators for:
  • Lower than
  • Lower than or equals
  • Greater than
  • Greater than or equals
Only applies to numeric fields (float and integer) and dates
$begin, $end, $contains Check if a string begins, ends or contains with the given value Only apply to strings and text fields
$isNull Value should be true or false. Check whether the field is null or not Only apply to nullable fields
$in, $notIn Same as the equals/not equals operator but with an array of value. Check if the field's value is in / not in the array.
$empty Check whether or not an array is empty. Value should be true or false. Only apply to array fields

In operator example

{ "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "name": { "$in": [ "cubicCentimeter", "cubicMeter", "notAUnit" ] } } } }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicCentimeter", "title": "cubic centimeter", "abbreviation": "cm³", "coefficient": 0.000001 }, { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 } ] } }

More examples: { "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "coefficient": { "$gt": 3000 } } } }, "response": { "content": [ { "physicalQuantity": { "@id": 4 }, "name": "day", "title": "day", "abbreviation": "d", "coefficient": 86400 }, { "physicalQuantity": { "@id": 4 }, "name": "hour", "title": "hour", "abbreviation": "hr", "coefficient": 3600 } ] } } { "request": { "path": "/dataRepository/physicalQuantity", "queryParams": { "filter": { "SI": { "$eq": "kg" } } } }, "response": { "content": { "id": 3, "name": "mass", "title": "mass", "units": null, "fields": null, "SI": "kg" } } }

"And" and "or" special operators

To get even more precise filtering, it is required to combine multiple operators with an "$and" or "$or" special operators.

The special operators regroup multiple conditions. Here is the syntax:

{ "filter": { "$and": { "name": { "$contains": "cubic" }, "title": { "$contains": "meter" } } } }

The $or special operator can be used the same way.

Just like $eq is the default operator, $and is the default special operator. This means that no special operator will be interpreted as an $and operator.

The following condition is equivalent to the previous one:

{ "filter": { "name": { "$contains": "cubic" }, "title": { "$contains": "meter" } } }

For example, let's take units whose name are "cubicMeter" or whose title are "gallon"

{ "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "$or": { "name": "cubicMeter", "title": "gallon" } } } }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 }, { "physicalQuantity": { "@id": 1 }, "name": "gallon", "title": "gallon", "abbreviation": "gal", "coefficient": 0.003785411784 } ] } }

You can use $or operators in $and ones, and the inverse to create complex conditions.

More example: { "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "$or": { "coefficient": { "$gt": 3600 }, "abbreviation": "kg" } } } }, "response": { "content": [ { "physicalQuantity": { "@id": 3 }, "name": "kilogram", "title": "kilogram", "abbreviation": "kg", "coefficient": 1 }, { "physicalQuantity": { "@id": 4 }, "name": "day", "title": "day", "abbreviation": "d", "coefficient": 86400 } ] } }

Key duplication

Take a closer look at the previous example. What if we wanted to take all units with the name "cubicMeter" or "gallon", without using the $in operator ?

That would be impossible with the current syntax as it would lead to duplicated JSON keys.

In fact, the current syntax is a simplified syntax. The extended syntax allows you to use JSON arrays in special operators. Here is an example:

{ "filter": { "$or": [ { "name": "cubicMeter" },{ "name": "gallon" } ] } }

And the corresponding request:

{ "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "$or": [ { "name": "cubicMeter" },{ "name": "gallon" } ] } } }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 }, { "physicalQuantity": { "@id": 1 }, "name": "gallon", "title": "gallon", "abbreviation": "gal", "coefficient": 0.003785411784 } ] } }

More example: { "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "$or": [ { "$and": { "name": { "$end": "Meter" }, "coefficient": 1 } },{ "$and": { "name": "pound", "abbreviation": "lb" } } ] } } }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 }, { "physicalQuantity": { "@id": 2 }, "name": "meter", "title": "meter", "abbreviation": "m", "coefficient": 1 }, { "physicalQuantity": { "@id": 3 }, "name": "pound", "title": "pound", "abbreviation": "lb", "coefficient": 0.45359237 } ] } }

Conditions on relations

We have seen all the available operators and special operators. Now, what if we wanted to filter something based on a relation. E.g. filter all the units belonging to a certain physicalQuantity.

An example will be far easier to understand than a long sentence, so here is the request:

{ "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "physicalQuantity": { "id": 3 } } } }, "response": { "content": [ { "physicalQuantity": { "@id": 3 }, "name": "kilogram", "title": "kilogram", "abbreviation": "kg", "coefficient": 1 }, { "physicalQuantity": { "@id": 3 }, "name": "pound", "title": "pound", "abbreviation": "lb", "coefficient": 0.45359237 }, { "physicalQuantity": { "@id": 3 }, "name": "tonne", "title": "tonne", "abbreviation": "t", "coefficient": 1000 } ] } }

As you can see, the physicalQuantity filter is a sub-object containing the conditions we add on the physicalQuantity.

We can also use another field than "id" for the filtering: { "request": { "path": "/dataRepository/unit", "queryParams": { "filter": { "physicalQuantity": { "name": "mass" } } } }, "response": { "content": [ { "physicalQuantity": { "@id": 3 }, "name": "kilogram", "title": "kilogram", "abbreviation": "kg", "coefficient": 1 }, { "physicalQuantity": { "@id": 3 }, "name": "pound", "title": "pound", "abbreviation": "lb", "coefficient": 0.45359237 }, { "physicalQuantity": { "@id": 3 }, "name": "tonne", "title": "tonne", "abbreviation": "t", "coefficient": 1000 } ] } } .

Joining data

Now, we can GET the list of physical quantities and then filter all the units corresponding to a given physicalQuantity using filters.

To fetch a lot of related data, this could be very inefficient as you have to make a large quantity of request. To solve that problem, we have implemented joins. Joins are a way to retrieve a relational object in a single request.

The syntax is quite simple: you need to pass an array of string named join. This array will contain the name of the fields you want to join.

We can fetch the "mass" physicalQuantity and join all its units:

{ "request": { "path": "/dataRepository/physicalQuantity", "queryParams": { "filter": { "name": "mass" }, "join": ["units"] } }, "response": { "content": [ { "id": 3, "name": "mass", "title": "mass", "units": [ { "name": "kilogram", "title": "kilogram", "abbreviation": "kg", "coefficient": 1 }, { "name": "pound", "title": "pound", "abbreviation": "lb", "coefficient": 0.45359237 }, { "name": "tonne", "title": "tonne", "abbreviation": "t", "coefficient": 1000 } ], "fields": null, "SI": "kg" } ] } }

This simple syntax allows you to fetch all the data you need in a single request.

First, limit and count

Let's finish this part with something simple. To implement pagination in your application or cut huge requests, you need to limit the number or result and start at a given position.

You can count the amount of unit stored in the API using the { "request": { "path": "/dataRepository/unit", "queryParams": { "count": true } }, "response": { "content": { "count": 24 } } } parameter. Then, you can paginate the result using { "request": { "path": "/dataRepository/unit", "queryParams": { "first": 6, "limit": 2 } }, "response": { "content": [ { "physicalQuantity": { "@id": 2 }, "name": "inch", "title": "inch", "abbreviation": "in", "coefficient": 0.0254 }, { "physicalQuantity": { "@id": 2 }, "name": "kilometer", "title": "kilometer", "abbreviation": "km", "coefficient": 1000 } ] } } .

Projection

Projecting data is the action of filtering out some fields of the result. For example, you may only be interested in some field of a result. Then, you can ask the API to only return those fields.

This can lead to huge performance improvements and bandwidth savings.

To use projection, simply add the field array in your query parameters. This array contains the list of the fields you want to keep in the response. You can specify sub-fields using a dot (".").

The id field is always included.

{ "request": { "path": "/dataRepository/unit", "queryParams": { "join": [ "physicalQuantity" ], "field": [ "name", "physicalQuantity.name" ], "limit": 3 } }, "response": { "content": [ { "physicalQuantity": { "name": "volume" }, "name": "cubicCentimeter" },{ "physicalQuantity": { "name": "volume" }, "name": "cubicMeter" },{ "physicalQuantity": { "name": "volume" }, "name": "gallon" } ] } }

Ordering

It is sometimes useful to order your results. Doing this is quite simple. You just need the order query parameter.

The order parameter contains an array with the field used for the order as the first value, and the second value can be asc for ascending order (low to high) or desc for descending sort (high to low).

{ "request": { "path": "/dataRepository/unit", "queryParams": { "order": [ "name", "asc" ], "limit": 2 } }, "response": { "content": [ { "physicalQuantity": { "@id": 2 }, "name": "centimeter", "title": "centimeter", "abbreviation": "cm", "coefficient": 0.01 },{ "physicalQuantity": { "@id": 1 }, "name": "cubicCentimeter", "title": "cubic centimeter", "abbreviation": "cm³", "coefficient": 0.000001 } ] } }

It is also possible to use multiple sort. This is useful if the primary sort field is not unique or contains null fields.

You can see an example here : { "request": { "path": "/dataRepository/unit", "queryParams": { "order": [ [ "physicalQuantity", "asc" ], [ "name", "asc" ] ], "limit": 2 } }, "response": { "content": [ { "physicalQuantity": { "@id": 1 }, "name": "cubicCentimeter", "title": "cubic centimeter", "abbreviation": "cm³", "coefficient": 0.000001 }, { "physicalQuantity": { "@id": 1 }, "name": "cubicMeter", "title": "cubic meter", "abbreviation": "m³", "coefficient": 1 } ] } } .


GET one object

All the objects, except some special read only objects (like unit) have an id field. This id can be numerical for static object and a string for schemaBased objects (we'll talk about them later, in the next chapter).

Using the id is fasted way to get the object. You can fetch an object using a filter on the id field, or via a get query with a different path. You need to request /api/namespace/objectName/id.

For example:

{ "request": { "path": "/dataRepository/physicalQuantity/3" }, "response": { "content": { "id": 3, "name": "mass", "title": "mass", "units": null, "fields": null, "SI": "kg" } } }

You can't use filters on such requests as there is always one target object, so there is no point on filtering it. However, you can still use joins.


PUT data to the API

For now, all our actions have been limited to read only get requests. We'll see put requests in this section, to add new data in the API.

The unit object was a good training for GET requests as units are already loaded into the API, but you won't be provided with write access to it, so we'll be using another object.

First PUT request

The BLG API allows you to manage a website's content. A standard element is a cms page, which is a simple html static page you can add to your website. The path of the object is cms/dynamicPage.

First, you can grab the definition of cms/dynamicPage. The request can be found here: { "request": { "path": "/ref/cms/dynamicPage" }, "response": { "content": { "fields": { "id": { "title": "id", "readonly": true, "primaryKey": true, "autoCount": true, "type": "integer" }, "title": { "title": "titre", "length": 255, "type": "string" }, "path": { "title": "Chemin", "pattern": "^[a-zA-Z0-9._-]+$", "length": 255, "unique": "path", "type": "string" }, "createDate": { "title": "créée le", "auto": true, "readonly": true, "type": "datetime" }, "lastUpdate": { "auto": true, "autoUpdate": true, "readonly": true, "type": "datetime" }, "content": { "title": "contenu", "type": "text" }, "slideShow": { "title": "Diaporama", "nullable": true, "relation": "composition", "target": { "namespace": "cms", "objectName": "slideShow" }, "type": "oneToOne" }, "description": { "title": "description", "length": 512, "type": "string" } } } } } .

We can create some content that corresponds to this definition. For example, this JSON object:

{ "description": "A page dealing with tractors", "content": "<h1>Tractors</h1><p>New paragraph</p>", "title": "Tractors", "path": "about-tractors" }

Now, we can make our first put request. To do that, you just need make a PUT request containing the JSON data you want to add on the object's listing url.

{ "request": { "method": "PUT", "path": "/cms/dynamicPage", "content": { "description": "A page dealing with tractors", "content": "<h1>Tractors</h1><p>New paragraph</p>", "title": "Tractors", "path": "about-tractors" } }, "response": { "statusCode": 201, "content": { "id": 1, "createDate": "2015-06-05T14:13:55+0000", "lastUpdate": "2015-06-05T14:13:55+0000" } } }

If the PUT runs well, you will receive a 201 Created status code and as a content, the id of the object that was just inserted.

You can now retrieve your newly inserted object with a get request:

{ "request": { "method": "GET", "path": "/cms/dynamicPage/1" }, "response": { "content": { "id": 1, "title": "Tractors", "path": "tractors", "createDate": "2015-06-05T14:13:55+0000", "lastUpdate": "2015-06-05T14:13:55+0000", "content": "<h1>Tractors</h1><p>New paragraph</p>", "slideShow": null, "description": "A page dealing with tractors" } } }

Modifying data

You can update an object using a PATCH request. You don't need to send the whole object again, only the fields you want to change. The path you need to call is the object's path containing the id, as in a GET by id request.

{ "request": { "method": "PATCH", "path": "/cms/dynamicPage/1", "content": { "description": "All you ever wanted to know about tractors !" } }, "response": { "statusCode": 200, "content": {} } }

You have now modified your cms page !


DELETE an object from the API

Even easier than the PATCH request, the DELETE request is sent without any content on the requested object path (with id), to delete it.

{ "request": { "method": "DELETE", "path": "/cms/dynamicPage/1" }, "response": { "statusCode": 204 } }

Data types

Primitive types

Those are the primitive types available:

Type Description Constraints
integer An integer number.
  • min: the minimum value
  • max: the maximum value
float A floating number.
  • min: the minimum value
  • max: the maximum value
string A string of characters.
  • minLength: the minimum length of the string
  • maxLength: the maximum length of the string
boolean A boolean value (true or false). None
date A date without time.
The date is represented as an ISO-8601 formatted string (ex: "2015-06-09").
  • mustBeFuture: Indicated that the date should be at least today date
datetime A date with time.
The date is represented as an ISO-8601 formatted string (ex: "2015-06-09T19:45:00Z" or "2015-06-09T17:45:00+2000").
  • mustBeFuture: Indicated that the datetime should be at least current datetime
object A JSON object.
Warning: you cannot filter on properties of such objects.
None

Schema-based complex types

In this section, complex types are detailled, note that those types are only available on schema-based objects (see section Schema based objects):


EmbededObject

Those are JSON objects but unlike type object, they are defined by a schema which they need to respect, the definition is the same as usual object.


ApproximateDate

An approximate date is used to represent a date which certain part are unknow but still represented as a date for easy filtering.
That type is an embededObject with a predefined schema composed of the fields date & precision:

{ "date": { "type": "date" }, "precision": { "type": "integer" } }

Where date is a simple date and precision is an integer which can take those values:

Value Signification Comment
0 APPROXIMATE YEAR The year is approximate (but the year is still precised in the date)
1 YEAR The year is accurate but not the month & day.
2 MONTH The year & the month are accurate but not the day.
3 DAY The full date is accurate.

Generally the month is equals to June (06) if the month is inaccurate, and an inacurrate day is represented by the 15th.


Aggregation

Overview

The aggregation system is useful to make computation on a set of results on the API side. Although very powerful, complex aggregation might need some time to compute on large data set, therefore it is not recommanded to make an heavy usage of complex requests.

Like the other read features of the API, aggregation are composed of the query parameters. It uses the query parameters group and transform.

All the other query parameters (filter, field, ...) are applied first, then the transform paremeter is applied and finally the group parameter.

The group parameter

The group parameter is the core of the aggregation. You define how you want to group the results and the operation to apply via this parameter.

The group parameter has the following syntax:

<agregation>
{
	“by”: {
		(“<target_name>”: <expression>)+
	},
	“operation”: {
		(“<target_name>”: <accumulator>)+
	},
	“field”: [(“<field_name>”)+],
	“postTransform”: {
		(“<target_name>”: <expression>)+
	}
}

Where:

Expression

An expression can reprensent a field path, a literal or an operator expression.

A field path is just a string representing the field name, embeded fields can be acces via the dot notation.
Example: title or object.name.

A literal could be either a numeric value, a boolean value or a string literal. Since field are accessed directly via a string, a string literal must begin a double quote « " ».
Example: 42, true or "myString.

Operator expression

An operator expression is like a function which can take a list of parameters.

It has the following structure:

<operator>
[“<operator_name>” (, <expression>)+]

In the following sections, the different operators are defined.

Numeric operators

Those operators take numeric expressions as parameter.

Operator name Arity Description
add N Add two or more values.
subtract 2 Subtract the second value to the first one.
multiply N Multiply two or more values.
divide 2 Divide the first value by the second one.
mod 2 Returns the remainder of the first number divided by the second.

Example:

["add", "myField1", "myField2"] // Add myField1 to myField2
["divide", "myField1", 2] // Divide myField1 by 2
["subtract", 0, "myField1"] // return the negative value of myField1

Date operators

Those operators take a single parameter, a date expresion and return a number.

Operator name Description
year Return the year (ex: 2015).
month Return the month between 1 and 12.
hour Return the hour between 0 and 23.
minute Return the minute between 0 to 59.
second Return the minute between 0 to 59.
dayOfYear Return the day of the year between 1 to 365.
dayOfMonth Return the day of the month between 1 to 31.
dayOfWeek Return the the day of the week between 1 (Sunday) to 7 (Saturday).

Example:

// In the following example we consider myDate to be equals to 2015-09-03 18:05:30 (Thursday)
["year", "myDate"] // 2015
["dayOfYear", "myDate"] // 248
["dayOfMonth", "myDate"] // 3
["dayOfWeek", "myDate"] // 5

Array operators

Those operators take a single parameter, an array expresion.

Operator name Description
empty Returns true if the array is empty.

Example:

["empty", "myField1"] // Return true if myField1 is an empty array

Comparison operators

Those operators take two parameters and return a boolean.

Operator name Parameters type Description
eq Anything Returns true if the values are equivalent.
ne Anything Returns true if the values are not equivalent.
gt Number or date Returns true if the first value is greater than the second.
gte Number or date Returns true if the first value is greater than or equal to the second.
lt Number or date Returns true if the first value is less than the second.
lte Number or date Returns true if the first value is less than or equal to the second.

Example:

["eq", "myField1", "myField2"] // Return true if myField1 is the equivalent to myField2
["gt", "myField1", "myField2"] // Return true if myField1 is greater than myField2

Logical operators

Those operators return a boolean and all parameters must be boolean expressions.

Operator name Arity Description
and N Returns true only when all its expressions evaluate to true.
or N Returns true when any of its expressions evaluates to true.
not 1 Returns the boolean value that is the opposite of its argument expression.

Example:

["and", ["eq", "status", "\"success"], ["gt", "score", 5]] // Return true if the status is "success" and the score is greater than 5
["or", ["eq", "status", "\"success"], ["eq", "status", "\"fail"]] // Return true if status is equals to "success" or "fail"
["not", "myBooleanField"] // Return true if myBooleanField is equals to false

Condition operator

The condition operator ("cond") is useful to make conditions, it takes exactly one operator but it's syntax is a bit different from the other operator:

[“cond”,
	({if: <boolean_expression>, then: <expression>})+
	{else: <expression>}
]

The <boolean_expression> is an expression which return a boolean.
The <expression> could be of any type but all the expressions in the then and else clauses must have the same return type.
Note: there should be at least one if condition and one else condition (at last position).

// In the following example we return 5 if the status is "success", -5 if "fail" and 0 otherwise
["cond",
	{"if": ["eq", "status", "\"success"], "then": 5},
	{"if": ["eq", "status", "\"fail"], "then": -5},
	{"else": 0}
]

IfNull operator

The "ifNull" operator is used to return a default value when a field is null or not set.
The first parameter is the expression to evaluate (typically a field name) and the second one if the default value. If the expression is not null the expression is returned.
Note: the default value must have the same type as the expression evaluated.

Example:

["ifNull", "score", 0] // Return 0 if the score is null or not set, otherwise return the score

Accumulator

Accumulators are placed in the operation object, those operation applied when grouping the documents. They all take one parameter at the exception of the operator count which has an optional parameter.

Accumulator name Parameter Description
count boolean expression (facultative) Return the number of element which match the provided boolean expression.
If no expression is provided, count the number of objects in the group.
sum numeric expression Compute the sum of the expressions in the group.
avg numeric expression Compute the average of the expressions in the group.
max numeric expression Compute the maximum value of the expressions in the group.
min numeric expression Compute the minimum value of the expressions in the group.
first expression Return the first value for the expression.

Example

In the following example, we consider we have the following set of objects:

[ {"id": 1, "item": "A", "quantity": 5}, {"id": 2, "item": "A", "quantity": 10}, {"id": 3, "item": "B", "quantity": 15}, {"id": 4, "item": "B", "quantity": 5}, {"id": 5, "item": "B", "quantity": 20}, {"id": 6, "item": "C", "quantity": 10} ]

We apply the following group:

{ "group": { "by": { "item": "item" }, "operation": { "nb": ["count"], "nbLessThan10": ["count", ["lt", "quantity", 10]], "total": ["sum", "quantity"], "average": ["avg", "quantity"], "min": ["min", "quantity"], "max": ["max", "quantity"], "first": ["first", "id"] } } }

We get the following result:

[ { "item": "A", "nb": 2, "nbLessThan10": 1, "total": 15, "average": 7.5, "min": 5, "max": 10, "first": 1 }, { "item": "B", "nb": 3, "nbLessThan10": 1, "total": 40, "average": 13.333333, "min": 5, "max": 20, "first": 3 }, { "item": "C", "nb": 1, "nbLessThan10": 0, "total": 10, "average": 10, "min": 10, "max": 10, "first": 6 } ]

Sub aggregation

You can achieve sub-aggregation using the special accumulator group, the second parameter is the aggregation.

For instance, if we want to get the number of equipment per make and per models in one request we can do the following aggregation:

{ "request": { "method": "GET", "path": "/repository/all", "queryParams": { "group": { "by": {"make": "make"}, "operation": { "count": ["sum", "count"], "models": ["group", { "by": {"model": "model"}, "operation": { "count": ["count"] } }] } } } }, "response": { "statusCode": 200, "content": [ { "make":{ "id": 2, "name": "fella", "title": "Fella" }, "count": 1, "models":[ { "count": 1, "model":{"id": 7, "name": "tm640Fella", "title": "TM640"}, "make":{"id": 2, "name": "fella", "title": "Fella"} } ] } ] } }

Anonymous sub-aggregation

If you want to use sub-aggregation to compute fields but don't want the sub-aggregation results, you can declare anonymous sub-aggregation by setting '_' as the target name.
Example:

"_": ["group", ...]

Order & projection

Order (order parameter) and projection (field parameter) can be done inside a group they follow the same rules as mentionned in the List objects section.

For instance, we can add order to the previous aggregation to organize results by count (descending):

{ "request": { "method": "GET", "path": "/repository/all", "queryParams": { "group": { "by": {"make": "make"}, "operation": { "count": ["sum", "count"], "models": ["group", { "by": {"model": "model"}, "operation": { "count": ["count"] }, "order": ["count", "desc"] }] }, "order": ["count", "desc"] } } }, "response": { "statusCode": 200, "content": [ { "make": { "id": 45, "name": "claas", "title": "Claas" }, "count": 59, "models": [ { "count": 3, "model": { "id": 957, "name": "arion640CisClaas", "title": "Arion 640 Cis " }, "make": { "id": 45, "name": "claas", "title": "Claas" } }, { "count": 2, "model": { "id": 954, "name": "arion620CClaas", "title": "Arion 620 C" }, "make": { "id": 45, "name": "claas", "title": "Claas" } } ] } ] } }

Transform

Transformations are used to compute new fields on objects.
You can define transformations directly into the request in the transform query parameter or you can apply transformations after a group operation by specifying the postTranform key inside a group object.

Warning: Transform are applied after the field clause, therefore you cannot use a transform field in a field clause.

Transformations are defined as an object which has the following structure:

{
	("<target_name>": <expression>)+
}

Any expression can be used (see Expression section), including other field, which can be useful to rename or extract embeded fields.

Example:

{
	"score": ["multiply", "score", 2],
	"myNewField1": "myField1",
	"myNewField2": "myObject.myEmbededField"
}

Or in or previous request that count equipments in make and models if we want to only have the name of the models and makes:

{ "request": { "method": "GET", "path": "/repository/all", "queryParams": { "group": { "by": {"make": "make"}, "operation": { "count": ["sum", "count"], "models": ["group", { "by": {"model": "model"}, "operation": { "count": ["count"] }, "postTransform": { "model": "model.name" }, "order": ["count", "desc"] }] }, "postTransform": { "make": "make.name" }, "field": ["count", "models.model", "models.count"], "order": ["count", "desc"] } } }, "response": { "statusCode": 200, "content": [ { "make": "claas", "count": 59, "models": [ { "count": 3, "model": "arion640CisClaas" }, { "count": 2, "model": "arion620CClaas" }, { "count": 2, "model": "arion630CisClaas" } ] } ] } }

Examples

{ "request": { "method": "GET", "path": "/repository/all", "queryParams": { "group": { "by": {"marketplaces": "marketplaces.name"}, "operation": { "count": ["count"] }, "order": ["count", "desc"] } } }, "response": { "statusCode": 200, "content": [ { "count": 6, "marketplaces": "blgdiffusion1" }, { "count": 5, "marketplaces": "blgdiffusion2" } ] } }

{ "request": { "method": "GET", "path": "/crm/company", "queryParams": { "group": { "by": {"salesperson": "relationCommercial"}, "operation": { "count": ["count"] }, "postTransform": { "salesperson": "salesperson.firstName" }, "order": ["count", "desc"] } } }, "response": { "statusCode": 200, "content": [ { "count": 2085, "salesperson": "Sébastien" }, { "count": 1476, "salesperson": "Arnaud" }, { "count": 1308, "salesperson": "Guillaume" }, { "count": 260, "salesperson": "Marc" }, { "count": 124, "salesperson": "Julien" } ] } }

{ "request": { "method": "GET", "path": "/repository/all", "queryParams": { "group": { "by": { "month": ["month", "createdAt"], "year": ["year", "createdAt"] }, "operation": { "count": ["count"] }, "order": [ ["year", "asc"], ["month", "asc"] ] } } }, "response": { "statusCode": 200, "content": [ { "count": 22, "month": 11, "year": 2014 }, { "count": 14, "month": 12, "year": 2014 }, { "count": 13, "month": 1, "year": 2015 }, { "count": 12, "month": 2, "year": 2015 }, { "count": 17, "month": 3, "year": 2015 }, { "count": 16, "month": 4, "year": 2015 }, { "count": 21, "month": 5, "year": 2015 }, { "count": 15, "month": 6, "year": 2015 }, { "count": 22, "month": 7, "year": 2015 }, { "count": 3, "month": 8, "year": 2015 } ] } }

Schema based objects

Why schema based objects ?

For now, we have been using objects with a strict schema. Some type of data can't fit this rule. For example, we want to fill a datasheet about farm equipments.

Some product might be vineyardTractor, and some other a farmTractor. These products won't have exactly the same fields, but they still belong to the farmEquipment object type and we want to be able to retrieve all the tractors, whatever type they might be.

The schema based object is still bonded to a strict schema, but this schema will depend on the range of the object, and its context.

Repository, context and range

The definition of a schema based object will vary depending on tree parameters: the repository, the context and the range.

The repository, or universe, is the business sector category. It is intended to categorise the ranges by industry. The repository could be farmEquipment, handlingEquipment, constructionEquipment...

The context some field common to all the ranges and depending on the origin of the equipment. For example, if you want to sell a used equipment, you can use the "used" context, containing the firstHand, hours... The "new" context won't contain the firstHand and hours fields as the equipment is brand new.

The range of the object defines its type. In the farm equipment repository, it could be farmTractor, combineHarvester, trailedSprayer...

Get the definition

Now that you know the basis we'll see how to GET the definition of a schema based object. The path is the same as in a static object, but we add two more parameters to the path: context and range. The namespace is repository and the object name is the universe of the equipment (like farmEquipment).

The path will be:

  • /api/ref/repository/universe/context/range, to get the full definition
  • /api/ref/repository/universe/context, for the fields common to all ranges in the context
  • /api/ref/repository/universe, to get the base definition of the universe

Here is an example of full definition of the { "request": { "path": "/ref/repository/farmEquipment/used/farmTractor" }, "response": { "content": { "fields": { "id": { "readonly": true, "title": { "en_GB": "id", "fr_FR": "id", "fr_CH": "id" }, "draft": true, "type": "string" }, "context": { "fields": [ "name", "title" ], "draft": true, "title": { "en_GB": "fleet", "fr_FR": "parc", "fr_CH": "parc" }, "type": "partition", "target": { "namespace": "dataRepository", "objectName": "repositoryContext" } }, "range": { "fields": [ "name", "title" ], "draft": true, "type": "partition", "target": { "namespace": "dataRepository", "objectName": "range" } }, "make": { "fields": [ "name", "title" ], "draft": true, "title": { "en_GB": "make", "fr_FR": "marque", "fr_CH": "marque" }, "type": "partition", "target": { "namespace": "dataRepository", "objectName": "make" } }, "model": { "fields": [ "name", "title" ], "draft": true, "title": { "en_GB": "model", "fr_FR": "modèle", "fr_CH": "modèle" }, "type": "partition", "target": { "namespace": "dataRepository", "objectName": "model" } }, "reference": { "min_length": 1, "length": 32, "title": { "en_GB": "reference", "fr_FR": "référence", "fr_CH": "reference" }, "type": "string" }, "tags": { "type": "tag[]" }, "publicGallery": { "title": { "en_GB": "public", "fr_FR": "publique", "fr_CH": "publique" }, "draft": true, "auto": true, "type": "filesCollection" }, "privateGallery": { "title": { "en_GB": "private", "fr_FR": "privé", "fr_CH": "privé" }, "draft": true, "auto": true, "type": "filesCollection" }, "owner": { "fields": [ "companyName", "firstName", "lastName", "address" ], "nullable": true, "type": "partition", "target": { "namespace": "core", "objectName": "crm" } }, "comment": { "title": { "en_GB": "internal comment", "fr_FR": "commentaire interne", "fr_CH": "commentaire interne" }, "nullable": true, "type": "text" }, "createdAt": { "auto": true, "title": { "en_GB": "created at", "fr_FR": "créer le", "fr_CH": "créer le" }, "draft": true, "type": "datetime" }, "firstHand": { "title": "first hand", "nullable": true, "type": "boolean" }, "year": { "title": "year", "nullable": true, "type": "integer" }, "price": { "title": "price", "type": "float" }, "retailerPrice": { "title": "retailer price", "nullable": true, "type": "float" }, "purchasePrice": { "title": "purchase price", "nullable": true, "type": "float" }, "exportPrice": { "title": "export price", "nullable": true, "type": "float" }, "availability": { "title": "availability", "nullable": true, "choices": [ { "name": "available", "title": "available" }, { "name": "availableFrom", "title": "available from" }, { "name": "nc", "title": "not communicated" }, { "name": "unavailable", "title": "unavailable" } ], "type": "choice" }, "condition": { "title": "condition", "choices": [ { "name": "averageCondition", "title": "average condition" }, { "name": "goodCondition", "title": "good condition" }, { "name": "nc", "title": "not communicated" }, { "name": "new", "title": "new" }, { "name": "repairsNeeded", "title": "repairs needed" }, { "name": "veryGoodCondition", "title": "very good condition" } ], "type": "choice" }, "tag": { "title": "tag", "nullable": true, "choices": [ { "name": "new", "title": "new" }, { "name": "serviceWarranty", "title": "service & warranty" }, { "name": "sold", "title": "sold" } ], "type": "choice" }, "approximateYear": { "title": "approximate year", "nullable": true, "type": "boolean" }, "publicComment": { "title": "public comment", "nullable": true, "type": "text" }, "serialNumber": { "title": "serial number", "nullable": true, "type": "string" }, "hours": { "title": "hours", "physicalQuantity": "time", "unit": "hour", "type": "integer" }, "gear": { "title": "gear", "choices": [ { "name": "continuouslyVariable", "title": "continuously variable" }, { "name": "fullPowershift", "title": "full-powershift" }, { "name": "hiLow", "title": "hi-low" }, { "name": "hydrostatic", "title": "hydrostatic" }, { "name": "nc", "title": "not communicated" }, { "name": "semiPowershift", "title": "semi-powershift" }, { "name": "synchroGear", "title": "synchro gear" } ], "type": "choice" }, "power": { "title": "power", "physicalQuantity": "power", "unit": "horse", "max": "1000000", "min": "0", "type": "float" }, "wheelDrive": { "title": "wheel drive", "choices": [ { "name": "2RM", "title": "2 wheel drive" }, { "name": "4RM", "title": "4 wheel drive" }, { "name": "nc", "title": "not communicated" }, { "name": "tracks", "title": "tracks" } ], "type": "choice" }, "specificEquipmentFarmTractor": { "title": "specific equipment", "nullable": true, "choices": [ { "name": "airBrakeSystem", "title": "air-brake system" }, { "name": "frontAxleSuspensionSystem", "title": "front axle suspension system" }, { "name": "frontEndLoader", "title": "front-end-loader" }, { "name": "frontLift", "title": "front lift" }, { "name": "frontPto", "title": "front pto" }, { "name": "reverseStation", "title": "reverse station" }, { "name": "rollCage", "title": "roll cage" } ], "type": "multipleChoice" }, "dimensionOfFrontTires": { "title": "dimension of front tires", "nullable": true, "type": "string" }, "dimensionOfRearTires": { "title": "dimension of rear tires", "nullable": true, "type": "string" }, "wearOfRearTires": { "title": "wear of rear tires", "max": "1", "min": "0", "type": "float" }, "wearOfFrontTires": { "title": "wear of front tires", "physicalQuantity": "ratio", "max": "1", "min": "0", "type": "float" }, "numberOfDistributors": { "title": "number of distributors", "nullable": true, "type": "integer" }, "generalEquipment": { "title": "generalEquipment", "nullable": true, "choices": [ { "name": "airConditioning", "title": "air conditioning" }, { "name": "cab", "title": "cabin" } ], "type": "multipleChoice" }, "category": { "fields": [ "name", "title" ], "readonly": true, "type": "partition", "target": { "namespace": "dataRepository", "objectName": "level" } }, "repository": { "fields": [ "name", "title" ], "readonly": true, "type": "partition", "target": { "namespace": "dataRepository", "objectName": "level" } } } } } } .

PUT Request

Now that we have the object definition, we can make our PUT request:

{ "request": { "method": "PUT", "path": "/repository/farmEquipment", "content": { "reference": "30858", "condition": "veryGoodCondition", "hours": 400, "year": 2012, "price": 80000, "retailerPrice": 0, "purchasePrice": 0, "serialNumber": "", "createdAt": "2013-10-14T06:22:58+0000", "power": 184, "wheelDrive": "4RM", "dimensionOfFrontTires": "540/65r28", "dimensionOfRearTires": "650/65r38", "wearOfFrontTires": 0.1, "wearOfRearTires": 0.1, "gear": "semiPowershift", "availability": "available", "firstHand": true, "numberOfDistributors": 4, "tags": ["new", "promo"], "generalEquipment": [ "airConditioning", "cab" ], "make": { "@name": "claas" }, "range": { "@name": "farmTractor" }, "model": { "@name": "arion650CisClaas" }, "context": { "@name": "used" } } }, "response": { "statusCode": 201, "content": { "id": "548e796a7cfea77f2c8b4567" } } }

GET, PATCH, DELETE

These requests are the same as with static objects.

Note that you can use /api/repository/all to access equipments from any repository.

  • { "request": { "path": "/repository/farmEquipment/548e796a7cfea77f2c8b4567" }, "response": { "content": { "id": "548e796a7cfea77f2c8b4567", "reference": "30858", "condition": "veryGoodCondition", "hours": 400, "year": 2012, "price": 80000, "retailerPrice": 0, "purchasePrice": 0, "serialNumber": "", "createdAt": "2013-10-14T06:22:58+0000", "power": 184, "wheelDrive": "4RM", "dimensionOfFrontTires": "540/65r28", "dimensionOfRearTires": "650/65r38", "wearOfFrontTires": 0.1, "wearOfRearTires": 0.1, "gear": "semiPowershift", "availability": "available", "firstHand": true, "numberOfDistributors": 4, "tags": [ "new", "promo" ], "generalEquipment": [ "airConditioning", "cab" ], "make": { "id": 44, "name": "claas", "title": "Claas" }, "range": { "id": 30, "name": "farmTractor", "title": "farm tractors" }, "model": { "id": 961, "name": "arion650CisClaas", "title": "Arion 650 CIS" }, "context": { "id": 1, "name": "used", "title": "used" }, "category": { "id": 30, "name": "tractor", "title": "tractor" }, "repository": { "id": 1, "name": "farmEquipment", "title": "farm equipment" }, "publicGallery": [ ], "privateGallery": [ ] } } }
  • { "request": { "method": "PATCH", "path": "/repository/farmEquipment/548e796a7cfea77f2c8b4567", "content": { "gear": "continuouslyVariable" } }, "response": { "statusCode": 200, "content": {} } }
  • { "request": { "method": "DELETE", "path": "/repository/farmEquipment/548e796a7cfea77f2c8b4567" }, "response": { "statusCode": 204 } }

Introduction

Full text searching is a great addition to improve user experience and general productivity.

Full text searching enables the user to search words in multiple fields. The search can be limited to a single object, but you can also search all objects in a given namespace or even the full API content.

Please note that only some objects are available for searching.

Search request

The search request uses the unofficial SEARCH HTTP method.

It is very similar to the list GET queries as it supports the filter query parameter and the with-count, limit and first parameters. Some other parameters like fields and count are not currently supported.

The main difference with the list query is the q query parameter. This parameter contains the search terms.

Examples

Doing a global search (any kind of object can be returned)

{ "request": { "method": "SEARCH", "path": "/", "queryParams": { "q": "BLG" } }, "response": { "statusCode": 200, "content": [ { "target": { "namespace": "crm", "objectName": "company", "tag": "normal" }, "score": 1, "data": { "tag": ["blg"], "companyName": "BLG", "address": { "formatted": "1 rue de la Sous Préfecture, 60200 Compiègne, France", "position": { "type": "Point", "coordinates": [-1.6144673, 49.6147441] }, "streetNumber": "1", "route": "Rue de la Sous Préfecture", "locality": "Compiègne", "area": "Oise", "country": "France", "zipCode": "60200" }, "type": "company", "reference": "F000002", "lastUpdate": "2015-04-13T08:49:28+0000", "id": "552b83187cfea780148b4568" } }, { "target": { "namespace": "crm", "objectName": "contact", "tag": "normal" }, "score": 0.11380635074749, "data": { "firstName": "GILLE", "lastName": "Colin", "lastUpdate": "2015-05-11T08:50:28+0000", "mail": { "mailPro": "[email protected]" }, "reference": "C000010", "relationCompany": [ { "companyName": "BLG" } ], "tag": [ "agent" ], "type": "contact", "userSettings": { "id": "554e10487cfea769208b4567" }, "id": "55422a127cfea7711f8b4567" } } ] } }

It is also possible to restrict the search to a certain namespace or objectName. For example, you can { "request": { "method": "SEARCH", "path": "/crm/company", "queryParams": { "q": "BLG" } }, "response": { "statusCode": 200, "content": [ { "target": { "namespace": "crm", "objectName": "company", "tag": "normal" }, "score": 1, "data": { "tag": ["blg"], "companyName": "BLG", "address": { "formatted": "1 rue de la Sous Préfecture, 60200 Compiègne, France", "position": { "type": "Point", "coordinates": [-1.6144673, 49.6147441] }, "streetNumber": "1", "route": "Rue de la Sous Préfecture", "locality": "Compiègne", "area": "Oise", "country": "France", "zipCode": "60200" }, "type": "company", "reference": "F000002", "lastUpdate": "2015-04-13T08:49:28+0000", "id": "552b83187cfea780148b4568" } } ] } }

Search results

Search results are different than get / list results. The results contain 3 fields:

  • target: The namespace, objectName and internal tag of the object. As a search can return a list with objects of different type, this field will help you distinguish the objects.
  • score: The score is a floating point value between 0 and 1 giving the quality of the result. 1 means that the result is the best of the results.
  • data: This object contains a subset of the found object. The most important fields are present. data always contains the id of the object, enabling you to retrieve the full object via a get request.

Filter search results

We already saw that we can restrict a search to a particular namespace and objectName, but that may not be enough. We may want to search equipments and restrict the results to the used equipments (via the used context).

This can be done via this request: { "request": { "method": "SEARCH", "path": "/repository/all", "queryParams": { "q": "Grimme GT 170 S", "filter": { "context": { "name": "used" } } } }, "response": { "statusCode": 200, "content": [ { "target": { "namespace": "repository", "objectName": "farmEquipment", "tag": "normal" }, "score": 1, "data": { "context": { "title": { "fr_FR": "occasion" }, "name": "used" }, "lastUpdate": "2015-05-01T17:19:48+0000", "make": { "title": "Grimme", "name": "grimme" }, "marketplaces": [ { "name": "blgdiffusion1" } ], "model": { "title": "GT 170 S", "name": "gt170SGrimme" }, "price": { "value": 0, "currency": "EUR" }, "range": { "title": { "fr_FR": "arracheuse de pommes de terre" }, "name": "potatoHarvester" }, "reference": "31990", "year": 2013, "id": "552acbd97cfea786168b456e" } } ] } }

If you search into multiple objects, via a global search for example, then you need a way to filter a specific field among any type of object. To do that, you should include the namespace and objectName in your field name, separated by slashes.

The previous request can also be written { "request": { "method": "SEARCH", "path": "/repository", "queryParams": { "q": "Grimme GT 170 S", "filter": { "repository/all/context": { "name": "used" } } } }, "response": { "statusCode": 200, "content": [ { "target": { "namespace": "repository", "objectName": "farmEquipment", "tag": "normal" }, "score": 1, "data": { "context": { "title": { "fr_FR": "occasion" }, "name": "used" }, "lastUpdate": "2015-05-01T17:19:48+0000", "make": { "title": "Grimme", "name": "grimme" }, "marketplaces": [ { "name": "blgdiffusion1" } ], "model": { "title": "GT 170 S", "name": "gt170SGrimme" }, "price": { "value": 0, "currency": "EUR" }, "range": { "title": { "fr_FR": "arracheuse de pommes de terre" }, "name": "potatoHarvester" }, "reference": "31990", "year": 2013, "id": "552acbd97cfea786168b456e" } } ] } }


File uploads

For now, we have only worked with JSON data. This is sufficient for most tasks, but can't be used to store binary data, like images or PDF documents.

To deal with these files (to add an image to a tractor object for example), you need to use the file PUT requests.

Link a file to a farmTractor object

This request will allow you to upload an image and link it to a farmTractor object.

{ "request": { "path": "/image/repository-farmEquipment-548e796a7cfea77f2c8b4567-1.jpg", "method": "PUT", "headers": { "Content-Length": 589242, "Content-Type": "image/jpeg", "x-blg-link": "repository/farmEquipment/54761e6cfac87bd83a8b4589:publicGallery", "x-blg-overwrite": "false", "x-blg-title": "Tractor" }, "content": "#binary#" }, "response": { "statusCode": 201, "content": {"id":125,"overwritten":true} } }

Get a file

{ "request": { "path": "/image/repository-farmEquipment-548e796a7cfea77f2c8b4567-1.jpg" }, "response": { "content": "#binary#", "headers": { "Content-Length": 589242, "Content-Type": "image/jpeg", "x-blg-id": "125", "x-blg-title": "Tractor" } } }