curl --request POST \
--url https://{clinic}.{region}.practicehub.io/v3/api/billable_items \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"description": "<string>",
"amount": "45.00",
"tax_id": 123,
"modality_id": 123,
"sku": "<string>",
"stock_alert_level": 1,
"metadata": {}
}
'import requests
url = "https://{clinic}.{region}.practicehub.io/v3/api/billable_items"
payload = {
"description": "<string>",
"amount": "45.00",
"tax_id": 123,
"modality_id": 123,
"sku": "<string>",
"stock_alert_level": 1,
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
description: '<string>',
amount: '45.00',
tax_id: 123,
modality_id: 123,
sku: '<string>',
stock_alert_level: 1,
metadata: {}
})
};
fetch('https://{clinic}.{region}.practicehub.io/v3/api/billable_items', 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://{clinic}.{region}.practicehub.io/v3/api/billable_items",
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([
'description' => '<string>',
'amount' => '45.00',
'tax_id' => 123,
'modality_id' => 123,
'sku' => '<string>',
'stock_alert_level' => 1,
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://{clinic}.{region}.practicehub.io/v3/api/billable_items"
payload := strings.NewReader("{\n \"description\": \"<string>\",\n \"amount\": \"45.00\",\n \"tax_id\": 123,\n \"modality_id\": 123,\n \"sku\": \"<string>\",\n \"stock_alert_level\": 1,\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/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://{clinic}.{region}.practicehub.io/v3/api/billable_items")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"description\": \"<string>\",\n \"amount\": \"45.00\",\n \"tax_id\": 123,\n \"modality_id\": 123,\n \"sku\": \"<string>\",\n \"stock_alert_level\": 1,\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{clinic}.{region}.practicehub.io/v3/api/billable_items")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"description\": \"<string>\",\n \"amount\": \"45.00\",\n \"tax_id\": 123,\n \"modality_id\": 123,\n \"sku\": \"<string>\",\n \"stock_alert_level\": 1,\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": 12,
"type": "service",
"description": "Initial consultation",
"sku": null,
"amount": "60.00",
"tax_id": null,
"taxable": false,
"modality_id": null,
"stock_alert_level": null,
"active": true,
"sort_order": 1,
"created_at": "2026-09-01T09:15:00Z"
}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}Create billable_items
curl --request POST \
--url https://{clinic}.{region}.practicehub.io/v3/api/billable_items \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"description": "<string>",
"amount": "45.00",
"tax_id": 123,
"modality_id": 123,
"sku": "<string>",
"stock_alert_level": 1,
"metadata": {}
}
'import requests
url = "https://{clinic}.{region}.practicehub.io/v3/api/billable_items"
payload = {
"description": "<string>",
"amount": "45.00",
"tax_id": 123,
"modality_id": 123,
"sku": "<string>",
"stock_alert_level": 1,
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
description: '<string>',
amount: '45.00',
tax_id: 123,
modality_id: 123,
sku: '<string>',
stock_alert_level: 1,
metadata: {}
})
};
fetch('https://{clinic}.{region}.practicehub.io/v3/api/billable_items', 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://{clinic}.{region}.practicehub.io/v3/api/billable_items",
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([
'description' => '<string>',
'amount' => '45.00',
'tax_id' => 123,
'modality_id' => 123,
'sku' => '<string>',
'stock_alert_level' => 1,
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://{clinic}.{region}.practicehub.io/v3/api/billable_items"
payload := strings.NewReader("{\n \"description\": \"<string>\",\n \"amount\": \"45.00\",\n \"tax_id\": 123,\n \"modality_id\": 123,\n \"sku\": \"<string>\",\n \"stock_alert_level\": 1,\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/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://{clinic}.{region}.practicehub.io/v3/api/billable_items")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"description\": \"<string>\",\n \"amount\": \"45.00\",\n \"tax_id\": 123,\n \"modality_id\": 123,\n \"sku\": \"<string>\",\n \"stock_alert_level\": 1,\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{clinic}.{region}.practicehub.io/v3/api/billable_items")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"description\": \"<string>\",\n \"amount\": \"45.00\",\n \"tax_id\": 123,\n \"modality_id\": 123,\n \"sku\": \"<string>\",\n \"stock_alert_level\": 1,\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": 12,
"type": "service",
"description": "Initial consultation",
"sku": null,
"amount": "60.00",
"tax_id": null,
"taxable": false,
"modality_id": null,
"stock_alert_level": null,
"active": true,
"sort_order": 1,
"created_at": "2026-09-01T09:15:00Z"
}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}{
"message": "<string>",
"code": "<string>",
"errors": {}
}Authorizations
API key from Developers → API Keys, sent as Authorization: Bearer .
Headers
Any unique string (≤255 chars). A retry with the same key within 24 hours replays the original response (Idempotent-Replayed: true) instead of creating a second record.
255Body
The item's name as shown on invoices, up to 255 characters.
255service or product.
service, product Unit price in the account currency, two decimal places. Negative amounts are allowed (discounts).
^-?\d+(\.\d{1,2})?$"45.00"
Tax rate applied (taxes are managed in Settings › Billing); null for none.
Restrict the item to one practitioner modality; null for all.
Optional stock-keeping code.
255Products only: alert when stock falls to this level; null for no alert.
x >= 0Integrator-owned key/value metadata (max 50 keys). Values are strings (≤500 chars), numbers or booleans and read back with the type they were written with. On write, a null value removes the key. Filter with metadata[key]=op:value; number operands compare numerically.
Show child attributes
Show child attributes
Response
The resource
Show child attributes
Show child attributes
{
"id": 12,
"type": "service",
"description": "Initial consultation",
"sku": null,
"amount": "60.00",
"tax_id": null,
"taxable": false,
"modality_id": null,
"stock_alert_level": null,
"active": true,
"sort_order": 1,
"created_at": "2026-09-01T09:15:00Z"
}
Was this page helpful?