Klantly Developers

Référence de l'API

Devis

Les devis avec leurs lignes : les rédiger, les modifier tant qu'ils sont des brouillons, les envoyer par e-mail au client et enregistrer l'issue.

Endpoints

Lister les devis

GET /api/v1/quotes

Une liste de devis, les plus récents d'abord, avec leurs lignes. Filtrez par statut, client ou date de modification. Avec filter[status]=sent et filter[updated_since], vous récupérez ce qui a été envoyé depuis votre dernière synchronisation.

Scope
quotes.read — Lire les devis, avec leurs lignes, leurs montants et les coordonnées du client
Fonctionnalité requise
quotes

Paramètres de requête

NomTypeDescription
limit integer Nombre de résultats par page. de 1 à 100 · par défaut : 50
cursor string Le next_cursor ou prev_cursor de meta dans la réponse précédente.
sort string Tri par created_at ou updated_at ; un signe moins devant trie par ordre décroissant. l'une des valeurs : -created_at, created_at, -updated_at, updated_at · par défaut : -created_at
filter[status] string Uniquement les devis avec ce statut, par exemple sent ou accepted. l'une des valeurs : draft, pending_review, sent, viewed, accepted, rejected, expired, completed
filter[customer_id] string (uuid) Uniquement ce qui appartient à ce client.
filter[updated_since] string (date-time) Uniquement ce qui a changé depuis ce moment : ISO 8601 avec fuseau horaire, par exemple 2026-09-14T10:15:00Z. Pratique pour synchroniser.

Exemple de requête

cURL
curl "https://app.klantly.com/api/v1/quotes?filter[status]=sent&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', 'quotes', [
    'query' => [
        'filter[status]' => 'sent',
        'sort' => '-updated_at',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/quotes?filter[status]=sent&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/quotes",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
    params={
        "filter[status]": "sent",
        "sort": "-updated_at"
    },
)
data = response.json()["data"]

Réponse 200

La réponse est une liste avec pagination par curseur : data contient les objets, meta la pagination.

Exemple de réponse
{
  "data": [
    {
      "object": "quote",
      "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
      "number": "OFF-00042",
      "status": "sent",
      "title": "Veranda 400 × 300",
      "description": 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",
        "company_name": "De Vries Bouw",
        "vat_number": null,
        "coc_number": null
      },
      "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
      "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
      "language": "nl",
      "currency": "EUR",
      "quote_date": "2026-09-16",
      "valid_until": "2026-09-30",
      "intro_text": null,
      "outro_text": null,
      "terms_conditions": null,
      "notes": null,
      "hide_line_amounts": false,
      "discount_percentage": "0.00",
      "discount_amount": "0.00",
      "discount_description": null,
      "subtotal": "2450.00",
      "tax_amount": "514.50",
      "total": "2964.50",
      "sent_at": null,
      "viewed_at": null,
      "accepted_at": null,
      "rejected_at": null,
      "invoice_id": null,
      "items": [
        {
          "object": "quote_item",
          "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
          "type": "product",
          "name": "Veranda",
          "description": null,
          "sku": null,
          "quantity": "1.00",
          "unit": "stuk",
          "unit_price": "2450.00",
          "unit_price_incl": null,
          "discount_percentage": "0.00",
          "discount_amount": "0.00",
          "discount_description": null,
          "line_total": "2450.00",
          "line_total_incl": null,
          "is_taxable": true,
          "tax_rate": "21.00"
        }
      ],
      "created_at": "2026-09-14T10:15:00Z",
      "updated_at": "2026-09-14T10:15:00Z"
    }
  ],
  "meta": {
    "limit": 50,
    "next_cursor": "eyJpZCI6IjlkM2Y2YzFlIn0",
    "prev_cursor": null
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Récupérer un devis

GET /api/v1/quotes/{quote}

Un devis par id, avec ses lignes et ses montants. La réponse contient un ETag que vous pouvez renvoyer dans If-Match lors d'une modification.

Scope
quotes.read — Lire les devis, avec leurs lignes, leurs montants et les coordonnées du client
Fonctionnalité requise
quotes

Paramètres de chemin

NomTypeDescription
quote obligatoire string (uuid) L'id (UUID) du devis.

Exemple de requête

cURL
curl "https://app.klantly.com/api/v1/quotes/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', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/quotes/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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
)
data = response.json()["data"]

