REST API Usage

Introduction

In this guide we will describe the 4hse architecture, and the web services that it offers to developers. Then we will build a code example that includes the API usage and covers a common developing scenario.

Standard API

4hse platform provides a big set of web services that allows you to manage your projects’ data. All services follow HTTP REST API Standard, and they provide CRUD operations on resources.

 A resource is a list of attributes (a person is described by first name, last name etc). Each resource has a resource identifier, an id attribute that has a unique value. Resources are grouped in collections based on their type (all resources of type person are grouped into person collection).

For each collection there are generally five basic endpoints:

- GET <collection>/index
- GET <collection>/view/<my_resource_id>
- POST <collection>/create {payload}
- PUT <collection>/update/<my_resource_id> {payload}
- DELETE <collection>/delete/<my_resource_id>

It could happen that a collection doesn’t follow this standard. Anyway all available API are well documented here using the OpenAPI standard.

JSON data format is used to exchange information between clients and server.

The HTTP responses follow the HTTP status code standard. So you will have a code 200 OK for response with a content, 404 Not Found for unavailable resource, 500 Internal Server Error for unexpected error etc.

Authentication

To get access to the 4hse apis you must begin with the authentication. There are two ways to get authenticated:

  • oauth2
  • auth_code

Suppose you would retrieve the list of offices ordered by name, then you should call the api: service.4hse.com/office/index?sort=-name&limit=50&offset=51

If you make this call without any authentication you will receive the Unauthorized (401) response code. You must use one of the following two ways in order to make the previous call working.

 This is only an example, a better explanation of the apis and the ways you can call its is in the following chapters

oauth2

We use the oauth2 standard to authenticate users.

The api uri is: service.4hse.com/oauth2/token

In order to obtain a token you must know the following data:

  • client_id – a unique identifier representing the client
  • client_secret – the password associated to the client_id
  • username – your username
  • password – your password

or if you have already obtained a token

  • client_id – a unique identifier representing the client
  • client_secret – the password associated to the client_id
  • refresh_token – the refresh token you received in a previous call to the same api
 Every customer that want to use the api must have its own client_id and client_secret. You should ask 4hse support for this pair of data.

When you access the service for the first time you must use your username and password (also client_id and client_secret of course):

REQUEST
---
URI: service.4hse.com/oauth2/token
Host: service.4hse.com
Method: POST
Request Payload:
{
 "client_id": "example_client_id",
 "client_secret": "example_client_secret",
 "grant_type": "password",
 "username": "example_user",
 "password": "example_password"
}

Then you will receive a response like the following:

RESPONSE
---
Status Code: 200 OK
Content-Type: application/json
Body:
{
 "access_token": "433b8caa8ea9839b5a732a9455ee50bef31fe4e0",
 "expires_in": 86400,
 "token_type": "Bearer",
 "scope": null,
 "refresh_token": "84c893bdd88fb52400c5d8fc246e81bd544a3486",
 "user_id": "example_user"
}

The access_token is the bearer token you can use to access all the apis. As indicated it expires in 86400 seconds (24 hours). As you can see, in the response there is also the refresh_token, that you can use to obtain a new access_token without the username and password. Remember that the refresh_token expires in 14 days. The call in this case is something like:

REQUEST
---
URI: service.4hse.com/oauth2/token
Host: service.4hse.com
Method: POST
Request Payload:
{
 "client_id": "example_client_id",
 "client_secret": "example_client_secret",
 "grant_type": "refresh_token",
 "refresh_token": "84c893bdd88fb52400c5d8fc246e81bd544a3486"
}

This call will give you a new access_token and a new refresh_token (the previous one will be disabled).

RESPONSE
---
Status Code: 200 OK
Content-Type: application/json
Body:
{
 "access_token": "b483aff34847b87786bb1a6c313b9e40b2bcf749",
 "expires_in": 86400,
 "token_type": "Bearer",
 "scope": null,
 "refresh_token": "c645d84be64791ccf1ee1f148c4f2281a97ae49b",
 "user_id": "example_user"
}

In order to make call to the system using the bearer token you must add to all your requests a new header, using the example above:

Authorization: Bearer b483aff34847b87786bb1a6c313b9e40b2bcf749

Now we may come back to the previous api that you called: service.4hse.com/office/index?sort=-name&limit=50&offset=51. As you can remember, without authentication you received the Unauthorized (401) response code. But now you have the bearer token, so you can make the same call adding the required authorization header:

REQUEST
---
URI: service.4hse.com/office/index?sort=-name&limit=50&offset=51
Host: service.4hse.com
Method: GET
Content-Type: application/json
Authorization: Bearer b483aff34847b87786bb1a6c313b9e40b2bcf749

Making the call this way you will receive the Success (200) code and the wished list of offices.

access-token

