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 clientclient_secret
– the password associated to the client_idusername
– your usernamepassword
– your password
or if you have already obtained a token
client_id
– a unique identifier representing the clientclient_secret
– the password associated to the client_idrefresh_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 is100
.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 parameterstotal_count
(5) the number of elements that match the current query. It could be different from the number of the resources inside thedata
.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]>
- Import the library for make http requests
- 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. - 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]>
- 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 theindex
call. - Use
uuid
python library to generate a unique identifier for the office. - Define a payload that includes
office_id
,project_id
, and the mandatoryname
attribute. - Make the request and print results.
We are going to fetch the newly created office using office/view
service.
|
- Define the params including the
office_id
. - Make the request and print result.
- 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]>
- Use
new_office
as payload request and change its name attribute. - 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]>
- Define the params including the
office_id
we want to delete andforce=true
to confirm we want to delete the element. - Make request and print result.