Service order time
Hours logged against a service order (an order with ordertype service). Hours are not order lines: they become lines only when billed, at which point status becomes billed and the record is frozen.
The object#
| Field | Type | Description |
|---|---|---|
| idread-only | integer | NolaPro id. |
| employeeid | integer | Employeeid. Id only - premployee is read-only or has no code key. |
| vendorid | integer | Reference to vendors, by id. Write either this or vendor. |
| vendor | string(30) | Code for the referenced vendor, instead of the id. |
| ordered | integer | 1 marks pre-ordered hours - time sold in advance rather than actual worked time. |
| techname | string(50) | Name of the technician who worked the time, as entered. |
| hoursqty | string(2dp) | Quantity, 2 decimal places, as a string. Stored as decimal(2dp). |
| startdatetime | string | When the work started. |
| enddatetime | string | When the work ended. |
| ratemoney | string(4dp) | Stored as decimal(10,4). |
| totalpricemoney | string(4dp) | Stored as decimal(10,4). |
| totalcostmoney | string(4dp) | Stored as decimal(10,4). |
| worktypeid | integer | Reference to worktypes, by id. Write either this or worktype. |
| worktype | string(100) | Code for the referenced worktype, instead of the id. |
| worksubtypeid | integer | Worksubtypeid. Id only. |
| notes | text | Notes. Stored as text, no practical length limit. |
| entrydate | string | When the record was created. |
| cancel | boolean | Cancelled. Always a boolean on the wire, whatever integer width the column uses (D23). |
| lastchangedateread-only | string | Last modification. Drives modifiedsince. |
| internal_comment | text | Internal_comment. Stored as text, no practical length limit. |
| costratemoney | string(4dp) | Stored as decimal(10,4). |
| statusread-only | integer | 0 = zero-hour placeholder (never bills), 1 = ready to bill (set automatically the moment hours are non-zero - there is no approval step), 2 = billed (an order line exists for it; the entry refuses PATCH and DELETE). Derived, never accepted as input. |
| orderid | integer | Orderid. Id only. |
| currency | string(10) | Currency the billable rate is stated in. |
| assetid | integer | Assetid. Id only. |
| assetidlastchangedate | string | When the assigned asset last changed on the time entry. |
| baseratemoney | string(4dp) | Stored as decimal(10,4). |
| prpaytypeid | integer | Prpaytypeid. Id only. |
| prpaytypemultipliernumber | string(2dp) | Numeric string, 2 decimal places. Not money - do not apply currency rounding. |
| taxclassid | integer | Taxclassid. Id only. |
| taxclass | string(100) | The name of the referenced taxclasses, instead of the id. Send this or taxclassid, not both unless they agree. |
| sortorder | integer | Display order. Lower sorts first. |
| externalid | string(100) | Your own key. Scoped to your company. |
Endpoints#
POST/serviceordertimes/batch
207
Create many.
Requires scope orders:write.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
Body array
| Field | Type | Description |
|---|---|---|
| orderidrequired | integer | The service order the hours belong to. Must be a service order (422 hours_require_service_order) that has not reached billed (409 order_already_billed). |
| employeeid | integer | The employee who did the work. Must be active with a work class - the time screen only offers those. Send this or a vendor, never both. |
| vendorid | integer | The vendor/subcontractor who did the work. Must carry a work class and not be a child location. |
| vendor | string(30) | The vendor by code instead of id. Same validation either way. |
| worktypeidrequired | integer | The kind of work. GET /serviceworktypes lists them. |
| worksubtypeid | integer | Optional sub-classification. Must belong to worktypeid. |
| hoursqty | string(2dp) | Hours worked, 2 decimal places, as a string. Negative is a correction. Zero is allowed only with a note - the entry stays a placeholder (status 0) and never bills. |
| entrydate | string | The date the work was done. Defaults to today. |
| ratemoney | string(4dp) | The billing rate per hour, in the ORDER currency. When omitted it derives from the work class rate for the worker, converted at the order date, times the pay type multiplier. When sent it is stored as sent - the multiplier is not applied on top. |
| costratemoney | string(4dp) | Cost per hour to the company, in the company BASE currency. When omitted it derives from the worker: fringe-loaded pay for an employee, default cost per hour for a vendor. |
| notes | text | Customer-visible notes. Concatenated onto the invoice line when hours convert. |
| internal_comment | text | Internal notes. Never shown to the customer. |
| techname | string(50) | Technician name override. Defaults from the worker. |
| taxclassid | integer | Tax class, when labor is taxable. |
| assetid | integer | The customer asset being serviced. |
| prpaytypeid | integer | AR pay type (straight time, overtime...). Only pay types flagged for AR are accepted - the screen offers no others. |
| prpaytypemultipliernumber | string(2dp) | Rate multiplier. Defaults to the pay type's own multiplier, else 1.00. Applied only when rate is derived. |
| startdatetime | string | Timer start, for entries imported from a timer. |
| enddatetime | string | Timer stop. |
| externalid | string(100) | Your own key. Scoped to your company. |
curl -X POST \ 'https://acme.nolapro.com/!/api/v2/serviceordertimes/batch' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '[ { "orderid": 104, "worktypeid": 104 } ]'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/serviceordertimes/batch'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode([ [ 'orderid' => 104, 'worktypeid' => 104, ], ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.post( "https://acme.nolapro.com/!/api/v2/serviceordertimes/batch", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json=[ { "orderid": 104, "worktypeid": 104, }, ], ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/serviceordertimes/batch', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify([ { orderid: 104, worktypeid: 104, }, ]), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new[] { new { orderid = 104, worktypeid = 104, }, }; var req = new HttpRequestMessage(HttpMethod.Post, "https://acme.nolapro.com/!/api/v2/serviceordertimes/batch") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
GET/serviceordertimes
200403
List service order time.
Requires scope orders:read.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| status | query | string | Restrict to one status. |
| vendorid | query | string | Restrict to one vendorid. |
| employeeid | query | string | Restrict to one employeeid. |
| orderid | query | string | Restrict to one orderid. |
| cancel | query | string | Defaults to false. Pass true or any. |
| modifiedsince | query | string | RFC 3339 UTC timestamp. Returns only records changed since then, including cancelled ones - so cancel defaults to any rather than false when this is used. The response carries _meta.synced_through; store it and send it back next time. |
| externalid | query | string | Exact match on your own key. |
| include | query | string | Comma-separated extras to embed. Only custom_fields is available: the extra fields this install has defined on the record. Off by default, and an unrecognised value is refused rather than ignored. See Conventions. |
When it fails
| Status | Code | Meaning |
|---|---|---|
| 403 | insufficient_scope | Token lacks read scope. |
curl \ 'https://acme.nolapro.com/!/api/v2/serviceordertimes' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/serviceordertimes'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/serviceordertimes", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/serviceordertimes', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/serviceordertimes"); res.EnsureSuccessStatusCode();
POST/serviceordertimes
201400409422
Create.
Requires scope orders:write.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
Body
| Field | Type | Description |
|---|---|---|
| orderidrequired | integer | The service order the hours belong to. Must be a service order (422 hours_require_service_order) that has not reached billed (409 order_already_billed). |
| employeeid | integer | The employee who did the work. Must be active with a work class - the time screen only offers those. Send this or a vendor, never both. |
| vendorid | integer | The vendor/subcontractor who did the work. Must carry a work class and not be a child location. |
| vendor | string(30) | The vendor by code instead of id. Same validation either way. |
| worktypeidrequired | integer | The kind of work. GET /serviceworktypes lists them. |
| worksubtypeid | integer | Optional sub-classification. Must belong to worktypeid. |
| hoursqty | string(2dp) | Hours worked, 2 decimal places, as a string. Negative is a correction. Zero is allowed only with a note - the entry stays a placeholder (status 0) and never bills. |
| entrydate | string | The date the work was done. Defaults to today. |
| ratemoney | string(4dp) | The billing rate per hour, in the ORDER currency. When omitted it derives from the work class rate for the worker, converted at the order date, times the pay type multiplier. When sent it is stored as sent - the multiplier is not applied on top. |
| costratemoney | string(4dp) | Cost per hour to the company, in the company BASE currency. When omitted it derives from the worker: fringe-loaded pay for an employee, default cost per hour for a vendor. |
| notes | text | Customer-visible notes. Concatenated onto the invoice line when hours convert. |
| internal_comment | text | Internal notes. Never shown to the customer. |
| techname | string(50) | Technician name override. Defaults from the worker. |
| taxclassid | integer | Tax class, when labor is taxable. |
| assetid | integer | The customer asset being serviced. |
| prpaytypeid | integer | AR pay type (straight time, overtime...). Only pay types flagged for AR are accepted - the screen offers no others. |
| prpaytypemultipliernumber | string(2dp) | Rate multiplier. Defaults to the pay type's own multiplier, else 1.00. Applied only when rate is derived. |
| startdatetime | string | Timer start, for entries imported from a timer. |
| enddatetime | string | Timer stop. |
| externalid | string(100) | Your own key. Scoped to your company. |
When it fails
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A required field was missing, or hours were zero with no note. |
| 400 | invalid_type | A decimal arrived as a JSON number, or an id was not an integer. |
| 400 | invalid_precision | More decimal places than the field holds. |
| 400 | unknown_parameter | A field outside the accepted set, including the derived ones (totalprice, totalcost, baserate, status, currency). |
| 409 | order_already_billed | The order has reached billed. The service order screen refuses hours there too. |
| 422 | hours_require_service_order | The order is not a service order. |
| 422 | unknown_value | The worker, work type, tax class, asset or pay type did not resolve in this company. |
| 422 | rule_violation | NolaPro refused the write. message carries its reason. |
curl -X POST \ 'https://acme.nolapro.com/!/api/v2/serviceordertimes' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '{ "orderid": 104, "worktypeid": 104 }'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/serviceordertimes'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode([ 'orderid' => 104, 'worktypeid' => 104, ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.post( "https://acme.nolapro.com/!/api/v2/serviceordertimes", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json={ "orderid": 104, "worktypeid": 104, }, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/serviceordertimes', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify({ orderid: 104, worktypeid: 104, }), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new { orderid = 104, worktypeid = 104, }; var req = new HttpRequestMessage(HttpMethod.Post, "https://acme.nolapro.com/!/api/v2/serviceordertimes") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
Response 201
{
"employeeid": 104,
"vendorid": 104,
"vendor": "Example vendor",
"ordered": 1,
"techname": "Example techname",
"hours": "1.000000"
}
GET/serviceordertimes/{id}
200404
Retrieve one record.
Requires scope orders:read.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The record id. |
| include | query | string | Comma-separated extras to embed. Only custom_fields is available: the extra fields this install has defined on the record. Off by default, and an unrecognised value is refused rather than ignored. See Conventions. |
When it fails
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No record with that id. |
curl \ 'https://acme.nolapro.com/!/api/v2/serviceordertimes/104' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/serviceordertimes/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/serviceordertimes/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/serviceordertimes/104', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/serviceordertimes/104"); res.EnsureSuccessStatusCode();
Response 200
{
"employeeid": 104,
"vendorid": 104,
"vendor": "Example vendor",
"ordered": 1,
"techname": "Example techname",
"hours": "1.000000"
}
PATCH/serviceordertimes/{id}
200400404409422
Update.
Requires scope orders:write.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The record id. |
Body
| Field | Type | Description |
|---|---|---|
| worksubtypeid | integer | Optional sub-classification. Must belong to worktypeid. |
| hoursqty | string(2dp) | Hours worked, 2 decimal places, as a string. Negative is a correction. Zero is allowed only with a note - the entry stays a placeholder (status 0) and never bills. |
| entrydate | string | The date the work was done. Defaults to today. |
| ratemoney | string(4dp) | The billing rate per hour, in the ORDER currency. When omitted it derives from the work class rate for the worker, converted at the order date, times the pay type multiplier. When sent it is stored as sent - the multiplier is not applied on top. |
| costratemoney | string(4dp) | Cost per hour to the company, in the company BASE currency. When omitted it derives from the worker: fringe-loaded pay for an employee, default cost per hour for a vendor. |
| notes | text | Customer-visible notes. Concatenated onto the invoice line when hours convert. |
| internal_comment | text | Internal notes. Never shown to the customer. |
| techname | string(50) | Technician name override. Defaults from the worker. |
| taxclassid | integer | Tax class, when labor is taxable. |
| assetid | integer | The customer asset being serviced. |
| prpaytypeid | integer | AR pay type (straight time, overtime...). Only pay types flagged for AR are accepted - the screen offers no others. |
| prpaytypemultipliernumber | string(2dp) | Rate multiplier. Defaults to the pay type's own multiplier, else 1.00. Applied only when rate is derived. |
| startdatetime | string | Timer start, for entries imported from a timer. |
| enddatetime | string | Timer stop. |
| externalid | string(100) | Your own key. Scoped to your company. |
When it fails
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Nothing to change, or a malformed value. |
| 400 | unknown_parameter | A field outside the amendable set - including the worker and work type, which are the entry's identity. |
| 404 | not_found | No such record, or it belongs to another company. |
| 409 | hours_already_billed | Billing already converted this entry into an order line. |
| 409 | stale_record | If-Match did not match; someone else changed it first. |
| 422 | unknown_value | A reference did not resolve in this company. |
| 422 | rule_violation | The entry is cancelled, or NolaPro refused the change. |
curl -X PATCH \ 'https://acme.nolapro.com/!/api/v2/serviceordertimes/104' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '{ "worksubtypeid": 104, "hours": "1.000000", "rate": "125.0000", "costrate": "125.0000", "notes": "Example notes", "internal_comment": "Example internal comment" }'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/serviceordertimes/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PATCH', CURLOPT_POSTFIELDS => json_encode([ 'worksubtypeid' => 104, 'hours' => '1.000000', 'rate' => '125.0000', 'costrate' => '125.0000', 'notes' => 'Example notes', 'internal_comment' => 'Example internal comment', ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.patch( "https://acme.nolapro.com/!/api/v2/serviceordertimes/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json={ "worksubtypeid": 104, "hours": "1.000000", "rate": "125.0000", "costrate": "125.0000", "notes": "Example notes", "internal_comment": "Example internal comment", }, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/serviceordertimes/104', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify({ worksubtypeid: 104, hours: "1.000000", rate: "125.0000", costrate: "125.0000", notes: "Example notes", internal_comment: "Example internal comment", }), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new { worksubtypeid = 104, hours = "1.000000", rate = "125.0000", costrate = "125.0000", notes = "Example notes", internal_comment = "Example internal comment", }; var req = new HttpRequestMessage(HttpMethod.Patch, "https://acme.nolapro.com/!/api/v2/serviceordertimes/104") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
Response 200
{
"employeeid": 104,
"vendorid": 104,
"vendor": "Example vendor",
"ordered": 1,
"techname": "Example techname",
"hours": "1.000000"
}
DELETE/serviceordertimes/{id}
200404409
Cancel.
Requires scope orders:cancel.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The record id. |
When it fails
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No such record, or it belongs to another company. |
| 409 | hours_already_billed | Billing already converted this entry into an order line. Credit the invoice and cancel the line first. |
curl -X DELETE \ 'https://acme.nolapro.com/!/api/v2/serviceordertimes/104' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/serviceordertimes/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'DELETE', ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.delete( "https://acme.nolapro.com/!/api/v2/serviceordertimes/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/serviceordertimes/104', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var req = new HttpRequestMessage(HttpMethod.Delete, "https://acme.nolapro.com/!/api/v2/serviceordertimes/104"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();