This way is very simple, as you are required only to add the access-token query param to every api call. You must contact the 4hse support to obtain the access-token. This will be generated at your request.

 Be aware that this access-token is totally different and does not have any relation to the oauth2

When you have this token you can make the same previous call adding the access-token query param. If your access token is sample-access-token you can simply call:

service.4hse.com/office/index?sort=-name&limit=50&offset=51&access-token=sample-access-token

Making the call this way you will receive the Success (200) code and the wished list of offices.

Authorization (ACL)

All web services execute inside an access control system. The access control rules follow the authorizations defined for the current user.

Example

The user-1 is manager for office-1 and don’t have any access to office-2.
If user-1 tries to access to the office-2 calling the service office/view/office-2, the server responds with the status code 404 Not found because the user is not able to see the resource office-2.

This behavior is common to all services except for index.
When a user asks for list of resources (a collection) using index, the server responds with the list of resources that the user can see according to its access control rules.

Example

The user-1 is a manager office-1 and office-3, and he calls office/index.
The server responds with a list of offices that contains only office-1 and office-3.
The office-2 is skipped because he can’t access to it.

Calls

INDEX

This call returns a list of resources of the collection that it refers.
Optional parameters:

  • sort(1): order the result by a resource attribute. By default it orders in ASC mode. To order in DESC mode add - as prefix.
  • limit(2): limit result set to a max number of elements. By default its value is 100.
  • offset(3): the first element of result set has this index.

For specific collection parameters (filters, additional data etc.) you can see our API documentation.

REQUEST
---
URI: service.4hse.com/office/index?sort=-name&limit=50&offset=51
Host: service.4hse.com
Method: GET
Content-Type: application/json
Query String Parameters:
 sort: -name // <b>(1)</b>
 limit: 50 // <b>(2)</b>
 offset: 51 // <b>(3)</b>

The response contains:

  • data(4): list of resources that meet the request parameters
  • total_count(5) the number of elements that match the current query. It could be different from the number of the resources inside the data.
  • pos(6): the current offset.
RESPONSE
---
Status Code: 200 OK
Content-Type: application/json
Body:
{
 "data": [
 {
 "id": "office-2-534443", // <b>(4)</b>
 "office_id": "office-2-534443",
 "name": "Office 2",
 ...
 },
 {
 "id": "office-1-213414",
 "office_id": "office-1-213414",
 "name": "Office 1",
 ...
 },
 ...
 ],
 "total_count": "2000" // <b>(5)</b>
 "pos": 51 // <b>(6)</b>
}

VIEW

It returns a resource with its details. The mandatory parameter is id(1) needful to identify the resource. It will be reported two times into response as attribute id(2) and <collection>_id(3). An office will have id and office_id attributes with the same value.

For specific collection parameters you can see our API documentation.

REQUEST
---
URI: service.4hse.com/office/view/office-1-534443
Host: service.4hse.com
Method: GET
Query String Parameters:
 id: office-1-534443 // <b>(1)</b>

The response contains the resource in the body.

RESPONSE
---
Status Code: 200 OK
Content-Type: application/json
Body:
{
 "data": {
 "id": "office-1-534443", // <b>(2)</b>
 "office_id": "office-1-534443", // <b>(3)</b>
 "name": "Office 1",
 ...
 }
}

CREATE

It requires a payload(1) containing <collection>_id(2) attribute and all the resource required attributes.

For specific resource parameters you can see our API documentation.

REQUEST
---
URI: service.4hse.com/office/create
Host: service.4hse.com
Method: POST
Request Payload: // <b>(1)</b>
{
 "office_id": "office-1-534443", // <b>(2)</b>
 "name": "Office 1",
 ...
}

The response contains the created resource inside the body.

RESPONSE
---
Status Code: 201 Created
Content-Type: application/json
Body:
{
 "data": {
 "id": "office-1-534443",
 "office_id": "office-1-534443",
 "name": "Office 1",
 ...
 }
}

UPDATE

As for the view, it needs the id(1) parameter to identify the resource. In addition, you need to provide a payload(2) containing id(3), <collection>_id(4) and updated attributes (5).

REQUEST
---
URI: service.4hse.com/office/update/office-1-534443
Host: service.4hse.com
Method: PUT
Query String Parameters:
 id: office-1-534443 //<b>(1)</b>
Payload: //<b>(2)</b>
 {
 "id": "office-1-534443", //<b>(3)</b>
 "office_id": "office-1-534443", //<b>(4)</b>
 "name":"new name for office 1", //<b>(5)</b>
 ...
 }

The response contains the updated resource inside body.

RESPONSE
---
Status Code: 200 OK
Content-Type: application/json
Body:
{
 "data": {
 "id": "office-1-534443",
 "office_id": "office-1-534443",
 "name":"new name for office 1",
 ...
 }
}

DELETE

