ENGARAGE Open API

Version 1.0.0 · 43 endpoints · generated from the live specification

ENGARAGE Open API — v1.0.0

Read and write your workshop data from your own systems.

Base URL: https://app.engarage.io/api/v1

The ENGARAGE Open API gives your own systems the same view of your workshop that the app has: customers, vehicles, bookings, work orders, quotations, inventory and procurement.

Availability

The API is included with the Pro plan. Keys are issued per workshop from Account Settings → API Docs & Keys, and every request is scoped to the workshop that issued the key — there is no way to read another workshop's data.

Authentication

Send your secret key on every request, either as an X-API-Key header or as Authorization: Bearer <key>. Keys are shown once, at creation; store them somewhere safe and never ship one in client-side code.

Environments

Every key is either live (eng_live_…) or sandbox (eng_test_…). A sandbox key runs against an isolated fixture dataset with identical validation, envelopes and error codes, so you can build and test a client — including writes — without touching production data. Reset the sandbox at any time from the portal or with POST /sandbox/reset.

Scopes

A key carries one of read, write or admin, and they nest: admin implies write, which implies read. GET needs read, POST and PATCH need write, and DELETE needs admin.

Pagination

List endpoints take page and limit (max 200) and return a meta block with total, page, limit and total_pages. Sort with sort=field or sort=-field.

Rate limits

600 requests per minute per live key and 1200 per sandbox key. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 carries retry_after in seconds.

Errors

Failures always return the same envelope, so one handler covers them all:

{ "error": { "code": "validation_error", "message": "…", "field": "name" } }

Endpoints

Meta

GET/ping

Health check

Confirms the API is reachable and your key is valid. The cheapest call to wire up first.

Responses: 200, 401

{
    "ok": true,
    "version": "1.0.0",
    "environment": "sandbox",
    "time": "2026-09-08T04:20:00Z"
}

GET/me

Describe the calling key

Who you are: the workshop the key belongs to, its environment, scopes and rate limit. Useful for showing "connected as…" in your own UI.

Responses: 200, 401

{
    "workshop": {
        "id": 128,
        "name": "Workshop Engarage Demo"
    },
    "key": {
        "name": "Partner ERP Integration",
        "environment": "sandbox",
        "scopes": [
            "read",
            "write"
        ],
        "last_used_at": "2026-09-08T04:19:12Z"
    },
    "plan": "pro",
    "rate_limit": {
        "limit": 1200,
        "window": "1 minute"
    }
}

POST/sandbox/reset

Reset the sandbox dataset

Throws away everything your sandbox key has created or changed and reseeds the fixtures. Rejected with 409 if called with a live key — there is deliberately no way to reset production.

Responses: 200, 409

{
    "ok": true,
    "message": "Sandbox dataset reset."
}

Customers

GET/customers

List customers

A customer owns vehicles and work orders. Phone number is the natural key most workshops search on, so it is indexed and filterable.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across name, phone and email.
sort query string Sort field. Prefix with - for descending.
phone query string Exact phone number match.
email query string Exact email match.
status query integer Record status. 1 = active.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "name": "Nurul Aisyah binti Rahman",
            "phone": "123456789",
            "country_code": "60",
            "email": "[email protected]",
            "gender": "female",
            "dob": "1991-04-18",
            "note": "Prefers WhatsApp reminders.",
            "status": 1,
            "vehicle_count": 2,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

POST/customers

Create a customer

Creates a customer in the workshop that owns the key. Requires the write scope.

Request body:

{
    "name": "Nurul Aisyah binti Rahman",
    "phone": "123456789",
    "country_code": "60",
    "email": "[email protected]",
    "gender": "female",
    "dob": "1991-04-18",
    "note": "Prefers WhatsApp reminders."
}

Responses: 201, 401, 403, 422