Réponse 200

Exemple de réponse
{
  "data": {
    "object": "quote",
    "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
    "number": "OFF-00042",
    "status": "sent",
    "title": "Veranda 400 × 300",
    "description": 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",
      "company_name": "De Vries Bouw",
      "vat_number": null,
      "coc_number": null
    },
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
    "language": "nl",
    "currency": "EUR",
    "quote_date": "2026-09-16",
    "valid_until": "2026-09-30",
    "intro_text": null,
    "outro_text": null,
    "terms_conditions": null,
    "notes": null,
    "hide_line_amounts": false,
    "discount_percentage": "0.00",
    "discount_amount": "0.00",
    "discount_description": null,
    "subtotal": "2450.00",
    "tax_amount": "514.50",
    "total": "2964.50",
    "sent_at": null,
    "viewed_at": null,
    "accepted_at": null,
    "rejected_at": null,
    "invoice_id": null,
    "items": [
      {
        "object": "quote_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
        "type": "product",
        "name": "Veranda",
        "description": null,
        "sku": null,
        "quantity": "1.00",
        "unit": "stuk",
        "unit_price": "2450.00",
        "unit_price_incl": null,
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "discount_description": null,
        "line_total": "2450.00",
        "line_total_incl": null,
        "is_taxable": true,
        "tax_rate": "21.00"
      }
    ],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Créer un devis

POST /api/v1/quotes

Crée un devis pour un client, toujours en brouillon. Klantly attribue le numéro et calcule les totaux ; le nom, l'adresse et les coordonnées proviennent du client. Envoyez-le ensuite avec Envoyer un devis.

Scope
quotes.write — Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client
Fonctionnalité requise
quotes

Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.

Corps (JSON)

ChampTypeDescription
customer_id obligatoire string (uuid) Le client. Obligatoire à la création ; le nom, l'adresse et les coordonnées proviennent du client.
title obligatoire string Titre du devis. Obligatoire à la création. au maximum 255 caractères
items obligatoire array Les lignes (200 au maximum). À l'envoi : une liste avec, par ligne, name (obligatoire) et éventuellement type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate et is_taxable. Elles remplacent toutes les lignes existantes.
description facultatif string Brève description ; affichée au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères
language facultatif string Langue du devis : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr
quote_date facultatif string (date) Date du devis (AAAA-MM-JJ).
valid_until facultatif string (date) Valable jusqu'au (AAAA-MM-JJ) inclus. Sans indication, le délai par défaut de l'entreprise s'applique.
intro_text facultatif string Introduction au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères
outro_text facultatif string Texte de clôture sous les lignes. peut être vide (null) · au maximum 20000 caractères
terms_conditions facultatif string Conditions figurant sur le devis. peut être vide (null) · au maximum 20000 caractères
notes facultatif string Remarques pour le client. peut être vide (null) · au maximum 20000 caractères
hide_line_amounts facultatif boolean Masque les montants par ligne sur le devis ; le client ne voit que le total.
discount_percentage facultatif number Remise sur le total, en pourcentage. de 0 à 100
discount_amount facultatif number Remise sur le total, en montant. de 0 à 9999999
discount_description facultatif string Pourquoi la remise a été accordée ; figure sur le devis. peut être vide (null) · au maximum 500 caractères

Exemple de requête

cURL
curl -X POST "https://app.klantly.com/api/v1/quotes" \
  -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": "Veranda 400 × 300",
  "items": [
    {
      "name": "Veranda",
      "quantity": 1,
      "unit_price": "2450.00",
      "tax_rate": "21.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', 'quotes', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'customer_id' => '9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70',
        'title' => 'Veranda 400 × 300',
        'items' => [
            0 => [
                'name' => 'Veranda',
                'quantity' => 1,
                'unit_price' => '2450.00',
                'tax_rate' => '21.00',
            ],
        ],
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/quotes', {
  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": "Veranda 400 × 300",
  "items": [
    {
      "name": "Veranda",
      "quantity": 1,
      "unit_price": "2450.00",
      "tax_rate": "21.00"
    }
  ]
}),
});

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

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/quotes",
    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": "Veranda 400 × 300",
        "items": [
            {
                "name": "Veranda",
                "quantity": 1,
                "unit_price": "2450.00",
                "tax_rate": "21.00"
            }
        ]
    },
)
data = response.json()["data"]

