Service Promos API
18 PracBill API endpoints for service promos. Base URL https://billing.pracbill.com.au/api.
List the promo assignment rows on a engineering service, newest `date_start` first. Soft-deleted rows and rows whose promo is deleted or inactive are excluded. The service must exist and be non-deleted, and its customer must belong to the token's department (engineering has no department column of its own), otherwise every method fails with `service not found`.
GET /{api_key}/engineering/{enid}/service_promos
Responses
200— The promo assignment rows with their promo
Create a promo assignment row on a engineering service. `service_promo_id` and `date_start` are required; the promo must be non-deleted, active and in the token's department. `date_end`, when given, may not precede `date_start`. When `date_end` is omitted (or sent as null) it is derived as `date_start + duration_months - 1 day` from the promo, exactly like the staff UI; a promo without a duration leaves the assignment open-ended. A PUT that clears `date_end` re-derives it the same way. The whole body is validated before anything is written; an unknown key (including `deptid`, `enid`, `id`) rejects the request. The service must exist and be non-deleted, and its customer must belong to the token's department (engineering has no department column of its own), otherwise every method fails with `service not found`.
POST /{api_key}/engineering/{enid}/service_promos
Request body
| Field | Type | Description |
|---|---|---|
service_promo_id | integer | A non-deleted, active promo in the token's department |
date_start | string | YYYY-MM-DD |
date_end | string | YYYY-MM-DD, not before date_start; null / "" clears it (assignments then re-derive it from the promo duration) |
active | boolean | Boolean-like (true/false, 1/0, "yes"/"no"); defaults to true on create |
Responses
200— The created promo assignment row
Soft-delete a promo assignment row (sets `deleted = 1`). Only the relationship row is touched — the promo and the engineering service are left as they were. Allowed even when the row's promo has since been deleted or deactivated, so orphaned rows stay removable. The service must exist and be non-deleted, and its customer must belong to the token's department (engineering has no department column of its own), otherwise every method fails with `service not found`.
DELETE /{api_key}/engineering/{enid}/service_promos/{id}
Responses
200— Deletion result
Fetch a single promo assignment row. Fails with `service promo relationship not found` when the row does not exist, is deleted, belongs to another department, sits under a different engineering service than the one in the URL, or its promo is deleted / inactive. The service must exist and be non-deleted, and its customer must belong to the token's department (engineering has no department column of its own), otherwise every method fails with `service not found`.
GET /{api_key}/engineering/{enid}/service_promos/{id}
Responses
200— The promo assignment row
Identical to the PUT form of this endpoint, for clients that cannot send PUT.
POST /{api_key}/engineering/{enid}/service_promos/{id}
Request body
| Field | Type | Description |
|---|---|---|
service_promo_id | integer | A non-deleted, active promo in the token's department |
date_start | string | YYYY-MM-DD |
date_end | string | YYYY-MM-DD, not before date_start; null / "" clears it (assignments then re-derive it from the promo duration) |
active | boolean | Boolean-like (true/false, 1/0, "yes"/"no"); defaults to true on create |
Responses
200— The updated promo assignment row
Partially update a promo assignment row: only the fields present in the body change. The same validation as create applies (minus the required-field rule); `date_end` / `date_start` are checked against the stored values for anything not sent, and a new `service_promo_id` must be a live, active promo in the token's department. When `date_end` is omitted (or sent as null) it is derived as `date_start + duration_months - 1 day` from the promo, exactly like the staff UI; a promo without a duration leaves the assignment open-ended. A PUT that clears `date_end` re-derives it the same way. The service must exist and be non-deleted, and its customer must belong to the token's department (engineering has no department column of its own), otherwise every method fails with `service not found`.
PUT /{api_key}/engineering/{enid}/service_promos/{id}
Request body
| Field | Type | Description |
|---|---|---|
service_promo_id | integer | A non-deleted, active promo in the token's department |
date_start | string | YYYY-MM-DD |
date_end | string | YYYY-MM-DD, not before date_start; null / "" clears it (assignments then re-derive it from the promo duration) |
active | boolean | Boolean-like (true/false, 1/0, "yes"/"no"); defaults to true on create |
Responses
200— The updated promo assignment row
List service promos. Only non-deleted promos belonging to the API token's department are returned (inactive promos are included). Results are ordered by promo name, then id.
GET /{api_key}/service_promos
Responses
200— A page of service promos
Create a service promo. `name`, `discount_type` and `discount_value` are required. The body is validated as a whole before anything is written: any key outside the writable set (including `deptid`, `deleted` and `id`) or any invalid value rejects the request with `success: false`. `deptid` is taken from the API token. `signup_code` is trimmed and uppercased exactly as the UI does.
POST /{api_key}/service_promos
Request body
| Field | Type | Description |
|---|---|---|
name | string | Promo name. Required on create; trimmed; cannot be blank. |
description | string | |
signup_code | string | Optional customer-entered code. Trimmed and uppercased on save, identically to the UI; send an empty string or null to clear it. |
discount_type | string (percent | fixed) | Required on create. |
discount_value | number | Required on create. Non-negative; stored to two decimal places. |
duration_months | integer | Positive integer, or null / empty string for an ongoing promo. |
stackable | object | Boolean-like: true/false, 1/0, "1"/"0", "true"/"false", "yes"/"no". Defaults to false on create. |
active | object | Boolean-like (see `stackable`). Defaults to true on create. |
Responses
200— The created service promo
Soft-delete a service promo (sets `deleted = 1`). The row is never physically removed and the promo disappears from the list and get endpoints. Service-type availability, assignment and inclusion rows referencing the promo are left untouched.
DELETE /{api_key}/service_promos/{id}
Responses
200— Deletion result
Fetch a single service promo. Fails with `service promo not found` when the promo does not exist, is deleted, or belongs to another department — the three cases are indistinguishable.
GET /{api_key}/service_promos/{id}
Responses
200— The service promo
Identical to the PUT form of this endpoint, for clients that cannot send PUT.
POST /{api_key}/service_promos/{id}
Request body
| Field | Type | Description |
|---|---|---|
name | string | Promo name. Required on create; trimmed; cannot be blank. |
description | string | |
signup_code | string | Optional customer-entered code. Trimmed and uppercased on save, identically to the UI; send an empty string or null to clear it. |
discount_type | string (percent | fixed) | Required on create. |
discount_value | number | Required on create. Non-negative; stored to two decimal places. |
duration_months | integer | Positive integer, or null / empty string for an ongoing promo. |
stackable | object | Boolean-like: true/false, 1/0, "1"/"0", "true"/"false", "yes"/"no". Defaults to false on create. |
active | object | Boolean-like (see `stackable`). Defaults to true on create. |
Responses
200— The updated service promo
Partially update a service promo: only the fields present in the body change. The same validation as create applies (minus the required-field rule) and the whole request is rejected, with nothing written, if any field is invalid or unsupported.
PUT /{api_key}/service_promos/{id}
Request body
| Field | Type | Description |
|---|---|---|
name | string | Promo name. Required on create; trimmed; cannot be blank. |
description | string | |
signup_code | string | Optional customer-entered code. Trimmed and uppercased on save, identically to the UI; send an empty string or null to clear it. |
discount_type | string (percent | fixed) | Required on create. |
discount_value | number | Required on create. Non-negative; stored to two decimal places. |
duration_months | integer | Positive integer, or null / empty string for an ongoing promo. |
stackable | object | Boolean-like: true/false, 1/0, "1"/"0", "true"/"false", "yes"/"no". Defaults to false on create. |
active | object | Boolean-like (see `stackable`). Defaults to true on create. |
Responses
200— The updated service promo
List the promo availability rows on a service type, newest `date_start` first. Soft-deleted rows and rows whose promo is deleted or inactive are excluded. The service type must exist, be non-deleted and belong to the token's department, otherwise every method fails with `service type not found`.
GET /{api_key}/service_types/{esid}/service_promos
Responses
200— The promo availability rows with their promo
Create a promo availability row on a service type. `service_promo_id` and `date_start` are required; the promo must be non-deleted, active and in the token's department. `date_end`, when given, may not precede `date_start`. Omitting `date_end` (or sending null) leaves the availability open-ended; it is never derived from the promo duration. The whole body is validated before anything is written; an unknown key (including `deptid`, `esid`, `id`) rejects the request. The service type must exist, be non-deleted and belong to the token's department, otherwise every method fails with `service type not found`.
POST /{api_key}/service_types/{esid}/service_promos
Request body
| Field | Type | Description |
|---|---|---|
service_promo_id | integer | A non-deleted, active promo in the token's department |
date_start | string | YYYY-MM-DD |
date_end | string | YYYY-MM-DD, not before date_start; null / "" clears it (assignments then re-derive it from the promo duration) |
active | boolean | Boolean-like (true/false, 1/0, "yes"/"no"); defaults to true on create |
Responses
200— The created promo availability row
Soft-delete a promo availability row (sets `deleted = 1`). Only the relationship row is touched — the promo and the service type are left as they were. Allowed even when the row's promo has since been deleted or deactivated, so orphaned rows stay removable. The service type must exist, be non-deleted and belong to the token's department, otherwise every method fails with `service type not found`.
DELETE /{api_key}/service_types/{esid}/service_promos/{id}
Responses
200— Deletion result
Fetch a single promo availability row. Fails with `service promo relationship not found` when the row does not exist, is deleted, belongs to another department, sits under a different service type than the one in the URL, or its promo is deleted / inactive. The service type must exist, be non-deleted and belong to the token's department, otherwise every method fails with `service type not found`.
GET /{api_key}/service_types/{esid}/service_promos/{id}
Responses
200— The promo availability row
Identical to the PUT form of this endpoint, for clients that cannot send PUT.
POST /{api_key}/service_types/{esid}/service_promos/{id}
Request body
| Field | Type | Description |
|---|---|---|
service_promo_id | integer | A non-deleted, active promo in the token's department |
date_start | string | YYYY-MM-DD |
date_end | string | YYYY-MM-DD, not before date_start; null / "" clears it (assignments then re-derive it from the promo duration) |
active | boolean | Boolean-like (true/false, 1/0, "yes"/"no"); defaults to true on create |
Responses
200— The updated promo availability row
Partially update a promo availability row: only the fields present in the body change. The same validation as create applies (minus the required-field rule); `date_end` / `date_start` are checked against the stored values for anything not sent, and a new `service_promo_id` must be a live, active promo in the token's department. Omitting `date_end` (or sending null) leaves the availability open-ended; it is never derived from the promo duration. The service type must exist, be non-deleted and belong to the token's department, otherwise every method fails with `service type not found`.
PUT /{api_key}/service_types/{esid}/service_promos/{id}
Request body
| Field | Type | Description |
|---|---|---|
service_promo_id | integer | A non-deleted, active promo in the token's department |
date_start | string | YYYY-MM-DD |
date_end | string | YYYY-MM-DD, not before date_start; null / "" clears it (assignments then re-derive it from the promo duration) |
active | boolean | Boolean-like (true/false, 1/0, "yes"/"no"); defaults to true on create |
Responses
200— The updated promo availability row