{
    "data": {
        "id": 1,
        "name": "Nurul Aisyah binti Rahman",
        "phone": "123456789",
        "country_code": "60",
        "email": "[email protected]",
        "gender": "female",
        "dob": "1991-04-18",
        "note": "Prefers WhatsApp reminders.",
        "status": 1,
        "vehicle_count": 2,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/customers/{id}

Retrieve a customer

Fetch a single customer by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "name": "Nurul Aisyah binti Rahman",
        "phone": "123456789",
        "country_code": "60",
        "email": "[email protected]",
        "gender": "female",
        "dob": "1991-04-18",
        "note": "Prefers WhatsApp reminders.",
        "status": 1,
        "vehicle_count": 2,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/customers/{id}

Update a customer

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "name": "Nurul Aisyah binti Rahman",
    "phone": "123456789"
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "name": "Nurul Aisyah binti Rahman",
        "phone": "123456789",
        "country_code": "60",
        "email": "[email protected]",
        "gender": "female",
        "dob": "1991-04-18",
        "note": "Prefers WhatsApp reminders.",
        "status": 1,
        "vehicle_count": 2,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

Vehicles

GET/vehicles

List vehicles

Vehicles hang off a customer. plate_no is what every counter workflow looks up, so filter on it rather than scanning the list.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across plate_no and chassis_no.
sort query string Sort field. Prefix with - for descending.
plate_no query string Exact registration-plate match.
customer_id query integer Only vehicles owned by this customer.
status query integer Record status. 1 = active.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "customer_id": 1,
            "customer_name": "Nurul Aisyah binti Rahman",
            "plate_no": "WXY 1234",
            "chassis_no": "JHMCM56557C404453",
            "ref_car_id": 1,
            "make": "Perodua",
            "model": "Myvi 1.5 AV",
            "segment": "Hatchback",
            "fuel_type": "Petrol",
            "year": "2021",
            "next_service": "2026-12-01",
            "status": 1,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

POST/vehicles

Create a vehicle

Creates a vehicle in the workshop that owns the key. Requires the write scope.

Request body:

{
    "customer_id": 1,
    "plate_no": "WXY 1234",
    "ref_car_id": 1,
    "chassis_no": "JHMCM56557C404453",
    "year": "2021",
    "next_service": "2026-12-01"
}

Responses: 201, 401, 403, 422

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "plate_no": "WXY 1234",
        "chassis_no": "JHMCM56557C404453",
        "ref_car_id": 1,
        "make": "Perodua",
        "model": "Myvi 1.5 AV",
        "segment": "Hatchback",
        "fuel_type": "Petrol",
        "year": "2021",
        "next_service": "2026-12-01",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/vehicles/{id}

Retrieve a vehicle

Fetch a single vehicle by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "plate_no": "WXY 1234",
        "chassis_no": "JHMCM56557C404453",
        "ref_car_id": 1,
        "make": "Perodua",
        "model": "Myvi 1.5 AV",
        "segment": "Hatchback",
        "fuel_type": "Petrol",
        "year": "2021",
        "next_service": "2026-12-01",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/vehicles/{id}

Update a vehicle

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "customer_id": 1,
    "plate_no": "WXY 1234"
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "plate_no": "WXY 1234",
        "chassis_no": "JHMCM56557C404453",
        "ref_car_id": 1,
        "make": "Perodua",
        "model": "Myvi 1.5 AV",
        "segment": "Hatchback",
        "fuel_type": "Petrol",
        "year": "2021",
        "next_service": "2026-12-01",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

DELETE/vehicles/{id}

Delete a vehicle

Soft-deletes the record. Requires the admin scope.

Parameter In Type Description
id path integer Record id.

Responses: 204, 401, 403, 404

GET/vehicle-models

List vehicle models

Read-only reference data shared by every workshop. Look a model up here to get the ref_car_id that POST /vehicles requires.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across make and model.
sort query string Sort field. Prefix with - for descending.
make query string Exact manufacturer match.
segment query string Exact segment match, e.g. "SUV".
fuel_type query string Exact fuel-type match.
status query integer Record status. 1 = active.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "make": "Perodua",
            "model": "Myvi 1.5 AV",
            "segment": "Hatchback",
            "fuel_type": "Petrol",
            "oil_capacity": "3.2L",
            "car_battery": "NS60",
            "status": 1
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

GET/vehicle-models/{id}

Retrieve a vehicle model

Fetch a single vehicle model by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "make": "Perodua",
        "model": "Myvi 1.5 AV",
        "segment": "Hatchback",
        "fuel_type": "Petrol",
        "oil_capacity": "3.2L",
        "car_battery": "NS60",
        "status": 1
    }
}

Bookings

GET/bookings

List bookings

A booking is a request for a slot. Accepting one creates a work order; this endpoint covers the request itself.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across remark.
sort query string Sort field. Prefix with - for descending.
status query integer Booking status. 1 = pending, 2 = accepted, 3 = rejected.
customer_id query integer Only bookings for this customer.
from query string (date) Only bookings at or after this date.
to query string (date) Only bookings at or before this date.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "customer_id": 1,
            "customer_name": "Nurul Aisyah binti Rahman",
            "vehicle_id": 1,
            "plate_no": "WXY 1234",
            "scheduled_at": "2026-09-15T09:30:00Z",
            "remark": "Engine oil + filter, brakes squealing.",
            "status": 1,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

