Skip to main content

Laabam One Public API (1.0.0)

Download OpenAPI specification:Download

The Laabam One API lets your systems and approved third parties read and write data in your company's Laabam One account — invoices, customers, products, payments — and send messages over SMS, WhatsApp and email.

Every request authenticates with two headers — X-Client-Id and X-Client-Secret, created in Settings → API Keys. See the Authentication and Security guides for the full details. Each operation below is marked x-status: available (callable today) or x-status: planned (contract defined, not yet built).

Rate limits

60 requests per minute per key, unless a different figure is set on the key itself — the current value is shown on the key in Settings → API Keys, and you can ask us to raise it.

The window is per X-Client-Id, not per IP, so an embedded widget calling from many end-user browsers shares one budget, and one key can never exhaust another's. Every 429 carries Retry-After (seconds), X-RateLimit-Limit and X-RateLimit-Remaining — retry on the header, not on a guess.

Pagination

List endpoints are cursor-paged: ?limit= (1–100, default 25) and ?cursor=, with the next cursor in paging.next_cursor and paging.has_more telling you whether to ask again. Stop when has_more is false; do not construct a cursor yourself.

Retrying a write safely

Send an Idempotency-Key header (any UUID you generate) on every POST. The first 2xx is cached against that key for 24 hours and replayed on a repeat, so a timeout you retry cannot create a second invoice, work item or payment. A handful of operations do not support it and say so explicitly.

Invoices

✅ Read, create, send & void available (create posts the Sales JE + moves stock; INR). Update (PATCH draft) planned.

List invoices

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
limit
integer [ 1 .. 100 ]
Default: 25

Page size (1–100).

cursor
string

Opaque cursor from the previous response's paging.next_cursor.

status
string
Enum: "unpaid" "partial" "paid"

Filters on the invoice's payment_status (not the status field also returned in the response — they are different columns). Voided invoices are removed, so they can never appear in this list.

customer_id
string

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "paging": {
    }
}

Create an invoice

Creates an invoice, posts the Sales journal entry (DR receivable, CR sales + GST split) and deducts stock — the exact same accounting engine as the in-app flow. Totals, tax and round-off are computed server-side and are authoritative (client-sent totals are ignored). Each line requires item_name, quantity, unit, unit_price and tax_rate. INR only in v1 (recurring, attachments and pay-at-create are app-only).

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
customer_id
required
string
invoice_date
required
string <date>
due_date
string <date>
required
Array of objects (InvoiceItemCreate) non-empty
document_type
string
Default: "invoice"
Enum: "invoice" "quotation" "estimate" "proforma_invoice" "sales_order"
invoice_number
string <= 50 characters

Auto-generated when omitted.

reference_number
string <= 255 characters
gst_invoice_type
string
Enum: "regular" "bill_of_supply" "export_sez"
reverse_charge
string
Enum: "Y" "N"
place_of_supply
string <= 100 characters

India: the buyer's state — a 2-digit GST state code ("33"), "33 - Tamil Nadu" or the state name. Decides CGST + SGST or IGST.

currency
string = 3 characters

ISO 4217. Omit for the company's own currency.

base_currency_rate
number > 0

Required with a foreign currency: units of the company's own currency per ONE unit of currency.

charged_total
number >= 0.01

The amount already collected, tax included, in the invoice's currency. Holds the total to it instead of the company's round-off, so a payment of that amount settles the invoice exactly.

terms
string
notes
string

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "invoice_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "items": [
    ],
  • "document_type": "invoice",
  • "invoice_number": "string",
  • "reference_number": "string",
  • "gst_invoice_type": "regular",
  • "reverse_charge": "Y",
  • "place_of_supply": "Karnataka",
  • "currency": "USD",
  • "base_currency_rate": 88.5,
  • "charged_total": 0.01,
  • "terms": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get an invoice

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a draft invoice

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Request Body schema: application/json
required
customer_id
required
string
invoice_date
required
string <date>
due_date
string <date>
required
Array of objects (InvoiceItemCreate) non-empty
document_type
string
Default: "invoice"
Enum: "invoice" "quotation" "estimate" "proforma_invoice" "sales_order"
invoice_number
string <= 50 characters

Auto-generated when omitted.

reference_number
string <= 255 characters
gst_invoice_type
string
Enum: "regular" "bill_of_supply" "export_sez"
reverse_charge
string
Enum: "Y" "N"
place_of_supply
string <= 100 characters

India: the buyer's state — a 2-digit GST state code ("33"), "33 - Tamil Nadu" or the state name. Decides CGST + SGST or IGST.

currency
string = 3 characters

ISO 4217. Omit for the company's own currency.

base_currency_rate
number > 0

Required with a foreign currency: units of the company's own currency per ONE unit of currency.

charged_total
number >= 0.01

The amount already collected, tax included, in the invoice's currency. Holds the total to it instead of the company's round-off, so a payment of that amount settles the invoice exactly.

terms
string
notes
string

Responses

Request samples

Content type
application/json
{
  • "customer_id": "string",
  • "invoice_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "items": [
    ],
  • "document_type": "invoice",
  • "invoice_number": "string",
  • "reference_number": "string",
  • "gst_invoice_type": "regular",
  • "reverse_charge": "Y",
  • "place_of_supply": "Karnataka",
  • "currency": "USD",
  • "base_currency_rate": 88.5,
  • "charged_total": 0.01,
  • "terms": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Void an invoice

Voids (soft-deletes) the invoice and reverses all its side-effects — its journal entries, any payments + their JEs, and restores deducted stock — the same reversal as the app. Returns a summary of what was reversed.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Email the invoice to the customer

Renders the invoice PDF (using your configured print template) and queues it for email to the customer — the same render the in‑app "Send" button uses. Recipients default to the invoice customer's email; override with email or emails[] (max 10). A draft invoice is marked as sent. Only channel: email is available today; WhatsApp invoice delivery is planned.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Request Body schema: application/json
optional
channel
string
Default: "email"
Value: "email"
email
string <email>

Single recipient (overrides the customer email).

emails
Array of strings <email> [ items <email > ]

Multiple recipients (max 10).

message
string <= 1000 characters

Optional note included in the email.

Responses

Request samples

