Klantly Developers

API reference

Work orders

Work orders with lines and a checklist: create them from an ERP, for example, update them, change their status and fetch completed ones.

Endpoints

List work orders

GET /api/v1/work-orders

A list of work orders, newest first, with lines, checklist and photos. Filter by status, customer, user, appointment or modification date. Use filter[status]=completed with filter[updated_since] to fetch completed work.

Scope
work_orders.read — Read work orders, with the customer's name, address and contact details
Required feature
work_orders

Query parameters

NameTypeDescription
limit integer Number of results per page. from 1 to 100 · default: 50
cursor string The next_cursor or prev_cursor from meta of the previous response.
sort string Sort by created_at or updated_at; a leading minus sign sorts descending. one of: -created_at, created_at, -updated_at, updated_at · default: -created_at
filter[status] string Only work orders with this status. one of: draft, planned, in_progress, completed, invoiced, cancelled
filter[customer_id] string (uuid) Only what belongs to this customer.
filter[assigned_user_id] string Only what is assigned to this user (the id from List users).
filter[appointment_id] string (uuid) Only work orders for this appointment.
filter[updated_since] string (date-time) Only what changed since this moment: ISO 8601 with a time zone, for example 2026-09-14T10:15:00Z. Useful for synchronising.

Example request

cURL
curl "https://app.klantly.com/api/v1/work-orders?filter[status]=completed&sort=-updated_at" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('GET', 'work-orders', [
    'query' => [
        'filter[status]' => 'completed',
        'sort' => '-updated_at',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/work-orders?filter[status]=completed&sort=-updated_at', {
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.get(
    "https://app.klantly.com/api/v1/work-orders",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
    params={
        "filter[status]": "completed",
        "sort": "-updated_at"
    },
)
data = response.json()["data"]

Response 200

The response is a list with cursor pagination: data contains the objects, meta the pagination.

Example response
{
  "data": [
    {
      "object": "work_order",
      "id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
      "number": "WB-2026-00042",
      "status": "planned",
      "title": "Onderhoud cv-ketel",
      "type": "onderhoud",
      "location": null,
      "description": null,
      "work_performed": null,
      "customer_notes": null,
      "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
      "customer": {
        "type": "business",
        "name": "De Vries Bouw",
        "email": "jan@example.com",
        "phone": "+31 6 12345678",
        "address": "Dorpsstraat 1",
        "postal_code": "3511 AB",
        "city": "Utrecht",
        "country": "NL"
      },
      "deal_id": null,
      "appointment_id": null,
      "quote_id": null,
      "invoice_id": null,
      "assigned_user_id": "usr_0k3j9x21m4zq8p",
      "language": "nl",
      "currency": "EUR",
      "subtotal": "90.00",
      "tax_amount": "18.90",
      "total": "108.90",
      "scheduled_at": "2026-10-01T08:00:00Z",
      "started_at": null,
      "completed_at": null,
      "sent_at": null,
      "signature": {
        "is_signed": false,
        "signed_by_name": null,
        "signed_at": null
      },
      "items": [
        {
          "object": "work_order_item",
          "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
          "type": "labor",
          "name": "Arbeid",
          "description": null,
          "sku": null,
          "quantity": "2.00",
          "unit": "uur",
          "unit_price": "45.00",
          "discount_percentage": "0.00",
          "discount_amount": "0.00",
          "line_total": "90.00",
          "is_taxable": true,
          "tax_rate": "21.00",
          "minutes": 120
        }
      ],
      "checklist": [
        {
          "object": "work_order_checklist_item",
          "id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
          "label": "Druk gecontroleerd",
          "checked": false,
          "required": true,
          "note": null
        }
      ],
      "photos": [],
      "created_at": "2026-09-14T10:15:00Z",
      "updated_at": "2026-09-14T10:15:00Z"
    }
  ],
  "meta": {
    "limit": 50,
    "next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
    "prev_cursor": null
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Retrieve a work order

GET /api/v1/work-orders/{work_order}

One work order by id, with lines, checklist, photos (data only) and the state of the signature.

Scope
work_orders.read — Read work orders, with the customer's name, address and contact details
Required feature
work_orders

Path parameters

NameTypeDescription
work_order required string (uuid) The id (UUID) of the work order.

Example request

cURL
curl "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('GET', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.get(
    "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "work_order",
    "id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
    "number": "WB-2026-00042",
    "status": "planned",
    "title": "Onderhoud cv-ketel",
    "type": "onderhoud",
    "location": null,
    "description": null,
    "work_performed": null,
    "customer_notes": null,
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "customer": {
      "type": "business",
      "name": "De Vries Bouw",
      "email": "jan@example.com",
      "phone": "+31 6 12345678",
      "address": "Dorpsstraat 1",
      "postal_code": "3511 AB",
      "city": "Utrecht",
      "country": "NL"
    },
    "deal_id": null,
    "appointment_id": null,
    "quote_id": null,
    "invoice_id": null,
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "language": "nl",
    "currency": "EUR",
    "subtotal": "90.00",
    "tax_amount": "18.90",
    "total": "108.90",
    "scheduled_at": "2026-10-01T08:00:00Z",
    "started_at": null,
    "completed_at": null,
    "sent_at": null,
    "signature": {
      "is_signed": false,
      "signed_by_name": null,
      "signed_at": null
    },
    "items": [
      {
        "object": "work_order_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
        "type": "labor",
        "name": "Arbeid",
        "description": null,
        "sku": null,
        "quantity": "2.00",
        "unit": "uur",
        "unit_price": "45.00",
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "line_total": "90.00",
        "is_taxable": true,
        "tax_rate": "21.00",
        "minutes": 120
      }
    ],
    "checklist": [
      {
        "object": "work_order_checklist_item",
        "id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
        "label": "Druk gecontroleerd",
        "checked": false,
        "required": true,
        "note": null
      }
    ],
    "photos": [],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Create a work order

POST /api/v1/work-orders

Creates a work order for a customer, as a draft or planned. Klantly gives it a number and calculates the totals; name, address and contact details come from the customer.

Scope
work_orders.write — Create and update work orders, and change their status (completing can send a review request)
Required feature
work_orders

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Body (JSON)

FieldTypeDescription
customer_id required string (uuid) The customer. Required when creating; name, address and contact details come from the customer.
status optional string draft, planned, in_progress, completed, invoiced or cancelled. When creating: draft or planned. one of: draft, planned
title optional string Title. can be empty (null) · at most 255 characters
type optional string Kind of work, for example installation, repair or maintenance. can be empty (null) · at most 64 characters
location optional string Where the work takes place, if not at the address of the customer. can be empty (null) · at most 255 characters
description optional string Description of the work or the complaint. can be empty (null) · at most 20000 characters
work_performed optional string What was done. can be empty (null) · at most 20000 characters
customer_notes optional string Notes for the customer; shown on the work order. can be empty (null) · at most 20000 characters
scheduled_at optional string (date-time) When the work is planned (UTC). As input: ISO 8601 with a time zone. can be empty (null)
assigned_user_id optional string The technician or user doing the work, or null. can be empty (null)
appointment_id optional string (uuid) The appointment the work order belongs to, or null. can be empty (null)
quote_id optional string (uuid) The quote the work order comes from, or null. can be empty (null)
language optional string Language of the work order: nl, en, de or fr. one of: nl, en, de, fr
items optional array The lines (up to 200). As input: a list with per line name (required) and optionally type, description, sku, quantity, unit, unit_price, discount_percentage, discount_amount, tax_rate, is_taxable and minutes; the list replaces all lines.
checklist optional array The checklist (up to 200 points). As input: per point label (required) and optionally checked, required and note; the list replaces the whole checklist.

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/work-orders" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -d '{
  "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
  "title": "Onderhoud cv-ketel",
  "status": "planned",
  "scheduled_at": "2026-10-01T08:00:00Z",
  "items": [
    {
      "type": "labor",
      "name": "Arbeid",
      "quantity": 2,
      "unit_price": "45.00"
    }
  ]
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'work-orders', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'customer_id' => '9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70',
        'title' => 'Onderhoud cv-ketel',
        'status' => 'planned',
        'scheduled_at' => '2026-10-01T08:00:00Z',
        'items' => [
            0 => [
                'type' => 'labor',
                'name' => 'Arbeid',
                'quantity' => 2,
                'unit_price' => '45.00',
            ],
        ],
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/work-orders', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
  body: JSON.stringify({
  "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
  "title": "Onderhoud cv-ketel",
  "status": "planned",
  "scheduled_at": "2026-10-01T08:00:00Z",
  "items": [
    {
      "type": "labor",
      "name": "Arbeid",
      "quantity": 2,
      "unit_price": "45.00"
    }
  ]
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/work-orders",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
    json={
        "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
        "title": "Onderhoud cv-ketel",
        "status": "planned",
        "scheduled_at": "2026-10-01T08:00:00Z",
        "items": [
            {
                "type": "labor",
                "name": "Arbeid",
                "quantity": 2,
                "unit_price": "45.00"
            }
        ]
    },
)
data = response.json()["data"]

Response 201

Example response
{
  "data": {
    "object": "work_order",
    "id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
    "number": "WB-2026-00042",
    "status": "planned",
    "title": "Onderhoud cv-ketel",
    "type": "onderhoud",
    "location": null,
    "description": null,
    "work_performed": null,
    "customer_notes": null,
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "customer": {
      "type": "business",
      "name": "De Vries Bouw",
      "email": "jan@example.com",
      "phone": "+31 6 12345678",
      "address": "Dorpsstraat 1",
      "postal_code": "3511 AB",
      "city": "Utrecht",
      "country": "NL"
    },
    "deal_id": null,
    "appointment_id": null,
    "quote_id": null,
    "invoice_id": null,
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "language": "nl",
    "currency": "EUR",
    "subtotal": "90.00",
    "tax_amount": "18.90",
    "total": "108.90",
    "scheduled_at": "2026-10-01T08:00:00Z",
    "started_at": null,
    "completed_at": null,
    "sent_at": null,
    "signature": {
      "is_signed": false,
      "signed_by_name": null,
      "signed_at": null
    },
    "items": [
      {
        "object": "work_order_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
        "type": "labor",
        "name": "Arbeid",
        "description": null,
        "sku": null,
        "quantity": "2.00",
        "unit": "uur",
        "unit_price": "45.00",
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "line_total": "90.00",
        "is_taxable": true,
        "tax_rate": "21.00",
        "minutes": 120
      }
    ],
    "checklist": [
      {
        "object": "work_order_checklist_item",
        "id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
        "label": "Druk gecontroleerd",
        "checked": false,
        "required": true,
        "note": null
      }
    ],
    "photos": [],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Update a work order

PATCH /api/v1/work-orders/{work_order}

Changes only the fields you send. items and checklist replace the lines and the checklist as a whole; the lines get new ids. A line and the total may not exceed 99,999,999.99. An invoiced work order can no longer change.

Scope
work_orders.write — Create and update work orders, and change their status (completing can send a review request)
Required feature
work_orders

Send the ETag in If-Match and you will never accidentally overwrite a newer version.

Path parameters

NameTypeDescription
work_order required string (uuid) The id (UUID) of the work order.

Body (JSON)

FieldTypeDescription
title optional string Title. can be empty (null) · at most 255 characters
type optional string Kind of work, for example installation, repair or maintenance. can be empty (null) · at most 64 characters
location optional string Where the work takes place, if not at the address of the customer. can be empty (null) · at most 255 characters
description optional string Description of the work or the complaint. can be empty (null) · at most 20000 characters
work_performed optional string What was done. can be empty (null) · at most 20000 characters
customer_notes optional string Notes for the customer; shown on the work order. can be empty (null) · at most 20000 characters
scheduled_at optional string (date-time) When the work is planned (UTC). As input: ISO 8601 with a time zone. can be empty (null)
assigned_user_id optional string The technician or user doing the work, or null. can be empty (null)
appointment_id optional string (uuid) The appointment the work order belongs to, or null. can be empty (null)
quote_id optional string (uuid) The quote the work order comes from, or null. can be empty (null)
language optional string Language of the work order: nl, en, de or fr. one of: nl, en, de, fr
items optional array The lines (up to 200). As input: a list with per line name (required) and optionally type, description, sku, quantity, unit, unit_price, discount_percentage, discount_amount, tax_rate, is_taxable and minutes; the list replaces all lines.
checklist optional array The checklist (up to 200 points). As input: per point label (required) and optionally checked, required and note; the list replaces the whole checklist.

Example request

cURL
curl -X PATCH "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "work_performed": "Filter vervangen en de ketel ontlucht."
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('PATCH', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', [
    'json' => [
        'work_performed' => 'Filter vervangen en de ketel ontlucht.',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
  "work_performed": "Filter vervangen en de ketel ontlucht."
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.patch(
    "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
    json={
        "work_performed": "Filter vervangen en de ketel ontlucht."
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "work_order",
    "id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
    "number": "WB-2026-00042",
    "status": "planned",
    "title": "Onderhoud cv-ketel",
    "type": "onderhoud",
    "location": null,
    "description": null,
    "work_performed": null,
    "customer_notes": null,
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "customer": {
      "type": "business",
      "name": "De Vries Bouw",
      "email": "jan@example.com",
      "phone": "+31 6 12345678",
      "address": "Dorpsstraat 1",
      "postal_code": "3511 AB",
      "city": "Utrecht",
      "country": "NL"
    },
    "deal_id": null,
    "appointment_id": null,
    "quote_id": null,
    "invoice_id": null,
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "language": "nl",
    "currency": "EUR",
    "subtotal": "90.00",
    "tax_amount": "18.90",
    "total": "108.90",
    "scheduled_at": "2026-10-01T08:00:00Z",
    "started_at": null,
    "completed_at": null,
    "sent_at": null,
    "signature": {
      "is_signed": false,
      "signed_by_name": null,
      "signed_at": null
    },
    "items": [
      {
        "object": "work_order_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
        "type": "labor",
        "name": "Arbeid",
        "description": null,
        "sku": null,
        "quantity": "2.00",
        "unit": "uur",
        "unit_price": "45.00",
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "line_total": "90.00",
        "is_taxable": true,
        "tax_rate": "21.00",
        "minutes": 120
      }
    ],
    "checklist": [
      {
        "object": "work_order_checklist_item",
        "id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
        "label": "Druk gecontroleerd",
        "checked": false,
        "required": true,
        "note": null
      }
    ],
    "photos": [],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Change the status of a work order

POST /api/v1/work-orders/{work_order}/status

Sets the work order to draft, planned, in progress, completed or cancelled. For in progress and completed, started_at and completed_at are set. Just like in the app, completing can send the customer a review request if the company has set that up. Only Klantly itself sets invoiced.

Scope
work_orders.write — Create and update work orders, and change their status (completing can send a review request)
Required feature
work_orders

Send an Idempotency-Key and a retry after a timeout will never create a duplicate record.

Path parameters

NameTypeDescription
work_order required string (uuid) The id (UUID) of the work order.

Body (JSON)

FieldTypeDescription
status required string draft, planned, in_progress, completed, invoiced or cancelled. When creating: draft or planned. one of: draft, planned, in_progress, completed, cancelled

Example request

cURL
curl -X POST "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -d '{
  "status": "completed"
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('POST', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'status' => 'completed',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
  },
  body: JSON.stringify({
  "status": "completed"
}),
});

const { data } = await response.json();
Python
import os

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/status",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
    json={
        "status": "completed"
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "work_order",
    "id": "9d3f8328-9ed0-4f5d-8cab-b0cedfe0f1a9",
    "number": "WB-2026-00042",
    "status": "planned",
    "title": "Onderhoud cv-ketel",
    "type": "onderhoud",
    "location": null,
    "description": null,
    "work_performed": null,
    "customer_notes": null,
    "customer_id": "9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    "customer": {
      "type": "business",
      "name": "De Vries Bouw",
      "email": "jan@example.com",
      "phone": "+31 6 12345678",
      "address": "Dorpsstraat 1",
      "postal_code": "3511 AB",
      "city": "Utrecht",
      "country": "NL"
    },
    "deal_id": null,
    "appointment_id": null,
    "quote_id": null,
    "invoice_id": null,
    "assigned_user_id": "usr_0k3j9x21m4zq8p",
    "language": "nl",
    "currency": "EUR",
    "subtotal": "90.00",
    "tax_amount": "18.90",
    "total": "108.90",
    "scheduled_at": "2026-10-01T08:00:00Z",
    "started_at": null,
    "completed_at": null,
    "sent_at": null,
    "signature": {
      "is_signed": false,
      "signed_by_name": null,
      "signed_at": null
    },
    "items": [
      {
        "object": "work_order_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cb",
        "type": "labor",
        "name": "Arbeid",
        "description": null,
        "sku": null,
        "quantity": "2.00",
        "unit": "uur",
        "unit_price": "45.00",
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "line_total": "90.00",
        "is_taxable": true,
        "tax_rate": "21.00",
        "minutes": 120
      }
    ],
    "checklist": [
      {
        "object": "work_order_checklist_item",
        "id": "9d3f868b-c1a3-4c8a-9fde-e3f1a2b3c4dc",
        "label": "Druk gecontroleerd",
        "checked": false,
        "required": true,
        "note": null
      }
    ],
    "photos": [],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

Delete a work order

DELETE /api/v1/work-orders/{work_order}

Deletes the work order. An invoiced work order cannot be deleted.

Scope
work_orders.delete — Delete work orders
Required feature
work_orders

Path parameters

NameTypeDescription
work_order required string (uuid) The id (UUID) of the work order.

Example request

cURL
curl -X DELETE "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY"
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('DELETE', 'work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
  },
});

const { data } = await response.json();
Python
import os

import requests

response = requests.delete(
    "https://app.klantly.com/api/v1/work-orders/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
)
data = response.json()["data"]

Response 200

Example response
{
  "data": {
    "object": "note",
    "id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
    "deleted": true
  }
}

Possible errors

In addition, every endpoint can return the general errors, such as an invalid key or a reached limit. See all error codes.

The object

All fields are always present; a field without a value is null.

FieldTypeDescription
object string Always "work_order".
id string (uuid) Unique id (UUID).
number string Work order number, for example WB-2026-00042; Klantly assigns it.
status string draft, planned, in_progress, completed, invoiced or cancelled. When creating: draft or planned. one of: draft, planned, in_progress, completed, invoiced, cancelled
title string Title. can be empty (null)
type string Kind of work, for example installation, repair or maintenance. can be empty (null)
location string Where the work takes place, if not at the address of the customer. can be empty (null)
description string Description of the work or the complaint. can be empty (null)
work_performed string What was done. can be empty (null)
customer_notes string Notes for the customer; shown on the work order. can be empty (null)
customer_id string (uuid) The customer. Required when creating; name, address and contact details come from the customer. can be empty (null)
customer object The customer details on the work order, as they were when it was created.
customer.type string individual or business. can be empty (null)
customer.name string Name; for a business, the company name. can be empty (null)
customer.email string Email address. can be empty (null)
customer.phone string Phone number. can be empty (null)
customer.address string Street and house number. can be empty (null)
customer.postal_code string Postal code. can be empty (null)
customer.city string City. can be empty (null)
customer.country string Country. can be empty (null)
deal_id string (uuid) The deal on the pipeline board, or null. can be empty (null)
appointment_id string (uuid) The appointment the work order belongs to, or null. can be empty (null)
quote_id string (uuid) The quote the work order comes from, or null. can be empty (null)
invoice_id string (uuid) The invoice of the work order, or null. can be empty (null)
assigned_user_id string The technician or user doing the work, or null. can be empty (null)
language string Language of the work order: nl, en, de or fr. one of: nl, en, de, fr
currency string Always "EUR".
subtotal string Total excluding VAT, as a string with two decimals.
tax_amount string VAT amount.
total string Total including VAT.
scheduled_at string (date-time) When the work is planned (UTC). As input: ISO 8601 with a time zone. can be empty (null)
started_at string (date-time) When the work started. can be empty (null)
completed_at string (date-time) When the work was finished. can be empty (null)
sent_at string (date-time) When the work order was sent to the customer. can be empty (null)
signature object The signature of the customer (data only, no image).
signature.is_signed boolean Whether the work order has been signed.
signature.signed_by_name string Name of the person who signed. can be empty (null)
signature.signed_at string (date-time) When it was signed. can be empty (null)
items array<object> The lines (up to 200). As input: a list with per line name (required) and optionally type, description, sku, quantity, unit, unit_price, discount_percentage, discount_amount, tax_rate, is_taxable and minutes; the list replaces all lines.
items.object string Always "work_order_item".
items.id string (uuid) Id of the line. Changes every time you send items (the lines are replaced).
items.type string labor, material or other. one of: labor, material, other
items.name string Description on the work order.
items.description string Details. can be empty (null)
items.sku string Item number. can be empty (null)
items.quantity string Quantity.
items.unit string Unit, for example hour or piece. can be empty (null)
items.unit_price string Price per unit excluding VAT.
items.discount_percentage string Discount as a percentage.
items.discount_amount string Fixed discount on the line excluding VAT; only counts when discount_percentage is 0.
items.line_total string Line total excluding VAT; calculated by Klantly.
items.is_taxable boolean Whether VAT applies to the line.
items.tax_rate string VAT percentage (default 21, also if the company uses a different rate: send it in that case).
items.minutes integer Minutes worked, for labour. can be empty (null)
checklist array<object> The checklist (up to 200 points). As input: per point label (required) and optionally checked, required and note; the list replaces the whole checklist.
checklist.object string Always "work_order_checklist_item".
checklist.id string (uuid) Id of the point.
checklist.label string The point, for example "Pressure checked".
checklist.checked boolean Ticked off.
checklist.required boolean Must be ticked off.
checklist.note string Note on the point. can be empty (null)
photos array<object> The photos of the work order (data only; the files are not in the API yet).
photos.object string Always "work_order_photo".
photos.id string (uuid) Id of the photo.
photos.kind string before, after or other. can be empty (null) · one of: before, after, other
photos.caption string Caption. can be empty (null)
photos.created_at string (date-time) Uploaded at (UTC).
created_at string (date-time) Created at (UTC).
updated_at string (date-time) Last updated at (UTC).