Skip to content
Leadtime
English
Esc
↑↓navigate↵open⌘Jpreview

Create product

What this does: Creates a new catalog product with variants and options. Catalog products are standardized, reusable offerings that can be added to projects, tickets, quotes, and invoices.

Product structure: A product consists of:

  1. Product details: Name, category, logo, description, and base pricing
  2. Variants (optional): Different configurations or performance levels (e.g., Standard, Pro, Enterprise) with individual prices
  3. Options (optional): Extra services or add-ons that customers can optionally select (e.g., “Add workshop” or “Enable premium support”)

Pricing: You can set one or more pricing types:

  • Fixed price: One-time payment (e.g., for hardware or one-time services)
  • Subscription price: Recurring payment (e.g., monthly SaaS subscription)
  • Price per unit: Flexible billing by quantity (e.g., per user, per hour)

Description format:

  • Descriptions can be provided as HTML or Markdown
  • The system automatically converts them to internal format (IDoc) for storage
  • Supported formatting: tables, callouts, emojis, and basic text formatting

Logo upload: To add a logo to your product:

  1. First, call POST /api/public/workspace/upload to upload the image file
  2. The upload endpoint returns a file ID
  3. Use that file ID in the logoId field when creating the product

Validation:

  • Product name is required
  • Category ID must be a valid UUID
  • At least one variant must be provided (even if just one)
  • Options array is required (can be empty)
  • If pricePerUnit is set, priceUnitName is required
  • If pricePerUnit or priceSubscription is set, priceFrequency must be 1, 3, 6, or 12 (months)

Response: Returns the created product with all details, including:

  • Descriptions converted to HTML
  • Logo URL (if logo was uploaded)
  • All variants and options with their configurations
POST/administration/product-catalog
Authorization
AuthorizationOAuth2 access token · headerrequired
Scopes:api:write
or
AuthorizationBearer token (JWT) · headerrequired
Query parameters
fieldsToReturnstring

Comma-separated list of top-level response fields to return. Overrides the endpoint compact default unless responseShape=full is used.

responseShapestring

Advanced override. Omit for the endpoint compact default. Use full only when you need the complete endpoint response, including nested fields that are not selectable with fieldsToReturn.

Allowed:compactfull
Header parameters
LT-Response-Shapestring

Advanced override. Set to full only when you need the complete endpoint response, including nested fields that are not selectable with fieldsToReturn.

Allowed:full
Request body
requiredapplication/json
categoryIdstringrequired

UUID of the product category this product belongs to. Categories help organize products into groups (e.g., "Hardware", "Software", "Services").

contractPeriodMonthsnumber

Contract period in months. Required when pricePerUnit or priceSubscription is greater than 0. Defaults to 12 months if not specified.

descriptionstringrequired

Product description explaining what the product includes, its benefits, and key features. Accepts Markdown or HTML for complex formatting. This content is rendered by the Leadtime rich editor, not by a plain Markdown viewer.

For quick/simple text, Markdown is acceptable. For polished user-facing content from an agent or integration, prefer structured HTML because it preserves editor blocks, links, and layout more predictably.

Use only the nodes and marks documented below for this field. Supported editor features differ by endpoint and field. Choose formatting for readability, not decoration: use headings for real sections, paragraphs for narrative text, lists or tables for structured facts, blockquotes for quoted context, callouts for important outcomes/risks/notes when supported, and explicit anchors for links. Do not rely on bare URLs or Markdown links when the link must be clickable; use explicit anchors such as <a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">link text</a>.

In HTML you can use:

Node Types and Marks

Nodes

paragraph Type: block Content: inline* (inline only — do not use block tags such as <p> inside; use e.g. <br>, <strong>, <em>, <span>…) Default paragraph. extraPlaceholder is an internal structure for the editor to show a ghost placeholder in empty “template” lines (ProseMirror JSON fragment or null). For normal agent output leave extraPlaceholder as null. Only set it if you are intentionally mirroring a field placeholder the user already has in the open editor; do not set random placeholder data for free-form answers. Atts:

  • extraPlaceholder: null
<p class="paragraph-base" extraplaceholder="null/some-value"></p>

heading Type: block Content: inline* (inline only — do not use block tags such as <p> inside; use e.g. <br>, <strong>, <em>, <span>…) Atts:

  • level: 1
<h1 class="heading-base"></h1>

bulletList Type: block list Content: listItem+ Atts: none

<ul class="list-base"><li></li></ul>

hardBreak Type: inline Atts: none

<br>

horizontalRule Type: block Atts: none

<hr class="hr-base">

orderedList Type: block list Content: listItem+ Atts:

  • start: 1
  • type: null
<ol class="order-list-base" type="null/some-value"><li></li></ol>

listItem Content: (paragraph|list)* (paragraphs and/or lists, in any order) Atts: none