Content type
application/json
{
  • "channel": "email",
  • "email": "user@example.com",
  • "emails": [
    ],
  • "message": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Customers

✅ Read, create, update & delete available (create also opens the Sundry Debtors ledger account).

List / search customers

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
limit
integer [ 1 .. 100 ]
Default: 25

Page size (1–100).

cursor
string

Opaque cursor from the previous response's paging.next_cursor.

q
string

Free‑text search (name / email / phone).

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "paging": {
    }
}

Create a customer

Creates a customer and its Sundry Debtors ledger account (same path as the UI). name, state and pincode are required. opening_balance and profile images are NOT settable via the API (the opening-balance ledger entry remains a UI-only action).

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
required
string <= 255 characters
email
string <email> <= 255 characters
phone
string <= 20 characters
phone_country_code
string
alternate_phone
string <= 20 characters
address
string
address_label
string
area
string
city
string
state
required
string
pincode
required
string
country
string <= 2 characters

ISO alpha-2.

gst_number
string <= 15 characters
gst_type
string
legal_name
string
pan_number
string <= 10 characters
customer_type
string
Enum: "individual" "business"
currency_code
string = 3 characters
discount
number [ 0 .. 100 ]
credit_limit
number >= 0
website_url
string <uri>
also_vendor
boolean
is_active
boolean
notes
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "email": "user@example.com",
  • "phone": "string",
  • "phone_country_code": "string",
  • "alternate_phone": "string",
  • "address": "string",
  • "address_label": "string",
  • "area": "string",
  • "city": "string",
  • "state": "string",
  • "pincode": "string",
  • "country": "st",
  • "gst_number": "string",
  • "gst_type": "string",
  • "legal_name": "string",
  • "pan_number": "string",
  • "customer_type": "individual",
  • "currency_code": "str",
  • "discount": 100,
  • "credit_limit": 0,
  • "website_url": "http://example.com",
  • "also_vendor": true,
  • "is_active": true,
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a customer

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a customer

Partial update — send only the fields you want to change. Renaming keeps the linked ledger account names in sync. PUT is accepted as well as PATCH; both behave as a partial update, so a PUT will not blank the fields you leave out.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Request Body schema: application/json
optional
name
required
string <= 255 characters
email
string <email> <= 255 characters
phone
string <= 20 characters
phone_country_code
string
alternate_phone
string <= 20 characters
address
string
address_label
string
area
string
city
string
state
required
string
pincode
required
string
country
string <= 2 characters

ISO alpha-2.

gst_number
string <= 15 characters
gst_type
string
legal_name
string
pan_number
string <= 10 characters
customer_type
string
Enum: "individual" "business"
currency_code
string = 3 characters
discount
number [ 0 .. 100 ]
credit_limit
number >= 0
website_url
string <uri>
also_vendor
boolean
is_active
boolean
notes
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "email": "user@example.com",
  • "phone": "string",
  • "phone_country_code": "string",
  • "alternate_phone": "string",
  • "address": "string",
  • "address_label": "string",
  • "area": "string",
  • "city": "string",
  • "state": "string",
  • "pincode": "string",
  • "country": "st",
  • "gst_number": "string",
  • "gst_type": "string",
  • "legal_name": "string",
  • "pan_number": "string",
  • "customer_type": "individual",
  • "currency_code": "str",
  • "discount": 100,
  • "credit_limit": 0,
  • "website_url": "http://example.com",
  • "also_vendor": true,
  • "is_active": true,
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Soft‑delete a customer

Soft-deletes the customer and removes its debtor account. Blocked with 409 if the customer has invoices, payments or posted ledger transactions. If the record is also a vendor, only the customer role is removed (vendor data is retained).

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Products

✅ Read, create, update & stock-adjust available.

List / search products

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
limit
integer [ 1 .. 100 ]
Default: 25

Page size (1–100).

cursor
string

Opaque cursor from the previous response's paging.next_cursor.

q
string
category_id
string

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "paging": {
    }
}

Create a product

Creates a product. If track_stock is true and opening_stock > 0, an opening stock movement (and a batch when is_batch_tracked) is recorded. name and unit_id are required. Images, variants and bundles are not settable via the API.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
name
required
string
unit_id
required
integer

Required. Must exist in the company's units.

type
string
Default: "product"
Enum: "product" "service"
invoice_name
string

Defaults to name.

sku
string <= 100 characters

Must be unique.

barcode
string <= 100 characters

Must be unique.

category_id
integer
brand_id
integer
description
string
purchase_price
number >= 0
selling_price
number >= 0
mrp_price
number >= 0
selling_price_tax_mode
string
Enum: "exclusive" "inclusive"
gst_rate
number [ 0 .. 100 ]
hsn_code
string <= 20 characters
sac_code
string <= 20 characters
status
string
Default: "active"
Enum: "active" "inactive"
track_stock
boolean

Create only — not updatable via PATCH.

opening_stock
number
min_stock
number
max_stock
number
low_stock_alert
boolean
is_batch_tracked
boolean
is_expiry_tracked
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "unit_id": 0,
  • "type": "product",
  • "invoice_name": "string",
  • "sku": "string",
  • "barcode": "string",
  • "category_id": 0,
  • "brand_id": 0,
  • "description": "string",
  • "purchase_price": 0,
  • "selling_price": 0,
  • "mrp_price": 0,
  • "selling_price_tax_mode": "exclusive",
  • "gst_rate": 18,
  • "hsn_code": "string",
  • "sac_code": "string",
  • "status": "active",
  • "track_stock": true,
  • "opening_stock": 0,
  • "min_stock": 0,
  • "max_stock": 0,
  • "low_stock_alert": true,
  • "is_batch_tracked": true,
  • "is_expiry_tracked": true
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get a product (with current stock)

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a product

