Créer et utiliser une grille tarifaire

Ce guide montre comment créer une grille tarifaire, y placer des produits et services à des prix convenus, relire ces prix, les modifier ou les supprimer, et demander à l'API quelle grille (le cas échéant) s'applique à un produit pour une quantité et une date données. Il vous faut seulement un jeton d'API et votre URL de base, comme indiqué dans le guide de démarrage ; pour les noms de champs, les types et les formats de valeurs, consultez Champs, formats et mise en forme des réponses, et pour l'enveloppe d'écriture (POST/PATCH/PUT, erreurs de validation), consultez Créer, modifier et supprimer des fiches — ce guide ne couvre que ce qui est spécifique aux grilles tarifaires.

1. Qu'est-ce qu'une grille tarifaire

Une grille tarifaire est un catalogue nommé qui attribue à un produit ou un service un prix différent pour un client ou un segment, borné par une plage de quantités (min_order/max_order) et une fenêtre de dates (start_date/end_date). Le unit_price propre à un produit est son prix catalogue partout ; une grille tarifaire remplace ce prix partout où la grille s'applique.

2. En créer une

POST /api/v1/pricebooks avec l'enveloppe d'écriture :

curl -sS -X POST "https://app.initiative-crm.com/api/v1/pricebooks" \
  -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" -H "Content-Type: application/json" \
  -d '{"data":{"attributes":{"bookname":"Wholesale 2026","currency_id":"1","active":true,"min_order":1,"max_order":500,"start_date":"2026-01-01","end_date":"2026-12-31"}}}'
{
  "data": {
    "id": "63",
    "type": "pricebooks",
    "attributes": {
      "bookname": "Wholesale 2026",
      "active": true,
      "pricebook_auto_edit": false,
      "pricebook_edit_percent": null,
      "min_order": 1,
      "start_date": "2026-01-01",
      "max_order": 500,
      "end_date": "2026-12-31",
      "account_id": null,
      "accounttype": "",
      "vendor_id": null,
      "description": "",
      "createdtime": "2026-08-03T10:57:53Z",
      "modifiedtime": "2026-08-03T10:57:53Z",
      "pricebook_no": "GT-000001",
      "creator": { "id": "7206", "label": "Taylor Morgan" },
      "modifiedby": { "id": "7206", "label": "Taylor Morgan" }
    }
  }
}

bookname est le seul champ obligatoire (GET /api/v1/modules/pricebooks le montre comme la seule entrée mandatory: true). Le reste définit ce que représente la grille :

  • active — censé conditionner si la grille peut s'appliquer ou non. C'est appliqué lorsqu'une grille est effectivement consommée sur une ligne de devis ou de facture (voir le guide suivant) — mais pas par la requête de découverte de l'étape 6, qui résout quand même un prix pour une grille inactive.
  • min_order / max_order — la plage de quantités pour laquelle le prix de la grille est valable.
  • start_date / end_date — la fenêtre de dates pendant laquelle la grille est valable.
  • currency_id — définit la devise de la grille à la création. Il est accepté en écriture mais n'apparaît nulle part en lecture — ni dans cette réponse de création, ni dans un GET /api/v1/pricebooks/{id} ultérieur, ni même dans la liste des champs du module. Son seul effet observable est de devenir le usedcurrency affiché sur chaque lien produit/service (étape 4) — la relation lit la devise de la grille directement depuis la table sous-jacente plutôt que via le champ que vous avez défini.

3. Ajouter des articles à des prix convenus

Liez un produit ou un service à la grille avec POST .../related/{module}, en portant le prix convenu dans listprice. Cela fonctionne des deux côtés — l'endpoint de relations propre à la grille tarifaire, ou l'endpoint inverse du produit/service :

curl -sS -X POST "https://app.initiative-crm.com/api/v1/pricebooks/63/related/services" \
  -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" -H "Content-Type: application/json" \
  -d '{"data":{"id":"69","attributes":{"listprice":600.00}}}' \
  -w '\n%{http_code}\n' | jq '{id: .data.id, unit_price: .data.attributes.unit_price, relation_attributes: .data.relation_attributes}'
{
  "id": "69",
  "unit_price": 750,
  "relation_attributes": {
    "listprice": 600,
    "usedcurrency": { "id": 1, "label": "Euro", "symbol": "€", "code": "EUR", "conversion_rate": 1 }
  }
}
201

201 car il s'agit d'un nouveau lien. La réponse est la fiche liée complète, à laquelle s'ajoute un bloc relation_attributes — c'est ce bloc qui porte les données propres à la grille tarifaire, pas les champs propres de la fiche. unit_price (750) est le prix catalogue du service partout ; listprice (600) est ce qu'il coûte spécifiquement dans cette grille.