Réponse 201

Exemple de réponse
{
  "data": {
    "object": "quote",
    "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
    "number": "OFF-00042",
    "status": "sent",
    "title": "Veranda 400 × 300",
    "description": 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",
      "company_name": "De Vries Bouw",
      "vat_number": null,
      "coc_number": null
    },
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
    "language": "nl",
    "currency": "EUR",
    "quote_date": "2026-09-16",
    "valid_until": "2026-09-30",
    "intro_text": null,
    "outro_text": null,
    "terms_conditions": null,
    "notes": null,
    "hide_line_amounts": false,
    "discount_percentage": "0.00",
    "discount_amount": "0.00",
    "discount_description": null,
    "subtotal": "2450.00",
    "tax_amount": "514.50",
    "total": "2964.50",
    "sent_at": null,
    "viewed_at": null,
    "accepted_at": null,
    "rejected_at": null,
    "invoice_id": null,
    "items": [
      {
        "object": "quote_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
        "type": "product",
        "name": "Veranda",
        "description": null,
        "sku": null,
        "quantity": "1.00",
        "unit": "stuk",
        "unit_price": "2450.00",
        "unit_price_incl": null,
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "discount_description": null,
        "line_total": "2450.00",
        "line_total_incl": null,
        "is_taxable": true,
        "tax_rate": "21.00"
      }
    ],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Modifier un devis

PATCH /api/v1/quotes/{quote}

Ne modifie que les champs que vous envoyez. items remplace toutes les lignes en bloc, et ces lignes reçoivent de nouveaux ids. Seul un brouillon peut encore changer : une fois le devis envoyé, le client l'a vu et vous obtenez 409.

Scope
quotes.write — Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client
Fonctionnalité requise
quotes

Envoyez l'ETag dans If-Match : vous n'écraserez jamais par erreur une version plus récente.

Paramètres de chemin

NomTypeDescription
quote obligatoire string (uuid) L'id (UUID) du devis.

Corps (JSON)

ChampTypeDescription
title facultatif string Titre du devis. Obligatoire à la création. au maximum 255 caractères
description facultatif string Brève description ; affichée au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères
language facultatif string Langue du devis : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr
quote_date facultatif string (date) Date du devis (AAAA-MM-JJ).
valid_until facultatif string (date) Valable jusqu'au (AAAA-MM-JJ) inclus. Sans indication, le délai par défaut de l'entreprise s'applique.
intro_text facultatif string Introduction au-dessus des lignes. peut être vide (null) · au maximum 20000 caractères
outro_text facultatif string Texte de clôture sous les lignes. peut être vide (null) · au maximum 20000 caractères
terms_conditions facultatif string Conditions figurant sur le devis. peut être vide (null) · au maximum 20000 caractères
notes facultatif string Remarques pour le client. peut être vide (null) · au maximum 20000 caractères
hide_line_amounts facultatif boolean Masque les montants par ligne sur le devis ; le client ne voit que le total.
discount_percentage facultatif number Remise sur le total, en pourcentage. de 0 à 100
discount_amount facultatif number Remise sur le total, en montant. de 0 à 9999999
discount_description facultatif string Pourquoi la remise a été accordée ; figure sur le devis. peut être vide (null) · au maximum 500 caractères
items facultatif array Les lignes (200 au maximum). À l'envoi : une liste avec, par ligne, name (obligatoire) et éventuellement type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate et is_taxable. Elles remplacent toutes les lignes existantes.

Exemple de requête

cURL
curl -X PATCH "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Veranda 400 × 300 (herzien)"
}'
PHP
$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.klantly.com/api/v1/',
    'headers' => ['Authorization' => 'Bearer ' . getenv('KLANTLY_API_KEY')],
]);