Partial update of scalar fields (name, prices, tax/HSN, sku, status, …). Stock and track_stock are NOT updatable here — use the stock-adjustments endpoint to move stock. PUT is accepted as well as PATCH; both behave as a partial update, so a PUT will not blank the fields you leave out.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Request Body schema: application/json
optional
name
string
unit_id
integer
invoice_name
string
sku
string <= 100 characters
barcode
string <= 100 characters
category_id
integer
brand_id
integer
description
string
purchase_price
number >= 0
selling_price
number >= 0
mrp_price
number >= 0
selling_price_tax_mode
string
Enum: "exclusive" "inclusive"
gst_rate
number [ 0 .. 100 ]
hsn_code
string <= 20 characters
sac_code
string <= 20 characters
status
string
Enum: "active" "inactive"
min_stock
number
max_stock
number
low_stock_alert
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "unit_id": 0,
  • "invoice_name": "string",
  • "sku": "string",
  • "barcode": "string",
  • "category_id": 0,
  • "brand_id": 0,
  • "description": "string",
  • "purchase_price": 0,
  • "selling_price": 0,
  • "mrp_price": 0,
  • "selling_price_tax_mode": "exclusive",
  • "gst_rate": 100,
  • "hsn_code": "string",
  • "sac_code": "string",
  • "status": "active",
  • "min_stock": 0,
  • "max_stock": 0,
  • "low_stock_alert": true
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Adjust stock

Records a manual stock-adjustment movement and moves the product's current stock.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Request Body schema: application/json
required
adjustment_type
required
string
Enum: "increase" "decrease"
quantity
required
number >= 0.01

Always positive; direction is set by adjustment_type.

reason
string <= 100 characters
notes
string <= 500 characters

Responses

Request samples

Content type
application/json
{
  • "adjustment_type": "increase",
  • "quantity": 0.01,
  • "reason": "string",
  • "notes": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Payments

✅ Read, record & refund available (record posts the receipt JE; INR only).

List payments

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
limit
integer [ 1 .. 100 ]
Default: 25

Page size (1–100).

cursor
string

Opaque cursor from the previous response's paging.next_cursor.

invoice_id
string
customer_id
string

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "paging": {
    }
}

Record a payment against an invoice

