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
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, which 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):
And with some query parameters. We ask the API to return all the available languages and join the user'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 which 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"
}
]
}
}
To get all field but one, add "*" to "field" array to get all root fields, and "-field" to
remove a field. The wildcard "*" means you need all root field (and only root fields). So you
may should add other joined fields and embedded object, in addition to "*".
{
"request": {
"path": "/dataRepository/physicalQuantity",
"queryParams": {
"limit": 1,
"join": [
"units"
],
"field": [
"*",
"-baseSiUnit",
"units"
]
}
},
"response": {
"content": [
{
"id": 1,
"name": "area",
"title": "surface",
"units": [
{
"id": 1,
"name": "are",
"title": "are",
"abbreviation": "a",
"coefficient": 100
},
{
"id": 12,
"name": "hectare",
"title": "hectare",
"abbreviation": "ha",
"coefficient": 10000
},
{
"id": 40,
"name": "squareKilometres",
"title": "kilomètre carré",
"abbreviation": "km²",
"coefficient": 1000000
},
{
"id": 41,
"name": "squareMetres",
"title": "mètre carré",
"abbreviation": "m²",
"coefficient": 1
}
],
"fields": null
}
]
}
}
To get all fields but just one subfield from a join (to avoid to load full joined object),
use "*" then the name of joined subfield(s) you need.
{
"request": {
"path": "/dataRepository/physicalQuantity",
"queryParams": {
"limit": 1,
"join": [
"fields"
],
"field": [
"*",
"fields.name"
]
}
},
"response": {
"content": [
{
"id": 1,
"name": "area",
"title": "surface",
"units": null,
"fields": [
{
"name": "surface"
},
{
"name": "workingAreaCapacity"
},
{
"name": "spreadingSurface"
}
],
"baseSiUnit": "squareMetres"
}
]
}
}
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"]
["divide", "myField1", 2]
["subtract", 0, "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:
["year", "myDate"]
["dayOfYear", "myDate"]
["dayOfMonth", "myDate"]
["dayOfWeek", "myDate"]
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"]
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"]
["gt", "myField1", "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]]
["or", ["eq", "status", "\"success"], ["eq", "status", "\"fail"]]
["not", "myBooleanField"]
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).
["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]
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"
}
}
]
}
]
}
}
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
}
}
Searching in objects
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"
}
}
}