It needs the id(1) parameter to identify the resource to delete.
In addition, you could specify the force(2) parameter that is false by default. When force=false the service does not delete the resource, but it responds with a confirmation question that includes eventually resources that are related to the current one.

 Each element in 4hse is part of hierarchical dependency structure. If you are deleting a person, all the resources related to this person (certificates, trainings, etc.) will be removed with it. This practice preserve the data consistency and avoid orphan elements.
REQUEST
---
URI: service.4hse.com/office/delete/office-1-534443
Host: service.4hse.com
Method: DELETE
Query String Parameters:
 id: office-1-534443 // <b>(1)</b>
 force: false // <b>(2)</b>

Because of force=false it responds with 400 Bad Request and related resources inside the body. Each of these resources is identified by its id(3),label(4) and type(5).

RESPONSE
---
Status Code: 400 Bad Request
Content-Type: application/json
Body:
{
 "message": "Addictions are present",
 "code": 1001,
 "data": [
 {
 "id": "office-1-534443", // <b>(3)</b>
 "label": "Office 1", // <b>(4)</b>
 "type": "OFFICE" // <b>(5)</b>
 }
 ]
}

If force=true(2) it removes the resource identified by id(1) and its related resources.

REQUEST
---
URI: service.4hse.com/office/delete/office-1-534443
Host: service.4hse.com
Method: DELETE
Query String Parameters:
 id: office-1-534443 //<b>(1)</b>
 force: true //<b>(2)</b>
RESPONSE
---
Status Code: 204 No Content
Content-Type: application/json

Code example

In this code example we will release a simple CRUD application that uses the 4hse REST API.

Requirements:

  • python 3 working environment
  • python-requests library
  • python-uuid library

We start to write the code to retrieve a list of offices by calling office/index service.

import requests <b>(1)</b>

params = {'limit': 10, 'sort': '-name'} <b>(2)</b>
r = requests.get('https://service.4hse.com/office/index', params=params) <b>(3)</b>
print(r.text) #{data: [{'office_id':'sad14024000hdg002252','name':'My first office', 'project_id':'prj1234567'}], total_count: 1, pos: 0}
print(r) #<Response [200]>
  1. Import the library for make http requests
  2. Define a set of parameters for the request. In this case we want to limit the result set to 10 resources sorted in descendant order by office name.
  3. Make the request and print results.

The next step is a new office creation using office/create service.

import uuid

project_id = r.json().get('data')[0].get('project_id') # es. prj1234567 <b>(1)</b>
office_id = uuid.uuid1() # es. 00a5d37c-4fe2-46ee-b820-77ce0ac22725 <b>(2)</b>

payload = {'office_id': office_id, 'name': 'My new office', 'project_id': project_id} <b>(3)</b>
r = requests.post('https://service.4hse.com/office/create', data=payload, params=authentication) <b>(4)</b>
print(r.text) #{'data': {'office_id': '00a5d37c-4fe2-46ee-b820-77ce0ac22725', 'name': 'My new office', 'project_id': 'prj1234567'} }
print(r) #<Response [201]>
  1. A required attribute for the office is the project_id that identifies the project inside we want to create the office. In this example we retrieve it from the first office returned from the index call.
  2. Use uuid python library to generate a unique identifier for the office.
  3. Define a payload that includes office_idproject_id, and the mandatory name attribute.
  4. Make the request and print results.

We are going to fetch the newly created office using office/view service.

params = {'id': office_id} <b>(1)</b>
r = requests.get('https://service.4hse.com/office/view', params=params) <b>(2)</b>
print(r.text) #{'data': {'office_id': '00a5d37c-4fe2-46ee-b820-77ce0ac22725', 'name': 'My new office', 'project_id': 'prj1234567'} }
print(r) #<Response [200]>
new_office = r.json().get('data') <b>(3)</b>
  1. Define the params including the office_id.
  2. Make the request and print result.
  3. Finally we stored the newly created office inside the new_office.

Now we want to change our office name using the service office/update.

params = {'id': office_id}
payload = new_office
payload.update({'name': 'new name for my office'}) <b>(1)</b>
r = requests.put('https://service.4hse.com/office/update', data=payload, params=params) <b>(2)</b>
print(r.text) #{'data': {'office_id': '00a5d37c-4fe2-46ee-b820-77ce0ac22725', 'name': 'new name for my office', 'project_id': 'prj1234567'} }
print(r) #<Response [200]>
  1. Use new_office as payload request and change its name attribute.
  2. Make request and print result.

Finally we delete the office using service office/delete.

params = {'id': office_id, 'force': 'true'} <b>(1)</b>
r = requests.delete('https://service.4hse.com/office/delete', params=params) <b>(2)</b>
print(r) #<Response [204]>
  1. Define the params including the office_id we want to delete and force=true to confirm we want to delete the element.
  2. Make request and print result.

Sources: https://github.com/4hse/example-rest-api-client