Records a payment and posts the receipt journal entry (DR bank/cash, CR the customer's receivable) as the in-app "Record Payment" does, then updates the invoice's paid/due/status. payment_mode_id chooses the bank/cash account — list options via GET /payments/{company}/methods.

Currency: for an invoice in the company's own currency, amount is in that currency. For an invoice raised in another currency (currency + base_currency_rate on create), amount is what was received in the company's own currency (for example the rupees a gateway settled); the invoice is settled in its own currency and any exchange difference is booked, as the in-app Record Payment does. base_currency_rate may give the rate on the payment date. TDS is not supported on a foreign-currency invoice.

The journal entry is best-effort: if the cash/bank account, the receivable account or the financial year can't be resolved, the payment is still recorded and journal_entry_id comes back null.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
invoice_id
required
string
amount
required
number >= 0.01

amount + tds_amount together must not exceed the invoice's due amount.

payment_date
required
string <date>
payment_mode_id
required
integer

From GET /payments/{company}/methods.

tds_amount
number >= 0

TDS withheld by the buyer. Counts toward settling the invoice alongside amount.

tds_section
string <= 40 characters
reference_number
string <= 100 characters
notes
string <= 500 characters
base_currency_rate
number > 0

Foreign-currency invoices only: how many units of the company's own currency one unit of the invoice's currency was worth on the payment date. Defaults to the invoice's rate.

Responses

Request samples

Content type
application/json
{
  • "invoice_id": "string",
  • "amount": 0.01,
  • "payment_date": "2019-08-24",
  • "payment_mode_id": 0,
  • "tds_amount": 0,
  • "tds_section": "string",
  • "reference_number": "string",
  • "notes": "string",
  • "base_currency_rate": 88.5
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List payment modes

Active payment modes for the company — use the returned id as payment_mode_id when recording a payment.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get a payment

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Refund (void) a payment

Reverses the payment — voids its journal entry and recomputes the invoice's paid/due/status. Full reversal only (partial refunds are not supported yet; no request body is read).

⚠️ The payment record is removed, not flagged. After a refund GET /payments/{company}/{id} returns 404, and refunding the same id twice also returns 404 — there is no refunded: true state to read back.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Messaging

✅ SMS, WhatsApp & Email available. Security‑sensitive — dedicated send scopes, lower rate limits and a daily cap. Path carries the company slug. Everything you need to configure a send — slug, endpoint, pe_id, and each approved sender_id with its template_ids and exact message text — is in Settings → API Keys → “Integration details”, with copy buttons. For India, read the DLT notes on Send an SMS first: a mismatched template is dropped by the carrier while this API still returns 202.

Send an SMS

Sends an SMS via the company's gateway. Requires the messages:sms:send scope. Subject to the messaging rate limit + daily cap.

India (DLT) — read this before you debug for an afternoon. Pass an approved sender_id plus its registered template_id (and pe_id), and make message match the approved template text exactly — including punctuation and any sender suffix (e.g. ... -ACMEID). Variables occupy the {#var#} slots. sender_id and template_id must be a matched pair: the template is registered against one sender.

⚠️ A mismatch fails silently. If message does not match the registered template, or template_id is missing/wrong, the carrier DROPS the message — but this endpoint still returns 202. It will look like it worked while nothing is ever delivered. 202 means accepted, not delivered (lifecycle: queued → submitted → delivered); confirm real delivery with GET /messaging/{company}/messages/{id}.

Where to get all four values. Open Settings → API Keys → “Integration details”. It lists, with copy buttons: the company slug, the full endpoint URL, your pe_id, and every approved sender ID with its own templates nested underneath — each showing the template_id, the exact registered message text, the variable count and the character limit. Only approved rows appear, because anything else is dropped by the carrier.

Because the templates are grouped under their sender there, you can read off a valid sender_id + template_id pair directly instead of correlating two separate lists and guessing. A sender with no templates listed cannot send anything yet. The same data lives in Marketing → SMS Gateway (DLT Registration / Sender IDs / Templates) if you prefer to manage it there.

Copy the message text from that panel and substitute only the {#var#} slots — retyping it is how the trailing sender suffix gets lost.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
to
required
string

Recipient number, digits only (8–15).

message
required
string <= 1000 characters
sender_id
string <= 11 characters

An APPROVED sender ID, copied from Settings → API Keys → Integration details. Must be the sender the template_id is nested under there — a template is registered against exactly one sender.

template_id
string <= 64 characters

Registered DLT template id, copied from under its sender in Settings → API Keys → Integration details. Omit it, or pair it with a different sender, and Indian carriers drop the SMS silently while this API still returns 202.

pe_id
string <= 64 characters

DLT principal entity (PE) id, shown in Settings → API Keys → Integration details. One per company — the same value for every sender and template.

type
string
Default: "transactional"
Enum: "transactional" "promotional"

Responses

Request samples

Content type
application/json
{
  • "to": "919800000000",
  • "message": "Your OTP is 123456",
  • "sender_id": "ACMEID",
  • "template_id": "1007487766981813506",
  • "pe_id": "1001609937061127455",
  • "type": "transactional"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Send a WhatsApp message

Sends via the company's Meta Cloud API provider. Business‑initiated messages must use a Meta‑APPROVED template; free text is only delivered inside an open 24‑hour customer‑service window. Requires the messages:whatsapp:send scope.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
to
required
string
type
string
Default: "template"
Enum: "template" "text"
object

Required when type=template.

object

Required when type=text.

Responses

Request samples

Content type
application/json
{
  • "to": "919800000000",
  • "type": "template",
  • "template": {
    },
  • "text": {
    }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get message delivery status

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

The provider message id returned by a send call.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Send an email

Sends a transactional email through your company's configured email provider (Settings → Marketing → Email provider); if none is set the platform default mailer is used. The sender identity is taken from that provider — you cannot set an arbitrary from. HTML is sanitised server‑side (script/iframe/event‑handler vectors are stripped). Up to 50 recipients per call.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
required
Array of objects

One recipient or an array. Each item may be a plain email string or an object.

subject
required
string <= 255 characters
html
required
string

Email body as HTML. Sanitised server‑side before send.

Responses

Request samples

Content type
application/json
{
  • "to": [
    ],
  • "subject": "string",
  • "html": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Recruitment

✅ Available. Public, embeddable career‑site API.

List open jobs

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get a job

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

slug
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Submit an application

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

slug
required
string
Request Body schema: multipart/form-data
required
first_name
required
string <= 100 characters
last_name
string <= 100 characters
email
required
string <email> <= 255 characters
phone
string <= 30 characters
resume
required
string <binary>

Required. Allowed: pdf, doc, docx. Max 10 MB.

cover_letter
string <= 5000 characters
total_experience
number [ 0 .. 50 ]

Years.

expected_salary
number
notice_period_days
integer >= 0
linkedin_url
string <uri> <= 500 characters
skills
string

A JSON-encoded string (not an array), e.g. '["PHP","Vue"]'.

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

List departments

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

Apply without naming a job

A speculative application — the "send us your CV" form, for someone who is not applying to a specific opening. Everything else is the same as applying to a job; the application is simply filed with no job attached and surfaces in the same pipeline.

Authorizations:
(ClientIdClientSecret)
Request Body schema: multipart/form-data
required
first_name
required
string <= 100 characters
last_name
string <= 100 characters
email
required
string <email>
phone
string <= 30 characters
resume
string <binary>

The CV.

cover_letter
string

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "string",
  • "data": {
    }
}

Leads

✅ Available. Capture leads from external sites.

Capture a lead

Captures a lead. Send application/json, or multipart/form-data if you need to include an attachment file. Re-submitting an existing lead is de-duplicated server-side and still returns 200.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema:
required
first_name
string

Required unless full_name is sent.

last_name
string
full_name
string

Used when first_name is not sent.

email
string <email>

Required unless phone is sent.

phone
string

Required unless email is sent. Include the country code, e.g. +919876543210.

company_name
string
lead_type
string
Enum: "contact" "rfp" "enquiry" "demo_request"
service
string
source
string
source_url
string <uri>
form_code
string
message
string <= 10000 characters
job_title
string
company_size
string
industry
string
country
string
services_needed
string
budget_range
string
timeline
string
project_overview
string
goals_expectations
string
current_systems
string
how_heard
string
meta_lead_id
string^\d+$

For a Facebook / Instagram lead-form lead: the lead id Meta issued (15-17 digits). Send it as a string, since a JSON number that long loses digits. With it, the stages of the lead can be reported back to Meta (Meta Conversion Leads). Sending a lead id that was already received returns 200 with data.duplicate: true and the existing lead's lead_id, so a retried webhook does not create a second lead.

meta_form_id
string^\d+$

Meta lead form id.

meta_ad_id
string^\d+$

Meta ad id.

meta_adset_id
string^\d+$

Meta ad set id.

meta_campaign_id
string^\d+$

Meta campaign id.

meta_page_id
string^\d+$

Facebook Page id.

meta_created_time
string <date-time>

When the person submitted the Meta lead form, as Meta reports it.

fbclid
string <= 500 characters

The fbclid from a landing page's URL. Stored as Meta's fbc click id.

fbc
string <= 255 characters

A ready-made Meta fbc click id (fb.1.<ms>.<fbclid>). Wins over fbclid.

Responses

Request samples

Content type
{
  • "first_name": "string",
  • "last_name": "string",
  • "full_name": "string",
  • "email": "user@example.com",
  • "phone": "string",
  • "company_name": "string",
  • "lead_type": "contact",
  • "service": "string",
  • "source": "string",
  • "source_url": "http://example.com",
  • "form_code": "string",
  • "message": "string",
  • "job_title": "string",
  • "company_size": "string",
  • "industry": "string",
  • "country": "string",
  • "services_needed": "string",
  • "budget_range": "string",
  • "timeline": "string",
  • "project_overview": "string",
  • "goals_expectations": "string",
  • "current_systems": "string",
  • "how_heard": "string",
  • "meta_lead_id": "string",
  • "meta_form_id": "string",
  • "meta_ad_id": "string",
  • "meta_adset_id": "string",
  • "meta_campaign_id": "string",
  • "meta_page_id": "string",
  • "meta_created_time": "2019-08-24T14:15:22Z",
  • "fbclid": "string",
  • "fbc": "string"
}

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

List allowed lead sources

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

Storefront

✅ Available. Headless storefront / checkout.

List catalog

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

Get a catalog product

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

productId
required
string

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

Create an order / checkout

Creates a sales order (document_type=sales_order, status draft) and starts a payment intent — it does not create an invoice, post a journal entry, or reserve or decrement stock. The order becomes a real invoice only after the payment gateway webhook confirms payment. A tenant customer is created (or matched by email, then by the last 9 digits of the phone) as a side effect.

Prices are server-authoritative when product_id is supplied: your unit_price and name are discarded and replaced with the catalogue values. They are only honoured for ad-hoc lines that omit product_id.

⚠️ This endpoint does not support idempotency — the Idempotency-Key header is ignored here, so a retry creates a duplicate order.

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
required
Array of objects [ 1 .. 50 ] items
required
object
currency
required
string = 3 characters
Enum: "INR" "USD" "EUR" "GBP" "AUD" "CAD" "SGD" "MYR"
source_website
required
string <= 255 characters
return_url
required
string <uri> <= 500 characters
cancel_url
required
string <uri> <= 500 characters
tax_amount
number
shipping_amount
number
notes
string
metadata
object

Responses

Request samples

Content type
application/json
{
  • "items": [
    ],
  • "customer": {
    },
  • "currency": "INR",
  • "source_website": "string",
  • "return_url": "http://example.com",
  • "cancel_url": "http://example.com",
  • "tax_amount": 0,
  • "shipping_amount": 0,
  • "notes": "string",
  • "metadata": { }
}

Response samples

Content type
application/json
{
  • "success": true,
  • "order_id": 0,
  • "order_number": "string",
  • "total_amount": 0,
  • "currency": "string",
  • "gateway": "razorpay",
  • "payment_data": { }
}

Order status

Returns an order created through this storefront checkout. Documents raised inside the app are not visible here and return 404, even though they live in the same company — this endpoint is deliberately scoped to storefront-origin orders only.

invoice_url stays null until the order converts to an invoice (i.e. until the payment webhook confirms payment).

Authorizations:
(ClientIdClientSecret)
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

orderId
required
string

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

Projects

✅ Available. Projects, sprints, and the work inside them — a user story, a task and a bug are one TYPED work item, so they are one endpoint with a type. Status changes have their own endpoint because they write the history the burndown and cycle time are derived from. Lists are cursor-paged, writes accept Idempotency-Key, and GET /assignees resolves a person to the id assigned_to wants. Iterations, areas and milestones have their own endpoints — iterations and areas are trees with a derived path.

Who work can be assigned to

The active employees assigned_to accepts, so you can resolve a person to an id instead of hardcoding one.

Four fields, deliberately. employees is an HR table — it carries salary, bank and identity columns — and this is a picker, not an HR export. Nothing beyond id, name, employee code and designation is returned, and there is no employee API behind it.

An assigned_to that is not in this list is rejected with 422; it is not silently ignored.

Authorizations:
(ClientIdClientSecret)

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

The vocabulary

The work-item types, priorities, the five fixed state categories, and this company's own configured statuses.

Call it once before you write anything. state is free text — a company renames "In Review" to whatever it likes — so a client that hardcodes a status list breaks on the first rename. The state values returned here are the ones this company actually uses, each with the state_category it belongs to.

Authorizations:
(ClientIdClientSecret)

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

List projects

Authorizations:
(ClientIdClientSecret)
query Parameters
status
string

e.g. active

search
string

Matches name or code.

limit
integer [ 1 .. 100 ]
Default: 25

Page size (1–100).

cursor
string

Opaque cursor from the previous response's paging.next_cursor.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ],
  • "paging": {
    }
}

Create a project

code is optional. Left out, one is derived from the name — and the code becomes the prefix of every work-item reference in the project, so "Website Rebuild" yields WEB-1, WEB-2, … Send your own if the team already says "WEB-14" out loud. It must be unique within the company.

Authorizations:
(ClientIdClientSecret)
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
required
string <= 255 characters
code
string <= 50 characters

Unique per company. Derived from the name when omitted.

description
string
status
string
Default: "active"
start_date
string <date>
end_date
string <date>
is_billable
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "Website Rebuild",
  • "code": "WEB",
  • "description": "string",
  • "status": "active",
  • "start_date": "2019-08-24",
  • "end_date": "2019-08-24",
  • "is_billable": true
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Get a project

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Update a project

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
string
code
string
description
string or null
status
string
start_date
string or null <date>
end_date
string or null <date>
is_billable
boolean

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "code": "string",
  • "description": "string",
  • "status": "string",
  • "start_date": "2019-08-24",
  • "end_date": "2019-08-24",
  • "is_billable": true
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

List sprints

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

Create a sprint

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
required
string
goal
string
start_date
string <date>
end_date
string <date>
status
string
Default: "planned"
Enum: "planned" "active" "completed"

Responses

Request samples

Content type
application/json
{
  • "name": "Sprint 12",
  • "goal": "Ship the checkout rewrite",
  • "start_date": "2019-08-24",
  • "end_date": "2019-08-24",
  • "status": "planned"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Update a sprint

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
sprint
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
string
goal
string or null
start_date
string or null <date>
end_date
string or null <date>
status
string
Enum: "planned" "active" "completed"

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "goal": "string",
  • "start_date": "2019-08-24",
  • "end_date": "2019-08-24",
  • "status": "planned"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

List iterations

Iterations are the time boxes a board plans in, and they nest — a release containing sprints containing weeks.

Iterations are a TREE: send parent_id to nest one. path is derived from the ancestry ("Release 1/Sprint 2") and is read-only — sending it is not supported, because a path that disagrees with its parent_id is a node that displays in one place and belongs in another.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

Create an iteration

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
required
string <= 255 characters
parent_id
integer or null

Another iteration in THIS project.

sprint_id
integer or null

A sprint in THIS project.

start_date
string <date>
end_date
string <date>

Must not precede start_date.

sort_order
integer

Responses

Request samples

Content type
application/json
{
  • "name": "Sprint 2",
  • "parent_id": 0,
  • "sprint_id": 0,
  • "start_date": "2019-08-24",
  • "end_date": "2019-08-24",
  • "sort_order": 0
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Update an iteration

Reparenting is refused if it would make a loop — a node cannot become its own ancestor. Changing name or parent_id rebuilds path.

A body naming none of the fields below is refused with 422 rather than answered "updated", so a call that changes nothing cannot look like one that worked.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
iteration
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
non-empty
name
string
parent_id
integer or null
sprint_id
integer or null
start_date
string or null <date>
end_date
string or null <date>
sort_order
integer

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "parent_id": 0,
  • "sprint_id": 0,
  • "start_date": "2019-08-24",
  • "end_date": "2019-08-24",
  • "sort_order": 0
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

List areas

Areas are the parts of the product work belongs to — "Checkout", "Checkout/Payments" — and they nest the same way iterations do. path is derived and read-only.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

Create an area

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
required
string <= 255 characters
parent_id
integer or null

Another area in THIS project.

sort_order
integer

Responses

Request samples

Content type
application/json
{
  • "name": "Payments",
  • "parent_id": 0,
  • "sort_order": 0
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Update an area

Reparenting is refused if it would make a loop. A body naming none of the fields is refused with 422.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
area
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
non-empty
name
string
parent_id
integer or null
sort_order
integer

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "parent_id": 0,
  • "sort_order": 0
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

List milestones

Dated commitments a project is measured against. Flat, not a tree.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

Create a milestone

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer

Project id.

header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
name
required
string <= 255 characters
description
string
due_date
string <date>
completed_date
string <date>
status
string
Default: "pending"
Enum: "pending" "in_progress" "completed" "overdue"
sort_order
integer

Responses

Request samples

Content type
application/json
{
  • "name": "Public beta",
  • "description": "string",
  • "due_date": "2019-08-24",
  • "completed_date": "2019-08-24",
  • "status": "pending",
  • "sort_order": 0
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Update a milestone

status and completed_date move together. Setting status: completed without a date stamps today; moving out of completed clears the date. Otherwise "delivered on" reads null for finished work, or carries a finish date for work that is not finished.

A body naming none of the fields is refused with 422.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
milestone
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
non-empty
name
string
description
string or null
due_date
string or null <date>
completed_date
string or null <date>
status
string
Enum: "pending" "in_progress" "completed" "overdue"
sort_order
integer

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "due_date": "2019-08-24",
  • "completed_date": "2019-08-24",
  • "status": "pending",
  • "sort_order": 0
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

List work items

User stories, tasks, bugs, epics — one list, filtered by type.

Use open_only=true for "not finished": it excludes both the completed and the removed categories, which is the thing most easily got wrong by listing categories by hand.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
query Parameters
type
string
Enum: "epic" "feature" "user_story" "task" "bug" "issue" "test_case"
state
string
state_category
string
Enum: "proposed" "in_progress" "resolved" "completed" "removed"
priority
string
Enum: "urgent" "high" "medium" "low"
sprint_id
integer
parent_id
integer
assigned_to
integer
iteration_id
integer
area_id
integer
external_id
string

Your own id for one item.

external_ids
string
Example: external_ids=FP-S10-08-T02,FP-S10-08-T03

Comma-separated, up to 100. Returns only the ones that already exist, so an importer can ask "which of these do I already have" in one call instead of reading the whole backlog to find out.

Every match comes back in one response. When external_ids is sent, the page size defaults to the number of ids you asked about, so 100 ids return up to 100 matches with has_more: false. Pass an explicit limit only if you want to page the matches.

open_only
boolean

Excludes completed AND removed.

search
string

Matches title or reference.

limit
integer [ 1 .. 100 ]
Default: 25

Page size (1–100).

cursor
string

Opaque cursor from the previous response's paging.next_cursor.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ],
  • "paging": {
    }
}

Create a user story, task or bug

One endpoint for all of them — type decides which. The fields that only make sense for one type are simply optional: as_a / i_want / so_that and acceptance_criteria for a user story, repro_steps and severity for a bug.

The reference is generated, per project, under a lock — WEB-1, WEB-2 — so two concurrent callers can never be handed the same one. Do not send it.

State. Send nothing and the item opens at new / proposed. Otherwise send either a status slug from /meta (translated for you), or a state with its state_category. A state this company has not configured is rejected rather than filed under a guessed category — guessing is how a board starts claiming work is finished when it is not.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
type
required
string
Enum: "epic" "feature" "user_story" "task" "bug" "issue" "test_case"
title
required
string <= 500 characters
description
string
status
string

A slug from /meta, e.g. review. Translated to state + category.

state
string

Send state_category with it unless the company has configured this state.

state_category
string
Enum: "proposed" "in_progress" "resolved" "completed" "removed"
priority
string
Enum: "urgent" "high" "medium" "low"
severity
string

Bugs.

parent_id
integer

Must be in the SAME project.

sprint_id
integer

Must be in the SAME project.

iteration_id
integer
assigned_to
integer

Employee id.

story_points
integer
estimated_hours
number
start_date
string <date>
due_date
string <date>
acceptance_criteria
string

User stories.

as_a
string

User stories.

i_want
string

User stories.

so_that
string

User stories.

repro_steps
string

Bugs.

external_id
string <= 255 characters

YOUR id for this item. Unique among live items in the project; reusing one returns 422 naming the item that holds it, so you can PATCH that instead of creating a second. Retired (removed) items release theirs.

tags
Array of strings

Responses

Request samples

Content type
application/json
{
  • "type": "epic",
  • "title": "string",
  • "description": "string",
  • "status": "string",
  • "state": "string",
  • "state_category": "proposed",
  • "priority": "urgent",
  • "severity": "string",
  • "parent_id": 0,
  • "sprint_id": 0,
  • "iteration_id": 0,
  • "assigned_to": 0,
  • "story_points": 0,
  • "estimated_hours": 0,
  • "start_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "acceptance_criteria": "string",
  • "as_a": "string",
  • "i_want": "string",
  • "so_that": "string",
  • "repro_steps": "string",
  • "external_id": "string",
  • "tags": [
    ]
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Get a work item

Includes status_history — every state change with how long it sat in the previous one.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
workItem
required
integer

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Update a work item

Everything except the state. Status changes go to /transition — a state change writes the history row the burndown, the cumulative-flow diagram and cycle time are derived from, so it is an event rather than a field, and you should not be able to move a card by accident while renaming its title.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
workItem
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
title
string
description
string or null
type
string
Enum: "epic" "feature" "user_story" "task" "bug" "issue" "test_case"
priority
string
Enum: "urgent" "high" "medium" "low"
severity
string or null
parent_id
integer or null
sprint_id
integer or null
assigned_to
integer or null
story_points
integer or null
estimated_hours
number or null
remaining_hours
number or null
completed_hours
number or null
start_date
string or null <date>
due_date
string or null <date>
acceptance_criteria
string or null
repro_steps
string or null
external_id
string <= 255 characters

YOUR id for this item. Unique among live items in the project; reusing one returns 422 naming the item that holds it, so you can PATCH that instead of creating a second. Retired (removed) items release theirs.

tags
Array of strings or null

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "type": "epic",
  • "priority": "urgent",
  • "severity": "string",
  • "parent_id": 0,
  • "sprint_id": 0,
  • "assigned_to": 0,
  • "story_points": 0,
  • "estimated_hours": 0,
  • "remaining_hours": 0,
  • "completed_hours": 0,
  • "start_date": "2019-08-24",
  • "due_date": "2019-08-24",
  • "acceptance_criteria": "string",
  • "repro_steps": "string",
  • "external_id": "string",
  • "tags": [
    ]
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Move a work item to another state

The status update.

Send a status slug from /meta, or a state with its state_category. Entering the completed category stamps the finish time; leaving it clears it again, so "finished on" cannot outlive a reopen.

An optional comment is recorded on the item's timeline — without it the board says what changed and never why.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
workItem
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
status
string

A slug from /meta. Send this OR state.

state
string
state_category
string
Enum: "proposed" "in_progress" "resolved" "completed" "removed"
comment
string

Responses

Request samples

Content type
application/json
{
  • "status": "done",
  • "state": "in_review",
  • "state_category": "proposed",
  • "comment": "Deployed to staging"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "WEB-14 moved from active to done.",
  • "data": {
    }
}

List comments

Public comments only — the team's internal notes are not returned to an API key.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
workItem
required
integer

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "data": [
    ]
}

Add a comment

author is a display name only. An API key is not a user, so no team member's name is attached to it — left out, the comment is signed with the key's own name.

Authorizations:
(ClientIdClientSecret)
path Parameters
project
required
integer
workItem
required
integer
header Parameters
Idempotency-Key
string <uuid>

A unique UUID so retries don't double‑create/charge/send. Cached 24h.

Request Body schema: application/json
required
body
required
string
author
string

Responses

Request samples

Content type
application/json
{
  • "body": "string",
  • "author": "Jenkins"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "data": {
    }
}

Bookings

✅ Available. Driving-school booking flow — service locations, instructors by suburb, lessons & packages, availability, and bookings with online payment. Accepts a secret key OR a browser publishable (pk_) key.

List service locations

Active service locations (test centres / pickup points / offices).

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "locations": [
    ]
}

Find instructors

Bookable instructors, optionally filtered to those who service a suburb/postcode/point.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
lat
number

Latitude (with lng) — distance match vs each instructor's radius_km.

lng
number

Longitude (with lat).

postcode
string

Instructors servicing this postcode.

suburb
string

Instructors servicing this suburb.

location_id
integer

Instructors mapped to this service location.

transmission
string
Enum: "auto" "manual" "both"

Instructor's transmission (a custom field — both is a real value).

gender
string

Case-insensitive exact match.

language
string

Instructor speaks this language.

nationality
string

Case-insensitive exact match.

car
string

Filter by the instructor's car.

available_date
string <date>

Only instructors with free capacity on this date.

time_of_day
string
Enum: "morning" "afternoon" "evening"

Narrows available_date to a part of the day.

with_next
boolean

Include each instructor's next_available slot in the response (costs an extra lookup).

cf
object

Match arbitrary instructor custom fields, e.g. cf[transmission]=auto. See GET /schools/{company}/instructor-fields for the available keys.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "instructors": [
    ]
}

List lessons & packages

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
transmission
string
Enum: "auto" "manual"
type
string
Enum: "lesson" "package"
instructor_id
integer

Only the lessons/packages this instructor actually offers.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "courses": [
    ]
}

Get available time slots

Bookable slots for an instructor on a date, honouring the weekly schedule, overrides, existing bookings and booking settings.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
instructor_id
required
integer
date
required
string <date>
duration_minutes
integer [ 15 .. 480 ]

Slot length to fit. Outside 15–480 returns 422.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "available": true,
  • "reason": "string",
  • "slots": [
    ]
}

List instructor filter fields

The custom-field definitions available for filtering instructors — use the returned key values with the cf[...] query parameter on GET /instructors to build facets without hard-coding them.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "fields": [
    ]
}

Look up a returning customer

Returns the stored name for a matching email or phone so the widget can greet a returning student and pre-fill the form. Pass email or phone.

⚠️ This confirms whether a given email/phone is a customer of the school and reveals their name. Its only gate is your key's allowed-origins list, so treat the key as origin-locked and do not ship it anywhere you would not accept that lookup being run.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
email
string <email>

Exact match.

phone
string

Digits are extracted, so formatting is ignored.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "customer": {
    }
}

Resolve a point to the nearest serviced suburb

Maps browser geolocation to a suburb you can feed back into GET /instructors?suburb=.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
lat
required
number [ -90 .. 90 ]
lng
required
number [ -180 .. 180 ]

Responses

Response samples

Content type
application/json
Example
{
  • "success": false,
  • "message": "Invalid API credentials, or the company was not found."
}

Quote the travel charge for a driving test

Prices the travel component for a test-day pickup before booking, so the widget can show it up front. The same charge is recomputed server-side when the booking is created.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

query Parameters
location_id
required
integer

The test centre. Must exist, else 422.

lat
required
number [ -90 .. 90 ]

Pickup latitude.

lng
required
number [ -180 .. 180 ]

Pickup longitude.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "currency": "AUD",
  • "travel_distance_km": 0,
  • "travel_charge": 0
}

Reserve a lesson package

Buys a lesson package for a customer, optionally scheduling its lessons up front and optionally taking a deposit (pay_amount) rather than the full price. Supply return_url+cancel_url to start online payment and receive payment_data.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
required
object (SchoolCustomer)

Buyer details for the schools flows. phone is required and email is optional — note this is the opposite of most of the API.

product_id
required
integer

Must be a lesson package, else 422.

lessons
Array of objects <= 50 items

Optional lessons to schedule now. Each must be at least 15 minutes.

pay_amount
number or null >= 0

Deposit. Omit to charge the full package price.

return_url
string or null <uri>
cancel_url
string or null <uri>

Responses

Request samples

Content type
application/json
{
  • "customer": {
    },
  • "product_id": 0,
  • "lessons": [
    ],
  • "pay_amount": 0,
  • "return_url": "http://example.com",
  • "cancel_url": "http://example.com"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "string",
  • "booking": {
    },
  • "payment": {
    }
}

Reserve an order of services

Orders one or more services from an instructor's menu (up to 20 items), optionally with a deposit. Supply return_url+cancel_url to start online payment.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
required
object (SchoolCustomer)

Buyer details for the schools flows. phone is required and email is optional — note this is the opposite of most of the API.

instructor_id
required
integer
items
required
Array of objects [ 1 .. 20 ] items

Services from this instructor's menu. An unavailable service returns 422.

pay_amount
number or null >= 0

Deposit. Omit to charge the full total.

payment_method
string or null
Enum: "card" "bank_transfer"
return_url
string or null <uri>
cancel_url
string or null <uri>

Responses

Request samples

Content type
application/json
{
  • "customer": {
    },
  • "instructor_id": 0,
  • "items": [
    ],
  • "pay_amount": 0,
  • "payment_method": "card",
  • "return_url": "http://example.com",
  • "cancel_url": "http://example.com"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "string",
  • "booking": {
    },
  • "payment": {
    }
}

Create a booking

Match/create the customer and create the booking. If return_url+cancel_url are supplied and payment is due, payment is initiated and payment_url returned. Prices come from your catalogue server-side.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

Request Body schema: application/json
required
required
object (SchoolCustomer)

Buyer details for the schools flows. phone is required and email is optional — note this is the opposite of most of the API.

instructor_id
required
integer

Must exist — an unknown id is 422, not 404.

product_id
integer or null
service_location_id
integer or null
booking_package_id
integer or null

Book against a pre-paid package (no payment). 403 if the package belongs to another customer.

booking_date
required
string <date>

Today or later — a past date is rejected.

start_time
string or null^\d{2}:\d{2}$

24-hour HH:MM. Required unless test_time is given.

end_time
string or null^\d{2}:\d{2}$

24-hour HH:MM. Must be after start_time.

duration_minutes
integer or null [ 15 .. 480 ]
test_time
string or null^\d{2}:\d{2}$

24-hour HH:MM. Sending this makes it a DRIVING-TEST booking; the block is derived from this time.

lesson_before_minutes
integer or null
Enum: 0 45 60 90 120

Warm-up lesson immediately before the test.

pickup_required
boolean or null

Test-day pickup. The pickup coordinates drive the travel charge.

pickup_lat
number or null [ -90 .. 90 ]
pickup_lng
number or null [ -180 .. 180 ]
pickup_address
string or null <= 500 characters
dropoff_address
string or null <= 500 characters
student_notes
string or null <= 1000 characters
return_url
string or null <uri>

Supply BOTH urls to initiate online payment immediately — the response then carries payment_data/payment_url.

cancel_url
string or null <uri>

Responses

Request samples

Content type
application/json
{
  • "customer": {
    },
  • "instructor_id": 0,
  • "product_id": 0,
  • "service_location_id": 0,
  • "booking_package_id": 0,
  • "booking_date": "2019-08-24",
  • "start_time": "10:00",
  • "end_time": "12:00",
  • "duration_minutes": 15,
  • "test_time": "14:30",
  • "lesson_before_minutes": 0,
  • "pickup_required": true,
  • "pickup_lat": -90,
  • "pickup_lng": -180,
  • "pickup_address": "string",
  • "dropoff_address": "string",
  • "student_notes": "string",
  • "return_url": "http://example.com",
  • "cancel_url": "http://example.com"
}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "string",
  • "booking": {
    },
  • "payment": {
    }
}