POST/bookings

Create a booking

Creates a booking in the workshop that owns the key. Requires the write scope.

Request body:

{
    "customer_id": 1,
    "vehicle_id": 1,
    "scheduled_at": "2026-09-15T09:30:00Z",
    "remark": "Engine oil + filter, brakes squealing.",
    "status": 1
}

Responses: 201, 401, 403, 422

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "vehicle_id": 1,
        "plate_no": "WXY 1234",
        "scheduled_at": "2026-09-15T09:30:00Z",
        "remark": "Engine oil + filter, brakes squealing.",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/bookings/{id}

Retrieve a booking

Fetch a single booking by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "vehicle_id": 1,
        "plate_no": "WXY 1234",
        "scheduled_at": "2026-09-15T09:30:00Z",
        "remark": "Engine oil + filter, brakes squealing.",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/bookings/{id}

Update a booking

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "customer_id": 1,
    "vehicle_id": 1
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "vehicle_id": 1,
        "plate_no": "WXY 1234",
        "scheduled_at": "2026-09-15T09:30:00Z",
        "remark": "Engine oil + filter, brakes squealing.",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

Work Orders

GET/work-orders

List work orders

A work order is the spine of the system: it carries the vehicle, the customer, the assigned mechanic and bay, and every line item. Its status walks 1 → 10; see the status table in the guide.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across wo_no, invoice_no, quotation_no and complaint.
sort query string Sort field. Prefix with - for descending.
status query integer Work-order status, 1–10.
customer_id query integer Only jobs for this customer.
wo_no query string Exact work-order number.
invoice_no query string Exact invoice number.
from query string (date) Created at or after this date.
to query string (date) Created at or before this date.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "wo_no": "WO-2609-0042",
            "quotation_no": "QT-2609-0031",
            "invoice_no": "INV-2609-0028",
            "status": 7,
            "status_label": "Paid",
            "customer_id": 1,
            "customer_name": "Nurul Aisyah binti Rahman",
            "vehicle_id": 1,
            "plate_no": "WXY 1234",
            "mechanic_id": 3,
            "bay_id": 2,
            "complaint": "Brakes squealing on cold start.",
            "remark": "Front pads replaced, discs skimmed.",
            "mileage": "86540",
            "total_payment": 486.5,
            "payment_sst": 0,
            "method": "card",
            "booking_at": "2026-09-15T09:30:00Z",
            "completed_at": "2026-09-15T15:10:00Z",
            "invoiced_at": "2026-09-15T15:22:00Z",
            "paid_at": "2026-09-15T15:40:00Z",
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

GET/work-orders/{id}

Retrieve a work order

Fetch a single work order by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "wo_no": "WO-2609-0042",
        "quotation_no": "QT-2609-0031",
        "invoice_no": "INV-2609-0028",
        "status": 7,
        "status_label": "Paid",
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "vehicle_id": 1,
        "plate_no": "WXY 1234",
        "mechanic_id": 3,
        "bay_id": 2,
        "complaint": "Brakes squealing on cold start.",
        "remark": "Front pads replaced, discs skimmed.",
        "mileage": "86540",
        "total_payment": 486.5,
        "payment_sst": 0,
        "method": "card",
        "booking_at": "2026-09-15T09:30:00Z",
        "completed_at": "2026-09-15T15:10:00Z",
        "invoiced_at": "2026-09-15T15:22:00Z",
        "paid_at": "2026-09-15T15:40:00Z",
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/work-orders/{id}

Update a work order

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "status": 7,
    "mechanic_id": 3
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "wo_no": "WO-2609-0042",
        "quotation_no": "QT-2609-0031",
        "invoice_no": "INV-2609-0028",
        "status": 7,
        "status_label": "Paid",
        "customer_id": 1,
        "customer_name": "Nurul Aisyah binti Rahman",
        "vehicle_id": 1,
        "plate_no": "WXY 1234",
        "mechanic_id": 3,
        "bay_id": 2,
        "complaint": "Brakes squealing on cold start.",
        "remark": "Front pads replaced, discs skimmed.",
        "mileage": "86540",
        "total_payment": 486.5,
        "payment_sst": 0,
        "method": "card",
        "booking_at": "2026-09-15T09:30:00Z",
        "completed_at": "2026-09-15T15:10:00Z",
        "invoiced_at": "2026-09-15T15:22:00Z",
        "paid_at": "2026-09-15T15:40:00Z",
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/work-orders/{id}/items

