Cookbook: Onboarding a New Customer
Step-by-step walkthrough for onboarding a new customer via the BlueRockTEL API: create a catalog family, add an item, register the customer, and build their billing file with licences, one-time charges, and recurring subscriptions.
This cookbook walks through the complete onboarding sequence for a new customer using the BlueRockTEL API. You will create all required objects in the correct dependency order, from catalog setup through to a fully configured billing dossier.
Prerequisites: You must have a valid API bearer token. See Authentication for details.
export BASE_URL="https://your-instance.bluerocktel.net/api"
export TOKEN="your-bearer-token"
Step 1: Create a Product Family
Product families are top-level catalog categories. If a suitable family already exists, skip this step and use its id in Step 2.
Endpoint: POST /v1/families
Required fields:
| Field | Type | Description |
|---|---|---|
name |
string | Family name |
curl -s -X POST "$BASE_URL/v1/families" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name": "VoIP Services"}'
import requests
BASE_URL = "https://your-instance.bluerocktel.net/api"
TOKEN = "your-bearer-token"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}
response = requests.post(
f"{BASE_URL}/v1/families",
json={"name": "VoIP Services"},
headers=HEADERS,
)
family = response.json()
family_id = family["id"]
print(f"Family created: id={family_id}")
use Illuminate\Support\Facades\Http;
$response = Http::withToken($token)
->post("{$baseUrl}/v1/families", [
'name' => 'VoIP Services',
]);
$family = $response->json();
$familyId = $family['id'];
const response = await fetch(`${BASE_URL}/v1/families`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({ name: "VoIP Services" }),
});
const family = await response.json();
const familyId = family.id;
Response 201:
{
"id": 7,
"name": "VoIP Services",
"created_at": "2026-03-24T10:00:00Z",
"updated_at": "2026-03-24T10:00:00Z"
}
Step 2: Create a Catalog Item
Items are the individual products and services sold to customers. Link the item to the family created in Step 1.
Endpoint: POST /v1/items
Required fields:
| Field | Type | Description |
|---|---|---|
description |
string | Item name shown on invoices |
Commonly used optional fields:
| Field | Type | Description |
|---|---|---|
family_id |
integer | FK to families (from Step 1) |
selling_price_without_tax |
decimal | Selling price, excl. VAT |
billing_frequency |
integer | Default billing frequency in months |
active_for_recurring_billing |
boolean | Available as a subscription line |
active_for_initial_billing |
boolean | Available as a one-time charge |
curl -s -X POST "$BASE_URL/v1/items" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"description": "SIP Trunk — 2 channels",
"family_id": 7,
"selling_price_without_tax": 19.90,
"billing_frequency": 1,
"active_for_recurring_billing": true
}'
response = requests.post(
f"{BASE_URL}/v1/items",
json={
"description": "SIP Trunk — 2 channels",
"family_id": family_id,
"selling_price_without_tax": 19.90,
"billing_frequency": 1,
"active_for_recurring_billing": True,
},
headers=HEADERS,
)
item = response.json()
item_id = item["id"]
print(f"Item created: id={item_id}")
$response = Http::withToken($token)
->post("{$baseUrl}/v1/items", [
'description' => 'SIP Trunk — 2 channels',
'family_id' => $familyId,
'selling_price_without_tax' => 19.90,
'billing_frequency' => 1,
'active_for_recurring_billing' => true,
]);
$item = $response->json();
$itemId = $item['id'];
const itemResponse = await fetch(`${BASE_URL}/v1/items`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
description: "SIP Trunk — 2 channels",
family_id: familyId,
selling_price_without_tax: 19.90,
billing_frequency: 1,
active_for_recurring_billing: true,
}),
});
const item = await itemResponse.json();
const itemId = item.id;
Response 201:
{
"id": 42,
"description": "SIP Trunk — 2 channels",
"family_id": 7,
"selling_price_without_tax": "19.90",
"billing_frequency": 1,
"active_for_recurring_billing": true,
"active": true,
"created_at": "2026-03-24T10:01:00Z",
"updated_at": "2026-03-24T10:01:00Z"
}
Step 3: Create a Customer
The customer is the top-level billing entity.
Endpoint: POST /v1/customers
Required fields:
| Field | Type | Description |
|---|---|---|
name |
string | Company or individual name |
Commonly used optional fields:
| Field | Type | Description |
|---|---|---|
email_address |
string | Main contact email |
phone |
string | Main phone number |
main_address_line1 |
string | Address line 1 |
main_address_postal_code |
string | Postal code |
main_address_city |
string | City |
main_address_country |
string | Country (default: France) |
charge_vat |
boolean | Apply VAT to invoices |
curl -s -X POST "$BASE_URL/v1/customers" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Acme Telecom SAS",
"email_address": "contact@acme-telecom.fr",
"phone": "+33123456789",
"main_address_line1": "12 rue de la Paix",
"main_address_postal_code": "75001",
"main_address_city": "Paris",
"main_address_country": "France",
"charge_vat": true
}'
response = requests.post(
f"{BASE_URL}/v1/customers",
json={
"name": "Acme Telecom SAS",
"email_address": "contact@acme-telecom.fr",
"phone": "+33123456789",
"main_address_line1": "12 rue de la Paix",
"main_address_postal_code": "75001",
"main_address_city": "Paris",
"main_address_country": "France",
"charge_vat": True,
},
headers=HEADERS,
)
customer = response.json()
customer_id = customer["id"]
print(f"Customer created: id={customer_id}, account={customer['customer_account']}")
$response = Http::withToken($token)
->post("{$baseUrl}/v1/customers", [
'name' => 'Acme Telecom SAS',
'email_address' => 'contact@acme-telecom.fr',
'phone' => '+33123456789',
'main_address_line1' => '12 rue de la Paix',
'main_address_postal_code' => '75001',
'main_address_city' => 'Paris',
'main_address_country' => 'France',
'charge_vat' => true,
]);
$customer = $response->json();
$customerId = $customer['id'];
const customerResponse = await fetch(`${BASE_URL}/v1/customers`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
name: "Acme Telecom SAS",
email_address: "contact@acme-telecom.fr",
phone: "+33123456789",
main_address_line1: "12 rue de la Paix",
main_address_postal_code: "75001",
main_address_city: "Paris",
main_address_country: "France",
charge_vat: true,
}),
});
const customer = await customerResponse.json();
const customerId = customer.id;
Response 201:
{
"id": 101,
"name": "Acme Telecom SAS",
"customer_account": "CL000101",
"email_address": "contact@acme-telecom.fr",
"charge_vat": true,
"active": true,
"created_at": "2026-03-24T10:02:00Z",
"updated_at": "2026-03-24T10:02:00Z"
}
Step 4: Create a Customer File
A customer file is the billing dossier attached to the customer. Pass the customer ID as both the query parameter and body field.
Endpoint: POST /v1/customer-files?fileable_type=customer&fileable_id={customer_id}
Required query parameters:
| Parameter | Type | Description |
|---|---|---|
fileable_type |
string | customer (or prospect) |
fileable_id |
integer | Customer ID from Step 3 |
Commonly used body fields:
| Field | Type | Description |
|---|---|---|
name |
string | Label for this file (e.g. project or site name) |
billing_frequency |
integer | Billing cycle in months (1, 3, 6, 12, ...) |
services_start |
date | Date when services begin (YYYY-MM-DD) |
first_invoice |
date | Date of the first invoice (YYYY-MM-DD) |
curl -s -X POST "$BASE_URL/v1/customer-files?fileable_type=customer&fileable_id=101" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Main Contract 2026",
"billing_frequency": 1,
"services_start": "2026-04-01",
"first_invoice": "2026-04-01"
}'
response = requests.post(
f"{BASE_URL}/v1/customer-files",
params={"fileable_type": "customer", "fileable_id": customer_id},
json={
"name": "Main Contract 2026",
"billing_frequency": 1,
"services_start": "2026-04-01",
"first_invoice": "2026-04-01",
},
headers=HEADERS,
)
customer_file = response.json()
customer_file_id = customer_file["id"]
print(f"Customer file created: id={customer_file_id}")
$response = Http::withToken($token)
->post("{$baseUrl}/v1/customer-files", [
'fileable_type' => 'customer',
'fileable_id' => $customerId,
'name' => 'Main Contract 2026',
'billing_frequency' => 1,
'services_start' => '2026-04-01',
'first_invoice' => '2026-04-01',
]);
$customerFile = $response->json();
$customerFileId = $customerFile['id'];
const fileResponse = await fetch(
`${BASE_URL}/v1/customer-files?fileable_type=customer&fileable_id=${customerId}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
name: "Main Contract 2026",
billing_frequency: 1,
services_start: "2026-04-01",
first_invoice: "2026-04-01",
}),
}
);
const customerFile = await fileResponse.json();
const customerFileId = customerFile.id;
Response 201:
{
"id": 500,
"fileable_type": "customer",
"fileable_id": 101,
"name": "Main Contract 2026",
"billing_frequency": 1,
"services_start": "2026-04-01",
"first_invoice": "2026-04-01",
"created_at": "2026-03-24T10:03:00Z",
"updated_at": "2026-03-24T10:03:00Z"
}
Step 5: Add a Licence Line Item
Licence line items represent licence-based billing (e.g. software seats or access licences). They are billed at a configurable renewal frequency.
Endpoint: POST /v1/customer-files/licences
Required fields:
| Field | Type | Description |
|---|---|---|
customer_file_id |
integer | Customer file ID from Step 4 |
item_id |
integer | Catalog item ID from Step 2 |
quantity |
numeric | Number of licences (must be > 0) |
Commonly used optional fields:
| Field | Type | Description |
|---|---|---|
activation_date |
date | Activation date (YYYY-MM-DD) |
renewal_date |
date | First renewal date (YYYY-MM-DD) |
selling_price_without_tax |
decimal | Override the catalog selling price |
frequency |
integer | Renewal frequency in months |
active |
boolean | Whether this licence is active (default: true) |
curl -s -X POST "$BASE_URL/v1/customer-files/licences" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"customer_file_id": 500,
"item_id": 42,
"quantity": 5,
"activation_date": "2026-04-01",
"renewal_date": "2027-04-01",
"frequency": 12
}'
response = requests.post(
f"{BASE_URL}/v1/customer-files/licences",
json={
"customer_file_id": customer_file_id,
"item_id": item_id,
"quantity": 5,
"activation_date": "2026-04-01",
"renewal_date": "2027-04-01",
"frequency": 12,
},
headers=HEADERS,
)
licence = response.json()
print(f"Licence created: id={licence['id']}")
$response = Http::withToken($token)
->post("{$baseUrl}/v1/customer-files/licences", [
'customer_file_id' => $customerFileId,
'item_id' => $itemId,
'quantity' => 5,
'activation_date' => '2026-04-01',
'renewal_date' => '2027-04-01',
'frequency' => 12,
]);
$licence = $response->json();
const licenceResponse = await fetch(`${BASE_URL}/v1/customer-files/licences`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
customer_file_id: customerFileId,
item_id: itemId,
quantity: 5,
activation_date: "2026-04-01",
renewal_date: "2027-04-01",
frequency: 12,
}),
});
const licence = await licenceResponse.json();
Step 6: Add a One-Time Initial Charge
Initial line items represent one-time charges, such as installation fees or hardware purchases.
Endpoint: POST /v1/customer-files/initials
Required fields:
| Field | Type | Description |
|---|---|---|
customer_file_id |
integer | Customer file ID from Step 4 |
item_id |
integer | Catalog item ID from Step 2 |
quantity |
numeric | Quantity (must be > 0) |
Commonly used optional fields:
| Field | Type | Description |
|---|---|---|
label |
string | Custom label. Defaults to the item description |
selling_price_without_tax |
decimal | Override the catalog selling price |
discount_rate |
decimal | Discount rate (e.g. 0.1 = 10%) |
charge_after_date |
date | Do not invoice before this date (YYYY-MM-DD) |
curl -s -X POST "$BASE_URL/v1/customer-files/initials" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"customer_file_id": 500,
"item_id": 42,
"quantity": 1,
"label": "Installation fee — SIP Trunk setup",
"selling_price_without_tax": 150.00
}'
response = requests.post(
f"{BASE_URL}/v1/customer-files/initials",
json={
"customer_file_id": customer_file_id,
"item_id": item_id,
"quantity": 1,
"label": "Installation fee — SIP Trunk setup",
"selling_price_without_tax": 150.00,
},
headers=HEADERS,
)
initial = response.json()
print(f"Initial charge created: id={initial['id']}")
$response = Http::withToken($token)
->post("{$baseUrl}/v1/customer-files/initials", [
'customer_file_id' => $customerFileId,
'item_id' => $itemId,
'quantity' => 1,
'label' => 'Installation fee — SIP Trunk setup',
'selling_price_without_tax' => 150.00,
]);
$initial = $response->json();
const initialResponse = await fetch(`${BASE_URL}/v1/customer-files/initials`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
customer_file_id: customerFileId,
item_id: itemId,
quantity: 1,
label: "Installation fee — SIP Trunk setup",
selling_price_without_tax: 150.00,
}),
});
const initial = await initialResponse.json();
Step 7: Add a Recurring Subscription
Recurring line items represent periodic subscriptions billed at each billing cycle.
Endpoint: POST /v1/customer-files/recurrings
Required fields:
| Field | Type | Description |
|---|---|---|
customer_file_id |
integer | Customer file ID from Step 4 |
item_id |
integer | Catalog item ID from Step 2 |
quantity |
numeric | Quantity (must be > 0) |
Commonly used optional fields:
| Field | Type | Description |
|---|---|---|
label |
string | Custom label. Defaults to the item description |
selling_price_without_tax |
decimal | Override the catalog selling price |
service_start |
date | Planned service start date (YYYY-MM-DD) |
first_invoice |
date | Date of the first invoice for this item (YYYY-MM-DD) |
billing_frequency |
integer | Override the file-level billing frequency |
commitment |
integer | Commitment duration in months |
active |
boolean | Whether this subscription is active |
curl -s -X POST "$BASE_URL/v1/customer-files/recurrings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"customer_file_id": 500,
"item_id": 42,
"quantity": 2,
"service_start": "2026-04-01",
"first_invoice": "2026-04-01",
"billing_frequency": 1,
"commitment": 24,
"active": true
}'
response = requests.post(
f"{BASE_URL}/v1/customer-files/recurrings",
json={
"customer_file_id": customer_file_id,
"item_id": item_id,
"quantity": 2,
"service_start": "2026-04-01",
"first_invoice": "2026-04-01",
"billing_frequency": 1,
"commitment": 24,
"active": True,
},
headers=HEADERS,
)
recurring = response.json()
print(f"Recurring subscription created: id={recurring['id']}")
$response = Http::withToken($token)
->post("{$baseUrl}/v1/customer-files/recurrings", [
'customer_file_id' => $customerFileId,
'item_id' => $itemId,
'quantity' => 2,
'service_start' => '2026-04-01',
'first_invoice' => '2026-04-01',
'billing_frequency'=> 1,
'commitment' => 24,
'active' => true,
]);
$recurring = $response->json();
const recurringResponse = await fetch(`${BASE_URL}/v1/customer-files/recurrings`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
customer_file_id: customerFileId,
item_id: itemId,
quantity: 2,
service_start: "2026-04-01",
first_invoice: "2026-04-01",
billing_frequency: 1,
commitment: 24,
active: true,
}),
});
const recurring = await recurringResponse.json();
Complete Workflow (Python)
The following script combines all seven steps into a single, runnable onboarding script.
import requests
BASE_URL = "https://your-instance.bluerocktel.net/api"
TOKEN = "your-bearer-token"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}
def post(path, payload):
r = requests.post(f"{BASE_URL}{path}", json=payload, headers=HEADERS)
r.raise_for_status()
return r.json()
# Step 1 — Create a product family
family = post("/v1/families", {"name": "VoIP Services"})
family_id = family["id"]
# Step 2 — Create a catalog item
item = post("/v1/items", {
"description": "SIP Trunk — 2 channels",
"family_id": family_id,
"selling_price_without_tax": 19.90,
"billing_frequency": 1,
"active_for_recurring_billing": True,
})
item_id = item["id"]
# Step 3 — Create a customer
customer = post("/v1/customers", {
"name": "Acme Telecom SAS",
"email_address": "contact@acme-telecom.fr",
"phone": "+33123456789",
"main_address_line1": "12 rue de la Paix",
"main_address_postal_code": "75001",
"main_address_city": "Paris",
"main_address_country": "France",
"charge_vat": True,
})
customer_id = customer["id"]
# Step 4 — Create a customer file
r = requests.post(
f"{BASE_URL}/v1/customer-files",
params={"fileable_type": "customer", "fileable_id": customer_id},
json={
"name": "Main Contract 2026",
"billing_frequency": 1,
"services_start": "2026-04-01",
"first_invoice": "2026-04-01",
},
headers=HEADERS,
)
r.raise_for_status()
customer_file = r.json()
customer_file_id = customer_file["id"]
# Step 5 — Add a licence line item
licence = post("/v1/customer-files/licences", {
"customer_file_id": customer_file_id,
"item_id": item_id,
"quantity": 5,
"activation_date": "2026-04-01",
"renewal_date": "2027-04-01",
"frequency": 12,
})
# Step 6 — Add a one-time initial charge
initial = post("/v1/customer-files/initials", {
"customer_file_id": customer_file_id,
"item_id": item_id,
"quantity": 1,
"label": "Installation fee — SIP Trunk setup",
"selling_price_without_tax": 150.00,
})
# Step 7 — Add a recurring subscription
recurring = post("/v1/customer-files/recurrings", {
"customer_file_id": customer_file_id,
"item_id": item_id,
"quantity": 2,
"service_start": "2026-04-01",
"first_invoice": "2026-04-01",
"billing_frequency": 1,
"commitment": 24,
"active": True,
})
print("Onboarding complete:")
print(f" Family : id={family_id}")
print(f" Item : id={item_id}")
print(f" Customer : id={customer_id}, account={customer['customer_account']}")
print(f" Customer file : id={customer_file_id}")
print(f" Licence : id={licence['id']}")
print(f" Initial charge : id={initial['id']}")
print(f" Recurring : id={recurring['id']}")
What's Next?
After completing these steps you have a fully configured billing dossier. Common next actions:
- Retrieve the file:
GET /v1/customer-files/{id}returns the complete file with all line items and relations. - Update billing dates:
PUT /v1/customer-files/{id}to adjustbilling_nextorservices_stop. - Add contacts:
PUT /v1/customer-files/{id}with acontact_idsarray to link contacts to the file. - List invoices:
GET /v1/invoicesfiltered bycustomer_idto see generated invoices.
On this page