Start/retry online payment

Initiate payment for a pending, unpaid booking that this widget created. Returns payment_url/payment_data to redirect to; the booking is confirmed by the gateway webhook on success. The amount is derived server-side — there is no amount or payment-mode field. Requires a configured gateway for the currency, else 422.

Only widget-created, still-pending bookings are payable: handing this any other booking id in the tenant returns 403, not a checkout.

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Request Body schema: application/json
required
return_url
required
string <uri>
cancel_url
required
string <uri>

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "success": true,
  • "message": "string",
  • "booking": {
    },
  • "payment": {
    }
}

Get booking status

Authorizations:
(ClientIdClientSecret) PublishableKey
path Parameters
company
required
string
Example: acme-traders

Your company (workspace) slug — the same value as your {slug}.laabam.app subdomain.

Copy it from Settings → API Keys → “Integration details”, which shows the slug and the ready-made endpoint URL with a copy button. It is also in Settings → Company Profile.

⚠️ Do not derive it from the company name. It is not your display name, brand or product name, and it is not reliably a slugified version of them either — workspaces are often created under one name, or renamed later, while the slug stays as first issued. A workspace branded “Acme Halls” can legitimately have a slug that looks nothing like it. Guessing produces a plausible-looking URL that always fails. Copy it, don't infer it.