Line items on a work order

Parts, services, packages and miscellaneous charges billed on this job.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": [
        {
            "id": 5501,
            "type": "part",
            "part_id": 812,
            "service_id": null,
            "package_id": null,
            "quantity": 4,
            "total_price": 168,
            "total_discount": 0,
            "tax_rate": 0
        }
    ]
}

Quotations

GET/quotations

List quotations

A work order can carry several quotations across revisions; the one with status 2 is the accepted one.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across quotation_no.
sort query string Sort field. Prefix with - for descending.
status query integer Quotation status. 2 = accepted/converted.
work_order_id query integer Only quotations on this work order.
quotation_no query string Exact quotation number.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "quotation_no": "QT-2609-0031",
            "work_order_id": 42,
            "wo_no": "WO-2609-0042",
            "customer_id": 1,
            "status": 2,
            "discount": 20,
            "deposit": 100,
            "deposit_method": "cash",
            "deposit_at": "2026-09-15T10:05:00Z",
            "total": 486.5,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

GET/quotations/{id}

Retrieve a quotation

Fetch a single quotation by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "quotation_no": "QT-2609-0031",
        "work_order_id": 42,
        "wo_no": "WO-2609-0042",
        "customer_id": 1,
        "status": 2,
        "discount": 20,
        "deposit": 100,
        "deposit_method": "cash",
        "deposit_at": "2026-09-15T10:05:00Z",
        "total": 486.5,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

Inventory

GET/parts

List parts

Your inventory. quantity is on-hand stock; it moves when work orders are paid, so treat it as authoritative rather than caching it.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across name, part_no, brand and model.
sort query string Sort field. Prefix with - for descending.
part_no query string Exact part number.
brand query string Exact brand match.
supplier_id query integer Only parts from this supplier.
status query integer Record status. 1 = active.
low_stock query boolean When true, only parts at or below their stock-alert threshold.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "name": "Engine Oil Filter",
            "part_no": "OF-15400-RTA",
            "brand": "Denso",
            "model": "Myvi",
            "variant": "1.5L",
            "description": "Spin-on oil filter.",
            "unit": "pcs",
            "bin_number": "A-04-2",
            "cost_price": 12.5,
            "price": 24,
            "quantity": 36,
            "stock_alert_threshold": 10,
            "supplier_id": 4,
            "is_low_stock": false,
            "status": 1,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

POST/parts

Create a part

Creates a part in the workshop that owns the key. Requires the write scope.

Request body:

{
    "name": "Engine Oil Filter",
    "part_no": "OF-15400-RTA",
    "brand": "Denso",
    "model": "Myvi",
    "variant": "1.5L",
    "description": "Spin-on oil filter.",
    "unit": "pcs",
    "bin_number": "A-04-2",
    "cost_price": 12.5,
    "price": 24,
    "quantity": 36,
    "stock_alert_threshold": 10,
    "supplier_id": 4
}

Responses: 201, 401, 403, 422