$response = $client->request('PATCH', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', [
    'json' => [
        'title' => 'Veranda 400 × 300 (herzien)',
    ],
]);

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.KLANTLY_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
  "title": "Veranda 400 × 300 (herzien)"
}),
});

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

import requests

response = requests.patch(
    "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
    json={
        "title": "Veranda 400 × 300 (herzien)"
    },
)
data = response.json()["data"]

Réponse 200

Exemple de réponse
{
  "data": {
    "object": "quote",
    "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
    "number": "OFF-00042",
    "status": "sent",
    "title": "Veranda 400 × 300",
    "description": 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",
      "company_name": "De Vries Bouw",
      "vat_number": null,
      "coc_number": null
    },
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
    "language": "nl",
    "currency": "EUR",
    "quote_date": "2026-09-16",
    "valid_until": "2026-09-30",
    "intro_text": null,
    "outro_text": null,
    "terms_conditions": null,
    "notes": null,
    "hide_line_amounts": false,
    "discount_percentage": "0.00",
    "discount_amount": "0.00",
    "discount_description": null,
    "subtotal": "2450.00",
    "tax_amount": "514.50",
    "total": "2964.50",
    "sent_at": null,
    "viewed_at": null,
    "accepted_at": null,
    "rejected_at": null,
    "invoice_id": null,
    "items": [
      {
        "object": "quote_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
        "type": "product",
        "name": "Veranda",
        "description": null,
        "sku": null,
        "quantity": "1.00",
        "unit": "stuk",
        "unit_price": "2450.00",
        "unit_price_incl": null,
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "discount_description": null,
        "line_total": "2450.00",
        "line_total_incl": null,
        "is_taxable": true,
        "tax_rate": "21.00"
      }
    ],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Envoyer un devis

POST /api/v1/quotes/{quote}/send

Envoie le devis par e-mail au client avec le modèle de notification de l'entreprise, passe le statut à envoyé et enregistre l'heure. Votre message personnel apparaît dans l'e-mail. Un nouvel envoi après un refus remet le devis sur envoyé.

Scope
quotes.send — Envoyer les devis par e-mail aux clients
Fonctionnalité requise
quotes

Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.

Paramètres de chemin

NomTypeDescription
quote obligatoire string (uuid) L'id (UUID) du devis.

Corps (JSON)

ChampTypeDescription
message facultatif string Message personnel dans l'e-mail au client. peut être vide (null) · au maximum 5000 caractères

Exemple de requête

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

$response = $client->request('POST', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send', [
    'headers' => [
        'Idempotency-Key' => '6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f',
    ],
    'json' => [
        'message' => 'Bijgaand de offerte die we vanmorgen bespraken.',
    ],
]);

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

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

import requests

response = requests.post(
    "https://app.klantly.com/api/v1/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70/send",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
        "Idempotency-Key": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    },
    json={
        "message": "Bijgaand de offerte die we vanmorgen bespraken."
    },
)
data = response.json()["data"]

Réponse 200

