Introduction
Welcome to the HandsHQ API! You can use our API to access HandsHQ API endpoints, in order to manage your resources on your HandsHQ account.
Format
Supported types
Currently only JSON
is supported for both requests and responses. With the specifications and guidelines set out by
JSON:API
Authentication
Strategy
# With shell, you can just pass the correct header with each request
curl https://api.handshq.com/v1/authentication \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
In all subsequent examples make sure to replace
[api_token]
with your API token
HandsHQ uses API tokens to allow access to the API. You can find your API token in the API tab of your settings page here.
HandsHQ expects for the API token to be included in all API requests to the server in a header that looks like the following:
Authorization: bearer [api_token]
Testing your authentication details
{
"data": {
"id": "abcdefg",
"type": "actor",
"attributes": {
"company_name": "Example Company Ltd"
}
}
}
If you would like to test that your token is valid, the following request is available:
Request
GET https://api.handshq.com/v1/authentication
Response
200
Successful authentication.401
The token provided was not recognised as being valid.
Verifying requests made by HandsHQ
Verification strategy - Signed Requests
All examples assume a key of "my_key" and JSON data of {"bar":"foo"}
Ruby Example:
require 'openssl'
key = "my_key"
# NB: "{\"bar\":\"foo\"}" and JSON[{bar: :foo}] and '{"bar":"foo"}' are equivalent arguments in ruby.
data = "{\"bar\":\"foo\"}"
OpenSSL::HMAC.hexdigest('sha256', "my_key", data)
# "f0ccfece4923a8eb610fec19a031a769361d164860c4bb11dde380f6d8dc54bf"
Node example:
var crypto = require('crypto');
var key = 'my_key';
var data = '{"bar":"foo"}';
var signature = crypto.createHmac('sha256', key).update(data).digest('hex');
// 'f0ccfece4923a8eb610fec19a031a769361d164860c4bb11dde380f6d8dc54bf'
Note that different languages will have their own implementations of how to generate the HMAC hex digest.
While requests made to the HandsHQ API require the authentication measures set out here, we also provide measures to verify that any requests made from HandsHQ can be authenticated.
Such requests are made as a result of subscribing for webhook notifications (e.g. version_pdf_created
) where we will send a payload containing information about the event. e.g. attributes about a PDF that was just generated for one of your projects.
Our strategy involves the generation of a signature that is constructed for each request sent by HandsHQ and can be verified by the consumer. Due to the fact that the only other holder of the shared secret is HandsHQ, the signature also protects against request tamperings. See below for details.
Signature details
The signature can be found in a custom header called X-Handshq-Webhook-Signature
.
This value is generated for every webhook event request.
HandsHQ uses a HMAC hex digest
for the signature value, which has been generated using:
- Hashing Algorithm:
SHA-256
. - Secret value: Your HandsHQ
api_token
. - Data: The body of the request
application/json; charset=utf-8
.
If you wish to be able to generate a signature value to compare against then you will need to use the same hashing algorithm and encoding with the same secret value and data.
Example authentication workflow
Example Ruby-On-Rails controller contents
before_action :ensure_valid_signature
def create
# consume webhook
end
private
def ensure_valid_signature
# handle invalid signatures, e.g. return a 404 status
raise 'oh no - an invalid signature!' unless valid_signature?
end
def valid_signature?
# retrieve hex-encoded HMAC digest
provided_signature = request.headers['X-Handshq-Webhook-Signature']
# generate your own
generated_signature = OpenSSL::HMAC.hexdigest('sha256', ENV['MY_HANDSHQ_API_TOKEN'], request.body.read)
# compare values
Rack::Utils.secure_compare(generated_signature, provided_signature)
end
Different frameworks will have their own ways of achieving the workflow as described, and will have their own considerations of best practices, for example in this case the use of
#secure_compare
for additional protection against timing attacks.
Once you have an endpoint which is able to receive notifications and have registered an event subscription
with the URL pointing to this endpoint, you can do the following to verify the signature.
- Obtain the value of the signature from the
X-Handshq-Webhook-Signature
header. - Generate a HMAC hex digest with the details as described here
- Compare the value of the digest with the value in
X-Handshq-Webhook-Signature
. - Reject the request if the values do not match.
Additional considerations
- The signature would only be deemed secure so long as your
api_token
is kept a secret. If you wish to regenerate yourapi_token
you can do so here. - All URLs for webhook destinations must be using
https
to ensure that the contents of the request are secure. - If you wish to automatically remove an event subscription after it contacts your endpoint, by returning a
410 gone
http status we will automatically remove your event subscription so that you do not need to manually remove it later via the api. - Because the signature is generated using both the
api_token
and the body of the request, if either are incorrect then the comparison between digests will fail ensuring both authenticity of sender and request forgery protection.
Event Subscriptions
Viewing all your event subscriptions
curl https://api.handshq.com/v1/event_subscriptions \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
200
{
"data": [
{
"id": "123",
"type": "event_subscription",
"attributes": {
"event_type": "training_status_changed"
},
"relationships": {
"subscriber": {
"data": {
"id":"321",
"type":"subscriber"
}
}
},
"links": {
"related": "https://external.url.hello"
}
}
],
"meta": {
"pagination": {
"requested_page": 1,
"total_pages": 1
}
}
}
This endpoint will return a list of your event subscriptions.
Request
GET https://api.handshq.com/v1/event_subscriptions
Response
Successful requests will return a json payload of that division's event subscriptions as a collection and a 200
status code. The subscriber
relationship refers to the division that the event subscription belongs to.
Results in data
are paginated
Creating an event subscription
curl https://api.handshq.com/v1/event_subscriptions \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: bearer [api_token]" \
--request POST \
-d '[json_payload]'
Example Event Subscription creation payload.
{
"event_subscription": {
"external_url": "https://some.external_location.com/webhooks",
"event_type": "version_pdf_created"
}
}
This endpoint allows you to be notified of certain events. Where resources are account wide (such as roles), all divisions within that account with an event subscription will be notified of the event. Where resources are division specific (such as personnel), divisions with event subscriptions will be notified of events relating to their own resources only. The only exception to this is the primary division on an account, as they will be notified of events relating to all personnel within the account.
Events currently supported are:
RAMS
version_pdf_created
- this is fired after we have generated and stored a project version PDF, which is now ready to be downloaded. Please note that the event is scoped to look for subscriptions which were created by the same division that the project belongs to.project_archived
- this is fired after a project is archived (via the HandsHQ app or via the API).project_unarchived
- this is fired after a project is unarchived (via the HandsHQ app or via the API).personnel_assignment_created
- this is fired after a personnel has been added to a project (via the HandsHQ app or via the API).personnel_assignment_updated
- this is fired after a personnel has been updated on a project (via the HandsHQ app or via the API).personnel_assignment_deleted
- this is fired after a personnel has been removed from a project (via the HandsHQ app or via the API).
Training Register
The below events are available to customers with Training Register
role_created
- this is fired after a role is created (via the HandsHQ app or via the API).role_updated
- this is fired after a role is updated (via the HandsHQ app or via the API).role_deleted
- this is fired after a role is deleted (via the HandsHQ app or via the API).personnel_created
- this is fired after a personnel is created (via the HandsHQ app or via the API).personnel_updated
- this is fired after a personnel is updated (via the HandsHQ app or via the API).personnel_deleted
- this is fired after a personnel is deleted (via the HandsHQ app or via the API).personnel_archived
- this is fired after a personnel is archived (via the HandsHQ app or via the API).personnel_unarchived
- this is fired after a personnel is unarchived (via the HandsHQ app or via the API).training_status_changed
- this is fired after the training status of a personnel changes. Training statuses change as a result of changes to that personnel's roles, courses and training etc.
Request
POST https://api.handshq.com/v1/event_subscriptions
Allowed Event Subscription Parameters
Parameter | Format | Required | Description |
---|---|---|---|
external_url | String | Yes | A valid URL which we will send a POST request to when the event occurs, this must be using https . |
event_type | String | Yes | The name of the event you wish to be notified about, e.g version_pdf_created |
Response
Successful requests will return a json payload of the event subscription that was created and a 201
status code.
201
{
"id": "123",
"type": "event_subscription",
"attributes": {
"event_type": "version_pdf_created"
},
"relationships": {
"subscriber": {
"data": {
"id":"321",
"type":"subscriber"
}
}
},
"links": {
"related": "https://external.url.hello"
}
}
A convenience header of
location
will also be present in the response containing a url (e.g.https://api.handshq.com/v1/event_subscriptions/123
) where you are able to send a DELETE request to if you wish to remove the subscription that was just created.
Removing an event subscription
curl https://api.handshq.com/v1/event_subscriptions/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request DELETE
If you no longer wish for an event subscription to be active, then this endpoint can delete an already created one.
Request
DELETE https://api.handshq.com/v1/event_subscriptions/[id]
Allowed Event Subscription Parameters
Parameter | Format | Required | Description |
---|---|---|---|
id | String | Yes | The id of the event subscription you wish to destroy |
Response
Successful requests will return a 204
status code, and thusly no accompanying body.
Event Notifications
Each Event Subscription will send a POST
request to the location set as the external_url
when the event occurs.
These will be in a JSON
format and in-keeping with the JSON:API
specification.
The body of an event subscription will vary depending on the type of event being subscribed to.
Version PDF Created
{
"data": {
"id": "2",
"type": "version_pdf",
"attributes": {
"created_at": "2021-11-05T11:30:32.742+00:00",
"file_size": 32540
},
"relationships": {
"version": {
"data": {
"id": "50",
"type": "version"
}
},
"project": {
"data": {
"id":"40",
"type":"project"
}
}
},
"links": {
"content": "https://exampleRemoteStorage.com/123.pdf"
}
},
"included": [
{
"id": "50",
"type": "version",
"attributes": {
"is_fully_signed": false,
"pdf_filename": "my-lovely-project.pdf",
"display_number": 0,
"created_at": "2021-11-05T11:29:33.222+00:00"
},
"relationships": {
"history": {
"data": {
"id": "50",
"type": "history"
}
},
"version_pdf": {
"data": {
"id": "2",
"type": "version_pdf"
}
}
}
},
{
"id": "50",
"type": "history",
"attributes": {
"reason": "Jane D. created the project",
"createdAt": "2021-11-05T11:29:32.955+00:00",
"displayNumber": 0,
"eventType": "generic"
},
"relationships": {
"project": {
"data": {
"id": "19",
"type": "project"
}
}
}
},
{
"id": "19",
"type": "project",
"attributes": {
"name": "My lovely project",
"reference": "ref1",
"start_date": null,
"end_date": null,
"archived_at": null,
"state": "not_submitted"
},
"relationships": {
"fields": {
"data": [
{
"id": "261",
"type": "field"
},
{
"id": "265",
"type": "field"
},
]
},
"user": {
"data": {
"id": "5",
"type": "user"
}
}
},
"links": {
"app_url": "https://localhost:3000/projects/19"
}
}
]
}
Data:
- The attributes of PDF record
- Link to the URL of where the PDF is hosted
Included Resources:
The
Project
that it was derived from.- Link to the project within HandsHQ
The
History
describing what caused the creation of a newVersion
- Included description of the latest change
- The reason will identify why the version was created
The
Version
of aProject
which had been generated into a PDF- Included numbering of the version
The author (
User
) of the project.
Project Archived
{
"data": {
"id": "123",
"type": "project",
"attributes": {
"name": "Example Project",
"reference": "123",
"start_date": "2022-01-01",
"end_date": "2023-01-01",
"archived_at": "2022-01-01T00:00:00+00:00",
"state": "approved"
},
"relationships": {
"fields": {
"data": [{
"id":"456",
"type":"field"
}]
},
"user": {
"data": {
"id": "789",
"type": "user"
}
}
},
"links": {
"app_url": "https://handshq.com/projects/123"
}
},
"meta": {
"event_type": "project_archived"
}
}
Data:
- The attributes of the project, along with IDs of the project's user (author) and fields.
Project Unarchived
{
"data": {
"id": "123",
"type": "project",
"attributes": {
"name": "Example Project",
"reference": "123",
"start_date": "2022-01-01",
"end_date": "2023-01-01",
"archived_at": "null",
"state": "approved"
},
"relationships": {
"fields": {
"data": [{
"id":"456",
"type":"field"
}]
},
"user": {
"data": {
"id": "789",
"type": "user"
}
}
},
"links": {
"app_url": "https://handshq.com/projects/123"
}
},
"meta": {
"event_type": "project_unarchived"
}
}
Data:
- The attributes of the project, along with IDs of the project's user (author) and fields.
Personnel Assignment Created
{
"data": {
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
},
"meta": {
"event_type":"personnel_assignment_created"
}
}
Data:
- The attributes of the personnel assignment, along with IDs of the assignment's personnel, project and role.
Personnel Assignment Updated
{
"data": {
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
},
"meta": {
"event_type":"personnel_assignment_updated"
}
}
Data:
- The attributes of the personnel assignment, along with IDs of the assignment's personnel, project and role.
Personnel Assignment Deleted
{
"data": {
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
},
"meta": {
"event_type":"personnel_assignment_deleted"
}
}
Data:
- The attributes of the personnel assignment, along with IDs of the assignment's personnel, project and role.
Role Created
{
"data": {
"id": "123",
"type": "role",
"attributes": {
"position": "Technician"
},
},
"meta": {
"event_type": "role_created"
}
}
Data:
- The role that has been created.
Role Updated
{
"data": {
"id": "123",
"type": "role",
"attributes": {
"position": "Technician",
}
},
"meta": {
"event_type": "role_updated"
}
}
Data:
- The role that has been updated.
Role Deleted
{
"data": {
"id": "123",
"type": "role",
"attributes": {
"position": "Technician",
}
},
"meta": {
"event_type": "role_deleted"
}
}
Data:
- The role that has been deleted.
Personnel Created
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
}
}
},
"meta": {
"event_type": "personnel_created"
}
}
Data:
- The personnel that has been created, along with IDs of the personnel's line manager and associated roles.
Personnel Updated
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
}
}
},
"meta": {
"event_type": "personnel_updated"
}
}
Data:
- The personnel that has been updated, along with IDs of the personnel's line manager and associated roles.
Personnel Deleted
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
}
}
},
"meta": {
"event_type": "personnel_deleted"
}
}
Data:
- The personnel that has been deleted, along with IDs of the personnel's line manager and associated roles.
Personnel Archived
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": "2022-01-01T12:30:30.479+01:00",
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
}
}
},
"meta": {
"event_type": "personnel_archived"
}
}
Data:
- The personnel that has been archived, along with IDs of the personnel's line manager and associated roles.
Personnel Unarchived
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
}
}
},
"meta": {
"event_type":"personnel_unarchived"
}
}
Data:
- The personnel that has been unarchived, along with IDs of the personnel's line manager and associated roles.
Training Status Changed
{
"data": [
{
"id": "23_training-status",
"type": "training_status",
"attributes": {
"status": "valid",
"description": "training up-to-date"
},
"relationships": {
"personnel": {
"data": {
"id": "123",
"type": "personnel"
}
}
}
},
{
"id": "22_training-status",
"type": "training_status",
"attributes": {
"status": "expired",
"description": "expired training"
},
"relationships": {
"personnel": {
"data": {
"id":"231",
"type":"personnel"
}
}
}
}
],
"meta": {
"eventType":"training_status_changed"
}
}
Data:
- A collection of the training statuses that have changed as the result of an action taken via API or via the App.
- A list of possible statuses and their status descriptions:
missing
| missing trainingdate_missing
| training expiry date not setexpired
| expired trainingexpiring
| training expiring soonvalid
| training up-to-datedefault
| no training required
Projects
Viewing projects
curl https://api.handshq.com/v1/projects \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
200
{
"data": [
{
"id": "1234",
"type": "project",
"attributes": {
"name": "Test Project",
"reference": "ABC1",
"start_date": "2021-12-20",
"end_date": "2022-12-21",
"archived_at": null,
"state": "not_submitted"
},
"relationships": {
"fields": {
"data": [
{
"id": "123",
"type": "field"
},
{
"id": "234",
"type": "field"
}
]
},
"user": {
"data": {
"id": "345",
"type": "user"
}
}
},
"links": {
"app_url": "https://app.handshq.com/projects/1234"
}
},
{
"id": "5678",
"type": "project",
"attributes": {
"name": "Test Project 2",
"reference": "ABC2",
"start_date": "2021-12-20",
"end_date": "2022-12-21",
"archived_at": null,
"state": "not_submitted"
},
"relationships": {
"fields": {
"data": [
{
"id": "123",
"type": "field"
},
{
"id": "234",
"type": "field"
}
]
},
"user": {
"data": {
"id": "345",
"type": "user"
}
}
},
"links": {
"app_url": "https://app.handshq.com/projects/5678"
}
}
],
"meta": {
"pagination": {
"requested_page": 1,
"total_pages": 1
}
}
}
This endpoint allows you to view projects for the division who is registered with the API token you provide.
Request
GET https://api.handshq.com/v1/projects
Allowed Query Parameters
Parameter | Format | Required | Description |
---|---|---|---|
reference | String | No | Only projects with a matching reference will be returned |
with_fields | Boolean | No | If set to true will include the fields of projects in the included section of the response, see the fields index endpoint for an example of the attributes on fields. |
archived_status | String | No | Returns only archived projects if set to 'archived', unarchived projects if set to 'unarchived', and all projects if set to 'all'. When no query parameter is specified, the entire collection will be returned. |
Response
Successful requests will return a json payload of that division's projects and a 200
status code.
Results in data
are paginated
Viewing a specific project
curl https://api.handshq.com/v1/projects/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view a specific project for the division that is registered with the API token you provide.
Request
GET https://api.handshq.com/v1/projects/[project_id]
Response
Successful requests will return a json payload of the project a 200
status code.
200
{
"data": {
"id": "1",
"type": "project",
"attributes": {
"name": "My Project",
"start_date": "2021-12-20" ,
"end_date": "2022-12-20",
"reference": "abc123",
"archived_at": null,
"state": "not_submitted"
},
"relationships": {
"user": {
"data": {
"id":"8",
"type":"user"
}
},
"fields":{
"data":[
{
"id":"248",
"type":"field"
}
]
}
},
"links": {
"app_url": "https://app.handshq.com/projects/1"
}
},
"included":[
{
"id":"248",
"type":"field",
"attributes":{
"label":"Client reference",
"required":null,
"value":"DEF345",
"data_type":"string"
},
"relationships":{
"fieldset":{
"data":{
"id":"58",
"type":"fieldset"
}
}
}
}
]
}
Creating a project
curl https://api.handshq.com/v1/projects \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example Project creation payload.
{
"user_email": "maddox@daystrom.com",
"project": {
"name": "My project with extra details",
"start_date": "2021-12-20",
"end_date": "2022-12-20",
"reference": "abc123",
"fields_attributes": {
"1": "My field value"
}
}
}
This endpoint allows you to create a project for the division who is registered with the API token you provide.
Request
POST https://api.handshq.com/v1/projects
Required Parameters
Parameter | Format | Required | Description |
---|---|---|---|
user_email | String | Yes | The email of the HandsHQ user who will be marked as the author of the project |
Allowed Project Parameters
All parameters must be nested within project
Parameter | Format | Required | Description |
---|---|---|---|
name | String | Yes | Name of your project, used for document titles, names of PDF documents etc. |
start_date | Date | No | To denote when your project starts, used in conjunction with end_date to denote whether project is still active. |
end_date | Date | No | To denote when your project ends, used in conjunction with start_date to denote whether project is still active. |
reference | String | No | Your internal reference for a project e.g. 'RA01' |
fields_attributes | Object | No | More information available here |
Response
Successful requests will return a json payload of the project that was created and a 201
status code.
201
{
"data": {
"id": "1",
"type": "project",
"attributes": {
"name": "My Project",
"start_date": "2021-12-20",
"end_date": "2022-12-20",
"reference": "abc123",
"archived_at": null,
"state": null
},
"relationships": {
"user": {
"data": {
"id": "345",
"type": "user"
}
},
"fields":{
"data":[
{
"id":"248",
"type":"field"
}
]
}
},
"links": {
"app_url": "https://app.handshq.com/projects/1"
}
},
"included":[
{
"id":"248",
"type":"field",
"attributes":{
"label":"Client reference",
"required":null,
"value":"My field value",
"data_type":"string"
},
"relationships":{
"fieldset":{
"data":{
"id":"58",
"type":"fieldset"
}
}
}
}
]
}
Updating a project
curl https://api.handshq.com/v1/projects/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request PATCH \
-d '[json_payload]'
Example Project update payload.
{
"project": {
"name": "My Updated Project",
"start_date": "2021-12-20" ,
"end_date": "2022-12-20",
"reference": "abc123",
"fields_attributes": {
"248": "DEF345"
}
}
}
This endpoint allows you to update a project for the division who is registered with the API token you provide.
Request
PATCH https://api.handshq.com/v1/projects/[project_id]
Allowed Project Parameters
All parameters must be nested within project
Parameter | Format | Required | Description |
---|---|---|---|
name | String | No | Name of your project, used for document titles, names of PDF documents etc. |
start_date | Date | No | To denote when your project starts, used in conjunction with end_date to denote whether project is still active. |
end_date | Date | No | To denote when your project ends, used in conjunction with start_date to denote whether project is still active. |
reference | String | No | Your internal reference for a project e.g. 'RA01' |
fields_attributes | Object | No | More information available here |
Response
Successful requests will return a json payload of the project that was updated and a 200
status code.
200
{
"data": {
"id": "1",
"type": "project",
"attributes": {
"name": "My Updated Project",
"start_date": "2021-12-20" ,
"end_date": "2022-12-20",
"reference": "abc123",
"archived_at": null,
"state": "not_submitted"
},
"relationships": {
"user": {
"data": {
"id":"8",
"type":"user"
}
},
"fields":{
"data":[
{
"id":"248",
"type":"field"
}
]
}
},
"links": {
"app_url": "https://app.handshq.com/projects/1"
}
},
"included":[
{
"id":"248",
"type":"field",
"attributes":{
"label":"Client reference",
"required":null,
"value":"DEF345",
"data_type":"string"
},
"relationships":{
"fieldset":{
"data":{
"id":"58",
"type":"fieldset"
}
}
}
}
]
}
Archiving projects
curl https://api.handshq.com/v1/projects/[project_id]/archive \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request PATCH
200
{
"data": {
"id": "123",
"type": "project",
"attributes": {
"name": "Example Project",
"reference": "123",
"start_date": "2022-01-01",
"end_date": "2023-01-01",
"archived_at": "2022-01-01T00:00:00+00:00",
"state": "approved"
},
"relationships": {
"fields": {
"data": [{
"id": "456",
"type": "field"
}]
},
"user": {
"data": {
"id": "789",
"type": "user"
}
}
},
"links": {
"app_url": "https://handshq.com/projects/123"
}
}
}
This endpoint allows you to archive a project within the division that is registered with the API token you provide.
Request
GET https://api.handshq.com/v1/projects/[project_id]/archive
Response
Successful requests will return a json payload of the archived project and a 200
status code.
A 404 will be returned if you try to archive a project that has already been archived.
Unarchiving projects
curl https://api.handshq.com/v1/projects/[project_id]/unarchive \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request PATCH
200
{
"data": {
"id": "123",
"type": "project",
"attributes": {
"name": "Example Project",
"reference": "123",
"start_date": "2022-01-01",
"end_date": "2023-01-01",
"archived_at": null,
"state": "approved"
},
"relationships": {
"fields": {
"data": [{
"id": "456",
"type": "field"
}]
},
"user": {
"data": {
"id": "789",
"type": "user"
}
}
},
"links": {
"app_url": "https://handshq.com/projects/123"
}
}
}
This endpoint allows you to unarchive a project within the division that is registered with the API token you provide.
Request
GET https://api.handshq.com/v1/projects/[project_id]/unarchive
Response
Successful requests will return a json payload of the archived project and a 200
status code.
A 404 will be returned if you try to unarchive a project that has not already been archived.
Duplicating a project
curl https://api.handshq.com/v1/projects/[id]/duplications \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example Project duplicated payload.
{
"user_email": "maddox@daystrom.com",
"project": {
"name": "My duplicated project",
"start_date": "2022-12-20",
"end_date": "2023-12-20",
"reference": "abcd1234",
"fields_attributes": {
"1": "My field value"
}
}
}
This endpoint allows you to duplicate a project that exists within the division that is registered with the API token you provide.
The project is duplicated asynchronously, so will not be available at this point. Successful requests will return a json payload detailing the duplication process and a 201
status code. The duplication process information provides the id of the duplication process, the time that the duplication was requested and the current status of the duplication process - queued, in progress, complete or failed.
If you wish to access the status of the duplication process, and check if the duplicated project and version pdf has been generated, you can use the duplication process id to poll for the current status of the duplication process - further information here
You can also be notified when a version pdf has been generated through our version_pdf_created webhook. The payload will contain the reason that the version pdf was generated and will identify whether it was a result of a project duplication.
Request
POST https://api.handshq.com/v1/projects/[id]/duplications
Required Parameters
Parameter | Format | Required | Description |
---|---|---|---|
user_email | String | Yes | The email of the HandsHQ user who will be marked as the author of the project |
Allowed Project Parameters
All parameters must be nested within project
Parameter | Format | Required | Description |
---|---|---|---|
name | String | No | Name of your duplicated project, used for document titles, names of PDF documents etc. If not provided, this will default to the name of the original project suffixed with Copy. |
start_date | Date | No | To denote when your project starts, used in conjunction with end_date to denote whether project is still active. |
end_date | Date | No | To denote when your project ends, used in conjunction with start_date to denote whether project is still active. |
reference | String | No | Your internal reference for a project e.g. 'RA01' |
fields_attributes | Object | No | More information available here |
Response
Successful requests will return a json payload of the project duplication process and a 201 status code.
201
{
"data": {
"id": "0654478193284b93844884632ed5ff32",
"type": "project_duplication",
"attributes": {
"status": "queued",
"requested_at": "2022-01-01T00:00:00+00:00"
},
"relationships": {
"project": {
"data": null
},
"original_project": {
"data": {
"id": "123",
"type": "original_project"
}
},
"version_pdf": {
"data": null
}
}
}
}
Viewing project duplication process
curl https://api.handshq.com/v1/project_duplications/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request GET
This endpoint allows you to view the duplication process of a project, by polling us with the id returned from duplicating the project. You can view the duplication process of projects within the division that is registered with the API token you provide.
The duplication process can have the following statuses: "queued", "in_progress", "complete" and "failed". We recommend you poll this endpoint no more than twice a minute.
If the status is complete, it means the duplicated project has been generated as well as the corresponding version pdf for that duplicated project.
If the duplication status has failed, this could mean either the duplication of the project has failed, or the project duplication was a success and the version pdf generation failed. You can determine the cause of the failure within the relationships of the response. If an id exists for the project and not the version pdf, the duplication was successful and the pdf generation failed. If neither project nor version pdf id exist, the project duplication failed.
If the version pdf takes longer than 10 minutes to generate, the duplication process is considered a failure - although the project will have been duplicated.
Request
GET https://api.handshq.com/v1/project_duplications/[id]
Response
Successful requests will return a json payload of the project duplication process and a 200
status code.
Once the duplication process is complete, you can access the project and version that was generated using the project id and version pdf id returned in the response, along with the following endpoints - viewing a specific project & viewing a specific version pdf
200
Queued:
{
"data": {
"id": "f38aadd81a124b2ab14695e88629f4b8",
"type": "project_duplication",
"attributes": {
"status": "queued",
"requested_at": "2022-01-01T00:00:00+00:00"
},
"relationships": {
"project": {
"data": null
},
"original_project": {
"data": {
"id": "123",
"type": "original_project"
}
},
"version_pdf": {
"data": null
}
}
}
}
In Progress:
{
"data": {
"id": "f38aadd81a124b2ab14695e88629f4b8",
"type": "project_duplication",
"attributes": {
"status": "in_progress",
"requested_at": "2022-01-01T00:00:00+00:00"
},
"relationships": {
"project": {
"data": null
},
"original_project": {
"data": {
"id": "123",
"type": "original_project"
}
},
"version_pdf": {
"data": null
}
}
}
}
Complete:
{
"data": {
"id": "f38aadd81a124b2ab14695e88629f4b8",
"type": "project_duplication",
"attributes": {
"status": "complete",
"requested_at": "2022-01-01T00:00:00+00:00"
},
"relationships": {
"project": {
"data": {
"id": "456",
"type": "project"
}
},
"original_project": {
"data": {
"id": "123",
"type": "original_project"
}
},
"version_pdf": {
"data": {
"id": "789",
"type": "version_pdf"
}
}
}
}
}
Failed:
{
"data": {
"id": "f38aadd81a124b2ab14695e88629f4b8",
"type": "project_duplication",
"attributes": {
"status": "failed",
"requested_at": "2022-01-01T00:00:00+00:00"
},
"relationships": {
"project": {
"data": null
},
"original_project": {
"data": {
"id": "123",
"type": "original_project"
}
},
"version_pdf": {
"data": null
}
}
}
}
Version PDFs
A version pdf is a pdf representation of the current version of the project. As a project changes over time, different versions are generated, and these versions can have pdf representations.
Viewing a specific version pdf
curl https://api.handshq.com/v1/version_pdfs/[version_pdf_id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view the version pdf that has been generated for a specific version of the project, within the division that is registered with the API token you provide.
If, for example, you duplicate a project through the api, and the duplication process is complete, you can use the version pdf id provided in the response to retrieve the generated version pdf using this endpoint.
Request
GET https://api.handshq.com/v1/version_pdfs/[version_pdf_id]
Response
Successful requests will return a json payload of the version pdf and a 200
status code.
200
{
"data": {
"id": "123",
"type": "version_pdf",
"attributes": {
"created_at": "2022-01-01T00:00:00+00:00",
"file_size": 5000
},
"relationships": {
"version": {
"data": {
"id": "456",
"type": "version"
}
},
"project": {
"data": {
"id": "789",
"type": "project"
}
}
},
"links":{
"version_pdf_url":"https://example.pdf"
}
}
}
Viewing latest version pdf for a project
curl https://api.handshq.com/v1/projects/[project_id]/latest_version_pdf \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view the latest version pdf that has been generated for a project, within the division that is registered with the API token you provide.
The project, history, version and latest reviewed version will be included in the response.
Request
GET https://api.handshq.com/v1/projects/[project_id]/latest_version_pdf
Response
Successful requests will return a json payload of the latest version pdf and a 200
status code.
200
{
"data": {
"id": "321",
"type": "version_pdf",
"attributes": {
"created_at": "2022-01-01T00:00:00+00:00",
"file_size": 5000
},
"relationships": {
"version": {
"data": {
"id": "456",
"type": "version"
}
},
"project": {
"data": {
"id": "123",
"type": "project"
}
}
},
"links": {
"version_pdf_url": "https://example.pdf"
}
},
"included": [{
"id": "456",
"type": "version",
"attributes": {
"is_fully_signed": true,
"pdf_filename": "example-project.pdf",
"display_number": 2,
"created_at": "2022-01-01T00:00:00+00:00"
},
"relationships": {
"history": {
"data": {
"id": "789",
"type": "history"
}
},
"version_pdf": {
"data": {
"id": "321",
"type": "version_pdf"
}
}
}
},
{
"id": "789",
"type": "history",
"attributes": {
"reason": "Katie M. edited the project",
"createdAt": "2022-01-01T00:00:00+00:00",
"displayNumber": 2,
"eventType": "generic"
},
"relationships": {
"project": {
"data": {
"id": "123",
"type": "project"
}
},
"message": {
"data": null
}
}
},
{
"id": "123",
"type": "project",
"attributes": {
"name": "Example Project",
"reference": "123",
"start_date": "2022-08-10",
"end_date": "2023-08-10",
"archived_at": null,
"state": "approved"
},
"relationships": {
"fields": {
"data": [
{
"id": "1",
"type": "field"
}
]
},
"user": {
"data": {
"id": "2",
"type": "user"
}
}
},
"links": {
"app_url": "https://handshq.com/projects/123"
}
}
Fields
Introduction To Fields
While all projects
within HandsHQ have the attributes of name
, reference
, start_date
, end_date
, projects may also have a number of associated fields
that are used to describe more specific pieces of information relating to that project
.
In order to create projects
, every division
is assigned the use of a template
to determine how such projects
should be constructed.
By default all divisions
will use the standard HandsHQ template
which contains fields
such as "Location of Works", "Address Line 1" etc.
When a division is using the custom templates
feature however, a division
will have a far more specific set of fields more appropriate for the types of projects they are undertaking.
Within each template
(and later each project
), fields
are organised into a group called a fieldset
which might look something like this within the app when editing a project
.
- Location (
fieldset
)- Location of Works (
field
) - Address line 1 (
field
) - Address line 2 (
field
)
- Location of Works (
Whenever a new project
is created, this structure is used to generate a set of new fields
specific to that project
which will have the same names, data types, order and behaviour.
Fields Index
curl https://api.handshq.com/v1/fields \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
Description
Collection of fields
from your division
's applied template
, the details of these fields
are copied to the new fields which are generated for every new project
using that template.
URL
GET https://api.handshq.com/v1/fields
Params
None.
Sample Response
{
"data": [
{
"id": "5",
"type": "field",
"attributes": {
"label": "Location of works",
"required": null,
"value": null,
"data_type": "string"
},
"relationships": {
"fieldset": {
"data": {
"id": "2",
"type": "fieldset"
}
}
}
},
{
"id": "6",
"type": "field",
"attributes": {
"label": "Address line 1",
"required": null,
"value": null,
"data_type": "string"
},
"relationships": {
"fieldset": {
"data": {
"id": "2",
"type": "fieldset"
}
}
}
},
{
"id": "7",
"type": "field",
"attributes": {
"label": "Address line 2",
"required": null,
"value": null,
"data_type": "string"
},
"relationships": {
"fieldset": {
"data": {
"id": "2",
"type": "fieldset"
}
}
}
}
],
"included": [
{
"id": "2",
"type": "fieldset",
"attributes": {
"name": "Location"
}
}
]
}
Attribute descriptions
label
- Label on project forms and end documents, e.g. 'Address 1'required
- Whether or not the field is required when saving a project.value
- The default value that will be immediately be applied to fields which are being derived from it. e.g. '1600 Pennsylvania Avenue`data_type
- How the value of the field will be stored and the type of field provided to the user for editing. Will be one of the following:string
text
date
Assigning field values to Projects
{
"project": {
// ... other project attributes
"fields_attributes": {
"field_id_1": "My value 1",
"field_id_2": "My value 2",
"field_id_3": "My value 3",
}
}
}
In both creating and update, the values of these fields can be set when provided within project
portion of the body under the key of fields_attributes
.
In all cases, rather than having to provide details of which fieldset
the field
is a part of, a mapping of field_id
to field_value
is used, the value of the id
is behaviourally different for creation and updating (see below).
For Project creation
The fields
for a project
are generated during the project
creation process, therefore in order to immediately provide a non-default value for these fields
you must instead provide a mapping between the template
field
and the value that you wish to assign.
An example workflow for creating a project
immediate values within it's fields
might look like the following:
1) Retrieve details of your current template
's fields
(IDs
, default values, whether or not they are required etc) GET https://api.handshq.com/v1/fields
2) Map the template
field
IDs
with the values you wish to assign to the fields
for that project
Arranged field attributes example where the IDS of "5", "6", "7" were found from /fields and a value was provided for each.
"fields_attributes": {
"5": "My value for Location of works",
"6": "My value for Address line 1",
"7": "My value for Address line 2",
}
3) Include these attributes as part of the body when sending to POST https://api.handshq.com/v1/projects
Project creation example with custom fields being included, the response will include any fields that were created for this project - including those that were sent as part of this request. See FAQ for further details.
{
"user_email": "maddox@daystrom.com",
"project": {
"name": "My project with custom field values",
"start_date": "2021-12-20",
"end_date": "2022-12-20",
"reference": "CustomFields1",
"fields_attributes": {
"5": "My value for Location of works",
"6": "My value for Address line 1",
"7": "My value for Address line 2",
}
}
}
Note that the ids of the fields for the project returned in the payload will not be "5", "6", "7" (the ids of the template fields), instead a new field is created and will be assigned an appropriate id.
For Project updates
"fields_attributes": {
"123": "My value for Location of works",
"456": "My value for Address line 1",
"789": "My value for Address line 2",
}
The same shape of field_id: field_value
is used within fields_attributes
.
The difference for Project#update
being that the field_id
would be the ID that already exists on the project rather than the ID of the template
field
.
The IDs of these fields are returned when a project is created and from certain other endpoints such as those returned back from GET /projects
.
FAQ
No, you do not not need to provide the field_attributes
in order for them to be generated, they are created by default for each new project, by providing them in field_attributes
you are however able to immediately provide a value. For every template field that is omitted, a new field is still generated for the project, with its default value being assigned to that new field.
Where "Location" is the fieldset that contains multiple fields within it, it is worth noting that you do not have to provide details on the fieldsets when creating or updating projects, you only have to provided the id
=> value
of all fields in a flat collection. The fieldset to which they are assigned and the order of fields as they appear in the document are still determined by the template that you are using, please contact our support team for more information on how this can be customised.
Yes, some fields might be marked as required
for your current template, if this is the case - if you do not provide a value or one that is deemened valid it will not allow the project to be created/updated.
Also, field values must correspond to their specific data type (e.g. dates).
Both of these pieces of information can be found as part of the /fields
payload.
Personnel
Viewing your personnel
curl https://api.handshq.com/v1/personnel \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
200
{
"data": [
{
"id": "1234",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": null,
"external_id": "JSMITH",
"type": "employee",
"training_status": {
"status": "missing",
"description": "missing training"
}
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
}
},
"primary_role": {
"data": {
"id": "123",
"type": "primary_role"
}
}
}
],
"meta": {
"pagination": {
"requested_page": 1,
"total_pages": 1
},
"is_archived_set": false
}
}
This endpoint allows you to view the personnel that belong to the division registered to the API token you provide. If the division is the primary division for that account, all personnel across the account will be returned. If not, only the personnel of that division will be returned. By default the personnel index returns personnel that have not been archived.
Request
GET https://api.handshq.com/v1/personnel
Allowed Query Parameters
Parameter | Format | Required | Description |
---|---|---|---|
search | String | No | Provide a search field to search the first name, last name or email address of personnel |
archived | String | No | Provide a param of true to fetch only archived personnel |
Response
Successful requests will return a collection of personnel and a 200
status code.
Results in data
are paginated
Viewing one personnel
curl https://api.handshq.com/v1/personnel/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
200
{
"data": {
"id": "1234",
"type": "personnel",
"attributes": {
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@email.com",
"archived_at": null,
"external_id": "JSMITH",
"type": "employee",
"training_status": {
"status": "missing",
"description": "missing training"
},
"relationships": {
"line_manager": {
"data": {
"id": "4321",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "123",
"type": "role"
},
{
"id": "321",
"type": "role"
}
]
},
"primary_role": {
"data": {
"id": "123",
"type": "primary_role"
}
}
}
}
}
This endpoint allows you to view a personnel by providing the id.
Request
GET https://api.handshq.com/v1/personnel/[id]
Response
Successful requests will return a json payload of that personnel and a 200
status code
Creating a personnel
curl https://api.handshq.com/v1/personnel \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example Personnel creation payload.
{
"personnel": {
"first_name": "Sandra",
"last_name": "Smith",
"email":"sandra.smith@email.com",
"external_id": "JSMITH",
"line_manager_id": "456",
"role_ids": ["987", "765"],
"primary_role_id": "987"
}
}
This endpoint allows you to create a personnel. Personnel are division specific, so the personnel will belong to the division that the API key belongs to. You can set the first name, last name and email of the personnel through this endpoint. You can also assign the personnel a line manager using the ID of another personnel, and assign roles to the personnel using the IDs of roles that exist in the account.
Request
POST https://api.handshq.com/v1/personnel
Allowed Personnel Parameters for create
All parameters must be nested within personnel
Parameter | Format | Required | Description |
---|---|---|---|
first_name | String | Yes | First name of the personnel |
last_name | String | Yes | Last name of the personnel |
String | No | Email address of the personnel | |
profile_access_enabled | Boolean | No | Allow employee to view their profile (permitted if personnel profile access feature enabled) |
external_id | String | No | External ID of the personnel |
line_manager_id | String | No | ID of the line manager of the personnel |
role_ids | Association IDs | No | IDs of roles that exist in the training register, that the personnel holds. For more information see Associations |
primary_role_id | String | No | ID of the primary role of the personnel |
Response
Successful requests will return a json payload of the newly created personnel and a 201
status code
201
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "Sandra",
"last_name": "Smith",
"email": "sandra.smith@email.com",
"external_id": "JSMITH",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "456",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "987",
"type": "role"
},
{
"id": "765",
"type": "role"
}
]
},
"primary_role": {
"data": {
"id": "987",
"type": "primary_role"
}
}
}
}
}
Updating a personnel
curl https://api.handshq.com/v1/personnel/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--requst PATCH
-d '[json_payload]'
Example Personnel update payload.
{
"personnel": {
"first_name": "Sally",
"last_name": "Smith-West",
"email":"sally-sw@email.com",
"external_id": "JSMITH",
"line_manager_id": "567",
"role_ids": ["345"],
"primary_role_id": "345"
}
}
This endpoint allows you to update the first name, last name, email address, line manager and roles of an existing personnel.
Request
PATCH https://api.handshq.com/v1/personnel/[id]
Allowed Personnel Parameters for update
All parameters must be nested within personnel
Parameter | Format | Required | Description |
---|---|---|---|
first_name | String | No | First name of the personnel |
last_name | String | No | Last name of the personnel |
String | No | Email address of the personnel | |
profile_access_enabled | Boolean | No | Allow employee to view their profile (permitted if personnel profile access feature enabled) |
external_id | String | No | External ID of the personnel |
line_manager_id | String | No | ID of the line manager of the personnel |
role_ids | Association IDs | No | IDs of roles that exist in the training register, that the personnel holds. For more information see Associations |
primary_role_id | String | No | ID of the primary role of the personnel |
Response
Successful requests will return a json payload of the updated personnel and a 200
status code
200
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "Sally",
"last_name": "Smith-West",
"email": "sally-sw@email.com",
"external_id": "JSMITH",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "567",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "345",
"type": "role"
}
]
},
"primary_role": {
"data": {
"id": "345",
"type": "primary_role"
}
}
}
}
}
Deleting a personnel
curl https://api.handshq.com/v1/personnel/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
--request DELETE
204
This endpoint allows you to delete a personnel.
Request
DELETE https://api.handshq.com/v1/personnel/[id]
Response
Successful requests will return a 204
status code
Archiving a personnel
curl https://api.handshq.com/v1/personnel/[id]/archive \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
--request PATCH
200
This endpoint allows you to archive a personnel.
Request
PATCH https://api.handshq.com/v1/personnel/[id]/archive
Response
Successful requests will return a json payload of the archived personnel and a 200
status code
200
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "Sally",
"last_name": "Smith-West",
"email": "sally-sw@email.com",
"external_id": "JSMITH",
"archived_at": "2022-04-27T17:30:18.835+01:00",
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "567",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "345",
"type": "role"
}
]
},
"primary_role": {
"data": {
"id": "345",
"type": "primary_role"
}
}
}
}
}
Unarchiving a personnel
curl https://api.handshq.com/v1/personnel/[id]/unarchive \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
--request PATCH
200
This endpoint allows you to unarchive an archived personnel.
200
{
"data": {
"id": "123",
"type": "personnel",
"attributes": {
"first_name": "Sally",
"last_name": "Smith-West",
"email": "sally-sw@email.com",
"external_id": "JSMITH",
"archived_at": null,
"type": "employee"
},
"relationships": {
"line_manager": {
"data": {
"id": "567",
"type": "line_manager"
}
},
"roles": {
"data": [
{
"id": "345",
"type": "role"
}
]
},
"primary_role": {
"data": {
"id": "345",
"type": "primary_role"
}
}
}
}
}
Request
PATCH https://api.handshq.com/v1/personnel/[id]/unarchive
Response
Successful requests will return a json payload of the archived personnel and a 200
status code
Training
Creating a training
curl https://api.handshq.com/v1/trainings \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example Training creation payload.
{
"training": {
"personnel_id": 2,
"course_id": 3,
"start_date": "2022-12-20",
"expiry_date": "2024-12-20",
"notes": "Training session notes"
}
}
This endpoint allows you to add a training to a personnel using the ID of the personnel and the ID of the course.
Request
POST https://api.handshq.com/v1/trainings
Allowed Training Parameters
All parameters must be nested within training
Parameter | Format | Required | Description |
---|---|---|---|
personnel_id | String | Yes | The ID of the personnel |
course_id | String | Yes | The ID of the course |
start_date | String | Yes | To denote when the training starts |
expiry_date | String | Yes (if the specified course expires) | To denote when the training expires if the course does |
notes | String | No | Notes related to the training |
Response
Successful requests will return a json payload of the newly created training and a 201
status code
201
{
"data": {
"id": "1",
"type": "training",
"attributes": {
"start_date": "2022-12-20",
"expiry_date": "2024-12-20",
"notes": "Training session notes"
},
"relationships": {
"course": {
"data": {
"id": "3",
"type": "course"
}
},
"personnel": {
"data": {
"id": "2",
"type": "personnel"
}
}
}
}
}
Roles
Viewing your roles
curl https://api.handshq.com/v1/roles \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
200
{
"data": [
{
"id": "1234",
"type": "role",
"attributes": {
"position": "Engineer"
}
},
{
"id": "1235",
"type": "role",
"attributes": {
"position": "Technician"
}
}
],
"meta": {
"pagination": {
"requested_page": 1,
"total_pages": 1
}
}
}
This endpoint allows you to view the roles that belong to the account registered to the API token you provide.
Request
GET https://api.handshq.com/v1/roles
Allowed Query Parameters
Parameter | Format | Required | Description |
---|---|---|---|
search | String | No | Search roles by position name |
Response
Successful requests will return a json payload of that account's roles and a 200
status code.
Results in data
are paginated
Viewing one role
curl https://api.handshq.com/v1/roles/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
200
{
"data": {
"id": "1234",
"type": "role",
"attributes": {
"position": "Engineer"
},
"relationships": {
"courses": {
"data": [
{
"id": "111",
"type": "course"
},
{
"id": "222",
"type": "course"
}
]
}
}
},
"included": [
{
"id": "111",
"type": "course",
"attributes": {
"name": "Engineering course 101"
}
},
{
"id": "222",
"type": "course",
"attributes": {
"name": "Engineering course 102"
}
}
]
}
This endpoint allows you to view a role and its required courses by providing the role's id.
Request
GET https://api.handshq.com/v1/roles/[id]
Response
Successful requests will return a json payload of that role and a 200
status code
Creating a role
curl https://api.handshq.com/v1/roles \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example Role creation payload.
{
"role": {
"position": "Engineer",
"course_ids": ["123", "321"]
}
}
This endpoint allows you to create a role. Roles are account wide, so the role will be created for the account of the company that the API key belongs to.
Request
POST https://api.handshq.com/v1/roles
Allowed Role Parameters for create
All parameters must be nested within role
Parameter | Format | Required | Description |
---|---|---|---|
position | String | Yes | The position name of the role |
course_ids | Association IDs | No | IDs of courses that exist in the training register, that the role should be associated to. For more information see Associations |
Response
Successful requests will return a json payload of the newly created role and a 201
status code
201
{
"data": {
"id": "123",
"type": "role",
"attributes": {
"position": "Engineer"
}
}
}
Updating a role
curl https://api.handshq.com/v1/roles/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--requst PATCH
-d '[json_payload]'
Example Role update payload.
{
"role": {
"position": "Engineer",
"course_ids": ["123", "321"]
}
}
This endpoint allows you to update the position
attribute of a role belonging to your account
Request
PATCH https://api.handshq.com/v1/roles/[id]
Allowed Role Parameters for update
All parameters must be nested within role
Parameter | Format | Required | Description |
---|---|---|---|
position | String | No | The position name of the role |
course_ids | Association IDs | No | IDs of courses that exist in the training register, that the role should be associated to. For more information see Associations |
Response
Successful requests will return a json payload of the updated role and a 200
status code
200
{
"data": {
"id": "123",
"type": "role",
"attributes": {
"position": "Engineer Manager"
}
}
}
Deleting a role
curl https://api.handshq.com/v1/roles/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
--request DELETE
204
This endpoint allows you to delete a role belonging to your account.
Request
DELETE https://api.handshq.com/v1/roles/[id]
Response
Successful requests will return a 204
status code
Personnel Roles
Adding a role to a personnel
curl https://api.handshq.com/v1/personnel_roles \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example payload.
{
"personnel_role": {
"personnel_id": "123",
"role_id": "321"
}
}
This endpoint allows you to add a role to a personnel using the ID of the personnel and the ID of the role.
Request
POST https://api.handshq.com/v1/personnel_roles
Allowed Personnel Role Parameters
All parameters must be nested within personnel_role
Parameter | Format | Required | Description |
---|---|---|---|
personnel_id | String | Yes | The ID of the personnel |
role_id | String | Yes | The ID of the role |
Response
Successful requests will return a json payload of the newly created personnel role and a 201
status code
201
{
"data": {
"id": "345",
"type": "personnelRole",
"relationships": {
"personnel": {
"data": {
"id":"123",
"type":"personnel"
}
},
"role": {
"data": {
"id": "321",
"type": "role"
}
}
}
}
}
Removing a role from a personnel
curl https://api.handshq.com/v1/personnel_roles/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]"
--request DELETE
204
This endpoint allows you to delete a personnel.
Request
DELETE https://api.handshq.com/v1/personnel_roles/[id]
Response
Successful requests will return a 204
status code
Personnel Assignments
When personnel are added to projects, the relationship between the personnel and project is referred to as a personnel assignment.
You can interact with this relationship through the personnel assignment id, or the id of the project along with the id of the personnel that was added to the project.
Viewing personnel assignments for a project
curl https://api.handshq.com/v1/projects/[project_id]/personnel_assignments \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view the personnel assignments that exist for a project, within the division that is registered with the API token you provide.
Request
GET https://api.handshq.com/v1/projects/[project_id]/personnel_assignments
Response
Successful requests will return a json payload of the personnel assignments and a 200
status code.
Results in data
are paginated
200
{
"data": [{
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
}],
"meta": {
"pagination": {
"requested_page":1,
"total_pages":1
}
}
}
Viewing a personnel assignment
curl https://api.handshq.com/v1/personnel_assignments/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
curl https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view a personnel assignment for the division that is registered with the API token you provide. You can either view the assignment by providing the personnel assignment id, or the project id and personnel id.
Request
GET https://api.handshq.com/v1/personnel_assignments/[id]
OR
GET https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment
Response
Successful requests will return a json payload of the personnel assignment and a 200
status code.
200
{
"data": {
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
}
}
Creating a personnel assignment
curl https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request POST \
-d '[json_payload]'
Example Personnel Assignment update payload.
{
"user_email": "maddox@daystrom.com",
"personnel_assignment": {
"role_id": "123"
}
}
This endpoint allows you to add a personnel to a project within the division that is registered with the API token you provide. You have the option to determine the role of the personnel on the project, by providing the id of the role. This role must have already been assigned to the personnel.
Once a personnel is added to a project, if reviews have been requested for that project, a review will automatically be generated for that personnel. A user email must be provided along with this request, and this email will be used to identify the requester of the review.
Request
POST https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment
Required Parameters
Parameter | Format | Required | Description |
---|---|---|---|
user_email | String | Yes | The email of the HandsHQ user who will be marked as the requester of the generated review. The user in question must have access to the division that is registered with the API token you provide. |
Allowed Parameters
Parameter | Format | Required | Description |
---|---|---|---|
role_id | String | No | The id of the role the personnel should have on project. The personnel must already have this role. |
Response
Successful requests will return a json payload of the personnel assignment and a 201
status code.
200
{
"data": {
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "123",
"type": "role"
}
}
}
}
}
Updating a personnel assignment
curl https://api.handshq.com/v1/personnel_assignments/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request PATCH \
-d '[json_payload]'
curl https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
-H "Content-Type: application/json" \
--request PATCH \
-d '[json_payload]'
Example Personnel Assignment update payload.
{
"personnel_assignment": {
"role_id": "123"
}
}
This endpoint allows you to update a personnel on a project for the division that is registered with the API token you provide. You can either update the assignment by providing the personnel assignment id, or the project id and personnel id.
Request
PATCH https://api.handshq.com/v1/personnel_assignments/[id]
OR
PATCH https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment
Allowed Personnel Assignment Parameters
All parameters must be nested within personnel_assignment
Parameter | Format | Required | Description |
---|---|---|---|
role_id | String | No | ID of the role the personnel should have on the project. The personnel must already have this role. |
Response
Successful requests will return a json payload of the personnel assignment and a 200
status code.
200
{
"data": {
"id": "123",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "123",
"type": "role"
}
}
}
}
}
Deleting a personnel assignment
curl https://api.handshq.com/v1/personnel_assignments/[id] \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request DELETE
curl https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request DELETE
This endpoint allows you to remove a personnel from a project belonging to the division that is registered with the API token you provide. You can either remove the assignment by providing the personnel assignment id, or the project id and personnel id.
Request
DELETE https://api.handshq.com/v1/personnel_assignments/[id]
OR
DELETE https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/assignment
Response
Successful requests will return no content and a 204
status code.
204
Latest Document Reviews
A version of a document can be reviewed by the personnel that have been assigned to that project.
A personnel can review multiple versions of a document, and you can use this endpoint to access their review for the latest version of the document.
Reviews will only be found once personnel have been added to the project and reviews have been requested for this version of the document. If you add a personnel to a project after reviews have been requested for that version of a document, a review will automatically be created.
A review can have the state of requested, accepted or rejected. Reviews that have been accepted will have a corresponding signature.
Viewing latest document reviews for a project
curl https://api.handshq.com/v1/projects/[project_id]/latest_document_reviews \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view the latest document reviews that exist for a project, within the division that is registered with the API token you provide.
Personnel assignments and signatures will be included in the response, and the personnel the review was generated for can be determined through the personnel assignment relationships.
Request
GET https://api.handshq.com/v1/projects/[project_id]/latest_document_reviews
Response
Successful requests will return a json payload of the latest document reviews and a 200
status code. An empty collection indicates personnel have not yet been added to the project, or reviews have not yet been requested for this version of the document.
Results in data
are paginated
200
Reviews have been requested
{
"data": [{
"id": "123",
"type": "review",
"attributes": {
"state": "accepted",
"viewed_at": "2022-01-01T00:00:00+00:00",
"signed_at": "2022-02-01T00:00:00+00:00"
},
"relationships": {
"personnel_assignment": {
"data": {
"id": "123",
"type": "personnel_assignment"
}
},
"signature": {
"data": {
"id": "456",
"type": "signature"
}
}
},
"links": {
"review_url": "https://reviews/123abc"
}
}
],
"included": [{
"id": "789",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
},
{
"id": "321",
"type": "signature",
"links": {
"signature_url": "https://example-signature.png"
}
}],
"meta": {
"pagination": {
"requested_page": 1,
"total_pages": 1
}
}
}
No personnel have been added to project or reviews have not been requested
{
"data": [],
"included": [],
"meta": {
"pagination": {
"requested_page": 1,
"total_pages": 0
}
}
}
Viewing latest document review for a personnel on a project
curl https://api.handshq.com/v1/personnel_assignments/[id]/latest_document_review \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
curl https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/latest_document_review \
-H "Accept: application/json" \
-H "Authorization: bearer [api_token]" \
--request GET
This endpoint allows you to view the latest document review that exists for a personnel on a project, within the division that is registered with the API token you provide.
Personnel assignments and signatures will be included in the response, and the personnel the review was generated for can be determined through the personnel assignment relationships.
You can access the latest document review by providing the personnel assignment id, or the id of the project and personnel you wish to view the review for.
Request
GET https://api.handshq.com/v1/personnel_assignments/[id]/latest_document_review
OR
GET https://api.handshq.com/v1/projects/[project_id]/personnel/[personnel_id]/latest_document_review
Response
Successful requests will return a json payload of the latest document review and a 200
status code.
An empty object indicates reviews have not yet been requested for this version of the document.
200
{
"data": {
"id": "123",
"type": "review",
"attributes": {
"state": "accepted",
"viewed_at": "2022-01-01T00:00:00+00:00",
"signed_at": "2022-02-01T00:00:00+00:00"
},
"relationships": {
"personnel_assignment": {
"data": {
"id": "123",
"type": "personnel_assignment"
}
},
"signature": {
"data": {
"id": "456",
"type": "signature"
}
}
},
"links": {
"review_url": "https://reviews/123abc"
}
},
"included": [{
"id": "789",
"type": "personnel_assignment",
"relationships": {
"personnel": {
"data": {
"id": "1",
"type": "personnel"
}
},
"project": {
"data": {
"id": "2",
"type": "project"
}
},
"role": {
"data": {
"id": "3",
"type": "role"
}
}
}
},
{
"id": "321",
"type": "signature",
"links": {
"signature_url": "https://example-signature.png"
}
}]
}
Reviews have not been requested for this version of the document
{
"data": null
}
Pagination
{
"data": [...],
"meta": {
"pagination": {
"requested_page": 7,
"total_pages": 16
}
}
}
For those resources which are paginated, unless otherwise stated in the documentation of that resource, it will be returned in batches of 100.
To access a separate page of the collection then provide page
as a query parameter eg. /projects?page=3
Information on the pagination is found in the meta
section of the response.
Associations
Association IDs
Some resources take an array of IDs as a parameter for create or update actions. These IDs are then used to create the association between the resources. For example, when creating a role
you can provide a parameter of course_ids
. This role will then be associated to each of these courses.
Replacing associations
{
"role": {
"course_ids": ["2"]
}
}
When an array of IDs is accepted as a parameter, the IDs that are provided will replace any associations already in place.
For example:
If a role
had associations to courses
with the ID of 2 and 3, providing course_ids: ['2']
to the update of the role will replace the associations entirely, and only the association to course
with an ID of 2 would remain.
Adding to associations
{
"role": {
"course_ids": ["2", "3", "4"]
}
}
In order to add to a list of associations, provide any existing associations, plus the new one.
For example:
If a role
had associations to courses
with the ID of 2 and 3, providing course_ids: ['2', '3', '4']
to the update of the role will keep associations to courses
ID 2 and 3, and would add an association to course
ID 4.
Removing associations
{
"role": {
"course_ids": []
}
}
If you provide an empty array to a parameter that accepts an array of association IDs then all associations for that resource will be removed.
For example:
If a role
had associations to courses
with the ID of 2, providing course_ids: []
to the update of the role will remove this association.
Errors
Example 401 response:
{
"errors": [
{
"title": "Invalid authentication details",
"status": 401,
"code": "invalid_authentication"
}
]
}
The HandsHQ API uses the following error codes:
There may be additional info given in the error in order to help fix the issue, for example if you have reached your limits of document creation.
Error Code | Meaning |
---|---|
400 | Bad Request -- Your request is invalid. |
401 | Unauthorized -- Your API token is wrong. |
404 | Not Found -- The specified resource could not be found. |
418 | I'm a teapot. |
422 | The resource cannot be processed with the given request parameters |
500 | Internal Server Error -- We had a problem with our server. Try again later. |