User guide
Pricing, markup and margin
Looking for what it does rather than how to use it? Read the Pricing and margin overview .
How a cost becomes a sell price: the markup you earn, the discounts you give, and the margin left over.
What it is
Pricing takes what you pay — a distributor’s cost, a platform service’s cost price, a vendor invoice line — and turns it into what a client pays. It does that with four independent controls: a markup profile that sets your default selling price, discount rules that take money off list, bundles that fix a price for a group of items, and margin thresholds that tell you when the result is too thin.
It does not send the quote or raise the invoice. It decides the numbers those documents carry.
Concepts
| Concept | What it is |
|---|---|
| Pricing profile | Your account-wide markup policy for the storefront: a base multiplier on cost, per-category and per-brand overrides, and rounding. |
| Base multiplier | A multiplier applied to cost. 1.25 sells a $100 cost item at $125. Null means no markup — the item sells at cost. |
| Category / brand rule | A markup fraction for one catalogue category or brand (0.20 = +20%), plus an optional minimum-margin floor. |
| Rounding rule | How the computed price is tidied: NONE, NEAREST_CENT, NEAREST_DOLLAR, UP_TO_99 ($9.99), UP_TO_95 ($9.95). |
| Currency buffer | An extra hedge added to cost on a cross-currency sale, e.g. 0.03 for 3%. Opt-in; can only raise a price. |
| Customer markup config | Markup applied when re-billing a vendor cost to a client. Scoped by contract, account, organization or expense category. |
| Discount rule | A reduction off list price: volume tiers, contract-term tiers, promotions, VIP pricing, or a flat discount. |
| Pricing bundle | A fixed price for two or more products/services, which must be no higher than buying them separately. |
| Margin | (sell − cost) ÷ sell. A fraction of the selling price. |
| Markup | (sell − cost) ÷ cost. A fraction of the cost. Not the same number as margin — a 25% markup is a 20% margin. |
| Margin thresholds | Two fractions per account — a warning level and a critical level — that classify how thin a price is. |
The four controls, in the order they apply
distributor / platform cost
│
├─ pricing profile ──► currency buffer → base multiplier
│ → category markup → brand markup
│ → vendor markup → minimum-margin floor
│ → rounding
│ = LIST PRICE
│
├─ discount rules ───► volume / contract-term / promotional
│ / VIP / general, stacked by priority
│
├─ bundle ───────────► fixed price, substituted for the lines
│ the bundle covers (other lines keep theirs)
│
└─ margin check ─────► OK / WARNING / CRITICAL / BLOCKED
Markup makes the price. Discounts and bundles reduce it. The margin check reports on the result — it never silently changes it.
Roles and permissions
platform administrator > MSP administrator > MSP technician > organization administrator > power user > CUSTOMER`
Every pricing surface listed here is floored at MSP technician, which admits platform administrator, MSP administrator and MSP technician, and excludes organization administrator, power user and CUSTOMER. That is deliberate: these screens expose your cost prices, and a cost price is not client-facing.
| Action | platform administrator | MSP administrator | MSP technician | organization administrator | power user | CUSTOMER |
|---|---|---|---|---|---|---|
| View / edit the storefront pricing profile | ● | ● | ● | — | — | — |
| Create / edit / delete discount rules | ● | ● | ● | — | — | — |
| Create / edit / delete bundles | ● | ● | ● | — | — | — |
| Set margin thresholds | ● | ● | ● | — | — | — |
| View margin analytics | ● | ● | ● | — | — | — |
| Create / edit customer markup rules | ● | ● | ● | — | — | — |
| Run the price & margin calculator | ● | ● | ● | — | — | — |
| Manage another account’s pricing profile | ● | — | — | — | — | — |
Manage platform plan prices (ServiceTier) | ● | — | — | — | — | — |
Tenant scope. Pricing configuration is your own account’s settings and is never shared with, or inherited from, a client you service. Naming another account in a request body or query string is a 403, not a lookup — including on the read-only calculators.
Walkthroughs
Set your storefront markup
MSP portal → Store → Pricing
- Pick a profile: Cost Plus (add a percentage to cost), Target Margin (work back from a margin you want to keep), or Custom (per-category and per-brand rules).
- Drag the slider or type the percentage. The preview panel prices a worked example — $100.00 cost, $149.99 MSRP — through the same steps the engine uses.
- Choose a rounding rule.
UP_TO_99turns $127.34 into $127.99. - Add category rules for anything that needs a different number, and brand rules for anything that needs a different number again. A category rule can also carry a minimum margin, which is a floor: if markup alone would undershoot it, the price is raised to
cost ÷ (1 − minimumMargin). - Save. Every product list, product detail and checkout re-prices immediately.
If the page shows “Your catalogue is currently priced at cost”, no base markup is saved and every product is selling for exactly what you pay. Set a markup and save.
Create a volume discount
MSP portal → Settings → Pricing → Add Rule
- Name the rule and choose Volume.
- Add tiers — for example 10+ units at 5%, 50+ at 10%, 100+ at 15%. The engine picks the highest tier the quantity qualifies for; tiers do not stack with each other.
- Choose the scope: all products, or specific products and categories.
- Choose the customers: all, or specific client organizations.
- Set a priority. Higher priorities are evaluated first, and every qualifying rule’s discount is summed.
- Optionally set an end date and a usage cap. Save.
Build a bundle
MSP portal → Settings → Pricing → Bundles → New bundle
- Search the catalogue and add at least two items with their quantities.
- Type the bundle price. The server prices the same items individually and returns the savings; a bundle priced above the individual total is refused with both figures quoted.
- Set the effective window. Save.
A bundle substitutes its price for the lines it covers. Anything else in the basket keeps its own price and is added on.
Set margin thresholds and read the analytics
MSP portal → Settings → Pricing → Margin thresholds
- Set a warning threshold and a critical threshold as percentages. Warning must be greater than or equal to critical. Defaults are 10% and 5%.
- Choose whether to alert on quotes, on invoices, or both, and optionally an email address to copy.
- Save. Creating the record is the opt-in — an account with no record gets no alerts at all.
The analytics panel scores the last 30 days of quoted work: how many lines are healthy, warning or critical, the blended margin, and how many lines have no cost basis. A line has no cost basis when it has no product link or the product has no tracked cost — labour and ad-hoc services, typically. Those lines are excluded from both sides of the ratio and counted separately, because reporting them as 100%-margin work would be false.
When there is no cost-bearing work in the period, the health score reads ”—”, not 0% and not 100%.
Check a price before you quote it
MSP portal → Settings → Pricing → Calculator
- Add products and account services to the basket and set quantities.
- Optionally choose a bundle and a contract length in months (contract length is what makes
CONTRACT_TERMrules fire). - Press Calculate.
You get the list subtotal, the discount taken, the sell price, the cost, the margin, and a per-line table naming every rule that fired and what it took off. Anything that would sell below cost is flagged.
Mark a vendor cost up to a client
MSP portal → Purchasing → Customer markup
- Create a rule and choose its scope: Global default, Product category, Specific customer, or Contract.
- Choose the markup type and value.
PERCENTAGEandCOST_PLUSare the same calculation — cost × value — so0.15is +15% either way.FIXEDadds a flat dollar amount. - Optionally set minimum and maximum markup amounts, which clamp the result.
- Set a priority and an effective window. Save.
The calculator at the top of that page prices a cost through the live ladder and names which rung answered (CONTRACT, ACCOUNT, ORGANIZATION, PRODUCT or DEFAULT).
Configuration
Pricing profile (MspPricingProfile, one per account)
| Setting | Units | Bounds | Unset behaviour |
|---|---|---|---|
baseMultiplier | Multiplier on cost (1.25 = +25%) | 0 – 10 | No markup — the item sells at cost. |
categoryRules | Fraction per category (0.20 = +20%) | markup 0 – 2 | Category inherits the base multiplier. |
brandRules | Fraction per brand | as above | Brand inherits category, then base. |
minimumMargin | Fraction of sell (0.30 = 30%) | 0 – 1 | No floor. |
currencyBuffer | Fraction added to cost | 0 – 0.5 | No hedge. Negative and zero are ignored. |
Discount rule (PricingRule)
| Setting | Notes |
|---|---|
type | VOLUME, CONTRACT_TERM, PROMOTIONAL, CUSTOMER_VIP, GENERAL. |
discountType | PERCENTAGE (fraction), FIXED_AMOUNT (cents per unit), FIXED_PRICE (cents, replaces price). |
volumeTiers | Required for VOLUME. { minQty, discountPercent }, discount as a fraction. |
termTiers | Required for CONTRACT_TERM. { minMonths, discountPercent }. |
effectiveUntil | Required for PROMOTIONAL. |
priority | Higher first. Qualifying discounts are summed, then the line is floored at zero. |
applyToAll / ids | Scope by product, service or category. An empty scope with applyToAll off matches nothing. |
maxUsageCount | Stops the rule applying once usageCount reaches it. See §9 for how the counter advances. |
Margin thresholds (MarginAlertConfig, one per account)
| Setting | Default | Notes |
|---|---|---|
warningThreshold | 0.10 | Fraction. Must be ≥ criticalThreshold. |
criticalThreshold | 0.05 | Fraction. |
alertOnQuote | true | Evaluate on quote creation. |
alertOnInvoice | true | Evaluate on invoice creation. |
notifyEmail | null | Extra recipient alongside the account’s staff. |
Only accounts flagged as MSP/IT partners may hold margin thresholds; the API answers 400 for any other account.
Plan tiers
Pricing, markup, margin thresholds, bundles, discount rules and the calculator carry no plan gate. None of the routers in this feature area invoke a plan-gate middleware, so every tier that can reach the MSP portal can use all of it.
What is restricted is the role floor in §3, and the MSP/IT-partner flag on the account for discount rules, bundles and margin thresholds.
Troubleshooting
| What you see | What it means |
|---|---|
Cannot write to another account | The request body or query named an account that is not yours. Omit it — the server resolves your account from your session. |
Only MSP/IT partner accounts can create pricing rules | The account is not flagged as an MSP/IT partner. Discount rules, bundles and margin thresholds are MSP-only. |
Margin config is only available for MSP/IT partner accounts | Same cause, on the margin threshold routes. |
Bundle price exceeds individual items total | The bundle costs more than buying the parts. The response quotes both figures. |
Volume rules require volumeTiers / Contract term rules require termTiers | A tiered rule was saved with no tiers. |
Promotional rules require an end date (effectiveUntil) | A promotion with no expiry. Set one. |
Warning threshold must be greater than or equal to critical threshold | The two margin thresholds are the wrong way round. |
Pricing rule has reached its usage limit | usageCount has reached maxUsageCount. Raise the cap or clear the counter. |
costAmount is required and must be positive | The markup calculator was called with a zero or missing cost. |
At least one price must be set… | A tier per-currency price was submitted with every field null. Use DELETE to remove a currency instead. |
Organization is not in this account | A markup rule was scoped to an organization belonging to another tenant. |
| “Your catalogue is currently priced at cost” banner | No base multiplier is saved, so the engine returns cost unchanged. Set a markup. |
| Margin health reads ”—” | No cost-bearing quoted work in the last 30 days. Not the same as 0%. |
| A discount rule exists but never applies | Check the effective window, the usage cap, and that the scope is non-empty — a rule with applyToAll off and no ids matches nothing. |
Limits and known behaviour
- But the storefront checkout engine deliberately does not enforce usage caps and does not advance the counter, so a cap only bites where the counter is advanced explicitly. Treat it as a manual allowance, not an automatic one.
- Discount rules do not reach quotes or invoices. Quote and invoice creation apply no
PricingRuleand noPricingBundle. Discounts reach the customer through the storefront and the cart; on a quote you enter the price yourself. Use the calculator (§4.5) to work out what to enter. blockedis a warning, not a block. The engine reportsblocked: truewhen a line would sell below cost. It does not refuse to return the price and nothing downstream refuses the sale.- There is no MSRP pricing mode. The engine prices from cost.
CatalogProductcarries an MSRP for display, but no pricing path reads it, so a “sell at MSRP” profile cannot be expressed and is not offered. COST_PLUSis an alias ofPERCENTAGE. Both computecost × value.- Brand rules price nothing in the platform-admin editor.
brandRulesare read by the storefront engine and are editable from Store → Pricing; the platform-admin profile editor does not expose them. - It is opt-in and can only raise a price.
profileTypedoes not price anything. It is stored, displayed and reported on, but the storefront engine reads the multiplier and the rules, not the label.- Margin alerts fire on document creation only. Editing a quote or invoice does not re-evaluate it. A duplicated or revised document is a new document and is evaluated on its own merits.
- Platform tier
yearlyPriceCentsis a monthly-equivalent rate billed annually, not an annual total.