Exemple de réponse
{
  "data": {
    "object": "quote",
    "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
    "number": "OFF-00042",
    "status": "sent",
    "title": "Veranda 400 × 300",
    "description": 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",
      "company_name": "De Vries Bouw",
      "vat_number": null,
      "coc_number": null
    },
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
    "language": "nl",
    "currency": "EUR",
    "quote_date": "2026-09-16",
    "valid_until": "2026-09-30",
    "intro_text": null,
    "outro_text": null,
    "terms_conditions": null,
    "notes": null,
    "hide_line_amounts": false,
    "discount_percentage": "0.00",
    "discount_amount": "0.00",
    "discount_description": null,
    "subtotal": "2450.00",
    "tax_amount": "514.50",
    "total": "2964.50",
    "sent_at": null,
    "viewed_at": null,
    "accepted_at": null,
    "rejected_at": null,
    "invoice_id": null,
    "items": [
      {
        "object": "quote_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
        "type": "product",
        "name": "Veranda",
        "description": null,
        "sku": null,
        "quantity": "1.00",
        "unit": "stuk",
        "unit_price": "2450.00",
        "unit_price_incl": null,
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "discount_description": null,
        "line_total": "2450.00",
        "line_total_incl": null,
        "is_taxable": true,
        "tax_rate": "21.00"
      }
    ],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Accepter un devis

POST /api/v1/quotes/{quote}/accept

Enregistre l'accord du client, par exemple après une validation en dehors de Klantly. Un devis accepté ne peut plus être refusé.

Scope
quotes.write — Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client
Fonctionnalité requise
quotes

Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.

Paramètres de chemin

NomTypeDescription
quote obligatoire string (uuid) L'id (UUID) du devis.

Exemple de requête

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

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

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

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

import requests

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

Réponse 200

Exemple de réponse
{
  "data": {
    "object": "quote",
    "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
    "number": "OFF-00042",
    "status": "sent",
    "title": "Veranda 400 × 300",
    "description": 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",
      "company_name": "De Vries Bouw",
      "vat_number": null,
      "coc_number": null
    },
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
    "language": "nl",
    "currency": "EUR",
    "quote_date": "2026-09-16",
    "valid_until": "2026-09-30",
    "intro_text": null,
    "outro_text": null,
    "terms_conditions": null,
    "notes": null,
    "hide_line_amounts": false,
    "discount_percentage": "0.00",
    "discount_amount": "0.00",
    "discount_description": null,
    "subtotal": "2450.00",
    "tax_amount": "514.50",
    "total": "2964.50",
    "sent_at": null,
    "viewed_at": null,
    "accepted_at": null,
    "rejected_at": null,
    "invoice_id": null,
    "items": [
      {
        "object": "quote_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
        "type": "product",
        "name": "Veranda",
        "description": null,
        "sku": null,
        "quantity": "1.00",
        "unit": "stuk",
        "unit_price": "2450.00",
        "unit_price_incl": null,
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "discount_description": null,
        "line_total": "2450.00",
        "line_total_incl": null,
        "is_taxable": true,
        "tax_rate": "21.00"
      }
    ],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Refuser un devis

POST /api/v1/quotes/{quote}/reject

Enregistre le refus du client. Vous pouvez ensuite envoyer un devis révisé, ce qui le remet sur envoyé.

Scope
quotes.write — Créer et modifier des devis (brouillons uniquement), et les accepter ou les refuser au nom du client
Fonctionnalité requise
quotes

Envoyez une Idempotency-Key : une nouvelle tentative après un délai d'attente ne crée alors jamais de doublon.

Paramètres de chemin

NomTypeDescription
quote obligatoire string (uuid) L'id (UUID) du devis.

Exemple de requête

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

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

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

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

import requests

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

Réponse 200

Exemple de réponse
{
  "data": {
    "object": "quote",
    "id": "9d3f8449-afe1-4a6e-9dbc-c1dfe0f1a2ba",
    "number": "OFF-00042",
    "status": "sent",
    "title": "Veranda 400 × 300",
    "description": 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",
      "company_name": "De Vries Bouw",
      "vat_number": null,
      "coc_number": null
    },
    "deal_id": "9d3f7a20-1c5e-4d7b-8a2f-3e4c5d6f7a81",
    "pipeline_stage_id": "stg_0q8v4n61h9x0zc",
    "language": "nl",
    "currency": "EUR",
    "quote_date": "2026-09-16",
    "valid_until": "2026-09-30",
    "intro_text": null,
    "outro_text": null,
    "terms_conditions": null,
    "notes": null,
    "hide_line_amounts": false,
    "discount_percentage": "0.00",
    "discount_amount": "0.00",
    "discount_description": null,
    "subtotal": "2450.00",
    "tax_amount": "514.50",
    "total": "2964.50",
    "sent_at": null,
    "viewed_at": null,
    "accepted_at": null,
    "rejected_at": null,
    "invoice_id": null,
    "items": [
      {
        "object": "quote_item",
        "id": "9d3f856a-b0f2-4b7f-8ecd-d2e0f1a2b3cc",
        "type": "product",
        "name": "Veranda",
        "description": null,
        "sku": null,
        "quantity": "1.00",
        "unit": "stuk",
        "unit_price": "2450.00",
        "unit_price_incl": null,
        "discount_percentage": "0.00",
        "discount_amount": "0.00",
        "discount_description": null,
        "line_total": "2450.00",
        "line_total_incl": null,
        "is_taxable": true,
        "tax_rate": "21.00"
      }
    ],
    "created_at": "2026-09-14T10:15:00Z",
    "updated_at": "2026-09-14T10:15:00Z"
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

Supprimer un devis

DELETE /api/v1/quotes/{quote}

Supprime le devis. Seul un brouillon peut être supprimé : un devis envoyé reste.

Scope
quotes.delete — Supprimer des devis
Fonctionnalité requise
quotes

Paramètres de chemin

NomTypeDescription
quote obligatoire string (uuid) L'id (UUID) du devis.

Exemple de requête

cURL
curl -X DELETE "https://app.klantly.com/api/v1/quotes/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', 'quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70');

$data = json_decode((string) $response->getBody(), true)['data'];
JavaScript
const response = await fetch('https://app.klantly.com/api/v1/quotes/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/quotes/9d3f6c1e-4b2a-4c8e-9f1a-2b3c4d5e6f70",
    headers={
        "Authorization": f"Bearer {os.environ['KLANTLY_API_KEY']}",
    },
)
data = response.json()["data"]

Réponse 200

Exemple de réponse
{
  "data": {
    "object": "note",
    "id": "9d3f7b41-2d6f-4e8c-9b3a-4f5d6e7a8b92",
    "deleted": true
  }
}

Erreurs possibles

En outre, chaque endpoint peut renvoyer les erreurs générales, comme une clé invalide ou une limite atteinte. Voir tous les codes d'erreur.

L'objet

Tous les champs sont toujours présents ; un champ sans valeur vaut null.

ChampTypeDescription
object string Toujours "quote".
id string (uuid) Id unique (UUID).
number string Numéro de devis, par exemple OFF-00042 ; attribué par Klantly.
status string draft (brouillon), pending_review (à vérifier), sent (envoyé), viewed (consulté), accepted (accepté), rejected (refusé), expired (expiré) ou completed (terminé). l'une des valeurs : draft, pending_review, sent, viewed, accepted, rejected, expired, completed
title string Titre du devis. Obligatoire à la création. peut être vide (null)
description string Brève description ; affichée au-dessus des lignes. peut être vide (null)
customer_id string (uuid) Le client. Obligatoire à la création ; le nom, l'adresse et les coordonnées proviennent du client. peut être vide (null)
customer object Les coordonnées du client sur le devis, telles qu'elles étaient à la création.
customer.type string individual (particulier) ou business (entreprise). peut être vide (null)
customer.name string Nom ; pour une entreprise, la raison sociale. peut être vide (null)
customer.email string Adresse e-mail. peut être vide (null)
customer.phone string Numéro de téléphone. peut être vide (null)
customer.address string Rue et numéro. peut être vide (null)
customer.postal_code string Code postal. peut être vide (null)
customer.city string Ville. peut être vide (null)
customer.country string Pays. peut être vide (null)
customer.company_name string Raison sociale. peut être vide (null)
customer.vat_number string Numéro de TVA. peut être vide (null)
customer.coc_number string Numéro d'immatriculation. peut être vide (null)
deal_id string (uuid) L'affaire du pipeline à laquelle ce devis appartient, ou null. peut être vide (null)
pipeline_stage_id string L'étape dans laquelle se trouve le devis, ou null. peut être vide (null)
language string Langue du devis : nl, en, de ou fr. l'une des valeurs : nl, en, de, fr
currency string Toujours "EUR".
quote_date string (date) Date du devis (AAAA-MM-JJ). peut être vide (null)
valid_until string (date) Valable jusqu'au (AAAA-MM-JJ) inclus. Sans indication, le délai par défaut de l'entreprise s'applique. peut être vide (null)
intro_text string Introduction au-dessus des lignes. peut être vide (null)
outro_text string Texte de clôture sous les lignes. peut être vide (null)
terms_conditions string Conditions figurant sur le devis. peut être vide (null)
notes string Remarques pour le client. peut être vide (null)
hide_line_amounts boolean Masque les montants par ligne sur le devis ; le client ne voit que le total.
discount_percentage string Remise sur le total, en pourcentage.
discount_amount string Remise sur le total, en montant.
discount_description string Pourquoi la remise a été accordée ; figure sur le devis. peut être vide (null)
subtotal string Total hors TVA, sous forme de texte à deux décimales.
tax_amount string Montant de la TVA.
total string Total TTC.
sent_at string (date-time) Quand le devis a été envoyé au client. peut être vide (null)
viewed_at string (date-time) Quand le client l'a ouvert. peut être vide (null)
accepted_at string (date-time) Quand le client a donné son accord. peut être vide (null)
rejected_at string (date-time) Quand le client a refusé. peut être vide (null)
invoice_id string (uuid) La facture créée à partir de ce devis, ou null. peut être vide (null)
items array<object> Les lignes (200 au maximum). À l'envoi : une liste avec, par ligne, name (obligatoire) et éventuellement type, description, sku, quantity, unit, unit_price, unit_price_incl, discount_percentage, discount_amount, discount_description, tax_rate et is_taxable. Elles remplacent toutes les lignes existantes.
items.object string Toujours "quote_item".
items.id string (uuid) Id unique de la ligne (UUID).
items.type string product, service, discount ou fee. l'une des valeurs : product, service, discount, fee
items.name string Nom de la ligne.
items.description string Description sous la ligne. peut être vide (null)
items.sku string Référence article. peut être vide (null)
items.quantity string Quantité.
items.unit string Unité, par exemple pièce ou heure. peut être vide (null)
items.unit_price string Prix unitaire, hors TVA.
items.unit_price_incl string Prix unitaire TVA comprise, tel que saisi. S'il est renseigné, la ligne est calculée à partir de ce montant (unit_price et line_total en sont déduits) et le total correspond au centime près au prix saisi. null = la ligne est calculée à partir de unit_price. peut être vide (null)
items.discount_percentage string Remise sur cette ligne, en pourcentage.
items.discount_amount string Remise sur cette ligne, en montant.
items.discount_description string Pourquoi la remise sur cette ligne a été accordée. peut être vide (null)
items.line_total string Total de la ligne hors TVA, après remise.
items.line_total_incl string Total de la ligne TVA comprise, après remise. Renseigné uniquement si la ligne est calculée à partir de unit_price_incl ; sinon null. peut être vide (null)
items.is_taxable boolean Si cette ligne compte pour la TVA.
items.tax_rate string Taux de TVA de cette ligne, en pourcentage.
created_at string (date-time) Quand le devis a été créé.
updated_at string (date-time) Quand le devis a été modifié pour la dernière fois.