API reference Services
Services Service API Endpoints
The production API base URL is https://api.tradesmentechnologies.com/.
Authentication
Most endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <your_token>
Mutating requests may carry an Idempotency-Key header so a retry never acts twice — see Idempotent Retries in the Accounts API documentation.
Base Path
/prod/services
Program settings on services
When a company uses maintenance programs (allowMaintenancePrograms), linear and 1D non-linear services carry the same optional program settings a program-template line has, so the price book is the single place to define how a service behaves inside a program. 2D non-linear services never carry them (they cannot be program lines). All keys are optional on create and update; sending null clears a value. Absent keys are returned as null.
{
"programPrice": 45.0,
"programPrices": { "1": 80, "2": 150 },
"frequency": { "type": "QUARTERLY", "quarters": [2, 4] },
"diyCost": 25.0,
"diyTimeMinutes": 30,
"variants": [
{ "variantId": "uuid", "label": "Standard" },
{ "variantId": "uuid2", "label": "Deep" }
]
}
programPrice(linear services only; non-negative number, per unit) — the discounted price charged on program visits.programPrices(1D non-linear only;{quantity: total}likeprices, positive-integer keys, non-negative totals; an empty object clears it) is its tiered counterpart. When absent, template lines fall back to the catalog price.frequency— the same calendar object as a program line (MONTHLY, orQUARTERLYwith a non-emptyquarterssubset of 1–4, deduplicated and sorted;quartersis rejected onMONTHLY).null/absent behaves asMONTHLYwhen copied onto lines.diyCost(non-negative number) /diyTimeMinutes(non-negative integer) — display-only DIY comparison values, same unit semantics as the prices.variants— label-only service options ({variantId?, label}); the server generatesvariantIdwhen omitted and preserves sent ids; a legacypriceis dropped; an empty array clears the options.
Propagation to program templates. PUT /services/services/{serviceId} and PUT /services/services/non-linear/{serviceId} rewrite every program template line whose serviceId (and matching serviceType) references the saved service: name, unit, oneTimePrice (catalog price — non-linear: the tier for the line's quantity, highest tier as fallback), programPrice (the service's program price, or the catalog price when none), the non-linear line's prices/programPrices tier-table snapshots (copied whole; a cleared service table removes the line's copy), frequency, diyCost/diyTimeMinutes, and variants are all copied from the service; the line's lineId and quantity are kept. The response carries service (the full stored row) and updatedTemplates (the full rewritten template records — clients should replace their local copies). The sync runs after the service itself is saved and is best-effort: if it fails, the response is still 200 with updatedTemplates: [] and templateSyncFailed: true. Customer programs are never touched — their lines stay grandfathered snapshots (see programs-endpoints.md).
Inventory items on services
Linear services and 1D non-linear services may carry inventoryItems — the parts consumed per unit of the service, used to derive a job's stock deduction from its invoice/estimate lines (see inventory-endpoints.md, "Job deductions"). 2D non-linear services never carry the field (it is ignored on their create/update).
{
"inventoryItems": [
{ "itemId": "item-uuid", "quantity": 1 },
{ "itemId": "wire-item-uuid", "quantity": 2.5 }
]
}
- Each entry needs a non-empty
itemIdand a finitequantity > 0; at most 50 entries; ids unique. Ids are not validated against the inventory table — an id that no longer exists simply derives nothing. - Create: omitted/
null/[]storesnull. Update: omitted keeps the stored list;nullor[]clears it. - Program-template sync is not affected by this field.
Endpoints
Create Services
Create one or more services for the company.
- Method: POST
- Path:
/prod/services/services - Headers:
Authorization: Bearer <token> - Request Body:
Note: The{ "services": [ { "name": "Service Name", "unit": "sqft", "price": 5.0, "cost": 3.0, "description": "Optional description", "parentCategoryId": "uuid", "allowInitials": true, "displayOrder": 1 } ] }servicesfield is required and must be a non-empty array. Each service object requiresnameandprice. The following fields are optional per service:unit(defaults to null — a free-form unit label such assqftorhour),description(defaults to null),cost(defaults to null — tracks the business's cost for accounting purposes),parentCategoryId(defaults to null),allowInitials(defaults to false — indicates whether customer initials can be captured for this service on documents),displayOrder(defaults to null — an integer used only to determine the order in which the service is shown in the front end; must be an integer when provided). All services are validated before any are created — if validation fails for any service, none are created. Validation errors include the index of the offending service. Each service also accepts the optional program settings described under "Program settings on services" above (programPrice,frequency,diyCost,diyTimeMinutes,variants); all default to null. Each service also accepts the optionalinventoryItemslist described under "Inventory items on services" (defaults to null). - Success Response:
201 Created{ "message": "Services created successfully", "serviceIds": ["uuid1", "uuid2"] }
Delete Service
- Method: DELETE
- Path:
/prod/services/services/{serviceId} - Headers:
Authorization: Bearer <token> - Success Response:
200 OK{ "message": "Service deleted successfully" }
Update Service
- Method: PUT
- Path:
/prod/services/services/{serviceId} - Headers:
Authorization: Bearer <token> - Request Body: (All fields are optional)
Note:{ "name": "Updated Service Name", "unit": "sqft", "price": 6.0, "cost": 3.5, "description": "Updated description", "parentCategoryId": "uuid", "allowInitials": true, "displayOrder": 1 }displayOrdermust be an integer when provided, and may be set tonullto clear it. Also accepts the optional program settings (programPrice,frequency,diyCost,diyTimeMinutes,variants— see "Program settings on services"); each may be set tonullto clear it. Also acceptsinventoryItems(see "Inventory items on services"): omitted keeps,null/[]clears. - Success Response:
200 OK{ "message": "Service updated successfully", "service": { "companyId": "uuid", "serviceId": "uuid", "name": "Updated Service Name", "price": 6.0, "programPrice": 4.5 }, "updatedTemplates": [] }serviceis the full stored row after the update;updatedTemplateslists every program template whose lines were rewritten from this service (see "Program settings on services").templateSyncFailed: trueis added when that propagation failed. - Error Responses:
400 Bad Request— missing body, no recognized field in the body, or a field fails validation.404 Not Found— no service with that id in the company.
Convert Service to Non-Linear
- Method: POST
- Path:
/prod/services/services/{serviceId}/convert-to-non-linear - Headers:
Authorization: Bearer <token> - Request Body: optional; every field is optional
Turns a regular (linear) service into a non-linear service with the same{ "prices": { "1": 110, "5": 450 }, "costs": { "1": 55 }, "programPrices": { "1": 90, "5": 380 } }serviceId: the row leaves the services list and appears in the non-linear services list of the session bootstrap.name,unit,description,parentCategoryId,allowInitials,displayOrder, the program settings (frequency,diyCost,diyTimeMinutes,variants),inventoryItemsandcreatedAtare carried over;convertedFromLinearAtrecords the conversion. The tier tables ({ quantity: amount }, same rules as Create Non-Linear Service) come from the body; each one omitted is seeded from the linear amount as the quantity-1 tier:pricesfromprice({}when the service has no price),costsfromcost,programPricesfromprogramPrice(nullwhen the linear field is absent). Anullcosts/programPricesclears it. The move is one transaction, so the service is never in both lists. Program template lines that reference the service asLINEARare re-typed toNON_LINEAR_1Dand re-priced from the new tier tables (snapshots included, as on Update Non-Linear Service); customer programs are never touched. Existing estimates and invoices keep their line-item snapshots. - Success Response:
200 OK{ "message": "Service converted to non-linear successfully", "service": { "companyId": "uuid", "serviceId": "uuid", "name": "Lawn Mowing", "pricingModel": "non_linear_1d", "prices": { "1": 110 }, "costs": { "1": 55 }, "programPrices": null, "convertedFromLinearAt": "2026-10-14T12:00:00.000Z" }, "updatedTemplates": [] }serviceis the full stored non-linear row;updatedTemplates/templateSyncFailedas on Update Service. - Error Responses:
400 Bad Request— missingserviceId, a body that is not a JSON object, or an invalid tier table.404 Not Found— no linear service with that id in the company.409 Conflict— the service was deleted or converted by a concurrent request; reload and retry.
Bulk Update Display Order
Update the displayOrder of many items at once across all four service item types (services, categories, non-linear services, and 2D non-linear services). Intended for reordering: send the full set of items whose order changed in a single request.
- Method: PUT
- Path:
/prod/services/display-order - Headers:
Authorization: Bearer <token> - Request Body:
Note:{ "items": [ { "type": "service", "id": "uuid", "displayOrder": 0 }, { "type": "category", "id": "uuid", "displayOrder": 1 }, { "type": "non-linear", "id": "uuid", "displayOrder": 2 }, { "type": "2d-non-linear", "id": "uuid", "displayOrder": 3 } ] }itemsis required and must be a non-empty array. For each item,typemust be one ofservice,category,non-linear,2d-non-linear;idis the item'sserviceId(orcategoryIdfor categories); anddisplayOrderis required and must be an integer. All items are validated before any are written — if any item is structurally invalid, the request is rejected with400and nothing is updated. Updates are then applied on a best-effort basis: each item is updated independently, so an item that does not exist (or belongs to another company) is reported asupdated: falsewithreason: "not found"(an unexpected write failure asreason: "error") while the remaining items still succeed. - Success Response:
200 OK{ "message": "Display order updated", "updatedCount": 3, "results": [ { "id": "uuid", "type": "service", "updated": true }, { "id": "uuid", "type": "category", "updated": false, "reason": "not found" } ] }
Create Category
- Method: POST
- Path:
/prod/services/categories - Headers:
Authorization: Bearer <token> - Request Body:
Note:{ "name": "Category Name", "parentCategoryId": "uuid", "displayOrder": 1 }displayOrderis optional (defaults to null — an integer used only to determine the order in which the category is shown in the front end; must be an integer when provided). - Success Response:
201 Created{ "message": "Category created successfully", "categoryId": "uuid" }
Delete Category
- Method: DELETE
- Path:
/prod/services/categories/{categoryId} - Headers:
Authorization: Bearer <token> - Success Response:
200 OK{ "message": "Category and its contents deleted successfully" }
Update Category
- Method: PUT
- Path:
/prod/services/categories/{categoryId} - Headers:
Authorization: Bearer <token> - Request Body: (All fields are optional)
Note:{ "name": "Updated Category Name", "parentCategoryId": "uuid", "displayOrder": 1 }displayOrdermust be an integer when provided, and may be set tonullto clear it. An empty body (no recognized field) is a400. - Success Response:
200 OK{ "message": "Category updated successfully" } - Error Responses:
400 Bad Request— validation.404 Not Found— no category with that id in the company.
Create Non-Linear Service
- Method: POST
- Path:
/prod/services/services/non-linear - Headers:
Authorization: Bearer <token> - Request Body:
Note:{ "name": "Service Name", "unit": "vents", "prices": { "1": 100, "5": 400 }, "costs": { "1": 50, "5": 200 }, "description": "Description", "parentCategoryId": "uuid", "allowInitials": true, "displayOrder": 1 }nameandpricesare required:pricesis an object of{ quantity: price }whose keys are non-negative numbers and whose values are finite numbers.unit,description,costs,parentCategoryId,allowInitials, anddisplayOrderare optional (unit/descriptiondefault to null and are ignored unless strings). The optional program settings (programPrices,frequency,diyCost,diyTimeMinutes,variants— see "Program settings on services") default to null, as does the optionalinventoryItemslist (see "Inventory items on services").costsis the tiered counterpart of a linear service'scost— an object of{ quantity: cost }tracking the business's cost for accounting purposes, stored as sent (not validated); defaults to null. A top-levelcostis ignored on non-linear services.allowInitialsdefaults tofalse.displayOrderdefaults to null — an integer used only to determine the order in which the service is shown in the front end; must be an integer when provided. - Success Response:
201 Created{ "message": "Non-linear service created successfully", "serviceId": "uuid" }
Delete Non-Linear Service
- Method: DELETE
- Path:
/prod/services/services/non-linear/{serviceId} - Headers:
Authorization: Bearer <token> - Success Response:
200 OK{ "message": "Non-linear service deleted successfully" }
Update Non-Linear Service
- Method: PUT
- Path:
/prod/services/services/non-linear/{serviceId} - Headers:
Authorization: Bearer <token> - Request Body: (All fields are optional)
Note:{ "name": "Updated Service Name", "unit": "vents", "prices": { "1": 110, "5": 450 }, "costs": { "1": 55, "5": 220 }, "description": "Updated description", "parentCategoryId": "uuid", "allowInitials": true, "displayOrder": 1 }displayOrdermust be an integer when provided, and may be set tonullto clear it.prices, when present, follows the Create rules;costsis the{ quantity: cost }map (stored as sent). Also accepts the optional program settings (programPrices,frequency,diyCost,diyTimeMinutes,variants— see "Program settings on services"); each may be set tonullto clear it. Also acceptsinventoryItems(see "Inventory items on services"): omitted keeps,null/[]clears. - Success Response:
200 OK
Same response semantics as Update Service:{ "message": "Non-linear service updated successfully", "service": { "companyId": "uuid", "serviceId": "uuid", "name": "Updated Service Name", "pricingModel": "non_linear_1d", "prices": { "1": 110, "5": 450 }, "programPrices": { "1": 90, "5": 380 } }, "updatedTemplates": [] }serviceis the full stored row,updatedTemplatesthe rewritten program templates,templateSyncFailedpresent only when propagation failed. - Error Responses:
400 Bad Request— missing body, no recognized field, or a field fails validation.404 Not Found— no non-linear service with that id in the company.
Create 2D Non-Linear Service
- Method: POST
- Path:
/prod/services/services/2d-non-linear - Headers:
Authorization: Bearer <token> - Request Body:
Note:{ "name": "Service Name", "dimXName": "Width", "dimYName": "Height", "prices": { "10": { "10": 100, "20": 150 }, "20": { "10": 180, "20": 250 } }, "cost": 75.0, "description": "Description", "parentCategoryId": "uuid", "allowInitials": true, "displayOrder": 1 }description,cost,parentCategoryId,allowInitials, anddisplayOrderare optional.costtracks the business's cost for accounting purposes.allowInitialsdefaults tofalse.displayOrderdefaults to null — an integer used only to determine the order in which the service is shown in the front end; must be an integer when provided. - Success Response:
201 Created{ "message": "2D non-linear service created successfully", "serviceId": "uuid" }
Delete 2D Non-Linear Service
- Method: DELETE
- Path:
/prod/services/services/2d-non-linear/{serviceId} - Headers:
Authorization: Bearer <token> - Success Response:
200 OK{ "message": "2D non-linear service deleted successfully" }
Update 2D Non-Linear Service
- Method: PUT
- Path:
/prod/services/services/2d-non-linear/{serviceId} - Headers:
Authorization: Bearer <token> - Request Body: (All fields are optional)
Note:{ "name": "Updated Service Name", "dimXName": "Updated Width", "dimYName": "Updated Height", "prices": { "10": { "10": 110, "20": 160 }, "20": { "10": 190, "20": 260 } }, "cost": 80.0, "description": "Updated description", "parentCategoryId": "uuid", "allowInitials": true, "displayOrder": 1 }displayOrdermust be an integer when provided, and may be set tonullto clear it. An empty body (no recognized field) is a400. - Success Response:
200 OK{ "message": "2D non-linear service updated successfully" } - Error Responses:
400 Bad Request— validation.404 Not Found— no 2D non-linear service with that id in the company.