<li></li>

blockquote Type: block Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Atts: none

<blockquote class="blockquote-base"><p class="paragraph-base"></p></blockquote>

table Type: block Content: tableRow+ Atts: none

<table style="width: 0px;"><colgroup></colgroup><tbody><tr></tr></tbody></table>

tableRow Content: (tableCell | tableHeader)* Atts: none

<tr></tr>

tableHeader Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Atts:

  • colspan: 1
  • rowspan: 1
  • colwidth: null
  • align: null
<th colspan="1" rowspan="1" colwidth="null/some-value" style="text-align: null/some-value;"><p class="paragraph-base"></p></th>

tableCell Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Atts:

  • colspan: 1
  • rowspan: 1
  • colwidth: null
  • align: null
<td colspan="1" rowspan="1" colwidth="null/some-value" style="text-align: null/some-value;"><p class="paragraph-base"></p></td>

callout Type: block Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Callout / info box. The icon attr is an emoji shortcode (e.g. :information_source:, :warning:); it appears on the callout. Body is block content (paragraphs, lists, etc.) — use normal block HTML such as <p class="paragraph-base"> inside the callout <div>. data-type is "callout". Atts:

  • icon: :information_source:
<div data-type="callout" icon=":information_source:"><p class="paragraph-base"></p></div>

emoji Type: inline Inline emoji. icon is a colon shortcode (e.g. :thumbsup:, :white_check_mark:) stored in data-icon; body is often empty. Use names your emoji set supports; avoid inventing invalid shortcodes in final HTML if you need them to render. Atts:

  • icon: :smile:
<span data-type="emoji" data-icon=":smile:"></span>

Marks

link Atts:

  • href: null
  • target: _blank
  • rel: noopener noreferrer nofollow
  • class: null
  • title: null
<a target="_blank" rel="noopener noreferrer nofollow" href="">link text example</a>

bold Atts: none

<strong>bold text example</strong>

code Atts: none

<code>code text example</code>

italic Atts: none

<em>italic text example</em>

strike Atts: none

<s>strike text example</s>

underline Atts: none

<u>underline text example</u>
logoIdobject | null

File ID of the product logo image. To upload a logo, first call POST /api/public/workspace/upload to upload the image file, then use the returned file ID here. Set to null if no logo is needed.

namestringrequired

Name of the product or service. This is the primary identifier shown in the catalog, quotes, and invoices.

optionsProductOptionApiDto[]required

List of product options. Options are extra services or add-ons that customers can optionally select (e.g., "Add workshop", "Enable premium support"). Options are organized into groups with multiple selectable values. Can be an empty array if no options are needed.

Show properties
Array of ProductOptionApiDto
idstring

Option ID. Include this when updating an existing option. Omit when creating a new option.

isMultiplebooleanrequired

Whether customers can select multiple values from this option. If true, customers can select multiple values (e.g., "Add workshop" AND "Enable premium support"). If false, only one value can be selected.

isRequiredbooleanrequired

Whether this option must be selected. If true, customers must choose at least one value from this option before proceeding.

namestringrequired

Name of the option group (e.g., "Additional Services", "Storage Options", "Support Level"). This groups related option values together.

valuesProductOptionValueApiDto[]required

List of available values for this option. Each value can have its own pricing. At least one value must be provided.

Show properties
Array of ProductOptionValueApiDto
extraPriceFixedobject | null

Additional one-time fixed price added when this option value is selected. Use this for one-time add-ons or upgrades.

extraPricePerUnitobject | null

Additional per-unit price added when this option value is selected. Use this for quantity-based add-ons.

extraPriceSubscriptionobject | null

Additional recurring subscription price added when this option value is selected. Use this for recurring add-ons (e.g., monthly premium support).

idstring

Option value ID. Include this when updating an existing option value. Omit when creating a new option value.

namestringrequired

Name of the option value (e.g., "Add workshop", "Enable premium support", "Extra 10GB storage"). This is what customers see when selecting options.

priceFixedobject | null

One-time fixed price for the product. Use this for one-time purchases (e.g., hardware, installation fees, one-time services). Can be combined with subscription or per-unit pricing. Use 0 or null when the product has no fixed charge.

priceFrequencynumber

Billing frequency in months for subscription or per-unit pricing. Required when pricePerUnit or priceSubscription is greater than 0. Valid values: 1 (monthly), 3 (quarterly), 6 (semi-annually), 12 (annually).

Allowed:13612
pricePerUnitobject | null

Price per unit for flexible quantity-based billing. Use this when pricing depends on quantity (e.g., per user, per hour, per license, per GB). Requires priceUnitName to be set. Use 0 or null when the product has no per-unit charge.

priceSubscriptionobject | null