Envoyer un POST sur une paire déjà liée est un upsert, pas un doublon ni une erreur : cela modifie le prix et renvoie 200, pas 201. Cela fonctionne aussi depuis l'endpoint inverse — sur le produit lui-même, plutôt que sur la grille :

curl -sS -X POST "https://app.initiative-crm.com/api/v1/products/68/related/pricebooks" \
  -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" -H "Content-Type: application/json" \
  -d '{"data":{"id":"63","attributes":{"listprice":74.00}}}' -w '\n%{http_code}\n'
200

Le produit 68 était déjà lié à la grille 63 (c'est la fixture sur laquelle repose ce guide) — /products/{id}/related/pricebooks et /pricebooks/{id}/related/products sont deux portes ouvrant sur le même lien, et l'une comme l'autre peuvent le créer la première fois ou mettre à jour le prix lors d'un appel ultérieur.

4. Relire les prix

GET .../related/{module} sur la grille renvoie chaque fiche liée avec son relation_attributes :

curl -sS -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" \
  "https://app.initiative-crm.com/api/v1/pricebooks/63/related/products" | jq '.data[] | {id, relation_attributes}'
{
  "id": "68",
  "relation_attributes": {
    "listprice": 79,
    "usedcurrency": { "id": 1, "label": "Euro", "symbol": "€", "code": "EUR", "conversion_rate": 1 }
  }
}

listprice est le prix convenu pour ce produit dans cette grille. usedcurrency est en lecture seule — c'est la devise de la grille (définie une seule fois via currency_id à la création, étape 2), enrichie sous la forme {id, label, symbol, code, conversion_rate}, de la même façon qu'une référence de devise se lit ailleurs dans l'API. Ce n'est jamais quelque chose que vous envoyez ; voir les erreurs courantes ci-dessous pour ce qui se passe si vous essayez.

5. Modifier ou supprimer un prix

PATCH sur la relation pour modifier listprice sur place — rien d'autre concernant le lien n'a besoin d'être renvoyé :

curl -sS -X PATCH "https://app.initiative-crm.com/api/v1/pricebooks/63/related/products/68" \
  -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" -H "Content-Type: application/json" \
  -d '{"data":{"attributes":{"listprice":74.00}}}' -w '\n%{http_code}\n'
200

DELETE supprime entièrement le lien — le produit revient à son propre unit_price partout où cette grille se serait sinon appliquée :

curl -sS -X DELETE "https://app.initiative-crm.com/api/v1/pricebooks/63/related/services/69" \
  -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" -o /dev/null -w '%{http_code}\n'
curl -sS -X DELETE "https://app.initiative-crm.com/api/v1/pricebooks/63/related/services/69" \
  -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" -o /dev/null -w '%{http_code}\n'
204
204

Les deux appels renvoient 204, y compris le second, alors que le lien avait déjà disparu — la suppression de lien est idempotente. Comparez cela avec l'upsert côté création de l'étape 3 (201 puis 200) : lier vous indique si c'était nouveau ; délier ne distingue pas « supprimé » de « était déjà supprimé ».

6. Trouver quelle grille s'applique

GET /api/v1/pricebooks accepte trois filtres ensemble pour résoudre la tarification d'un cas précis : filter[product_id], filter[qty] et filter[when]. Ce n'est que lorsque filter[product_id] est présent que chaque grille de la réponse porte un resolved_listprice :

curl -sS -G -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" "https://app.initiative-crm.com/api/v1/pricebooks" \
  --data-urlencode "filter[product_id]=68" --data-urlencode "filter[when]=2026-06-15" \
  | jq '.data[] | {id, bookname: .attributes.bookname, resolved: .attributes.resolved_listprice}'
{ "id": "63", "bookname": "Wholesale 2026", "resolved": 79 }
curl -sS -G -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" "https://app.initiative-crm.com/api/v1/pricebooks" \
  --data-urlencode "filter[product_id]=68" --data-urlencode "filter[when]=2025-06-15" \
  | jq '.data[] | {id, bookname: .attributes.bookname, resolved: .attributes.resolved_listprice}'
{ "id": "64", "bookname": "Expired 2025", "resolved": 79 }

filter[when] est ce qui restreint réellement le résultat à une seule grille : une date de 2026 ne correspond qu'à Wholesale 2026 (valable du 2026-01-01 au 2026-12-31) ; une date de 2025 ne correspond qu'à Expired 2025 (valable du 2025-01-01 au 2025-12-31). Omettre filter[when] ne restreint pas implicitement à aujourd'hui — cela renvoie toutes les grilles liées au produit qui satisfont également la plage de quantités, quelle que soit la date, si bien que les deux grilles peuvent revenir ensemble. Passez filter[when] explicitement chaque fois que vous avez besoin « de la grille qui s'applique en ce moment ».

Une quantité en dehors du min_order/max_order de chaque grille correspondante renvoie une liste vide plutôt qu'un prix de repli :

curl -sS -G -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" "https://app.initiative-crm.com/api/v1/pricebooks" \
  --data-urlencode "filter[product_id]=68" --data-urlencode "filter[qty]=9999" \
  | jq '.data'
[]

Les deux grilles de la fixture autorisent 1500 ; 9999 est en dehors de cette plage pour les deux, donc rien ne se résout. Sans filter[product_id] du tout, resolved_listprice n'apparaît sur aucune grille — c'est une valeur calculée par produit, elle ne fait pas partie des attributs propres d'une grille tarifaire.

resolved_listprice ne vérifie pas active. La quantité et la date restreignent toutes deux le résultat (ci-dessus) ; active non, même si ce champ est censé conditionner si une grille peut s'appliquer ou non. Régler Expired 2025 sur active: false et relancer la requête when=2025-06-15 de l'étape 6 résout quand même le prix :

{ "id": "64", "bookname": "Expired 2025", "active": false, "resolved": 79 }

Cette requête de découverte n'implémente elle-même que les règles de produit/quantité/date — le contrôle d'applicabilité complet (qui inclut bien active) s'exécute séparément, au moment où une grille est effectivement consommée sur une ligne de devis ou de facture (le guide suivant). Considérez un résultat de cet endpoint comme « quelle grille pourrait tarifer ceci », pas comme la garantie que son application réussira ; une grille inactive peut apparaître ici et être quand même rejetée au moment de l'utiliser.

7. Découvrir ce qui est modifiable

GET /api/v1/pricebooks/related-modules liste chaque relation prise en charge par ce module et si elle est modifiable, ainsi que le schéma d'attributs propre à la relation :

curl -sS -H "Authorization: Bearer itv_YOUR_TOKEN_HERE" "https://app.initiative-crm.com/api/v1/pricebooks/related-modules" | jq '.'
{
  "data": [
    { "id": "products", "type": "module_relation", "attributes": { "writable": true, "relation_attributes": [{ "field": "listprice", "type": "decimal", "mandatory": true }] } },
    { "id": "services", "type": "module_relation", "attributes": { "writable": true, "relation_attributes": [{ "field": "listprice", "type": "decimal", "mandatory": true }] } },
    { "id": "comments", "type": "module_relation", "attributes": { "writable": false, "relation_attributes": [] } }
  ]
}

listprice est le seul attribut de relation modifiable pour l'un ou l'autre module — usedcurrency n'est délibérément pas dans cette liste, ce qui explique pourquoi l'écrire est rejeté (voir les erreurs courantes). comments apparaît comme une relation réelle mais avec writable: false : vous pouvez lire les commentaires d'une grille tarifaire, pas en créer un via cet endpoint de relation. Pour la forme générale de la découverte et de l'écriture des relations à travers les modules, consultez Lier des fiches entre elles.

Erreurs courantes

  • Essayer d'écrire usedcurrency. Il est dérivé du currency_id de la grille, en lecture seule, et absent du schéma des attributs de relation modifiables (étape 7). L'envoyer est rejeté purement et simplement :

    {"error":{"code":"VALIDATION_ERROR","message":"Validation failed.","request_id":"...","details":[{"field":"usedcurrency","code":"NOT_SUPPORTED","message":"Unknown relation attribute 'usedcurrency'."}]}}

    Définissez la devise de la grille une seule fois, à la création, via currency_id (étape 2) — il n'existe aucun moyen de la modifier ensuite via une écriture de relation.

  • Omettre listprice sur le lien. C'est le seul attribut de relation obligatoire pour products comme pour services :

    {"error":{"code":"VALIDATION_ERROR","message":"Validation failed.","request_id":"...","details":[{"field":"listprice","code":"REQUIRED","message":"listprice is required."}]}}
  • S'attendre à resolved_listprice sans filter[product_id]. C'est une valeur calculée par produit, pas un champ propre d'une grille tarifaire — elle n'apparaît que lorsque vous interrogez un produit précis (étape 6).

  • Une quantité en dehors de min_order/max_order. La requête de résolution renvoie une liste vide, pas une erreur et pas un prix écrêté (étape 6).


Did this page help you?