curl --request POST \
--url https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply \
--header 'Content-Type: application/vnd.api+json' \
--header 'X-Session-Token: <api-key>' \
--data '
{
"data": {
"type": "<string>",
"attributes": {
"quantity": 2,
"work_date": "2023-12-25",
"line_type": "actual"
},
"relationships": {
"organisation": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_sheet": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
}
}
}
}
'import requests
url = "https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply"
payload = { "data": {
"type": "<string>",
"attributes": {
"quantity": 2,
"work_date": "2023-12-25",
"line_type": "actual"
},
"relationships": {
"organisation": { "data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
} },
"cost_sheet": { "data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
} }
}
} }
headers = {
"X-Session-Token": "<api-key>",
"Content-Type": "application/vnd.api+json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Session-Token': '<api-key>', 'Content-Type': 'application/vnd.api+json'},
body: JSON.stringify({
data: {
type: '<string>',
attributes: {quantity: 2, work_date: '2023-12-25', line_type: 'actual'},
relationships: {
organisation: {data: {id: '3c90c3cc-0d44-4b50-8888-8dd25736052a', type: '<string>'}},
cost_sheet: {data: {id: '3c90c3cc-0d44-4b50-8888-8dd25736052a', type: '<string>'}}
}
}
})
};
fetch('https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
'type' => '<string>',
'attributes' => [
'quantity' => 2,
'work_date' => '2023-12-25',
'line_type' => 'actual'
],
'relationships' => [
'organisation' => [
'data' => [
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'type' => '<string>'
]
],
'cost_sheet' => [
'data' => [
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'type' => '<string>'
]
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/vnd.api+json",
"X-Session-Token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply"
payload := strings.NewReader("{\n \"data\": {\n \"type\": \"<string>\",\n \"attributes\": {\n \"quantity\": 2,\n \"work_date\": \"2023-12-25\",\n \"line_type\": \"actual\"\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n },\n \"cost_sheet\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Session-Token", "<api-key>")
req.Header.Add("Content-Type", "application/vnd.api+json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply")
.header("X-Session-Token", "<api-key>")
.header("Content-Type", "application/vnd.api+json")
.body("{\n \"data\": {\n \"type\": \"<string>\",\n \"attributes\": {\n \"quantity\": 2,\n \"work_date\": \"2023-12-25\",\n \"line_type\": \"actual\"\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n },\n \"cost_sheet\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Session-Token"] = '<api-key>'
request["Content-Type"] = 'application/vnd.api+json'
request.body = "{\n \"data\": {\n \"type\": \"<string>\",\n \"attributes\": {\n \"quantity\": 2,\n \"work_date\": \"2023-12-25\",\n \"line_type\": \"actual\"\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n },\n \"cost_sheet\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>",
"attributes": {
"line_type": "actual",
"quantity": 123,
"work_date": "2023-12-25",
"rate": {
"amount": 123,
"currency": "<string>"
},
"amount": {
"amount": 123,
"currency": "<string>"
},
"margin": {
"percentage": 1,
"fixed": {
"amount": 123,
"currency": "<string>"
}
},
"charge": {
"amount": 123,
"currency": "<string>"
},
"margin_override": {
"percentage": 1,
"fixed": {
"amount": 123,
"currency": "<string>"
}
},
"margin_source": "override",
"profit": {
"amount": 123,
"currency": "<string>"
},
"cost_item_snapshot": {
"code": "<string>",
"name": "<string>",
"description": "<string>",
"unit": "<string>",
"margin": {
"percentage": 1,
"fixed": {
"amount": 123,
"currency": "<string>"
}
}
},
"description": "<string>",
"cost_kit": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"code": "<string>",
"name": "<string>"
},
"cost_kit_application_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
},
"meta": {
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"relationships": {
"organisation": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_sheet": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_item": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_item_version": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
}
}
}
],
"meta": {
"pagination": {
"counts": {
"pages": 123,
"resources": 123
},
"current_page": 123,
"offsets": {
"next": 123,
"previous": 123
},
"requested": {
"limit": 123,
"offset": 123
}
},
"features": {
"include": {
"options": [
"<string>"
]
}
}
},
"jsonapi": {
"version": "<string>"
},
"included": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>",
"attributes": {
"name": "<string>",
"slug": "<string>",
"sandbox": true,
"description": "<string>",
"settings": {
"teams": {
"customer_representatives": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
]
}
}
},
"meta": {
"v3": true,
"status": "active",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"features": [
{
"name": "<string>",
"enabled": true,
"limit": 123
}
]
},
"relationships": {
"users": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"service_accounts": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"groups": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"teams": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"nomenclature": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
}
}
}
]
}{
"id": "98ca4a78-b66f-4234-9719-aaf832ee6669",
"status": "400",
"title": "A validation error was encountered",
"source": {
"parameter": "include"
},
"meta": {
"resource": "wrong_value"
}
}{
"id": "05fc9c8d-73b9-4697-9337-57f7a567a48f",
"status": "401",
"title": "You are not authorised to access this resource",
"detail": "In order to access this resource, you need the 'admin' role.",
"code": "AUTH.001"
}{
"id": "7b4c8f12-3e9a-4d5b-8c6f-1a2b3c4d5e6f",
"status": "404",
"title": "Resource not found",
"detail": "The requested resource could not be found or does not exist.",
"code": "NOT_FOUND.001"
}{
"id": "fe9d9a69-f0a7-4fdc-bb2c-176027f316c5",
"status": "500",
"title": "Internal Server Error",
"detail": "An unexpected error occurred on the server."
}Apply a cost kit
Write a kit onto a cost sheet as ordinary cost lines.
One line is created for each item in the kit, with its quantity scaled by how many of the kit are being applied. Each line is priced from the register exactly as a hand-entered one is — the rate in force on the work date, the rate code’s own margin or its category’s default — and frozen at that price. From that moment they are ordinary cost lines: they roll up, they are edited and they are deleted like any other, and revising the kit afterwards does not move them.
work_date selects the rate each line is priced at, so a backdated apply costs
the work at the rate that applied on the day. It defaults to today.
The apply is all or nothing. If any of the kit’s items has no rate in force on the work date the whole request is refused and nothing is written, because a half-applied kit is worse than none — nothing on the sheet would say it was half applied.
Each line records the kit it came from and shares an application id with the others written by the same call, so a client can show them as one group.
Served by a custom handler rather than a generated one, because it is a verb over an existing resource rather than a write to it.
curl --request POST \
--url https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply \
--header 'Content-Type: application/vnd.api+json' \
--header 'X-Session-Token: <api-key>' \
--data '
{
"data": {
"type": "<string>",
"attributes": {
"quantity": 2,
"work_date": "2023-12-25",
"line_type": "actual"
},
"relationships": {
"organisation": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_sheet": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
}
}
}
}
'import requests
url = "https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply"
payload = { "data": {
"type": "<string>",
"attributes": {
"quantity": 2,
"work_date": "2023-12-25",
"line_type": "actual"
},
"relationships": {
"organisation": { "data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
} },
"cost_sheet": { "data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
} }
}
} }
headers = {
"X-Session-Token": "<api-key>",
"Content-Type": "application/vnd.api+json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Session-Token': '<api-key>', 'Content-Type': 'application/vnd.api+json'},
body: JSON.stringify({
data: {
type: '<string>',
attributes: {quantity: 2, work_date: '2023-12-25', line_type: 'actual'},
relationships: {
organisation: {data: {id: '3c90c3cc-0d44-4b50-8888-8dd25736052a', type: '<string>'}},
cost_sheet: {data: {id: '3c90c3cc-0d44-4b50-8888-8dd25736052a', type: '<string>'}}
}
}
})
};
fetch('https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
'type' => '<string>',
'attributes' => [
'quantity' => 2,
'work_date' => '2023-12-25',
'line_type' => 'actual'
],
'relationships' => [
'organisation' => [
'data' => [
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'type' => '<string>'
]
],
'cost_sheet' => [
'data' => [
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'type' => '<string>'
]
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/vnd.api+json",
"X-Session-Token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply"
payload := strings.NewReader("{\n \"data\": {\n \"type\": \"<string>\",\n \"attributes\": {\n \"quantity\": 2,\n \"work_date\": \"2023-12-25\",\n \"line_type\": \"actual\"\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n },\n \"cost_sheet\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Session-Token", "<api-key>")
req.Header.Add("Content-Type", "application/vnd.api+json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply")
.header("X-Session-Token", "<api-key>")
.header("Content-Type", "application/vnd.api+json")
.body("{\n \"data\": {\n \"type\": \"<string>\",\n \"attributes\": {\n \"quantity\": 2,\n \"work_date\": \"2023-12-25\",\n \"line_type\": \"actual\"\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n },\n \"cost_sheet\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ctrl-hub.com/v3/cost-kits/{cost_kit_id}/apply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Session-Token"] = '<api-key>'
request["Content-Type"] = 'application/vnd.api+json'
request.body = "{\n \"data\": {\n \"type\": \"<string>\",\n \"attributes\": {\n \"quantity\": 2,\n \"work_date\": \"2023-12-25\",\n \"line_type\": \"actual\"\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n },\n \"cost_sheet\": {\n \"data\": {\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"type\": \"<string>\"\n }\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>",
"attributes": {
"line_type": "actual",
"quantity": 123,
"work_date": "2023-12-25",
"rate": {
"amount": 123,
"currency": "<string>"
},
"amount": {
"amount": 123,
"currency": "<string>"
},
"margin": {
"percentage": 1,
"fixed": {
"amount": 123,
"currency": "<string>"
}
},
"charge": {
"amount": 123,
"currency": "<string>"
},
"margin_override": {
"percentage": 1,
"fixed": {
"amount": 123,
"currency": "<string>"
}
},
"margin_source": "override",
"profit": {
"amount": 123,
"currency": "<string>"
},
"cost_item_snapshot": {
"code": "<string>",
"name": "<string>",
"description": "<string>",
"unit": "<string>",
"margin": {
"percentage": 1,
"fixed": {
"amount": 123,
"currency": "<string>"
}
}
},
"description": "<string>",
"cost_kit": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"code": "<string>",
"name": "<string>"
},
"cost_kit_application_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
},
"meta": {
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
},
"relationships": {
"organisation": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_sheet": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_item": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
},
"cost_item_version": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
}
}
}
],
"meta": {
"pagination": {
"counts": {
"pages": 123,
"resources": 123
},
"current_page": 123,
"offsets": {
"next": 123,
"previous": 123
},
"requested": {
"limit": 123,
"offset": 123
}
},
"features": {
"include": {
"options": [
"<string>"
]
}
}
},
"jsonapi": {
"version": "<string>"
},
"included": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>",
"attributes": {
"name": "<string>",
"slug": "<string>",
"sandbox": true,
"description": "<string>",
"settings": {
"teams": {
"customer_representatives": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
]
}
}
},
"meta": {
"v3": true,
"status": "active",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"features": [
{
"name": "<string>",
"enabled": true,
"limit": 123
}
]
},
"relationships": {
"users": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"service_accounts": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"groups": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"teams": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
]
},
"nomenclature": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "<string>"
}
}
}
}
]
}{
"id": "98ca4a78-b66f-4234-9719-aaf832ee6669",
"status": "400",
"title": "A validation error was encountered",
"source": {
"parameter": "include"
},
"meta": {
"resource": "wrong_value"
}
}{
"id": "05fc9c8d-73b9-4697-9337-57f7a567a48f",
"status": "401",
"title": "You are not authorised to access this resource",
"detail": "In order to access this resource, you need the 'admin' role.",
"code": "AUTH.001"
}{
"id": "7b4c8f12-3e9a-4d5b-8c6f-1a2b3c4d5e6f",
"status": "404",
"title": "Resource not found",
"detail": "The requested resource could not be found or does not exist.",
"code": "NOT_FOUND.001"
}{
"id": "fe9d9a69-f0a7-4fdc-bb2c-176027f316c5",
"status": "500",
"title": "Internal Server Error",
"detail": "An unexpected error occurred on the server."
}Authorizations
Session token for authentication.
Path Parameters
The unique identifier for the cost kit.
Body
Where to apply the kit, how much of it, and as at what date.
The request type is cost-kit-applications, but there is no such resource:
nothing of that name is stored and nothing can be fetched back by that id. It
names the action, and the response is the collection of cost lines the action
produced. The id that groups those lines travels on the lines themselves as
cost_kit_application_id.
Show child attributes
Show child attributes
Response
The cost lines the kit produced — one per item in force on the work date, each priced and frozen exactly as a hand-entered line is.
They all share a cost_kit_application_id and carry the same cost_kit
snapshot, so a client can render and remove them as the single action they
were.
JSON API response object
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Related resources that can be included when a cost line is returned.
cost-items and cost-item-versions are both optional on a line, so asking
for either simply yields nothing for the lines that have none — an ad-hoc line
has no rate code, and a line priced on the job has no register version behind
its rate. Neither is an error, and a request for both across a mixed collection
comes back with whatever exists.
An organisation
- Option 1
- Option 2
- Option 3
- Option 4
Show child attributes
Show child attributes