Recurring subscription price for the product. Use this for recurring revenue models (e.g., monthly SaaS subscription, annual maintenance). The billing frequency is controlled by priceFrequency. Use 0 or null when the product has no subscription charge.

priceUnitNamestring

Name of the unit for per-unit pricing (e.g., "user", "hour", "license", "GB"). Required when pricePerUnit is greater than 0. This is displayed in quotes and invoices.

renewalModestring

Renewal mode for the product subscription. Automatic means the subscription will automatically renew, Stops means it will stop at the end of the contract period. Required when pricePerUnit or priceSubscription is greater than 0. Defaults to Automatic.

Allowed:AutomaticStops
variantsProductVariantApiDto[]required

List of product variants. Variants represent different configurations or performance levels of the same product (e.g., Standard, Pro, Enterprise). Each variant can have its own pricing and description. At least one variant must be provided.

Show properties
Array of ProductVariantApiDto
descriptionobject | nullrequired

Variant description explaining what this variant includes or offers. Accepts Markdown or HTML for complex formatting. This content is rendered by the Leadtime rich editor, not by a plain Markdown viewer.

For quick/simple text, Markdown is acceptable. For polished user-facing content from an agent or integration, prefer structured HTML because it preserves editor blocks, links, and layout more predictably.

Use only the nodes and marks documented below for this field. Supported editor features differ by endpoint and field. Choose formatting for readability, not decoration: use headings for real sections, paragraphs for narrative text, lists or tables for structured facts, blockquotes for quoted context, callouts for important outcomes/risks/notes when supported, and explicit anchors for links. Do not rely on bare URLs or Markdown links when the link must be clickable; use explicit anchors such as <a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">link text</a>.

In HTML you can use:

Node Types and Marks

Nodes

paragraph Type: block Content: inline* (inline only — do not use block tags such as <p> inside; use e.g. <br>, <strong>, <em>, <span>…) Default paragraph. extraPlaceholder is an internal structure for the editor to show a ghost placeholder in empty “template” lines (ProseMirror JSON fragment or null). For normal agent output leave extraPlaceholder as null. Only set it if you are intentionally mirroring a field placeholder the user already has in the open editor; do not set random placeholder data for free-form answers. Atts:

  • extraPlaceholder: null
<p class="paragraph-base" extraplaceholder="null/some-value"></p>

heading Type: block Content: inline* (inline only — do not use block tags such as <p> inside; use e.g. <br>, <strong>, <em>, <span>…) Atts:

  • level: 1
<h1 class="heading-base"></h1>

bulletList Type: block list Content: listItem+ Atts: none

<ul class="list-base"><li></li></ul>

hardBreak Type: inline Atts: none

<br>

horizontalRule Type: block Atts: none

<hr class="hr-base">

orderedList Type: block list Content: listItem+ Atts:

  • start: 1
  • type: null
<ol class="order-list-base" type="null/some-value"><li></li></ol>

listItem Content: (paragraph|list)* (paragraphs and/or lists, in any order) Atts: none

<li></li>

blockquote Type: block Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Atts: none

<blockquote class="blockquote-base"><p class="paragraph-base"></p></blockquote>

table Type: block Content: tableRow+ Atts: none

<table style="width: 0px;"><colgroup></colgroup><tbody><tr></tr></tbody></table>

tableRow Content: (tableCell | tableHeader)* Atts: none

<tr></tr>

tableHeader Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Atts:

  • colspan: 1
  • rowspan: 1
  • colwidth: null
  • align: null
<th colspan="1" rowspan="1" colwidth="null/some-value" style="text-align: null/some-value;"><p class="paragraph-base"></p></th>

tableCell Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Atts:

  • colspan: 1
  • rowspan: 1
  • colwidth: null
  • align: null
<td colspan="1" rowspan="1" colwidth="null/some-value" style="text-align: null/some-value;"><p class="paragraph-base"></p></td>

callout Type: block Content: block+ (one or more block child nodes (e.g. paragraph, list, …)) Callout / info box. The icon attr is an emoji shortcode (e.g. :information_source:, :warning:); it appears on the callout. Body is block content (paragraphs, lists, etc.) — use normal block HTML such as <p class="paragraph-base"> inside the callout <div>. data-type is "callout". Atts:

  • icon: :information_source:
<div data-type="callout" icon=":information_source:"><p class="paragraph-base"></p></div>

emoji Type: inline Inline emoji. icon is a colon shortcode (e.g. :thumbsup:, :white_check_mark:) stored in data-icon; body is often empty. Use names your emoji set supports; avoid inventing invalid shortcodes in final HTML if you need them to render. Atts:

  • icon: :smile:
<span data-type="emoji" data-icon=":smile:"></span>

Marks

link Atts:

  • href: null
  • target: _blank
  • rel: noopener noreferrer nofollow
  • class: null
  • title: null