{
    "data": {
        "id": 1,
        "name": "Engine Oil Filter",
        "part_no": "OF-15400-RTA",
        "brand": "Denso",
        "model": "Myvi",
        "variant": "1.5L",
        "description": "Spin-on oil filter.",
        "unit": "pcs",
        "bin_number": "A-04-2",
        "cost_price": 12.5,
        "price": 24,
        "quantity": 36,
        "stock_alert_threshold": 10,
        "supplier_id": 4,
        "is_low_stock": false,
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/parts/{id}

Retrieve a part

Fetch a single part by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "name": "Engine Oil Filter",
        "part_no": "OF-15400-RTA",
        "brand": "Denso",
        "model": "Myvi",
        "variant": "1.5L",
        "description": "Spin-on oil filter.",
        "unit": "pcs",
        "bin_number": "A-04-2",
        "cost_price": 12.5,
        "price": 24,
        "quantity": 36,
        "stock_alert_threshold": 10,
        "supplier_id": 4,
        "is_low_stock": false,
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/parts/{id}

Update a part

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "name": "Engine Oil Filter",
    "part_no": "OF-15400-RTA"
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "name": "Engine Oil Filter",
        "part_no": "OF-15400-RTA",
        "brand": "Denso",
        "model": "Myvi",
        "variant": "1.5L",
        "description": "Spin-on oil filter.",
        "unit": "pcs",
        "bin_number": "A-04-2",
        "cost_price": 12.5,
        "price": 24,
        "quantity": 36,
        "stock_alert_threshold": 10,
        "supplier_id": 4,
        "is_low_stock": false,
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

DELETE/parts/{id}

Delete a part

Soft-deletes the record. Requires the admin scope.

Parameter In Type Description
id path integer Record id.

Responses: 204, 401, 403, 404

GET/suppliers

List suppliers

Suppliers referenced by parts and purchase orders.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across name, company_reg, email and phone_no.
sort query string Sort field. Prefix with - for descending.
status query integer Record status. 1 = active.
state query string Exact state match.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "name": "Southern Auto Parts Sdn Bhd",
            "company_reg": "201801023456",
            "sales_p": "Mr Tan",
            "phone_no": "0377881234",
            "email": "[email protected]",
            "address_1": "Lot 12, Jalan Perindustrian 3",
            "address_2": null,
            "postcode": "47100",
            "city": "Puchong",
            "state": "Selangor",
            "status": 1,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

POST/suppliers

Create a supplier

Creates a supplier in the workshop that owns the key. Requires the write scope.

Request body:

{
    "name": "Southern Auto Parts Sdn Bhd",
    "company_reg": "201801023456",
    "sales_p": "Mr Tan",
    "phone_no": "0377881234",
    "email": "[email protected]",
    "address_1": "Lot 12, Jalan Perindustrian 3",
    "address_2": null,
    "postcode": "47100",
    "city": "Puchong",
    "state": "Selangor"
}

Responses: 201, 401, 403, 422

{
    "data": {
        "id": 1,
        "name": "Southern Auto Parts Sdn Bhd",
        "company_reg": "201801023456",
        "sales_p": "Mr Tan",
        "phone_no": "0377881234",
        "email": "[email protected]",
        "address_1": "Lot 12, Jalan Perindustrian 3",
        "address_2": null,
        "postcode": "47100",
        "city": "Puchong",
        "state": "Selangor",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/suppliers/{id}

Retrieve a supplier

Fetch a single supplier by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "name": "Southern Auto Parts Sdn Bhd",
        "company_reg": "201801023456",
        "sales_p": "Mr Tan",
        "phone_no": "0377881234",
        "email": "[email protected]",
        "address_1": "Lot 12, Jalan Perindustrian 3",
        "address_2": null,
        "postcode": "47100",
        "city": "Puchong",
        "state": "Selangor",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/suppliers/{id}

Update a supplier

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "name": "Southern Auto Parts Sdn Bhd",
    "company_reg": "201801023456"
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "name": "Southern Auto Parts Sdn Bhd",
        "company_reg": "201801023456",
        "sales_p": "Mr Tan",
        "phone_no": "0377881234",
        "email": "[email protected]",
        "address_1": "Lot 12, Jalan Perindustrian 3",
        "address_2": null,
        "postcode": "47100",
        "city": "Puchong",
        "state": "Selangor",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

DELETE/suppliers/{id}

Delete a supplier

Soft-deletes the record. Requires the admin scope.

Parameter In Type Description
id path integer Record id.

Responses: 204, 401, 403, 404

GET/purchase-orders

List purchase orders

Read-only view of procurement. Raising and receiving stock stays in the app so the stock ledger cannot be bypassed.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across po_no and supplier_invoice_no.
sort query string Sort field. Prefix with - for descending.
status query integer Purchase-order status.
supplier_id query integer Only orders to this supplier.
po_no query string Exact purchase-order number.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "po_no": "PO-2609-0011",
            "supplier_id": 4,
            "supplier_name": "Southern Auto Parts Sdn Bhd",
            "supplier_invoice_no": "SAP-88213",
            "total": 1420,
            "tax_rate": 0,
            "discount": 0,
            "shipping_cost": 25,
            "status": 3,
            "notes": "Monthly filter restock.",
            "ordered_at": "2026-09-01T08:00:00Z",
            "approved_at": "2026-09-01T09:12:00Z",
            "paid_at": null,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

GET/purchase-orders/{id}

Retrieve a purchase order

Fetch a single purchase order by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "po_no": "PO-2609-0011",
        "supplier_id": 4,
        "supplier_name": "Southern Auto Parts Sdn Bhd",
        "supplier_invoice_no": "SAP-88213",
        "total": 1420,
        "tax_rate": 0,
        "discount": 0,
        "shipping_cost": 25,
        "status": 3,
        "notes": "Monthly filter restock.",
        "ordered_at": "2026-09-01T08:00:00Z",
        "approved_at": "2026-09-01T09:12:00Z",
        "paid_at": null,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

Catalogue

GET/services

List services

The service catalogue billed on work orders and quotations.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across name, service_no and category.
sort query string Sort field. Prefix with - for descending.
category query string Exact category match.
status query integer Record status. 1 = active.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "name": "Engine Oil Service",
            "service_no": "SRV-001",
            "category": "Engine",
            "price": 80,
            "price_sst": 80,
            "sst": 0,
            "hours": 0.75,
            "skill_level": "junior",
            "status": 1,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

POST/services

Create a service

Creates a service in the workshop that owns the key. Requires the write scope.

Request body:

{
    "name": "Engine Oil Service",
    "service_no": "SRV-001",
    "category": "Engine",
    "price": 80,
    "price_sst": 80,
    "sst": 0,
    "hours": 0.75,
    "skill_level": "junior"
}

Responses: 201, 401, 403, 422

{
    "data": {
        "id": 1,
        "name": "Engine Oil Service",
        "service_no": "SRV-001",
        "category": "Engine",
        "price": 80,
        "price_sst": 80,
        "sst": 0,
        "hours": 0.75,
        "skill_level": "junior",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

GET/services/{id}

Retrieve a service

Fetch a single service by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "name": "Engine Oil Service",
        "service_no": "SRV-001",
        "category": "Engine",
        "price": 80,
        "price_sst": 80,
        "sst": 0,
        "hours": 0.75,
        "skill_level": "junior",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

PATCH/services/{id}

Update a service

Partial update — send only the fields you want to change. Requires the write scope.

Parameter In Type Description
id path integer Record id.

Request body:

{
    "name": "Engine Oil Service",
    "service_no": "SRV-001"
}

Responses: 200, 401, 404, 422

{
    "data": {
        "id": 1,
        "name": "Engine Oil Service",
        "service_no": "SRV-001",
        "category": "Engine",
        "price": 80,
        "price_sst": 80,
        "sst": 0,
        "hours": 0.75,
        "skill_level": "junior",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

DELETE/services/{id}

Delete a service

Soft-deletes the record. Requires the admin scope.

Parameter In Type Description
id path integer Record id.

Responses: 204, 401, 403, 404

Workshop

GET/mechanics

List mechanics

Read-only roster used to resolve mechanic_id on work orders.

Parameter In Type Description
page query integer Page to fetch, 1-based.
limit query integer Records per page.
search query string Partial match across name, nickname, email and reg_no.
sort query string Sort field. Prefix with - for descending.
status query integer Record status. 1 = active.

Responses: 200, 401, 403, 422, 429

{
    "data": [
        {
            "id": 1,
            "name": "Mohd Faizal bin Osman",
            "nickname": "Faizal",
            "email": "[email protected]",
            "phone_no": "0198887766",
            "reg_no": "MEC-003",
            "skill": "Brakes, suspension",
            "qualification": "SKM Level 3",
            "exp_year": "8",
            "status": 1,
            "created_at": "2026-09-01T08:15:00Z",
            "updated_at": "2026-09-06T11:42:00Z"
        }
    ],
    "meta": {
        "total": 8,
        "page": 1,
        "limit": 50,
        "total_pages": 1
    }
}

GET/mechanics/{id}

Retrieve a mechanic

Fetch a single mechanic by id. Ids from another workshop return 404.

Parameter In Type Description
id path integer Record id.

Responses: 200, 401, 404

{
    "data": {
        "id": 1,
        "name": "Mohd Faizal bin Osman",
        "nickname": "Faizal",
        "email": "[email protected]",
        "phone_no": "0198887766",
        "reg_no": "MEC-003",
        "skill": "Brakes, suspension",
        "qualification": "SKM Level 3",
        "exp_year": "8",
        "status": 1,
        "created_at": "2026-09-01T08:15:00Z",
        "updated_at": "2026-09-06T11:42:00Z"
    }
}

Schemas

PageMeta

Pagination block returned by every list endpoint.

Field Type Description
total integer Total records matching the query.
page integer Current page (1-based).
limit integer Records per page.
total_pages integer Number of pages available.

Error

Every failure uses this envelope.

Field Type Description
error object

KeyContext

Field Type Description
workshop object
key object
plan string
rate_limit object

Customer

People and companies your workshop serves.

Field Type Description
id integer Unique customer id.
name string Full name or company name.
phone string, null Contact number, without the country code.
country_code string, null Dialling code for phone, e.g. "60".
email string, null (email) Contact email.
gender string, null Free-text gender as captured at the counter.
dob string, null (date) Date of birth.
note string, null Internal note shown to counter staff.
status integer, null Record status. 1 = active.
vehicle_count integer Vehicles currently registered to this customer.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

CustomerInput

Field Type Description
name string Full name or company name.
phone string Contact number, without the country code.
country_code string Dialling code for phone.
email string Contact email.
gender string Gender.
dob string Date of birth (YYYY-MM-DD).
note string Internal note.

Vehicle

Vehicles registered against your customers.

Field Type Description
id integer Unique vehicle id.
customer_id integer Owning customer.
customer_name string, null Owning customer name (read-only convenience).
plate_no string Registration plate.
chassis_no string, null VIN / chassis number.
ref_car_id integer, null Catalogue model id — see GET /vehicle-models.
make string, null Manufacturer, from the ENGARAGE vehicle catalogue.
model string, null Model, from the ENGARAGE vehicle catalogue.
segment string, null Vehicle segment, e.g. "SUV".
fuel_type string, null Petrol, diesel, hybrid or EV.
year string, null Model year.
next_service string, null (date) Next service due date.
status integer, null Record status. 1 = active.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

VehicleInput

Field Type Description
customer_id integer Owning customer id.
plate_no string Registration plate.
ref_car_id integer Catalogue model id — look it up with GET /vehicle-models.
chassis_no string VIN / chassis number.
year string Model year.
next_service string Next service due date.

VehicleModel

ENGARAGE's shared make/model catalogue.

Field Type Description
id integer Catalogue id — this is the ref_car_id used when creating a vehicle.
make string, null Manufacturer.
model string, null Model and variant.
segment string, null Vehicle segment, e.g. "SUV".
fuel_type string, null Petrol, diesel, hybrid or EV.
oil_capacity string, null Engine oil capacity, as published by the manufacturer.
car_battery string, null Recommended battery specification.
status integer, null Record status. 1 = active.

Booking

Appointments customers have made with your workshop.

Field Type Description
id integer Unique booking id.
customer_id integer, null Customer who booked.
customer_name string, null Customer name (read-only convenience).
vehicle_id integer, null Vehicle being brought in.
plate_no string, null Vehicle plate (read-only convenience).
scheduled_at string, null (date-time) Requested slot.
remark string, null What the customer wants done.
status integer, null Booking status. 1 = pending, 2 = accepted, 3 = rejected.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

BookingInput

Field Type Description
customer_id integer Customer id.
vehicle_id integer Vehicle id.
scheduled_at string Requested slot (ISO-8601).
remark string What the customer wants done.
status integer Booking status.

WorkOrder

Jobs on the shop floor, from booking through to paid.

Field Type Description
id integer Unique work-order id.
wo_no string, null Human-readable work-order number.
quotation_no string, null Quotation number, once quoted.
invoice_no string, null Invoice number, once invoiced.
status integer Status code 1–10.
status_label string Human label for status.
customer_id integer, null Customer the job is for.
customer_name string, null Customer name.
vehicle_id integer, null Vehicle in the bay.
plate_no string, null Vehicle plate.
mechanic_id integer, null Assigned mechanic.
bay_id integer, null Assigned bay.
complaint string, null The customer's reported problem.
remark string, null Internal note.
mileage string, null Odometer reading on arrival.
total_payment number, null Invoice total, including tax.
payment_sst number, null Tax portion of the total.
method string, null Payment method, once paid.
booking_at string, null (date-time) When the job was booked.
completed_at string, null (date-time) When work finished.
invoiced_at string, null (date-time) When the invoice was raised.
paid_at string, null (date-time) When payment was received.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

WorkOrderInput

Field Type Description
status integer Move the job along the status ladder.
mechanic_id integer Assign a mechanic.
bay_id integer Assign a bay.
remark string Internal note.
complaint string The customer's reported problem.
mileage string Odometer reading.

WorkOrderItems

Line items on a work order

Field Type Description
id integer Line-item id.
type string, null What is being billed: part, package, service or misc.
part_id integer, null Inventory part, when type is part.
service_id integer, null Service, when type is service.
package_id integer, null Package, when type is package.
quantity number, null Quantity billed.
total_price number, null Line total before discount.
total_discount number, null Discount applied to the line.
tax_rate number, null Tax rate applied, as a percentage.

Quotation

Priced estimates issued against a work order.

Field Type Description
id integer Unique quotation id.
quotation_no string, null Human-readable quotation number.
work_order_id integer, null Work order this quotes.
wo_no string, null Work-order number.
customer_id integer, null Customer quoted.
status integer, null Quotation status. 2 = accepted.
discount number, null Discount value applied.
deposit number, null Deposit collected.
deposit_method string, null How the deposit was paid.
deposit_at string, null (date-time) When the deposit was collected.
total number, null Quoted total.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

Part

Stocked parts and consumables.

Field Type Description
id integer Unique part id.
name string Part name.
part_no string, null Manufacturer or internal part number.
brand string, null Brand.
model string, null Vehicle model this fits.
variant string, null Variant / specification.
description string, null Longer description.
unit string, null Unit of measure, e.g. "pcs", "litre".
bin_number string, null Storage location.
cost_price number, null What you pay your supplier.
price number, null What you charge the customer.
quantity number, null On-hand stock.
stock_alert_threshold number, null Level at which the part counts as low stock.
supplier_id integer, null Default supplier.
is_low_stock boolean True when on-hand stock is at or below the threshold.
status integer, null Record status. 1 = active.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

PartInput

Field Type Description
name string Part name.
part_no string Part number. Must be unique within your workshop.
brand string Brand.
model string Vehicle model this fits.
variant string Variant / specification.
description string Longer description.
unit string Unit of measure.
bin_number string Storage location.
cost_price number Supplier cost.
price number Selling price.
quantity number On-hand stock.
stock_alert_threshold number Low-stock threshold.
supplier_id integer Default supplier.

Service

Labour and service items you sell.

Field Type Description
id integer Unique service id.
name string Service name.
service_no string, null Internal service code.
category string, null Grouping, e.g. "Engine".
price number, null Price before tax.
price_sst number, null Price including tax.
sst number, null Tax rate applied, as a percentage.
hours number, null Standard labour hours.
skill_level string, null Skill required to perform it.
status integer, null Record status. 1 = active.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

ServiceInput

Field Type Description
name string Service name.
service_no string Internal service code.
category string Grouping.
price number Price before tax.
price_sst number Price including tax.
sst number Tax rate percentage.
hours number Standard labour hours.
skill_level string Skill required.

Supplier

Vendors you buy parts from.

Field Type Description
id integer Unique supplier id.
name string Supplier name.
company_reg string, null Company registration number.
sales_p string, null Sales contact person.
phone_no string, null Contact number.
email string, null (email) Contact email.
address_1 string, null Address line 1.
address_2 string, null Address line 2.
postcode string, null Postcode.
city string, null City.
state string, null State.
status integer, null Record status. 1 = active.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

SupplierInput

Field Type Description
name string Supplier name.
company_reg string Company registration number.
sales_p string Sales contact person.
phone_no string Contact number.
email string Contact email.
address_1 string Address line 1.
address_2 string Address line 2.
postcode string Postcode.
city string City.
state string State.

PurchaseOrder

Orders raised against your suppliers.

Field Type Description
id integer Unique purchase-order id.
po_no string, null Purchase-order number.
supplier_id integer, null Supplier ordered from.
supplier_name string, null Supplier name.
supplier_invoice_no string, null The supplier's own invoice number.
total number, null Order total.
tax_rate number, null Tax rate applied, as a percentage.
discount number, null Discount applied.
shipping_cost number, null Shipping charged.
status integer, null Purchase-order status.
notes string, null Internal note.
ordered_at string, null (date-time) When the order was raised.
approved_at string, null (date-time) When it was approved.
paid_at string, null (date-time) When it was paid.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

Mechanic

Technicians on your team.

Field Type Description
id integer Unique mechanic id.
name string, null Full name.
nickname string, null Name shown on the shop-floor board.
email string, null (email) Email.
phone_no string, null Contact number.
reg_no string, null Staff / registration number.
skill string, null Skills, as captured on the profile.
qualification string, null Qualifications.
exp_year string, null Years of experience.
status integer, null Record status. 1 = active.
created_at string (date-time) When the record was created (ISO-8601, UTC).
updated_at string (date-time) When the record last changed (ISO-8601, UTC).

Generated from the ENGARAGE OpenAPI specification on Wed, Oct 21, 2026 11:28 AM.