NAV
shell

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

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:

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.

Additional considerations

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

Training Register

The below events are available to customers with Training Register

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:

Included Resources:

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:

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:

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:

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:

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:

Role Created

 {
   "data": {
      "id": "123",
      "type": "role",
      "attributes": {
        "position": "Technician"
      },
    },
    "meta": {
      "event_type": "role_created"
    }
  }

Data:

Role Updated

 {
   "data": {
      "id": "123",
      "type": "role",
      "attributes": {
        "position": "Technician",
      }
   },
    "meta": {
      "event_type": "role_updated"
    }
  }

Data:

Role Deleted

  {
   "data": {
      "id": "123",
      "type": "role",
      "attributes": {
        "position": "Technician",
      }
   },
    "meta": {
      "event_type": "role_deleted"
    }
  }

Data:

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:

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:

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:

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:

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:

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:

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.

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

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
email String No Email address of the personnel
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
email String No Email address of the personnel
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.