<a target="_blank" rel="noopener noreferrer nofollow" href="">link text example</a>

bold Atts: none

<strong>bold text example</strong>

code Atts: none

<code>code text example</code>

italic Atts: none

<em>italic text example</em>

strike Atts: none

<s>strike text example</s>

underline Atts: none

<u>underline text example</u>
idstring

Variant ID. Include this when updating an existing variant. Omit when creating a new variant.

namestringrequired

Name of the variant (e.g., "Standard", "Pro", "Enterprise"). Used to distinguish different performance levels or configurations of the same product.

priceFixedobject | null

One-time fixed price for this variant. Use this for one-time purchases (e.g., hardware, installation). Can be combined with subscription or per-unit pricing. Use 0 or null when the variant has no fixed charge.

pricePerUnitobject | null

Price per unit for this variant. Use this for flexible billing by quantity (e.g., per user, per hour, per license). Requires priceUnitName to be set at the product level. Use 0 or null when the variant has no per-unit charge.

priceSubscriptionobject | null

Recurring subscription price for this variant. Use this for recurring revenue models (e.g., monthly SaaS subscription). The billing frequency is set at the product level via priceFrequency. Use 0 or null when the variant has no subscription charge.

Responses
200
categoryIdstringrequired

UUID of the product category this product belongs to

descriptionHtmlstringrequired

Product description converted to HTML format for easy display

idstringrequired

Unique identifier of the product

logoIdobject | nullrequired

File ID of the product logo. Null if no logo is set.

logoUrlobject | nullrequired

Public URL to access the product logo image. Constructed from logoId if a logo is set. Null if no logo is available.

namestringrequired

Name of the product or service

optionsstring[]required

List of all product options with their values and configurations. Structure matches ProductOptionApiDto but with additional internal fields.

priceFixednumberrequired

One-time fixed price for the product

priceFrequencyobject | nullrequired

Billing frequency in months (1, 3, 6, or 12). Null if subscription or per-unit pricing is not used.

pricePerUnitnumberrequired

Price per unit for quantity-based billing

priceSubscriptionnumberrequired

Recurring subscription price for the product

priceUnitNameobject | nullrequired

Name of the unit for per-unit pricing (e.g., "user", "hour"). Null if per-unit pricing is not used.

variantsIProductVariantApiResponse[]required

List of all product variants with their pricing and descriptions

Show properties
Array of IProductVariantApiResponse
descriptionHtmlobject | nullrequired

Variant description converted to HTML format. Null if no description is set.

idstringrequired

Unique identifier of the variant

isActivatedbooleanrequired

Whether this variant is currently activated and available for selection

namestringrequired

Name of the variant (e.g., "Standard", "Pro", "Enterprise")

priceFixednumberrequired

One-time fixed price for this variant

pricePerUnitnumberrequired

Price per unit for this variant

priceSubscriptionnumberrequired

Recurring subscription price for this variant

400

Validation errors

errorsobject
messagestring
statusCodenumber
401

Unauthorized - Invalid or missing authentication token

403

Forbidden - Insufficient API scopes or permissions

Try it
Server
Authorization
Parameters
Bodyapplication/json
Request
curl -X POST "https://leadtime.app/api/public/administration/product-catalog" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "categoryId": "123e4567-e89b-12d3-a456-426614174000",
  "contractPeriodMonths": 12,
  "description": "string",
  "logoId": "123e4567-e89b-12d3-a456-426614174000",
  "name": "Website Hosting Package",
  "options": [
    {
      "id": "string",
      "isMultiple": false,
      "isRequired": false,
      "name": "string",
      "values": [
        {
          "extraPriceFixed": 50,
          "extraPricePerUnit": 2.5,
          "extraPriceSubscription": 10,
          "id": "string",
          "name": "string"
        }
      ]
    }
  ],
  "priceFixed": 999.99,
  "priceFrequency": 1,
  "pricePerUnit": 5,
  "priceSubscription": 29.99,
  "priceUnitName": "user",
  "renewalMode": "Automatic",
  "variants": [
    {
      "description": {},
      "id": "string",
      "name": "string",
      "priceFixed": 999.99,
      "pricePerUnit": 5,
      "priceSubscription": 29.99
    }
  ]
}'
Response
{
  "categoryId": "string",
  "descriptionHtml": "string",
  "id": "string",
  "logoId": {},
  "logoUrl": {},
  "name": "string",
  "options": [
    "string"
  ],
  "priceFixed": 0,
  "priceFrequency": {},
  "pricePerUnit": 0,
  "priceSubscription": 0,
  "priceUnitName": {},
  "variants": [
    {
      "descriptionHtml": {},
      "id": "string",
      "isActivated": true,
      "name": "string",
      "priceFixed": 0,
      "pricePerUnit": 0,
      "priceSubscription": 0
    }
  ]
}