Commercial Price Rounding
commerce.price_rounding
Overview
You send an amount, its currency, and the rules your pricing policy uses, and we apply them in the order you gave, returning the final price plus every step that got there. Four different ideas stay separate on purpose: rounding to the currency's own decimals, rounding to a ladder step such as 0.50, rounding to the coin you name as the smallest one your till can give, and forcing a charm ending such as 99. We never reorder or optimise your chain — a ladder then a charm ending and a charm ending then a ladder are both legitimate policies and give different answers, so the order is yours. The number of decimals a currency has comes from the platform's single currency source, never from a table in this skill — and neither does the coin: we keep no denomination table, so CASH_ROUNDING always needs the increment from you. Any property other than amount, currency and rules is refused rather than ignored, at the top level of the request as much as inside a rule. This is not display formatting and not minor-unit conversion.
Pricing rules are simple one at a time and treacherous together: rounding to cents, snapping to a ladder, landing on a coin and forcing a 99 ending are four different decisions that teams routinely collapse into one helper, and the helper then disagrees with the till. Keeping them apart, applying them in the order you chose, and reporting each step makes the answer reviewable instead of surprising.
Reliability
Fully deterministic and exact: every step runs in decimal arithmetic, never in floating point, so a step never loses a cent, and the same amount under the same chain always gives the same price. The chain is applied verbatim, so reversing two rules is a different policy and a different answer. Every decimal string we return — price, original, and each step's before and after — is written at the currency's decimals, or at the value's own scale when a step legitimately produced more, and that writing never rounds: it pads with zeros or leaves the digits alone. A property this skill does not implement is refused, not ignored: an unknown top-level property is a broken request and comes back as the platform's TOOL_INPUT_INVALID, while an unknown property inside a rule is this skill's own PRICE_RULE_INVALID naming the rule's index. Failures, and what to do about each. PRICE_AMOUNT_INVALID — trigger: amount is unreadable as a plain decimal, negative, carries more than 12 decimals, or the chain pushes it past 1E18; retryable: no, the same request always fails; next: send the amount as a plain string of zero or more with at most 12 decimals. PRICE_RULE_INVALID — trigger: rules is empty, a rule's type is not one of the six, a rule carries a property its type does not use (mode on CASH_ROUNDING, increment on ENDING), or mode is not a rounding mode we know; retryable: no; next: fix the rule we name by index and remove the property that does not belong, since we refuse an unknown property rather than ignore it. PRICE_INCREMENT_INVALID — trigger: INCREMENT or CASH_ROUNDING without an increment, or with one that is zero, negative, unreadable, or finer than 12 decimals; retryable: no; next: send a positive decimal step such as "0.05". PRICE_ENDING_INVALID — trigger: an ENDING rule with no ending, an ending whose digit count does not match the currency's decimals, a non-digit ending, an endingDirection outside NEAREST, DOWN and UP, or an ENDING rule on a currency with no decimals at all; retryable: no; next: send exactly as many digits as the currency has decimals, and use an INCREMENT rule for a no-decimal currency. PRICE_RULE_CHAIN_TOO_LONG — trigger: more than 8 rules in one call; retryable: no; next: shorten the chain, because past a handful of rules the later ones almost always re-round what an earlier one already settled. CURRENCY_UNKNOWN — trigger: a currency code outside the twenty supported, or a blank one; retryable: no; next: pick a supported code, and convert on your side first if you price in another currency.
Context footprint
The answer is one small object: a price, the amount it came from, one entry per rule, and the currency with its decimals. Nothing scales with your catalogue. A successful call costs 1 execution credit.
When to use it
When a calculated amount has to become a price a shelf, a ladder or a cash drawer can carry, and you want to see how it got there.
When you want the price rendered for a human, converted into cents, converted into another currency, or taxed.
Scope
Non-goals
- Does not format a price for display — finance.format_currency does that
- Does not convert between major and minor units, which is finance.convert_minor_units
- Does not convert between currencies and does not fetch a rate
- Does not compute tax; apply tax with finance.calculate_vat on the result
- Does not choose a pricing strategy, reorder your chain, or optimise it
- Does not keep a currency table of its own
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| amount | object | Yes | Decimal accepted as a precise string or JSON number with at most 12 fractional digits. |
| currency | string | Yes | ISO 4217 pricing currency. |
| rules | array | Yes | From 1 through 8 closed rounding rules applied in caller order. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| price | string | Yes | Canonical plain-decimal amount. |
| original | string | Yes | Canonical plain-decimal amount. |
| changed | boolean | Yes | Whether the final value differs. |
| steps | array | Yes | From 1 through 8 applied steps in caller order, each a closed type with before and after amounts. |
| currency | string | Yes | Canonical ISO 4217 pricing currency. |
| exponent | integer | Yes | Currency minor-unit exponent. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"commerce.price_rounding","input":{"amount":"19.427","currency":"CHF","rules":[{"type":"MINOR_UNITS"},{"type":"CASH_ROUNDING","increment":"0.05"}]}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("commerce.price_rounding", {"currency":"CHF","rules":[{"type":"MINOR_UNITS"},{"type":"CASH_ROUNDING","increment":"0.05"}],"amount":"19.427"});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| PRICE_AMOUNT_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PRICE_RULE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PRICE_INCREMENT_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PRICE_ENDING_INVALID | 400 | No | The request payload failed this skill's input validation. |
| PRICE_RULE_CHAIN_TOO_LONG | 429 | No | A size, quota, rate, or time limit was exceeded. |
| CURRENCY_UNKNOWN | 400 | No | The request payload failed this skill's input validation. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not format a price for display — finance.format_currency does that
- Does not convert between major and minor units, which is finance.convert_minor_units
- Does not convert between currencies and does not fetch a rate
- Does not compute tax; apply tax with finance.calculate_vat on the result
- Does not choose a pricing strategy, reorder your chain, or optimise it
- Does not keep a currency table of its own
- Formatting a price with a symbol and thousands separators
- Converting between euros and cents
- Adding VAT, which is a separate skill and a separate decision
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "commerce.price_rounding",
"input": {
"currency": "CHF",
"rules": [
{
"type": "MINOR_UNITS"
},
{
"type": "CASH_ROUNDING",
"increment": "0.05"
}
],
"amount": "19.427"
}
}Examples
A Swiss shelf price, rounded to the 5-centime coin
{
"currency": "CHF",
"rules": [
{
"type": "MINOR_UNITS"
},
{
"type": "CASH_ROUNDING",
"increment": "0.05"
}
],
"amount": "19.427"
}{
"steps": [
{
"type": "MINOR_UNITS",
"before": "19.427",
"after": "19.43"
},
{
"type": "CASH_ROUNDING",
"before": "19.43",
"after": "19.45"
}
],
"exponent": 2,
"price": "19.45",
"currency": "CHF",
"changed": true,
"original": "19.427"
}A 0.50 price ladder, then a 99 charm ending — in that order
{
"currency": "EUR",
"rules": [
{
"type": "INCREMENT",
"increment": "0.50"
},
{
"endingDirection": "NEAREST",
"type": "ENDING",
"ending": "99"
}
],
"amount": "17.34"
}{
"steps": [
{
"type": "INCREMENT",
"before": "17.34",
"after": "17.50"
},
{
"type": "ENDING",
"before": "17.50",
"after": "17.99"
}
],
"exponent": 2,
"price": "17.99",
"currency": "EUR",
"changed": true,
"original": "17.34"
}A yen price, where the currency itself says there are no decimals
{
"currency": "JPY",
"rules": [
{
"type": "MINOR_UNITS"
}
],
"amount": "1234.56"
}{
"steps": [
{
"type": "MINOR_UNITS",
"before": "1234.56",
"after": "1235"
}
],
"exponent": 0,
"price": "1235",
"currency": "JPY",
"changed": true,
"original": "1234.56"
}Recommended use cases
- Land a calculated price on the 5-centime coin a Swiss till can actually give
- Snap a cost-plus price onto a 0.50 ladder, then force a 99 ending
- Round to a currency's real decimals without hard-coding that 0 means JPY
Avoid for
- Formatting a price with a symbol and thousands separators
- Converting between euros and cents
- Adding VAT, which is a separate skill and a separate decision
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.