An unknown slug returns 401 with the same body as a bad key — Invalid API credentials, or the company was not found. The two cases are intentionally indistinguishable so nobody can probe which company slugs exist without valid credentials. If you get that 401, check both the slug and the key.

id
required
string

Opaque resource identifier.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "booking": {
    }
}

The whole rate card

Every bookable service with its price on every tier, in one call — what a "Pricing" page renders without asking per product.

tiers names the price groups; each row in pricing carries base (what an instructor on NO tier is charged, and the fallback) plus a prices map keyed by tier id. A row also states its own duration_min, transmission and currency, so nothing has to be inferred from the name.

Authorizations:
(ClientIdClientSecret)

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "tiers": [
    ],
  • "pricing": [
    ]
}

Start times available for a driving test

The times a test can START on a given day with a given instructor, already accounting for the lesson the school requires beforehand.

lesson_before_minutes is how long that warm-up lesson is, so the caller can show "test at 10:00, pick-up at 09:00" rather than making the customer work it out. A test is not booked through this endpoint — use Create a booking with the time it returns.

Authorizations:
(ClientIdClientSecret)
query Parameters
instructor_id
required
integer
date
required
string <date>

Today or later.

product_id
required
integer

The test service.

Responses

Response samples

Content type
application/json
{
  • "success": true,
  • "times": [
    ],
  • "lesson_before_minutes": 0
}