# Approved prices: lifecycle, history & delivery Source: https://docs.retailgrid.io/agents/approved-prices Inspect the prices your team registered, follow each one through its lifecycle, amend effective dates, and see exactly what was delivered to API consumers. Approving a price in [Price Approval](/agents/price-approval) registers it for delivery; it doesn't end the story. The **Approved Prices** detail page is where you inspect what was registered, follow each price through its lifecycle, and confirm what actually reached the systems that consume it. Open it from the **Approved prices** row under **Outbound** on the [Datasets](/data-requirements/datasets) page. This page is read-only for the price values themselves — they come from [Price Approval](/agents/price-approval). What you manage here is the lifecycle around them: effective dates, and visibility into delivery. ## Current vs History The page has two views: * **Current** — the price that is in force for each product right now: the latest registered version, its source, and its delivery state. * **History** — the full trail of registered versions for a product over time, so you can see what changed, when, and what a consumer would have received at any point. Seeing both matters because downstream systems pull on their own schedule. The current price is what you intend; the history is what was actually handed over. ## The price lifecycle Each registered price carries a state so you can tell, at a glance, where it stands: * A price that differs from what the product already had is a real change, on its way to delivery. * A proposal that matched the current price is marked **Not Changed** — registered for completeness, but nothing for a consumer to act on. * Once a consumer has taken a version, that fact is recorded against it, so a later edit never rewrites history a client already received. The point of the lifecycle is a reliable answer to "which exact price was in force, and which version did each consumer get?" — without that, an amended date or a re-run can blur what was actually delivered. ## Amend an effective date An effective date can be corrected after a price is registered — for example to move a change to the start of a campaign. Amending the date adjusts when the price takes effect going forward; it does **not** overwrite a version a consumer has already received. The earlier version stays in the History trail exactly as it was delivered, and the amendment is a new entry on top of it. ## How consumers receive prices Registered prices are delivered to the systems that integrate with Retailgrid — your webshop, ERP, or BI tool — through your export path and the [public API](/api-reference/introduction). The delivery state on each row tells you whether a price is still pending, has been delivered, or was superseded. For how an integration reads and acknowledges delivered prices, see the [API reference](/api-reference/introduction). ## Related * [Review and approve price changes](/agents/price-approval) * [Datasets](/data-requirements/datasets) * [API introduction](/api-reference/introduction) * [Connect Shopify](/settings/shopify) # Schedule and automate pricing runs Source: https://docs.retailgrid.io/agents/automation Run Price Optimization and Dynamic Pricing on a schedule, auto-approve prices that clear your guardrails, and get an email when a run finishes. Most pricing work doesn't need you sitting in front of it. The **Automation** tab on [Price Optimization](/agents/price-optimization) and [Dynamic Pricing](/agents/dynamic-pricing) lets a run happen on a schedule, approve the prices that clear limits you set, and email you the outcome — so an overnight run is finished and reviewed by the time you open Grid. Automation is opt-in and per agent configuration. Nothing here changes how prices are calculated; it changes when a run happens and what happens to the proposals afterward. ## Open the Automation tab Open [Price Optimization](/agents/price-optimization) or [Dynamic Pricing](/agents/dynamic-pricing) from the **Agents** menu in the grid toolbar. The configuration dialog has an **Automation** tab alongside **General** and **Source**. Set your schedule and approval options there, then save them with the agent's existing **Enable automation** / **Apply settings** action — the same action that saves the rest of the configuration. The Automation tab on Price Optimization, showing the schedule controls and the run-finished email toggle ## Schedule a run A schedule decides when the run fires. You can run: * **Daily**, at a time and timezone you set. * **Weekly**, on the weekdays you choose. * **After data updates** — the run fires when new data lands for the grid, so prices follow your latest imports without a fixed clock. The tab shows the saved schedule, the next run, and the controls to change or disable it. A scheduled run appears in [Runs](/runs/overview) like any other, triggered by `system`. ## Auto-approve prices that pass your guardrails By default every proposed price waits for you in [Price Approval](/agents/price-approval). Auto-approval lets a scheduled run clear the safe, in-policy changes on its own and hold everything else for review. Turn on **Automatically approve and export qualifying prices**. It's off for new and existing automations until you enable it. Two limits then apply to every proposal: * **Minimum unit margin (%)** — the smallest unit gross margin a proposed price may have, at the proposed price after tax treatment and unit cost. * **Maximum price change (%)** — the largest move from the current price, up or down, allowed in a single run. The Price approval section of the Automation tab, with the auto-approve checkbox and the two guardrail fields A proposal is approved and queued for export only when it clears **both** limits. Everything else stays in Price Approval with a reason that names the value and the limit it missed. ### What qualifies Each proposal is judged after the full run finishes and all pricing rules and rounding have produced the final price: * **Unit margin** is measured against the proposed price net of tax and the item's unit cost, following your organization's prices-exclude-tax setting. * **Price change** is measured against the current price the run started from, and the same limit applies to increases and decreases. It's a per-run limit, not a running weekly total — successive runs can accumulate a larger move as current prices change. * Rows are always held for review when an input is missing or invalid (no unit cost, no valid price, missing tax data), when a pricing rule is violated, or for **No Change** and manually overridden rows. A missing cost is never treated as zero. Only fresh proposals from that run qualify. Existing overrides, earlier proposals, and rows you already decided are left alone. ### What auto-approval does and doesn't do * It **approves and queues** qualifying prices for export, following the same explicit-export lifecycle as a manual approval. Queuing a price for export is not the same as changing your live price — delivery to your webshop, ERP, or Shopify happens through your existing export path. * It **does not** change the proposed price or bypass your pricing rules. The guardrails decide eligibility only. * Automatic decisions stay visible in Price Approval and in approved-price history, with the run, the user who set up the automation, the applied limits, and the values used. * Turning auto-approval off stops further automatic approvals, including a run already in flight that hasn't reached that step. Prices already delivered are not rolled back; pending exports can still be cancelled through your normal export controls. ## Email a summary when a run finishes Check **Email me when the run finishes (including failures)** on a schedule to get one message when its run completes. Existing schedules default to off. The email tells you what happened without opening the app: * The agent, the grid, and the run's start and finish in your timezone. * The outcome, and a short impact summary — products in scope, prices raised, cut, and unchanged, the average change, and the forecasted effect on gross profit, revenue, and units. * A link that opens the run's results straight in Grid. You get **one email per completed run**. A chained run (Price Optimization → Dynamic Pricing → Price Approval) sends one email for the chain, not one per step, and retries or duplicate triggers don't produce duplicate emails. A failure reports the failing step and reason instead of a misleading success. ## Common pitfalls * **Expecting live prices to change.** Auto-approval queues prices for export; it doesn't push them live on its own. Confirm your export path is set up if you rely on downstream delivery. * **Setting the change limit too tight.** A very low maximum price change holds most proposals for review — useful when you're cautious, but check the held count if you expected more to clear. * **Forgetting the limits are per run.** The maximum change caps a single run, not a week of runs. Frequent runs can move a price further over time as its current price shifts. ## Related * [Review and approve price changes](/agents/price-approval) * [Price Optimization](/agents/price-optimization) * [Dynamic Pricing](/agents/dynamic-pricing) * [Runs](/runs/overview) # Dynamic Pricing Source: https://docs.retailgrid.io/agents/dynamic-pricing Configure pricing rules and apply them deterministically across a grid - margin floors, competitor matching, rounding, and more. **Dynamic Pricing** runs your pricing rules across the rows in a grid - products, or product-store combinations when the grid is in SKU-Store view - and writes the resulting prices into a new column. Use it when you have a known pricing logic - margin floors, competitor matching, percentage changes, promo caps - and want it applied consistently across SKUs. Dynamic Pricing is a deterministic constraint solver: each rule narrows the range of valid prices for a row, and the final price is the value inside that range that's closest to the row's current price. Dynamic Pricing was previously called **Rules Based Pricing**. Same agent, same engine - only the name changed. ## When to use Dynamic Pricing Reach for Dynamic Pricing when you can describe the pricing logic you want: * Operational pricing - guardrails, promotions, parity to a competitor. * Margin enforcement - you know the floor or band you want to stay in. * Sync prices across a product family or set. * Apply rounding to psychological price endings. * Relative pricing within a group (good / better / best laddering). If instead you want the system to *suggest* prices based on demand and elasticity, that's [Price Optimization](/agents/price-optimization). ## Open Dynamic Pricing In any grid: 1. Click **Agents** in the top toolbar (or the pinned **Dynamic Pricing** button if you have one). 2. Select **Dynamic Pricing** from the All Agents modal. 3. The configuration dialog opens with a **Config by AI** panel beside two configuration tabs: **Rules** and **Parameters**. The **Run** button is enabled once inputs are mapped and at least one rule exists. Dynamic Pricing configuration dialog - Config by AI panel, rule list, Config Manager ## Configure by AI - describe what you want in plain language The **Config by AI** panel sits beside the configuration tabs as a chat-style assistant, usable at any point. Use it when you know the outcome you want but not which rule type fits. * **Prompt** - describe your goal in 1-3 sentences. Example: *"Set price 5% above cost, but never below the cheapest competitor."* * **AI model** - currently Gemini 2.5 Flash. * **Run AI** - generates rules and adds them to the **Rules** tab. AI-generated rules **append** to the existing rule list. Use **Reset** before running AI when you want a clean slate. AI output is a starting point. Review every generated rule before clicking **Run** - the AI gives a structured guess, not a final answer. Because Config by AI is independent of the tabs, you can keep editing rules manually after the AI fills them in - the two surfaces don't lock each other out. If the assistant can't turn your prompt into rules, it will ask you to rephrase rather than failing silently - try describing the goal more concretely (name the column, the threshold, the competitor reference). ## Configure the run The **Rules** tab holds the rule list. Input mappings and run scope live under **Parameters**. The recommended order for first-time setup is inputs → scope → rules. ### Inputs (Parameters tab) - map Price and Cost Two grid columns map to the algorithm's reserved fields: * **Price** - the current selling price (defaults to `price`). * **Cost** - cost per unit (defaults to `unit_cost`). Both are required. Margin Rules need Cost; every rule type uses Price as the anchor for "closest to current price" selection. If your data uses different column names, pick them from the dropdowns. The mappings are non-destructive; your original columns aren't renamed in the grid. ### Scope (Parameters tab) - Full Dataset or Sample 10 Rows The scope controls which rows the rules run against: * **Full Dataset** - every row in the current workbook. * **Sample 10 Rows** - server-side sampling against ten rows, intended to validate a rule setup before committing to a full run. Returns quickly and exposes configuration errors (missing references, contradictory rules, invalid conditions) without processing the whole grid. Sample runs still log to the Runs section and write outputs back to the grid for the sampled rows. ### Rules - the heart of the configuration The **Rules** tab is where you list the rules to apply. Click **Add Rule** to open the rule type picker, configure each rule, and save it. The list shows all rules with their priorities; the badge on the tab header is the rule count. ## How rules are evaluated For every row in scope, independently: 1. Rules are sorted by **Priority** (1 = highest, max 50). 2. The engine walks rules top-to-bottom. Each rule narrows the row's feasible price corridor. 3. If a rule's corridor doesn't intersect the current corridor, the rule is **violated** (see Fallback Behavior). 4. After all rules run, the final price is the value in the surviving corridor closest to the row's **current price**. 5. **Price Rounding** is always evaluated last regardless of its priority position; its priority number controls which other rules it can violate. The execution model is documented internally as the **Interval Selection Algorithm (ISA)**. The result is reproducible - same inputs and rules always give the same output. ## Store-level execution When your grid is in **SKU-Store view** (one row per product-store combination), Dynamic Pricing runs at the SKU-Store level: * The engine emits **one recommended price per product-store combination**, using each store's own price, cost, and competitor set. * **Conditions** can target store attributes - country, channel, pricing zone - so a rule applies only to the matching stores. A margin floor for one country, competitor parity for one channel. * Rules with no store conditions apply to all stores, so existing product-level rule setups keep working without modification. Stores and their attributes are defined in [Stores management](/stores/manage-stores); the grid's two granularity modes are covered in [Product and SKU-Store grid views](/grids/sku-store-view). ## Universal rule fields Every rule has the same envelope, regardless of type: * **Name** - free text. Make it readable in traces (e.g. *"Min margin 25%"*). * **Priority** (1-50) - lower number = higher priority. The engine evaluates highest-priority rules first. * **Conditions** (collapsible) - filter that limits which rows the rule applies to. * **Fallback Behavior** (collapsible) - what to do when the rule conflicts with the current corridor or the row is missing required data. * Rule-specific parameters (see each rule type below). Maximum 50 rules per execution. The rule list order in the **Rules** tab is purely visual; only **Priority** controls evaluation order. Select Rule Type picker ## The 8 rule types New to the rule types? The [Pricing rules guide](/agents/pricing-rules-guide) walks through all 8 from a business angle - when to use each rule, why it matters, and a worked retail example for every one. The sections below are the technical reference. ### Fixed Price Locks matching rows to their current price. The final price equals the input price; no later rule can move it. * **Parameters:** none. * **Corridor:** `[current_price, current_price]` - a single point. * **Use it for:** promo prices, regulated SKUs, manually approved prices. * **Gotcha:** any later rule whose corridor doesn't contain the current price will be marked violated for those rows. ### Price Change Limit Caps how far prices can move from the current price. Use it as an outer corridor (Maximum) or as a dead zone forbidding micro-changes (Minimum). * **Limit Type:** Maximum (outer corridor) or Minimum (dead zone). * **Increase:** Percent or Absolute, with a value. * **Decrease:** Percent or Absolute, with a value. * **Maximum corridor:** `[base - decrease, base + increase]`. Prices outside are pulled to the nearest boundary. * **Minimum (dead zone):** forbids changes smaller than the threshold. Encoded internally as an inverted corridor; this is the only rule type that legitimately produces `lo > hi`. ### Price Change Moves prices by a percent, an absolute amount, or a reference column. Always produces a single target price. * **Value Type:** Percent, Absolute, or Column. * **Value:** the percent / absolute number, or the reference column. * **Formulas:** ```text theme={null} percent: target = current_price * (1 + value / 100) absolute: target = current_price + value column: target = current_price + reference_column ``` * **Note:** always computed relative to the **base price (current price)**, not relative to whatever the running candidate is at this rule's priority. So a +5% Price Change applied after a Margin Rule still adds 5% to the original price, not to the margin-derived price. ### Price Match Aligns the price to a reference column (typically a competitor price), either exactly or within a percentage corridor. * **Reference Column:** numeric column. * **Operator:** `Equals` (single point) or `Between` (corridor). * **From / To values** (when `Between`): lower / upper percent bounds. * **Corridor:** ```text theme={null} equals: [ref, ref] between: [ref - from%, ref + to%] ``` * **When violated:** the engine clamps the price to the boundary closest to this rule's wanted corridor ("stretch toward what the rule wanted"). ### Sync Prices Forces all rows in a group to share one identical final price. A dynamic rule - it intersects the corridors of all group members and resolves a single group target at its priority moment. * **Group Column:** string or categorical column (e.g. `product_family`). * **Resolution cascade** (in order): 1. **Mode** - if any current price is shared by 2+ members, use it. 2. **Sales-weighted** - if all current prices are unique and a `sales_units` column exists, use the price of the highest-selling member. 3. **Median** of group's current prices. * **Three cases after intersecting member corridors:** * **Full intersection:** all group members get the same price. * **Partial intersection:** the engine drops outlier SKUs (smallest contribution to overlap) one by one until the rest agree. Outliers are marked Sync-violated and priced individually. * **No intersection:** the whole group is marked Sync-violated and each SKU is priced individually. * **Scope gotcha:** Sync only groups rows matching its conditions. Rows outside the conditions are completely unaffected - the rule doesn't appear in their applied / violated lists. * **Special fallback:** when Sync is violated, the engine doesn't "stretch toward Sync's corridor" (Sync has no numeric corridor). Instead, each affected SKU is priced closest to its own current price. ### Margin Rule Constrains the price to a margin or markup target derived from Cost. Static - corridor is computed before the engine runs. * **Margin Type:** Margin or Markup. * **Constraint:** Exact (single point) or Between (corridor). * **Value Source:** Percent, Absolute, or Column. * **Value** (or **From / To** when Constraint is Between). * **Formulas (with `T = value / 100` for percent):** ```text theme={null} Margin, percent / column: boundary = cost / (1 - T) Markup, percent / column: boundary = cost * (1 + T) Absolute (margin/markup): boundary = cost + T ``` * **Examples:** *"Margin exactly 25%"* -> `[cost / 0.75, cost / 0.75]`. *"Markup between 30% and 50%"* -> `[cost * 1.30, cost * 1.50]`. * **Validation:** `Value Source = Column` is only valid with `Constraint = Exact`. Combining Column with Between returns an error. ### Price Rounding Forces final prices to end in psychologically allowed endings (e.g. `*.99`, `*.95`, `*.00`). * **Price Scope:** All prices, or a price-range filter (Equals / Between). * **Pattern (allowed endings):** comma-separated patterns, e.g. `*.99, *.95`. * **Excluded endings:** patterns to avoid, e.g. `*.77`. * **Rounding Direction:** Up (smallest valid above candidate), Down (largest valid below candidate), or Closest (nearest valid that still falls inside the feasible range). * **Always last:** Rounding runs after every other rule, regardless of its position in the priority list. Its priority number determines which other rules it may violate (only those with lower priority). * **Gotcha:** if no valid ending exists inside the feasible range, the rule is violated and the price stays unrounded - it isn't nulled. ### Price Relations Maintains a relative price structure within a group. Example: keep the Profit Driver SKU 6% below the Traffic Driver. * **Group Column:** string or categorical. * **Price Type:** Price or Unit Price. * **Relationship Type:** *One by one* (each item N priced relative to item N-1) or *Same gap* (same gap from anchor for every member). * **Gap Value Type:** Percent or Absolute. * **Gap Value.** * **Anchor Logic:** Highest priced (anchor = group member with highest current price) or Explicit item. * **Formulas:** ```text theme={null} percent: price(N) = price(N-1) * (1 - gap / 100) absolute: price(N) = price(N-1) - gap ``` * **Cascade:** each chain SKU gets a single-point corridor. If the anchor moves due to a higher-priority conflict, the entire chain is recomputed. * **Composes with rounding only:** because Price Relations leaves single-point corridors, only Price Rounding can compose meaningfully after it. Other lower-priority rules will either intersect the exact point (no conflict) or be violated. * **Full-ladder explanation:** when a Price Relations rule set the price, the Price Analysis panel shows the SKU's **entire ladder** — every rung in the chain and where this SKU sits among them — not just the single neighbour it stepped from, so an inversion or a rung locked by another rule is visible in context. ## Conditions - filter which rows a rule applies to Open the **Conditions** section on any rule to limit it to a subset of rows. Rules with no conditions apply to every row in scope. * Each condition is a **column + operator + value** triplet. * Conditions combine via **AND** (no OR). * Supported operators: `=`, `!=`, `>`, `<`, `>=`, `<=`, `IN`, `NOT IN`, `IS TRUE`, `IS FALSE`. In SKU-Store view, store attribute columns (country, channel, pricing zone) are available as condition columns - this is how you scope a rule to a subset of stores. A row is matched by a rule only if every condition evaluates true. ## Fallback Behavior - what happens when a rule can't apply cleanly Open the **Fallback Behavior** section on any rule to control conflict and missing-data handling. **Conflict behavior** * **Follow priority** (default) - the rule is marked violated, the corridor stays at the last valid range, and the engine picks the boundary closest to what this rule wanted ("stretch toward the violated rule"). * **Return error** - calculation stops for this row. The row gets `rbp_status = error` and the offending rule's name. Recommended for compliance-critical rules. **Missing value behavior** * **Ignore** (default) - the rule is skipped for rows missing required data; the row continues evaluating other rules. * **Return error** - the run fails when a required column value is missing. Sync Prices, Price Rounding, and Price Relations have rule-specific fallbacks (described in their sections above). ## Save and load configurations Click **Config Manager** in the top right to save the current configuration as a named config or load a previously-saved one. A config captures the full setup - scope, inputs, AI prompt history, and the entire rule list. You can load a config onto the same grid or a different one. This is the right tool for monthly recurring runs and for sharing setups across teammates. ## Validate with Sample 10 Rows, then run The recommended flow: 1. Configure rules. 2. Set the scope to **Sample 10 Rows** and click **Run**. 3. Inspect the **Rules Based Price** column on the sampled rows; open the Price Analysis panel on a few to confirm rules fired as expected. 4. Switch the scope back to **Full Dataset** and click **Run**. Sample runs are fast and surface configuration errors (missing references, contradictory Conditions, validation failures) before you wait through a full execution. ## Read the result After a run, the grid shows a new **Rules Based Price** column with the calculated price per row. Click any cell in that column to open the right-side **Price Analysis** panel, which explains how the price was derived. Price Analysis panel The panel header shows the product (image, ID, name) the price was calculated for, plus a `Rule-based pricing trace` subtitle. The summary block has two columns: **Left column - Final Price** * **Final Price** - the calculated Rules Based Price, large and prominent. * **% delta vs Start Price** - how far the calculated price moved from the row's starting price (positive = up, negative = down). **Right column - inputs and applied rules** * **Start Price** - the row's starting price (the input). * **Cost** - the row's unit cost (the input). * **Feasible Range** - the price corridor that survived after every applied rule narrowed it. The Final Price is the point inside this range closest to Start Price. * **Applied Rules** - a compact card for each rule that fired, showing the rule's summary and status and its **priority** (so you can read the order rules were applied in), with an expandable **price-corridor chart** on each card that shows the constraint the rule imposed and the price it produced. A count of violated rules sits alongside. When the price came from a [Price Optimization](/agents/price-optimization) recommendation, the panel also surfaces the **elasticity** used and how it shaped the final price. The cards stay readable with three or more rules applied — no truncation. **Impact section (three metric cards below the summary)** * **New Unit Margin %** - margin at the calculated Rules Based Price, with the starting margin shown below as `vs Start: X%`. * **Unit Margin % pp change** - percentage-point change vs the starting margin. * **Unit Margin % Change (%)** - relative change in margin (pp change / starting margin). All values are rounded to 2 decimals. **Corridor visualization** A horizontal bar at the bottom shows the Feasible Range, with markers for Start Price (input) and Final Price (calculated) - useful for seeing at a glance how aggressively rules constrained the price. Press **ESC** or click the X in the panel header to close it. ## Output columns The agent writes these columns back to the grid: | Column | Engine key | What it means | | :- | :- | :- | | **Rules Based Price** | `rule_based_price` | The final calculated price per row. | | **rbp\_status** | `rbp_status` | `success` or `error`. | | **rbp\_error\_message** | `rbp_error_message` | Error detail when status is `error`. | | **rbp\_trigger\_rule** | `rbp_trigger_rule` | Name of the rule that produced the final price. | | **rbp\_applied\_rules** | `rbp_applied_rules` | Comma-separated rule IDs that fired successfully. | | **rbp\_violated\_rules** | `rbp_violated_rules` | Comma-separated rule IDs that were violated. | | **rbp\_skipped\_rules** | `rbp_skipped_rules` | Comma-separated rule IDs skipped due to missing data or non-matching conditions. | | **rbp\_delta\_abs** | `rbp_delta_abs` | `Rules Based Price - current_price`. | | **rbp\_delta\_pct** | `rbp_delta_pct` | `(Rules Based Price - current_price) / current_price`. | | **New Unit Margin %** | `rbp_new_margin` | `(Rules Based Price - cost) / Rules Based Price`. | | **RBP Price Change Trace** | `rbp_price_range_trace` | JSON corridor trace per row, used by the Price Analysis panel. | Hide what you don't need via the Columns panel. Pin **Rules Based Price** to the right for at-a-glance review. ## Common pitfalls * **Missing `cost`** - Margin Rules can't run without it. Either map a Cost column in the **Parameters** tab or remove margin-based rules. * **Currency mismatch** - if products and competitors use different currencies, configure FX in [account settings](/settings/currency) before running competitor-based rules. * **Conditions too narrow** - if no rows match, the rule is silently skipped. Watch the row counts in Sample runs. * **Fixed Price followed by other rules** - Fixed Price is a single-point corridor; almost any later rule will be violated for those rows. Expected behavior. * **Price Change relative to base** - Price Change always starts from the current price, not from whatever earlier rules produced. If you need cascading deltas, use Price Relations instead. * **Rounding never reaches a valid ending** - if the surviving corridor is narrower than your Pattern's tick spacing, Rounding is violated and the price stays unrounded. ## Related * [Pricing rules guide](/agents/pricing-rules-guide) - business-user walkthrough of the 8 rule types with worked examples * [Agents overview](/agents/overview) * [Price Optimization](/agents/price-optimization) * [Pricing Strategy](/agents/pricing-strategy) * [Manage stores](/stores/manage-stores) * [Product and SKU-Store grid views](/grids/sku-store-view) * [Add columns](/grids/manage-columns) * [Metrics glossary](/reference/metrics) * [Currency](/settings/currency) * [Runs](/runs/overview) # Forecast accuracy & Base Price Performance Source: https://docs.retailgrid.io/agents/forecast-accuracy Track how accurate your demand forecast has been week to week, and measure the realized benefit of the price changes you already made. The [sales forecast](/agents/sales-forecast) tells you what a price change is *expected* to do before you approve it. **Forecast accuracy** and **Base Price Performance** are the other half of the loop: they look *backward*, at how the forecast actually held up and at what your past price changes really earned. Together they let you trust — and tune — the numbers you price on. This page covers the backward-looking view. For the forward-looking impact shown at approval time, see [Sales forecast & price impact](/agents/sales-forecast). ## Why look backward A forecast you never check is a guess you keep repeating. Two questions decide whether to keep trusting it: * **Has the forecast been right?** If last week's forecast said 120 units and you sold 60, the model is telling you something — about that item, that category, or that season — worth knowing before you price on it again. * **Did the price change pay off?** A change looked good in the forecast at approval time. Weeks later, with real sales in, you can see whether it actually moved gross profit the way you expected, or whether the market moved instead. ## Forecast accuracy Forecast accuracy compares each week's forecast against what actually sold once the week closed. Read it to find where the model is dependable and where it is not: * **Where accuracy is high**, the forecast is a reliable input — you can lean on the approval-time impact numbers and, where you choose to, let scheduled runs act on them. * **Where accuracy is low**, treat the forecast as a hint rather than a number. Thin sales history, a promo the model didn't see, or a genuine demand shift all show up here as a gap between forecast and actual. Accuracy is tracked per week, so you see a trend rather than a single snapshot — a model that is drifting looks different from one that missed a single unusual week. ## Base Price Performance Base Price Performance (BPP) measures the **realized** effect of the price changes you already made — the difference between how an item sold after a change and how the model expected it to sell at the old price over the same period. It answers "did that repricing actually earn what we thought it would?" in money, not in theory. Use it to: * **Settle whether a pricing pass worked.** Instead of arguing from the approval-time forecast, read the measured benefit once real sales are in. * **Learn which moves pay off.** Patterns across items — which categories, roles, or kinds of change delivered — feed directly into how you set up the next run. * **Catch changes that quietly cost you.** A move that looked fine at approval but underperformed is exactly what BPP surfaces. ## How the two fit together Forecast accuracy tells you how much to trust the model; Base Price Performance tells you whether acting on it paid. Read accuracy first — if the forecast has been reliable for a segment, its BPP is a clean read on your pricing decisions; if accuracy is poor there, a weak BPP is as likely a model problem as a pricing one, and the fix is better data or more history, not a different price. ## Related * [Sales forecast & price impact](/agents/sales-forecast) * [Price Optimization](/agents/price-optimization) * [Review and approve price changes](/agents/price-approval) * [Metrics glossary](/reference/metrics) # Agents overview Source: https://docs.retailgrid.io/agents/overview What agents are in Retailgrid, the agents available today, and the workflows that work the same way across all of them. An **agent** is something Retailgrid runs *for* you against a grid - a pricing rule engine, an optimizer, or a presentation generator. Agents take the products in a grid as input, execute on Retailgrid's backend, and write their results back to the grid as new columns or as a separate artifact (e.g. a Gamma deck). Agents are how you go from looking at data to acting on it. ## Available agents Click **Agents** in the grid toolbar to open the All Agents modal. The agents currently available: | Agent | What it does | When to use it | | :- | :- | :- | | **Dynamic Pricing** | Run pricing rules (margin floors, competitor matching, percentage changes, etc.) against the grid and write the resulting prices back. | You know the pricing logic you want and want it applied consistently across SKUs. | | **Price Optimization** | AI-powered optimal price recommendations - elasticity-driven and demand-aware. | You want suggested optimal prices instead of writing the rules yourself. | | **Pricing Strategy** | Generates an AI-powered pricing-strategy presentation (Gamma deck) from your portfolio data over any of seven historical timeframes (last 7 / 30 / 90 days, QTD, YTD, T12M, custom). | Quarterly business reviews, board decks, and pricing-strategy conversations with stakeholders - now driven by real grid data and your account's company name. | | **Product Role Classification** | Classifies every SKU into an explainable product role - KVI, Traffic Driver, Profit Driver, Niche, Tail, or New - with SKUs that have no sales history held in Unclassified until there's enough signal. | You want role-aware pricing, promos, and monitoring without tagging SKUs by hand. See [Product roles](/agents/product-roles). | The All Agents modal has three tabs - **All Agents**, **Optimization Agents**, and **Analytics Agents** - plus a search field for jumping straight to an agent by name. Once an agent has written proposed prices, review and publish them through the [price approval workflow](/agents/price-approval) - the shared review step that sits after any pricing run. **Dynamic Pricing** was previously called **Rules Based Pricing** - same agent, new name. All Agents modal ## Pin agents you use often Each agent in the All Agents modal has a pin icon. Click it to pin the agent to the grid toolbar - it appears as a compact button between **Agents** and **Export**. Click a pinned button to open that agent's configuration directly without opening the modal first. Use pinning when you run the same agent on every grid (most teams pin Dynamic Pricing). ## Configure manually or by AI Most agents expose a dedicated **Config by AI** section alongside their configuration tabs. Describe what you want in plain language and Retailgrid generates the configuration into the tabs for you to review before saving. You can keep using the tabs directly to enter or edit the configuration manually at any time - the AI section and the tabs are independent surfaces, so you're never locked into one or the other. The AI configuration speed-up is most useful for Dynamic Pricing, where the rule grammar takes a moment to learn. ## Save and load configurations For agents that take meaningful setup (Dynamic Pricing), click **Config Manager** in the agent dialog to save the current configuration as a named config and reload it later - on the same grid or on a different one. This is the right move when: * A rule set works for one category and you want to reuse it on another. * You're iterating on parameters and want a clean restore point. * A teammate needs to run "the same thing I ran last week." ## Read the Price Analysis panel When an agent writes its result back to the grid (e.g. Dynamic Pricing's calculated price), click the result cell to open the right-side **Price Analysis** panel. It shows what produced that value - which rules applied, which were violated, what inputs were used, and how the run affected unit margin. The Price Analysis panel is the answer to "why did the agent give me this number?" ## See what's running and what ran Every agent execution shows up in the **Runs** section in the left sidebar - status, duration, who triggered it, and the output. The Runs nav icon also shows a small badge with the count of currently running jobs. If an agent fails or partially succeeds, the Runs page is where you see the error. ## Related * [Dynamic Pricing](/agents/dynamic-pricing) * [Price Optimization](/agents/price-optimization) * [Pricing Strategy](/agents/pricing-strategy) * [Product roles](/agents/product-roles) * [Price Monitoring](/agents/price-monitoring) * [Review and approve price changes](/agents/price-approval) * [Runs](/runs/overview) # Review and approve price changes Source: https://docs.retailgrid.io/agents/price-approval Review, adjust, and publish every price your pricing agents propose - with comments, filters, bulk-approve, and one-click publish. The **price approval workflow** is where proposed prices become live prices. Both [Dynamic Pricing](/agents/dynamic-pricing) and [Price Optimization](/agents/price-optimization) write their results into the grid as proposals - they don't change your live prices on their own. You review each proposal here, then publish the ones you're happy with. This keeps you in control: an agent does the heavy lifting across thousands of SKUs, and you stay the final decision-maker on every price that goes out. ## When to use it Reach for the approval workflow after any agent run that produced prices you intend to act on: * A Dynamic Pricing run wrote a new **Rules Based Price** column. * A Price Optimization run wrote **Proposed Price** recommendations. * You want a deliberate review-and-publish step rather than treating the agent output as final. ## Open the review queue After an agent run completes, switch to the **Price Approval** tab in the grid. Each row is one proposed change, with the product, its current price, the **Proposed Price**, the price change in money and percent, the margin impact, and the forecasted sales impact, so you can judge each change in context. Status chips across the top count the queue and filter it in one click: * **All** - every row in the grid. * **To Review** - proposals still waiting on you. This is where you work. * **Approved** - proposals you've accepted, staged and ready to publish. * **Rejected** - proposals you've discarded. The **Status** column on each row tells you where that row stands: **To Review**, **Accepted**, **Rejected**, or **No Change** for a row where the agent proposed the price the product already has. **No Change** rows need nothing from you. ## Sales-impact columns Each row also shows the **forecasted impact** of the proposed price as the **Δ Sales Units**, **Δ Sales Value**, **Δ Gross Profit**, and **Δ Gross Margin %** columns, over a horizon you pick from the **Forecast horizon** control (7d, 30d, 90d, or 180d). Change the horizon and the columns re-compute and re-label to match. Use it to weigh a change on its financial consequences, not just the size of the move: a small cut that lifts forecasted gross profit can be the better call over a larger one that doesn't. When you **override** a proposed price, the forecasted impact recomputes against your number, so you're weighing the effect of your own edit rather than the agent's original. See [Sales forecast & price impact](/agents/sales-forecast) for how the forecast is built. ## Run-level Impact Summary Above the queue, an **Impact Summary** band rolls the whole run up into one view, so you can judge the batch before you work through it row by row. It reports the products in scope and how the run splits them - prices **raised**, **cut**, and **unchanged** - alongside the average change and the forecasted effect on **gross profit**, **revenue**, and **units** over the selected horizon. The band reflects the **current filters and forecast horizon**: filter the queue to a category or a confidence band and the summary re-computes for just that slice, so you can size the impact of the segment you're about to approve. Use it as the sanity check before a bulk-approve - a batch that reads fine row by row can still add up to a larger move on gross profit than you intended. ## Per-row actions For each proposed change you can: * **Approve** - accept the proposed price as-is. * **Reject** - discard the proposal; the live price is left unchanged. * **Override** - replace the proposed price with a manual price of your own, then approve that. * **Comment** - leave a note on a row (why you rejected, what you overrode and why) so the decision is auditable later. ## Filter the queue Work through a large queue in deliberate passes rather than row by row. Start with the **To Review** chip, then narrow further with the grid's own column filters, which work here like anywhere else in the grid: * Sort or filter on **Price Change %** to take the biggest moves first. * Filter on **New Unit Margin % pp change** to catch changes that push margin the wrong way. * Filter on product attributes (category, brand, store) to review one segment at a time. * Filter on **Confidence** to separate the recommendations the model is sure about from the thin ones. Save a pass you repeat as a [View](/grids/views), so next month's queue opens on the same filtered slice. ## Bulk-approve Once a filtered set looks right, **bulk-approve** the whole selection in one action instead of clicking each row. A common pattern: filter to small, in-policy changes, bulk-approve them, then review the large or unusual moves individually. To let **scheduled** runs clear in-policy changes without a manual pass at all, turn on auto-approval in the [Automation tab](/agents/automation) - it approves and queues the proposals that pass the margin and price-change limits you set, and holds everything else here for review. ## Publish When your approvals are ready, click **Publish Prices**. The button carries the count of staged approvals, so it reads **Publish Prices (12)** when twelve are ready to go. Publishing applies the approved prices as the live prices on those products. From that point the new prices flow through the rest of Retailgrid - [metrics](/reference/metrics) recompute against them, and they appear in [exports](/grids/export) and the Public API. Rejected and un-reviewed proposals are not published; only the changes you approved go live. ## Common pitfalls * **Forgetting to publish** - approving a row stages it; nothing goes live until you publish. If prices didn't change, check whether you published. * **Bulk-approving without a filter** - bulk-approve acts on the current selection. Filter first so you only approve the set you actually reviewed. * **Overrides skip the agent's guardrails** - a manual override is your number, not the agent's. Double-check it against your margin floor before publishing. ## Related * [Dynamic Pricing](/agents/dynamic-pricing) * [Price Optimization](/agents/price-optimization) * [Schedule and automate pricing runs](/agents/automation) - auto-approve prices that clear your guardrails * [Sales forecast & price impact](/agents/sales-forecast) * [Agents overview](/agents/overview) * [Runs](/runs/overview) * [Metrics glossary](/reference/metrics) # Price Monitoring Source: https://docs.retailgrid.io/agents/price-monitoring Discover live competitor prices across the web with AI search and load verified results straight into your grid. **Price Monitoring** finds competitor prices for the products in a grid. It searches the web with AI, verifies what it finds, and loads the verified offers into your competitor prices, so the grid's competitor columns fill in without a scraping project or a CSV. Use it when you want competitor coverage on a grid and you do not already have a feed for it. ## When to use Price Monitoring * You are onboarding a new category and have no competitor data for it yet. * Your existing feed covers some competitors but leaves gaps you want filled. * You want a fresh read on a specific grid before a pricing pass, rather than waiting for the next scheduled delivery. If you already have a matched competitor feed and want to check it before pricing against it, that is [Competitor matching](/price-monitoring/competitor-matching) instead. Price Monitoring produces competitor prices; Competitor matching validates them. ## Run Price Monitoring In any grid: 1. Click **Agents** in the top toolbar. 2. Select **Price Monitoring** from the All Agents modal. 3. Configure the run and start it. The run executes in the background. Watch it in the [Runs](/runs/overview) section in the left sidebar; the active-runs badge on the Runs icon ticks up while it works. ## What happens during a run Verified offers publish into your competitor prices as the run progresses, not only at the end. Your grid's competitor columns are re-resolved against the newly published prices as they land, so competitor values and gaps appear during the run rather than after it. That means you can watch coverage build on a long run, and a run that is interrupted still leaves behind every offer it had already verified. ## What the agent writes The agent adds to your **Competitor prices** dataset. Nothing about your own prices changes: Price Monitoring is a data-gathering agent, not a pricing agent. Once the competitor prices are in place, the usual flow applies: * Competitor columns and price position show up in the grid. * Competitor-based rules in [Dynamic Pricing](/agents/dynamic-pricing) (Price Match, parity, undercut) have data to evaluate against. * Competitor metrics and the [Competitive Position dashboard](/dashboards/competitive-position) fill in. ## Common pitfalls * **One run per grid at a time.** Starting a second run while one is in progress is refused. Wait for the first to finish, or cancel it from [Runs](/runs/overview). * **Discovered prices are not a verified feed.** The agent verifies what it finds, but web discovery is inherently patchier than a contracted feed. Check coverage and match quality on [Competitor matching](/price-monitoring/competitor-matching) before you point pricing rules at it. * **Currency.** If the offers the agent finds are priced in a different currency from your products, configure FX in [Currency](/settings/currency) before running competitor-based rules. * **Scope the grid first.** The run covers the products in the grid you started it from. A narrower grid finishes faster and is easier to validate. ## Related * [Competitor matching](/price-monitoring/competitor-matching) * [Agents overview](/agents/overview) * [Competitor prices data spec](/data-requirements/competitors) * [Dynamic Pricing](/agents/dynamic-pricing) * [Runs](/runs/overview) # Price Optimization Source: https://docs.retailgrid.io/agents/price-optimization AI-powered optimal price recommendations using elasticity and demand signals - suggested prices, not written rules. **Price Optimization** suggests an optimal price per SKU using your historical sales data, competitor prices (if available), and a demand model. Use it when you want the system to recommend prices instead of writing the rules yourself. Unlike [Dynamic Pricing](/agents/dynamic-pricing), Price Optimization is suggestion-driven: it looks at how your prices have moved and how units have responded, and picks the price that maximizes a goal you set. ## When to use Price Optimization Reach for Price Optimization when: * You have at least a few weeks of transaction history per SKU - the model needs signal. * You want a goal-based recommendation rather than a deterministic rule output. * You want to try "what if we re-priced this category to maximize margin?" without committing to specific guardrails. Use Dynamic Pricing instead when you have explicit constraints (margin floors, competitor parity, rounding patterns) you want enforced consistently. The 8 rule types behind those constraints - which also run as this workflow's **Apply Pricing Rules** step - are explained for business users in the [Pricing rules guide](/agents/pricing-rules-guide). ## Open Price Optimization In any grid: 1. Click **Agents** in the top toolbar (or the pinned **Price Optimization** button if you have one). 2. Select **Price Optimization** from the All Agents modal. 3. The configuration dialog opens with a **Config by AI** panel beside three tabs - **General**, **Source**, and **Automation** - and a **Workflow** panel on the right that shows the run pipeline: Optimize Prices → Apply Pricing Rules → Price Approval. The dialog is a short wizard: fill each tab and use **Next** to move through them, then run the optimization from the final step. Price Optimization - General tab with optimization goal and expected-impact timeframe ## Configure by AI - describe what you want in plain language The **Config by AI** panel sits beside the configuration tabs as a chat-style assistant, usable at any point. Use it when you know the outcome you want but not which knobs to turn. * **Prompt** - describe your goal in 1-3 sentences. Example: *"Maximize profit across the KVI products without dropping margin too far or moving prices more than 10%."* * **Run AI** - generates values into the configuration tabs. AI output is a starting point. Review the generated configuration before running - the AI gives a structured guess, not a final answer. Because Config by AI is independent of the tabs, you can keep editing the configuration manually after the AI fills it in - the two surfaces don't lock each other out. ## Configure across three tabs The tabs can be filled in order using **Next**. **Config by AI** lives beside the tabs and can be used at any point. ### General - goal, overrides, and timeframe * **Optimization Goal** - **Maximize Profit** (prioritize margin and bottom-line return) or **Maximize Revenue** (drive top-line growth and sales volume). * **Manual price overrides** - **Preserve manually updated prices** controls how prices you edited by hand are treated in the run. With it on, those prices stay as they are and the optimizer skips them instead of proposing a new price. Turn it on when you have hand-set prices you don't want overwritten. * **Expected Impact Timeframe** - how far ahead the optimizer projects the impact of the new prices when scoring them (e.g. Next 30 days). **Looking for confidence tiers and thresholds?** Those are account-level demand model parameters, not per-run settings. They live in **Settings → Modelling**, along with clustering, recommendation corridors, seasonality, and model freshness. See [Modelling](/settings/modelling). The tab is Admin only, because a change there affects every future run and recalculation on the account. ### Source - scope and inputs * **Scope** - run against the **Full grid** or a **Sample** of rows to validate the setup before a full run. * **Inputs** - map the grid columns the model reads: **Price** (current selling price) and **Cost** (cost per unit). Historical sales/transactions provide the demand signal the model fits, and competitor prices (if present) let the model account for competitive position. Without sales history the model has nothing to fit; without Cost the model can't compute margin-based goals. ## Read the result After a run, each recommendation lands in its own cell in the output grid - one value per cell, so recommendations are easy to scan and act on row by row. Per recommendation you can: * Set the **price type** the recommended price should apply as (e.g. regular or promotional). * Set the **effective date** the price should take effect. Recommendations land in the grid's **Price Approval** tab as a **Proposed Price** per row, with supporting columns giving the context behind each one: * **Proposed Price** - the recommended price per SKU. * **Price Change** and **Price Change %** - how far the recommendation moves the current price. * **New Unit Margin %**, **New Unit Margin % pp change**, and **Unit Margin % Change (%)** - the margin at the recommended price, and the change against the starting margin. * **Confidence** - High, Medium, or Low, reflecting how strong the model's signal was for that SKU. * **Elasticity Tier** and **Elasticity** - which fit the price response came from, and the coefficient used. **Item** is a fit on that SKU's own history and is the strongest; **Group** borrows from a cluster of similar SKUs; seasonal variants (for example **Item Low Season**) fit a specific part of the year; **Category Default** is the fallback where a SKU has too little signal for any of the above. The tier is your quickest read on how much evidence sits behind a recommendation. * **Δ Sales Units**, **Δ Sales Value**, **Δ Gross Profit**, and **Δ Gross Margin %** - the forecasted impact of the change over the selected horizon. See [Sales forecast and price impact](/agents/sales-forecast). Click any **Proposed Price** cell to open the right-side **Price Analysis** panel - same panel pattern as Dynamic Pricing - which explains *why* that price was proposed (current price, proposed price, elasticity used, expected impact). When you're happy with a recommendation, send it through the [price approval workflow](/agents/price-approval) to review and publish it. ## Common pitfalls * **Sparse sales history** - SKUs with very few transactions get low confidence and small recommended changes. That's intentional. For SKUs too thin to model on their own, the optimizer borrows demand signal from a cluster of similar SKUs rather than dropping out, so sparse items still get a sensible, lower-confidence suggestion. * **Recommendations go through approval, not straight to live** - Price Optimization proposes prices; you review, adjust, and publish them through the [price approval workflow](/agents/price-approval). Nothing goes live without your sign-off. * **Goal mismatch** - if you set Goal = Revenue, expect the model to drop prices on elastic SKUs. If you set Goal = Profit, expect the opposite. Pick the goal that matches the decision you're making. ## Related * [Agents overview](/agents/overview) * [Dynamic Pricing](/agents/dynamic-pricing) * [Pricing rules guide](/agents/pricing-rules-guide) - the 8 rule types with worked examples * [Modelling](/settings/modelling) - the account-level demand model parameters behind the recommendations * [Review and approve price changes](/agents/price-approval) * [Sales / Transactions data requirements](/data-requirements/sales) * [Metrics glossary](/reference/metrics) * [Runs](/runs/overview) # Pricing rules guide Source: https://docs.retailgrid.io/agents/pricing-rules-guide Business-user walkthrough of the 8 Dynamic Pricing rule types with parameters and worked examples This guide explains the 8 rule types behind [Dynamic Pricing](/agents/dynamic-pricing) for the people who own the pricing decisions - category managers, pricing analysts, commercial leads. For each rule: what it does in plain language, the business value, every parameter, and a worked example. This is the business-oriented companion to the [Dynamic Pricing reference](/agents/dynamic-pricing), which covers the full technical detail - corridor mechanics, fallback behavior, output columns, and the Price Analysis panel. The same rules also run as the **Apply Pricing Rules** step in the [Price Optimization](/agents/price-optimization) workflow, acting as guardrails on the model's recommended prices. ## How the pricing agent thinks Four ideas explain everything else. Rules don't fight each other - each one narrows a **price corridor**, and the final price is picked from what's left. Every rule translates into a range of acceptable prices. "Margin at least 25%" is a corridor. "Match the competitor ±2%" is a corridor. "Don't touch this price" is a corridor of exactly one point. Rules run by their **Priority** number (1 = highest, max 50). Each rule intersects its corridor with what previous rules left. The visual order in the Rules tab is cosmetic - only Priority matters. After all rules run, the final price is the point inside the surviving corridor **closest to the current price**. The agent moves prices as little as it can while satisfying every rule. Any rule can be limited to specific rows with **Conditions** - column + operator + value filters combined with AND. In SKU-Store view, store attributes (country, channel, pricing zone) work as condition columns too. When a rule's corridor doesn't overlap what's left, the rule is **violated**. With the default fallback (Follow priority) it's flagged and the calculation continues; with Return error the row halts instead. Violations are information, not failures - they show you where two rules encode conflicting intent. ## The 8 rule types ### 1. Fixed Price - lock a price exactly where it is Locks every matching row to its current price. No later rule can move it. **Why you'd use it:** some prices aren't the algorithm's to change - items on a printed flyer, prices agreed with a supplier, regulated SKUs, manually approved prices. Fixed Price lets you automate the rest of the assortment with full confidence these stay put. | Setting | What it means | | :- | :- | | - (none) | No parameters of its own. Control *which* rows it locks through **Conditions**. | **Example - grocery.** The weekly flyer advertises private-label olive oil at €6.99. Competitors sell at €7.49 and your margin rule would push the price up - but the flyer is printed. `Fixed Price · Priority 1 · Conditions: on_promo IS TRUE` keeps every promo SKU exactly where it is while the rest of the category reprices normally. Later rules that target these rows will show as violated - that's the audit trail telling you the lock won. ### 2. Price Change Limit - guardrails on movement Caps how far other rules can move a price (**Maximum**), or forbids changes smaller than a threshold (**Minimum** - the "dead zone"). **Why you'd use it:** price stability is customer trust. A shopper who saw €49.99 yesterday and €64.99 today feels cheated, and repricing thousands of SKUs by two cents creates operational noise for nothing. Change limits keep automation inside boundaries your commercial team can defend. | Setting | What it means | | :- | :- | | Limit Type | **Maximum** caps total movement: corridor `[current - decrease, current + increase]`. **Minimum** forbids changes smaller than the threshold. | | Increase | Percent or Absolute, with a value - how far the price may rise (Maximum) or the smallest allowed rise (Minimum). | | Decrease | Percent or Absolute, with a value - the same for downward movement. | **Example - electronics.** A 55" TV sells at €499. Competitor matching runs daily, but a rival's flash sale shouldn't drag your price off a cliff overnight. `Maximum · Increase 5% · Decrease 5%` keeps today's price inside €474.05 - €523.95; if the market really moved, the price walks there over several runs. A second rule, `Minimum · Absolute 0.10`, tells the agent not to bother with changes under 10 cents. ### 3. Price Change - actively move prices Adjusts matching prices by a percent, a fixed amount, or a value read from another column. The adjustment is always computed from the **current price** (the base), never from what earlier rules produced - so stacked rules stay predictable. **Why you'd use it:** markdowns, cost pass-throughs, and campaigns without a spreadsheet - one rule, thousands of rows, fully auditable. | Setting | What it means | | :- | :- | | Value Type | **Percent** relative change · **Absolute** fixed amount · **Column** per-row amount from a numeric column. | | Value | The number itself (`-20` for a 20% markdown, `0.50` for +50 cents) or the column to read. Negative decreases, positive increases. | **Example - fashion.** End of summer: everything tagged `season = SS26` with more than 12 weeks of stock cover goes 20% off. `Percent · -20 · Conditions: season = SS26 AND stock_cover_weeks > 12` takes the €59.00 linen shirt to €47.20 and the €89.00 midi dress to €71.20. Pair it with a Margin Rule so no markdown cuts through the floor, and Price Rounding to land on .95 endings. ### 4. Price Match - position against a reference Aligns your price to a **reference column** - typically a competitor price, but any numeric column works. **Equals** matches it exactly; **Between** defines a positioning band around it. You can also apply an optional **reference adjustment** first - multiply the reference by a factor, or shift it by a percent or a fixed amount - so you position against a derived value rather than the raw column. **Why you'd use it:** on the key-value items that shape your price image, "never more than 3% above the cheapest competitor" is worth more than any across-the-board discount - shoppers judge the whole store by those SKUs. The band keeps you competitive without following every competitor move to the cent. The reference adjustment handles the cases where the target is *derived* from a column rather than the column itself - for example "80% of Regular Price when stock is low." | Setting | What it means | | :- | :- | | Reference Column | The numeric column to position against - e.g. `cheapest_competitor`, `market_median`, or your own MSRP column. | | Reference adjustment | *(optional)* Transform the reference **before** matching: **multiply** by a factor, or add/subtract a **percent** or a fixed **absolute** amount. Leave it off to use the reference as-is. | | Operator | **Equals** match exactly · **Between** stay inside a band. | | From / To | With Between: percent bounds below and above the reference. `From 2, To 3` = anywhere from 2% below to 3% above. | **Example - electronics.** Your KVI list must track the market. The cheapest competitor sells the bestselling coffee machine at €179.00. `Between · From 2 · To 3 · Conditions: role = KVI` gives a corridor of €175.42 - €184.37. Your current €189.00 sits above the band, so the agent pulls it to €184.37 - and rounding lands it on €183.99. **Example - stock-aware markdown.** You want clearance pricing on low-stock lines: set the price to 80% of Regular Price when fewer than four units remain. `Price Match · Reference Column: regular_price · Reference adjustment: multiply × 0.8 · Equals · Conditions: stock < 4` positions each qualifying SKU at 80% of its own regular price. ### 5. Sync Prices - one price for the whole family Forces all rows sharing a value in a **group column** to end at one identical final price. **Why you'd use it:** nothing erodes trust faster than the strawberry yogurt costing more than the vanilla next to it, or the same t-shirt priced differently per color. Variant families are also where manual pricing errors hide. Sync makes consistency automatic. | Setting | What it means | | :- | :- | | Group Column | A text or categorical column whose shared value defines the family - e.g. `product_family`, `style_code`. | The shared price is chosen by a cascade: the **mode** if two or more members already share a current price, otherwise the **highest-selling member's price** (when a `sales_units` column exists), otherwise the **median**. If other rules leave the group's corridors only partly overlapping, the engine drops outlier SKUs from the sync and prices them individually rather than breaking the whole group. **Example - grocery.** Six flavors of own-brand 150g yogurt drifted apart over months of one-off edits: four at €1.19, one at €1.15, one at €1.29. `Sync Prices · Group Column: product_line` finds the mode - €1.19 - and all six flavors land there in one run. ### 6. Margin Rule - protect profitability Constrains price from **cost** via a margin or markup target - exactly, or as a range. Usually the non-negotiable floor under everything else. **Why you'd use it:** competitor matching and markdowns are only safe when something guarantees you're still making money. A margin floor means the agent can chase the market on Monday and you can still explain the P\&L on Friday. | Setting | What it means | | :- | :- | | Margin Type | **Margin** profit as % of selling price · **Markup** profit as % of cost. A 25% margin ≈ a 33% markup - pick the one your team reports in. | | Constraint | **Exact** hit the target precisely · **Between** stay inside a From-To range (this is how you build a floor). | | Value Source | **Percent** · **Absolute** (a fixed amount on top of cost) · **Column** (a per-row percent, e.g. per-category targets; Column requires Constraint = Exact). | | Value / From-To | The target number, or the range bounds with Between. | The boundary math: margin percent gives `cost / (1 - T)`, markup percent gives `cost * (1 + T)`, absolute gives `cost + value` (with `T = value / 100`). **Example - pet supplies.** Premium dog food costs €18.00 a bag; category policy is margin between 25% and 45%. That's a corridor of €24.00 - €32.73. Competitor matching can roam inside the band - but a rival at €22.99 won't drag you below €24.00; the match rule shows as violated and the floor holds. ### 7. Price Rounding - land on the endings customers expect Forces final prices onto allowed endings - .99, .95, whole euros, whatever your price image calls for. Rounding is special: it **always runs after every other rule**, regardless of its position in the priority list. **Why you'd use it:** a shelf full of €12.37 and €8.42 quietly damages price perception. Charm endings are among the best-documented effects in retail, and different endings can signal different things - .99 for value lines, clean numbers for premium. Rounding makes automated prices look deliberately set by a merchant. | Setting | What it means | | :- | :- | | Price Scope | All prices, or a price-range filter (Equals / Between) - so cheap and expensive items can follow different conventions. | | Pattern | Comma-separated allowed endings, e.g. `*.99, *.95`. | | Excluded endings | Patterns to avoid, e.g. `*.00`. | | Rounding Direction | **Up** · **Down** · **Closest** - which valid ending to move to, always staying inside the feasible range. | **Example - home & DIY.** Two rounding rules split the assortment: under €100, `Pattern *.99, *.95 · Closest` turns a computed €12.78 into €12.95; from €100 up, `Pattern *9.00 · Down` turns €342.60 into €339.00. Budget items get charm endings; big-ticket items get clean merchant numbers. ### 8. Price Relations - keep a deliberate price ladder Where Sync makes a group identical, Price Relations keeps a group **deliberately different** - maintaining a structured gap between members, on the shelf price or on **unit price** (per kg / per litre). **Why you'd use it:** good-better-best only works if the gaps tell the story - if "better" creeps within 50 cents of "best", the ladder collapses. Same for pack architecture: the 1kg bag must beat the 500g bag per kilo, always. Price Relations encodes that merchandising logic so no individual reprice can break it. Comparing within several attributes keeps only genuinely comparable products in one chain, and a gap *range* lets the ladder breathe instead of forcing a single exact step. | Setting | What it means | | :- | :- | | Group Column | The column defining the ladder - e.g. `ladder_group`, `pack_family`. | | Compare within | *(optional)* Extra attributes that ladder members must **also** share - e.g. `model`, `storage`, `colour`. Add these when one group column is too loose and unrelated products would otherwise land in the same chain. Up to 10 attributes; they must differ from the group column. | | Price Type | **Price** the shelf price · **Unit Price** per unit of measure - the right choice for pack-size families. | | Relationship Type | **One by one** each item steps down from the previous · **Same gap** every member holds a consistent gap from the anchor. | | Gap Value Type + Value | Percent or Absolute, plus the gap size (e.g. 15% or €5.00). Set a **maximum** alongside the base gap to allow a **range** rather than one exact gap. | | Anchor Logic | **Highest priced** the most expensive member anchors the ladder · **Explicit item** you name the anchor SKU. | Before you run, **preview** the ladder from the rule dialog: it shows the resulting chain and the gap each member lands at, so you can catch a mis-grouped SKU before it reaches a price. **Example - fashion.** A t-shirt wall runs good-better-best, anchored by the premium pima tee at €39.90. `One by one · Percent 25 · Anchor: Highest priced` steps the ladder to €29.93 and €22.44; rounding polishes those to €29.95 and €22.45. When the anchor moves - a markdown, a cost change - the whole ladder follows automatically. ## One SKU through four rules Real strategies stack rules. A 55" TV - current price €499.00, cost €380.00, cheapest competitor €479.00 - through a typical electronics strategy: | Priority | Rule | Corridor it produces | Running feasible range | | :- | :- | :- | :- | | 1 | Margin Rule - margin between 15% and 60% | €447.06 - €950.00 | €447.06 - €950.00 | | 2 | Price Match - cheapest\_competitor, Between -2% / +3% | €469.42 - €493.37 | €469.42 - €493.37 | | 3 | Price Change Limit - Maximum ±5% | €474.05 - €523.95 | €474.05 - €493.37 | | 4 | Price Rounding - `*.99`, Closest *(always runs last)* | allowed endings only | final: **€492.99** | The point in the feasible range closest to the current €499 is the upper edge, €493.37; rounding takes it to the nearest allowed ending inside the range, €492.99. Result: €6 sharper than yesterday, within 3% of the cheapest competitor, 22.9% margin, a shelf-friendly ending - and every step visible in the Price Analysis panel. ## Tips for building strategies * **Order by how negotiable a rule is.** Locks first (Fixed Price), then hard constraints (Margin floor, Change Limit), then targets (Price Match, Price Change, Sync, Relations). Rounding can sit anywhere - it always runs last. * **Test on the sample first.** Run against **Sample 10 Rows** (Parameters tab) and check the Price Analysis panel on a few rows before committing to the full dataset. * **Violations are information.** Recurring violations usually mean two rules encode conflicting intent. Reserve **Return error** for true red lines. * **Use Conditions generously.** The same Margin Rule with different Conditions becomes a per-category margin policy; in SKU-Store view, store-attribute conditions give you country- or channel-specific strategies. * **Set Priority explicitly.** When a result surprises you, check the Priority numbers before anything else. ## Related * [Dynamic Pricing](/agents/dynamic-pricing) - full technical reference for the rules engine * [Price Optimization](/agents/price-optimization) - model-recommended prices, with rules as guardrails * [Review and approve price changes](/agents/price-approval) * [Agents overview](/agents/overview) # Pricing Strategy Source: https://docs.retailgrid.io/agents/pricing-strategy Generate an AI-powered pricing strategy presentation directly from your retail data, ready to share with stakeholders. **Pricing Strategy** is an analytics agent that reads your **Account** and the products in a grid, queries your real transactional data over a chosen reporting period, and generates a pricing-strategy presentation. The output is a shareable narrative - portfolio overview, category roles, pricing pressure, and proposed moves - grounded in your actual data, not a templated demo. ## When to use Pricing Strategy Reach for Pricing Strategy when you need a stakeholder-facing narrative on top of your portfolio: * Quarterly business reviews and board decks. * Opening conversations with retailers - explain where the pricing levers are before you propose specific changes. * Cross-functional alignment - give Merchandising, Finance, and Leadership a shared picture of how the portfolio is priced today. * Pre-sales storytelling - turn raw data into a recognizable strategy frame in minutes. If you instead want concrete per-SKU prices to apply, use [Dynamic Pricing](/agents/dynamic-pricing) (rule-driven) or [Price Optimization](/agents/price-optimization) (AI-suggested). ## Run Pricing Strategy In any grid: 1. Click **Agents** in the top toolbar. 2. Switch to the **Analytics Agents** tab in the All Agents modal. 3. Select **Pricing Strategy**. 4. On the **Parameters** tab, pick a **Reporting period** and an **Export format** (see below). 5. Click **Generate**. The agent runs in the background. Watch progress in the [Runs](/runs/overview) section in the left sidebar; the active-runs badge on the Runs icon will tick up while it's working. Previously generated decks are listed on the **Generated Presentations** tab of the same dialog. Pricing Strategy - Parameters tab with Reporting period and Export format ## Choose a reporting period The **Reporting period** sets the window of data the AI strategy analyzes. Pick the one that matches the audience and the question you're answering: | Reporting period | Good for | | :- | :- | | **Yesterday** | Reacting to a same-day promo or competitor move. | | **Last 7 Days** | Reacting to a recent promo or competitor move. | | **Last 30 Days** | Monthly category review. | | **Last 90 Days** | Quarter-in-progress check, supplier conversations. | | **Last 180 Days** | Half-year trend view. | | **Last Year** | Full-year executive reviews. | | **Last 5 Years** | Long-run structural analysis. | | **All Time** | Everything on record. | | **Custom range** | Pick exact start and end dates - useful for promo windows or pre/post comparisons. | The agent only sees transactions inside the selected window. Pick a window with enough volume to support the analysis - very short windows on small product sets produce thin decks. ## Choose an export format The **Export format** controls how the finished presentation is delivered for download (e.g. PDF). Pick the format that suits how you'll share the deck. ## What the agent reads Beyond the reporting period and export format, no manual configuration is required. Pricing Strategy automatically pulls: * **Company name** - from your [Account](/settings/account). Appears on the deck cover and throughout the narrative. Set it before running if your account is freshly provisioned. * **Products and attributes** - the grid you ran the agent from (categories, prices, costs, custom attributes). * **Transactional data** - real sales over the chosen reporting period, queried directly from the analytics warehouse. No placeholder numbers; if a category has no transactions in the window, it shows as zero rather than a synthesized value. ## Output When the run completes, a **Download** button appears on the run progress card, and the deck is also listed on the **Generated Presentations** tab and in the [Runs](/runs/overview) detail view. The presentation is a complete strategy narrative covering: * Portfolio overview (catalog size, revenue, average price, margin band). * Category roles (Traffic Drivers, Profit Drivers, Niche, Promo - inferred from price elasticity proxies and contribution). See [Product roles](/agents/product-roles). * Pricing pressure (where competitor or margin signals suggest the most movement is needed). * Proposed strategic moves with expected directional impact. Re-running Pricing Strategy generates a new presentation rather than overwriting the previous one. ## Common pitfalls * **Empty window** - if there are no transactions in the chosen reporting period, the deck is sparse. Pick a wider window or confirm your transactional data has loaded. * **Account name unset** - the deck cover and narrative will reference a default placeholder. Set the company name on the [Account](/settings/account) page first. * **Very small product set** - the category-role analysis needs enough SKUs per category to be meaningful. Decks generated against fewer than \~20 products tend to read more like a per-SKU summary than a strategy. ## Related * [Agents overview](/agents/overview) * [Runs](/runs/overview) * [Account](/settings/account) * [Dynamic Pricing](/agents/dynamic-pricing) * [Price Optimization](/agents/price-optimization) # Product roles Source: https://docs.retailgrid.io/agents/product-roles Classify every SKU into a product role - KVI, Traffic Driver, Profit Driver, Niche, Tail, or New - so pricing and monitoring treat priority SKUs differently. A **product role** says what a SKU *does* for your business - whether it drives footfall, carries margin, or just rounds out the range. Retailgrid can classify every SKU into a role automatically, so your pricing rules, promo planning, and competitor monitoring can act differently on the SKUs that actually matter - without you maintaining role tags by hand. ## The roles Every SKU gets one of six roles, or sits in **Unclassified** until there's enough signal to place it: | Role | What it is | | :- | :- | | **KVI** (Known Value Item) | Price-sensitive items shoppers recognize and use to judge whether you're cheap. Watch these against competitors closely. | | **Traffic Driver** | High-visibility items that pull shoppers in. Priced to compete, not to maximize margin. | | **Profit Driver** | Items that carry your margin. Less price-sensitive, so there's room to hold or lift price. | | **Niche** | Specialist items with a narrow, loyal audience and little competitive pressure. | | **Tail** | Long-tail, low-volume items that fill out the assortment. | | **New** | Recently introduced items without enough sales history to classify yet. | | **Unclassified** | Items the classifier can't place into a role yet - as opposed to New, which is specifically newly introduced items. | ## Classify your assortment The **Product Role Classification** agent assigns a role to every SKU in a grid: 1. Click **Agents** in the grid toolbar. 2. Select **Product Role Classification** from the All Agents modal. 3. Run it. The agent reads each SKU's sales velocity, basket frequency, gross-profit rank, and age signals and writes a **Product Role** column back to the grid, shown as a colored single-select badge (KVI, Traffic Driver, Profit Driver, and so on). The supporting columns - **Role Confidence**, **Role Source**, and **Product Role Details** - are hidden by default; show them from the **Columns** panel when you want the detail. The defaults work out of the box. For teams that want to tune how aggressively SKUs fall into each role, an **Advanced Settings** panel exposes the classification thresholds, each with sensible bounds. Roles are a starting point, not a verdict. To change a role, click the **Product Role** cell and pick a different value from the single-select - your choice overrides the agent's and **pins** the role on that SKU. A pinned role sticks through re-classification and is kept in an audit trail, so a later run won't quietly overwrite it. Because the role is a normal grid column, you can filter, group, and color by it like any other. ## How roles are used * **Pricing rules** - scope a [Dynamic Pricing](/agents/dynamic-pricing) rule to a role using conditions (a tighter competitor-parity rule for KVIs, a firmer margin floor for Profit Drivers), or use the role column as the group in a Price Relations rule. * **Pricing Strategy** - the [Pricing Strategy](/agents/pricing-strategy) deck frames its category-role narrative around these roles. * **Monitoring** - filter a grid to KVIs or Traffic Drivers to watch the SKUs where price position matters most. ## Common pitfalls * **Thin history** - SKUs with little sales history land in **New** or **Unclassified** until there's enough signal. Re-run once more data has loaded. * **Roles drift** - an item's role can change as demand shifts. Re-classify periodically rather than treating a one-time run as permanent. * **Review before you act** - the classifier gives a structured guess. Confirm the roles on your highest-impact SKUs, and pin the ones you're sure of, before you scope pricing rules to them. ## Related * [Agents overview](/agents/overview) * [Dynamic Pricing](/agents/dynamic-pricing) * [Pricing Strategy](/agents/pricing-strategy) * [Add columns](/grids/manage-columns) # Sales forecast & price impact Source: https://docs.retailgrid.io/agents/sales-forecast See the forecasted units, value, gross profit, and margin impact of a price change - over 7, 30, 90, or 180 days - before you approve it. Retailgrid forecasts how a product will sell at both its current price and a proposed price, so you can see the financial consequences of a price change before you commit to it. The forecast shows up where you make pricing decisions - most visibly in the [Price Approval](/agents/price-approval) queue. ## What it estimates For each product and horizon, the forecast projects: * **Units** - expected quantity sold. * **Value** - expected sales value (revenue). * **Gross profit** - expected margin in money. * **Margin %** - expected margin rate. Each is projected at the **current price** and at the **proposed price**, so the difference is the estimated impact of the change. The impact is reported as the change on each measure - with the margin change shown in **percentage points**. Forecasts are computed both catalog-wide and per grid/store. ## Horizons Pick the horizon that matches the decision: **7, 30, 90, or 180 days**. Short horizons react to recent momentum; longer horizons smooth over week-to-week noise and suit structural pricing calls. ## Where it appears * **Price Approval** - each proposed price shows its forecasted units, value, gross profit, and margin over the selected horizon, so you can judge a change on its financial impact, not just its size. See [Review and approve price changes](/agents/price-approval). ## What drives it The forecast is built on each item's recent sales rate (base demand), its price sensitivity (elasticity), and seasonality - the same demand signals behind [Price Optimization](/agents/price-optimization). Price response is bounded to a plausible range, so an unusually large fitted elasticity on a thin-history item can't inflate the projected impact. Items with sparse history borrow demand signal from a cluster of similar items rather than dropping out. Every forecast row also carries a **confidence** level - High, Medium, or Low - so you can see at a glance how much to trust it. ## Common pitfalls * **Blank or skipped forecast** - a row can come back without numbers when there's **no recommendation**, **low confidence**, **missing inputs**, or **no recent sales** (a dormant SKU) for that item. That's signal about data coverage, not a bug. * **Very short horizons on sparse items** - a 7-day forecast on a slow seller is noisy; widen the horizon. * **Treating it as exact** - use the forecast to compare options and size impact, not as a promise of a specific number. Read the confidence level alongside the numbers. ## Related * [Review and approve price changes](/agents/price-approval) * [Price Optimization](/agents/price-optimization) * [Metrics glossary](/reference/metrics) # List approved prices Source: https://docs.retailgrid.io/api-reference/approved-prices/list GET https://clientapi.retailgrid.io/v1/approved_prices List the finalized prices approved in Price Approval, for syncing into your ERP, webshop, or POS. Returns the prices your team **approved** in the [Price Approval](/agents/price-approval) queue - the finalized decisions, not raw agent proposals. Use it to pull approved prices into your ERP, webshop, or POS on a schedule instead of exporting by hand. One row is returned per approved price. When a grid is in SKU-Store view, each approval carries its own `store_id`. ## Authentication Bearer token. See [Authentication](/api-reference/authentication). ## Query parameters All parameters are optional. With none set, the endpoint returns your organization's **current pending exports** - the approved prices that have not yet been acknowledged as processed downstream - newest first. Pass `history=true` to read the full registered history instead. | Name | Type | Description | | :- | :- | :- | | `history` | boolean | Default `false`. When `false`, only current pending exports are returned. Set `true` to include the registered history, including cancelled versions. | | `limit` | integer | Page size, default `100`, max `500`. | | `cursor` | string | Opaque pagination cursor returned as `next_cursor` by a previous response. | | `grid_id` | string (UUID) | Only prices approved on this grid. | | `store_id` | string (UUID) | Only prices for this store (SKU-Store approvals). | | `item_id` | string | Filter to a single SKU-level identifier. | | `sku` | string | Filter to a single SKU code. | | `approved_after` | string (ISO-8601, UTC) | Only prices approved at or after this timestamp. | | `approved_before` | string (ISO-8601, UTC) | Only prices approved at or before this timestamp. | | `effective_after` | string (YYYY-MM-DD) | Only prices whose effective date is on or after this date. | | `effective_before` | string (YYYY-MM-DD) | Only prices whose effective date is on or before this date. | ## Response ```json theme={null} { "items": [ { "id": 91422, "grid_id": "6b1e9c40-2f0a-4d8e-9d21-2f6a1c4b7e10", "grid_item_id": 5567, "item_id": "SKU-12345", "sku": "SKU-12345", "store_id": null, "current_price": "1.49", "proposed_price": "1.59", "approved_price": "1.55", "effective_date": "2026-07-01", "currency_code": "EUR", "proposal_source": "po", "source": "user_defined", "trace": [], "approved_at": "2026-06-28T14:12:03Z", "approved_by_user_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b" } ], "next_cursor": null } ``` ### Response fields | Field | Type | Description | | :- | :- | :- | | `id` | integer | Retailgrid's internal id for the approved-price row. | | `grid_id` | string (UUID) | The grid the price was approved on. | | `grid_item_id` | integer | Internal id of the item within that grid. | | `item_id` | string \| null | Your SKU-level identifier, when known. | | `sku` | string \| null | SKU code, when known. | | `store_id` | string (UUID) \| null | The store, for SKU-Store approvals; `null` for product-level. | | `current_price` | string \| null | The live price at the time of approval. | | `proposed_price` | string | The price the agent proposed. | | `approved_price` | string | The final approved price - differs from `proposed_price` when it was overridden. | | `effective_date` | string (date) \| null | When the approved price takes effect. | | `currency_code` | string | ISO 4217 currency of the prices. | | `proposal_source` | string | Which run produced the proposal: `dp` (Dynamic Pricing), `po` (Price Optimization), or `po_dp` (both). | | `source` | string | Where the approved price came from: `dp`, `po`, `po_dp`, or `user_defined` (a manual override). | | `trace` | array | Per-source breakdown behind the price (`dp` / `po`). | | `approved_at` | string (datetime, UTC) | When the price was approved. | | `approved_by_user_id` | string (UUID) | The user who approved it. | Money values are returned as **strings** - see [Conventions](/api-reference/conventions). ## Example request ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/approved_prices?approved_after=2026-07-01T00:00:00Z&limit=500" \ -H "Authorization: Bearer rg_live_xxx" ``` Page through results by passing the `next_cursor` from each response back as `cursor` until it comes back `null`. ## Acknowledge what you processed Because the default read returns only **pending** exports, a price stays in that set until you tell Retailgrid you've applied it. Confirm a batch with: ```bash theme={null} curl -s -X POST "https://clientapi.retailgrid.io/v1/approved_prices/acknowledge" \ -H "Authorization: Bearer rg_live_xxx" \ -H "Content-Type: application/json" \ -d '{"approved_price_ids": [91422, 91423]}' ``` Pass the `id` values from a previous list response, up to **500** per call. Acknowledged prices drop out of the default pending read, so a scheduled integration can loop list → apply → acknowledge without tracking a cursor or a watermark of its own. A `409` means the batch conflicts with the current state of those exports (for example one was cancelled or already acknowledged). Re-read the ids and retry with the ones still pending rather than retrying the same batch. ## Related * [Review and approve price changes](/agents/price-approval) * [Get grid rows](/api-reference/grids/get-rows) * [Conventions](/api-reference/conventions) * [Errors](/api-reference/errors) # Acknowledge Approved Prices Source: https://docs.retailgrid.io/api-reference/approved_prices/acknowledge-approved-prices https://clientapi.retailgrid.io/openapi.json post /v1/approved_prices/acknowledge Confirm successful downstream processing of up to 500 exact approval IDs. # List Approved Prices Source: https://docs.retailgrid.io/api-reference/approved_prices/list-approved-prices https://clientapi.retailgrid.io/openapi.json get /v1/approved_prices List approved prices for the authenticated caller's organization. # Verify API key Source: https://docs.retailgrid.io/api-reference/auth/verify GET /v1/auth/verify Validate a Retailgrid API key and confirm the authenticated account, environment, and key permissions before making other API calls. # Verify Api Key Source: https://docs.retailgrid.io/api-reference/auth/verify-api-key https://clientapi.retailgrid.io/openapi.json get /v1/auth/verify # Authentication Source: https://docs.retailgrid.io/api-reference/authentication How to issue an API key and authenticate requests against the Retailgrid Public API. The Retailgrid Public API uses bearer tokens. Each request must include an `Authorization` header with your API key. ```http theme={null} Authorization: Bearer rg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` API keys are prefixed `rg_live_` and are scoped to a single Retailgrid account. ## Generate an API key 1. Sign in to [app.retailgrid.io](https://app.retailgrid.io). 2. Open **Settings → Integrations**. 3. Find the **API access** card and click **Generate API Key**. 4. Copy the key from the modal. The key is shown **only once**, at the moment of creation. Retailgrid does not store the plaintext value, so it cannot be retrieved later. Save it to your secret manager immediately. Each account can have **one active key at a time**. Generating a new key replaces the previous one - existing integrations using the old key will start receiving `401 Unauthorized` and need to be updated. ## Verify a key The fastest way to check that a key works is to call `GET /v1/auth/verify`: ```bash theme={null} curl https://clientapi.retailgrid.io/v1/auth/verify \ -H "Authorization: Bearer $RETAILGRID_API_KEY" ``` A successful response confirms the key is valid and tells you which user and key id it belongs to: ```json theme={null} { "ok": true, "user_id": "f1c7e0b8-4a2b-4d0e-9a8b-2c3d4e5f6a7b", "api_key_id": "9f1c7e0b-4a2b-4d0e-9a8b-2c3d4e5f6a7b" } ``` See the full reference on the [Verify API key](/api-reference/auth/verify) page. ## Failure modes | Status | Meaning | | - | - | | `401 Unauthorized` | Header missing, malformed, or the key is no longer active (rotated, revoked, or deleted). | | `403 Forbidden` | Key is valid but does not have access to the requested resource. | ## Security guidance * **Never commit keys to source control.** Use environment variables, a secrets manager, or your CI's encrypted variables. * **Rotate on suspicion.** Generating a new key in the UI immediately invalidates the previous one. There is no separate revoke step. * **Use a dedicated key per integration** where possible - because each account holds one active key today, plan key rotations to align with deploys of dependent services. * **Treat keys like passwords.** A leaked key gives full read/write access to your Retailgrid catalog and transactions. ## Need help? If a key stops working unexpectedly or you need a key reset for an account you no longer have UI access to, contact [support](https://retailgrid.io/contact-us). # API changelog Source: https://docs.retailgrid.io/api-reference/changelog Release notes for the Retailgrid Public API, including new endpoints, breaking changes, and version updates across each release. This changelog tracks changes to the Retailgrid Public API only. For product-wide changes, see the [main changelog](/changelog/product-updates). ## 2026-07-01 - Approved prices: read and acknowledge Finalized pricing decisions are now reachable programmatically, so an ERP, webshop, or POS can pull approved prices on a schedule instead of waiting on a manual export. **Added:** * `GET /v1/approved_prices` - list the prices approved in [Price Approval](/agents/price-approval), filterable by grid, store, item, SKU, approval time, and effective date. By default the endpoint returns **current pending exports**; pass `history=true` for the registered history, including cancelled versions. See [List approved prices](/api-reference/approved-prices/list). * `POST /v1/approved_prices/acknowledge` - confirm downstream processing of up to **500** approval ids, so those prices drop out of the pending set. This is what lets an integration loop list → apply → acknowledge without keeping its own watermark. Returns `409` when the batch conflicts with the current state of those exports. ## 2026-06-22 - Product store endpoints; variants folded into products The catalog is keyed at the SKU (item) level. `item_id` is the required unique identifier for each item; `product_id` is now an optional product-level code that groups related items. **Added:** * **Product store** REST entity - `GET /v1/product_store` (list), `POST /v1/product_store` (upsert), `POST /v1/product_store/bulk`, and `GET` / `PATCH` / `DELETE /v1/product_store/{entity_id}` - per-store, market, or channel prices, costs, and stock. Upsert key `(item_id, store_id)`. See [Product store](/api-reference/product-store/list). * `POST /v1/imports/product_store` - asynchronous CSV import for product store-level data. See [Import product store CSV](/api-reference/imports/product-store). **Changed:** * Product create now requires `item_id` (the unique SKU-level key); `product_id` is optional. **Removed:** * `POST` / `PATCH` / `DELETE /v1/product_variants/*` and `POST /v1/product_variants/bulk`. The variant level is now the item-level product, addressed by `item_id`. Migrate variant writes to the [Products](/api-reference/products/list) endpoints. ## 2026-06-03 - Store-level ingestion The ingest path now supports store-level product data, so clients pushing data programmatically can send multiple prices per product per store, market, or channel. **Added:** * Ingest endpoints accept a `store_id` alongside `item_id` - one record per item-store combination, persisted per the [Product store data spec](/data-requirements/product-store-data). * Upsert semantics use `(item_id, store_id)` as the natural key. * Every incoming record is validated against existing stores and products; invalid records (unknown store, duplicate keys within a batch) return a 4xx with a specific error code per row. **Backward compatible:** payloads that omit `store_id` are treated as product-level and behave exactly as before. ## 2026-05-21 - Read endpoints The Public API now reads as well as writes. The inbound/outbound asymmetry that integrators raised at v1 launch is closed. **Added:** * `GET /v1/products` — list and filter enriched product records, including custom columns from product imports. See [List products](/api-reference/products/list). * `GET /v1/transactions` — list and filter sales transactions. See [List transactions](/api-reference/transactions/list). * `GET /v1/grids` — list grids in the account, including a column-schema summary and row count per grid. See [List grids](/api-reference/grids/list). * `GET /v1/grids/{grid_id}/rows` — export the full row contents of a grid (canonical + custom + formula/calculated columns). See [Get grid rows](/api-reference/grids/get-rows). ## 2026-05-04 - v1 launch The first public version of the Retailgrid API is live at `https://clientapi.retailgrid.io`. **Available now:** * **Auth** - `GET /v1/auth/verify` for key validation. Bearer tokens, prefixed `rg_live_`. * **Imports (async CSV)** - products, transactions, competitor prices. * **Products** - bulk upsert, create, patch, delete. * **Product variants** - bulk upsert, create, patch, delete. * **Transactions** - bulk upsert, create, patch, delete. * **Competitor prices** - bulk upsert, create, patch, delete. * **Jobs** - `GET /v1/jobs/{job_id}` for polling async import status. **Known gaps we plan to close:** * `Idempotency-Key` header support on non-idempotent creates. * A documented rate-limit ceiling. * Webhook callbacks for job completion (replacing polling). # Bulk upsert competitor prices Source: https://docs.retailgrid.io/api-reference/competitor-prices/bulk-upsert POST /v1/competitor_prices/bulk Create or update many competitor price observations in Retailgrid in a single request, ideal for syncing large feeds from scraping providers. # Create a competitor price Source: https://docs.retailgrid.io/api-reference/competitor-prices/create POST /v1/competitor_prices Record a single competitor price observation in Retailgrid, capturing the competitor, product match, price, currency, and observation date. # Delete a competitor price Source: https://docs.retailgrid.io/api-reference/competitor-prices/delete DELETE /v1/competitor_prices/{entity_id} Permanently remove a competitor price observation from Retailgrid by its entity ID, useful for correcting erroneous or duplicate data points. # Patch a competitor price Source: https://docs.retailgrid.io/api-reference/competitor-prices/patch PATCH /v1/competitor_prices/{entity_id} Update specific fields on an existing competitor price observation in Retailgrid without overwriting the full record, identified by entity ID. # Bulk Upsert Competitor Prices Source: https://docs.retailgrid.io/api-reference/competitor_prices/bulk-upsert-competitor-prices https://clientapi.retailgrid.io/openapi.json post /v1/competitor_prices/bulk # Create Competitor Price Source: https://docs.retailgrid.io/api-reference/competitor_prices/create-competitor-price https://clientapi.retailgrid.io/openapi.json post /v1/competitor_prices # Delete Competitor Price Source: https://docs.retailgrid.io/api-reference/competitor_prices/delete-competitor-price https://clientapi.retailgrid.io/openapi.json delete /v1/competitor_prices/{entity_id} # Get Competitor Price Source: https://docs.retailgrid.io/api-reference/competitor_prices/get-competitor-price https://clientapi.retailgrid.io/openapi.json get /v1/competitor_prices/{entity_id} # List Competitor Prices Source: https://docs.retailgrid.io/api-reference/competitor_prices/list-competitor-prices https://clientapi.retailgrid.io/openapi.json get /v1/competitor_prices # Patch Competitor Price Source: https://docs.retailgrid.io/api-reference/competitor_prices/patch-competitor-price https://clientapi.retailgrid.io/openapi.json patch /v1/competitor_prices/{entity_id} # Conventions Source: https://docs.retailgrid.io/api-reference/conventions Patterns every Retailgrid API endpoint follows - bulk responses, async jobs, identifiers, decimals, and timestamps. Read this once and you will be able to use any endpoint in the API without surprises. ## Identifiers Two kinds of ids show up in the API: * **`item_id` / `sku` / `competitor_name` / etc.** - your stable business identifier. You provide it. Use it for upserts. `item_id` is the unique SKU-level key for products and transactions; `product_id` is an optional product-level code that groups related items. * **`id` / `entity_id`** - Retailgrid's internal row id, returned in responses. Use it in path parameters when patching or deleting (`PATCH /v1/products/{entity_id}`). `entity_id` is a UUID for products, product variants, and transactions. **For competitor prices it is an integer** - the only place this varies in the API. ## Bulk endpoints Every entity has a `POST /v1//bulk` endpoint that accepts an `items` array and returns per-item results. **Request:** ```json theme={null} { "items": [ { "item_id": "SKU-001", "product_name": "Item one" }, { "item_id": "SKU-002", "product_name": "Item two" } ] } ``` **Response (`BulkResponse`):** ```json theme={null} { "results": [ { "index": 0, "ok": true, "id": "1c2d..." }, { "index": 1, "ok": false, "code": "validation_error", "message": "product_name too long" } ] } ``` A bulk request is **never** all-or-nothing. Successful rows are written even if other rows fail. Always inspect each `BulkItemResult.ok` before declaring victory. Recommended batch size: **500 to 2,000 items per request** depending on row width. Larger payloads will work but increase latency and the chance of a transport-level retry. ## Async jobs The `POST /v1/imports/*` endpoints accept a multipart CSV and return immediately with a `JobHandle`: ```json theme={null} { "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b" } ``` Poll status with `GET /v1/jobs/{job_id}`: ```json theme={null} { "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b", "job_type": "import_products", "status": "running", "progress": 47, "created_at": "2026-05-04T10:00:00Z", "started_at": "2026-05-04T10:00:01Z" } ``` `status` is one of `queued`, `running`, `completed`, `failed`. A reasonable polling cadence is every **5-10 seconds** for active jobs and every **30-60 seconds** for queued jobs. ## Sync vs async | You want to... | Use | Endpoint family | | - | - | - | | Push a few records right now | Sync, JSON | `POST /v1//{entity_id}` or `/bulk` | | Refresh a full catalog from a CSV | Async, multipart | `POST /v1/imports/` then poll `/v1/jobs/{id}` | See [Imports vs bulk](/api-reference/imports-vs-bulk) for the full decision tree. ## Decimals Money, quantities, and rates are returned as **strings**, not floats. This avoids the rounding surprises you would get with IEEE-754: ```json theme={null} { "regular_price": "19.99", "tax_rate": "0.20" } ``` You can submit them as either numbers or strings on the way in, but expect strings in responses. Parse with a decimal library on the client side. ## Timestamps All timestamps are **ISO 8601 in UTC**: ```text theme={null} 2026-05-04T10:00:00Z ``` Date-only fields use `YYYY-MM-DD`. The API does not perform timezone conversion - send UTC, get UTC. ## Idempotency `POST /v1//{entity_id}` is an **upsert keyed on the path's `entity_id`**, which makes it safe to retry. `PATCH` is also idempotent at the field level. `DELETE` is idempotent (deleting an already-deleted row returns `204`). The `/bulk` endpoints are idempotent per item, keyed on the business identifier inside each item (`item_id`, `sku`, etc.). There is no `Idempotency-Key` header today - we will add one before we recommend this API for payment-adjacent flows. ## Pagination List endpoints (products, transactions, grids, jobs, approved prices) use **cursor-based pagination**. Pass `limit` to set the page size, and to fetch the next page pass the `cursor` value returned as `next_cursor` in the previous response. A `null` `next_cursor` means there are no more rows. ## Usage limits Usage is metered **per organization** — the budget is shared across every API key, every user, and the MCP connector, so adding more keys or spreading calls across endpoints does not raise your ceiling. When the organization's usage budget is exhausted, the API rejects the request **before** it does any expensive processing and returns `429 Too Many Requests`, a `Retry-After` header, and a JSON body reporting the limit, the remaining allowance, and when it resets (see [Errors](/api-reference/errors#organization-usage-limit-429)). A separate per-key rate limit also applies and likewise returns `429` with a `Retry-After` header. Today's limits are generous enough that well-behaved sync pipelines do not hit them; specific numbers will be published here once we have stable production data. The one rule that matters for pipeline design: back off on `429` and honour `Retry-After` rather than retrying immediately. # Errors Source: https://docs.retailgrid.io/api-reference/errors Status codes used by the Retailgrid Public API and the shape of validation errors. ## Status codes | Status | Meaning | When you see it | | - | - | - | | `200 OK` | Success | `GET`, `PATCH`, and `/bulk` responses | | `201 Created` | Resource created | `POST /v1//{entity_id}` and `POST /v1/competitor_prices` | | `202 Accepted` | Job queued | `POST /v1/imports/*` returning a `JobHandle` | | `204 No Content` | Success, no body | `DELETE /v1//{entity_id}` | | `401 Unauthorized` | API key missing, malformed, or rotated | Auth header issue - see [Authentication](/api-reference/authentication) | | `404 Not Found` | The path's `entity_id` is unknown | Patching or deleting a non-existent row | | `422 Unprocessable Entity` | Validation failed | Bad payload shape, missing required field, type mismatch | | `429 Too Many Requests` | Per-key rate limit, or the organization usage budget is exhausted | Back off and retry per `Retry-After`; for a budget rejection read the body (below) | | `503 Service Unavailable` | Usage enforcement is temporarily unavailable (`usage_enforcement_unavailable`) | Retry after a short delay | | `5xx` | Server error | Retry with exponential backoff; if persistent, contact support | ## Organization usage limit (429) When the organization's shared usage budget is exhausted, the request is rejected before processing with `429 Too Many Requests`, a `Retry-After` header, and a body whose `detail` reports exactly what was hit and when it clears: ```json theme={null} { "detail": { "code": "organization_usage_limit_exceeded", "dimension": "transactions_read", "unit": "requests", "limit": 100000, "remaining": 0, "request_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b", "reset_at": "2026-10-08T00:00:00Z", "retry_after": 3600 } } ``` * `unit` is one of `requests`, `active_work`, `bytes`, or `calls`. * `reset_at` is when the budget refills; `retry_after` is the same wait in seconds and mirrors the `Retry-After` header. * The budget is shared across API keys, users, and the MCP connector (see [Conventions ▸ Usage limits](/api-reference/conventions)), so switching keys does not restore access — wait for the reset. If usage enforcement itself is temporarily unavailable, the API returns `503` with `{ "detail": { "code": "usage_enforcement_unavailable" } }`; retry after a short delay. ## Validation error shape When a request fails schema validation, the API returns a `422` with an `HTTPValidationError` body: ```json theme={null} { "detail": [ { "loc": ["body", "items", 0, "regular_price"], "msg": "field required", "type": "missing" }, { "loc": ["body", "items", 1, "observed_at"], "msg": "input should be a valid datetime", "type": "datetime_parsing" } ] } ``` Each entry in `detail` is a `ValidationError`: * `loc` - JSON path to the offending field, including array indices. * `msg` - human-readable message. * `type` - machine-readable error code (e.g., `missing`, `string_too_long`, `value_error`). ## Bulk endpoints: per-item errors `/bulk` endpoints return `200 OK` even when individual rows fail. Per-row failures are reported inside `BulkResponse.results` rather than as a top-level error: ```json theme={null} { "results": [ { "index": 0, "ok": true, "id": "1c2d..." }, { "index": 1, "ok": false, "code": "validation_error", "message": "product_name too long" } ] } ``` Always iterate `results` and check `ok`. A `200` does **not** mean every row was written. ## Common 422 cases by endpoint family * **Products** - missing `item_id`, missing `sku`, or a decimal field that can't parse. * **Product store** - missing `item_id`, `store_id`, or `current_price`; `store_id` referencing an unknown store. * **Transactions** - missing `item_id`, malformed `transaction_timestamp`, decimal field that can't parse. * **Competitor prices** - missing one of the four required fields (`competitor_name`, `observed_at`, `regular_price`, `effective_price`). * **Imports (CSV)** - file not attached, file empty, file not UTF-8, header row missing. ## Retry strategy * **Idempotent endpoints** (`POST /v1//{entity_id}`, `PATCH`, `DELETE`, `/bulk`) - retry on 5xx and on transport errors with exponential backoff (start at 1s, cap at 30s, max 5 attempts). * **`POST /v1/competitor_prices`** - this is a non-idempotent create (no path id). Retrying after a successful response will create a duplicate row. Check the network outcome before retrying. * **Async imports** - if `POST /v1/imports/*` fails, retry the upload. If it succeeds and the subsequent job fails, fix the CSV and submit a fresh job rather than retrying the same upload. # Export Grid Source: https://docs.retailgrid.io/api-reference/grids/export-grid https://clientapi.retailgrid.io/openapi.json get /v1/grids/{grid_id} # Get grid rows Source: https://docs.retailgrid.io/api-reference/grids/get-rows GET https://clientapi.retailgrid.io/v1/grids/{grid_id}/rows Export the full row contents of a grid - canonical, custom, and formula/calculated columns. Returns the row contents of a grid, including canonical columns, user-added custom columns, and formula or calculated columns. This is the programmatic counterpart of the in-app [Export](/grids/export) action. Use this endpoint when you want to pull a user-built workspace - with its column set and computed columns - into an ERP, BI tool, or warehouse on a schedule. ## Authentication Bearer token. See [Authentication](/api-reference/authentication). ## Path parameters | Name | Type | Required | Description | | :- | :- | :- | :- | | `grid_id` | string | Yes | The grid to export. Discover IDs via [List grids](/api-reference/grids/list). | ## Query parameters | Name | Type | Required | Description | | :- | :- | :- | :- | | `limit` | integer | No | Page size, default `1000`, max `10000`. | | `cursor` | string | No | Opaque pagination cursor returned by a previous response. | | `columns` | string | No | Comma-separated list of column names to include. Defaults to every column in the grid. | | `updated_after` | string (ISO-8601, UTC) | No | Only return rows updated at or after this timestamp. | ## Response ```json theme={null} { "grid_id": "grd_01HX2K8E4Y", "columns": [ { "name": "product_id", "type": "string", "source": "canonical" }, { "name": "price", "type": "number", "source": "canonical" }, { "name": "margin", "type": "number", "source": "formula" }, { "name": "role", "type": "single_select", "source": "custom" } ], "data": [ { "product_id": "SKU-12345", "price": "4.99", "margin": "0.31", "role": "KVI" } ], "next_cursor": "eyJvZmZzZXQiOjEwMDB9" } ``` Numeric values are returned as strings - see [Conventions](/api-reference/conventions) for the rationale. ## Large grids For grids above \~50k rows, page with `limit=5000` and follow `next_cursor` until it returns `null`. Pulling the entire grid in one call is supported up to the `limit` ceiling but is slower than chunked pulls and more likely to hit a retry on transient failures. If you only need a subset of columns, pass `columns=` - the response is smaller and the export is faster. ## Example request ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/grids/grd_01HX2K8E4Y/rows?limit=5000&columns=product_id,price,margin" \ -H "Authorization: Bearer rg_live_xxx" ``` ## Related * [List grids](/api-reference/grids/list) * [Export from the app](/grids/export) * [Conventions](/api-reference/conventions) * [Errors](/api-reference/errors) # List grids Source: https://docs.retailgrid.io/api-reference/grids/list GET https://clientapi.retailgrid.io/v1/grids List the grids in your account, including a column-schema summary and row count per grid. Returns the grids visible to your API key, with enough metadata to discover grid IDs before exporting their rows via [Get grid rows](/api-reference/grids/get-rows). ## Authentication Bearer token. See [Authentication](/api-reference/authentication). ## Query parameters | Name | Type | Required | Description | | :- | :- | :- | :- | | `limit` | integer | No | Page size, default `50`, max `200`. | | `cursor` | string | No | Opaque pagination cursor returned by a previous response. | | `name` | string | No | Case-insensitive substring match against grid name. | | `updated_after` | string (ISO-8601, UTC) | No | Only return grids updated at or after this timestamp. | ## Response ```json theme={null} { "data": [ { "grid_id": "grd_01HX2K8E4Y", "name": "Dairy Q2 review", "row_count": 1284, "created_at": "2026-04-12T08:42:11Z", "updated_at": "2026-05-21T19:00:55Z", "created_by": { "user_id": "usr_01J7", "email": "pricing@yourcompany.com" }, "columns": [ { "name": "product_id", "type": "string", "source": "canonical" }, { "name": "price", "type": "number", "source": "canonical" }, { "name": "margin", "type": "number", "source": "formula" }, { "name": "role", "type": "single_select", "source": "custom" } ] } ], "next_cursor": "eyJvZmZzZXQiOjUwfQ==" } ``` The `columns` array is a summary - column names, semantic type, and where each column came from (`canonical`, `custom`, `formula`, `ai`). It does **not** include row values. To pull row values, call [Get grid rows](/api-reference/grids/get-rows) with the `grid_id`. ## Example request ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/grids?limit=25&updated_after=2026-05-01T00:00:00Z" \ -H "Authorization: Bearer rg_live_xxx" ``` ## Related * [Get grid rows](/api-reference/grids/get-rows) * [Conventions](/api-reference/conventions) * [Errors](/api-reference/errors) # List Grids Source: https://docs.retailgrid.io/api-reference/grids/list-grids https://clientapi.retailgrid.io/openapi.json get /v1/grids # Imports vs bulk Source: https://docs.retailgrid.io/api-reference/imports-vs-bulk Choosing between async CSV imports and synchronous JSON bulk endpoints. The Public API offers two ways to load many records at once. Picking the right one keeps your integration simple and your latency low. ## Side by side | | `POST /v1/imports/*` | `POST /v1//bulk` | | - | - | - | | Transport | Multipart CSV | JSON | | Mode | **Async** - returns a `job_id` | **Sync** - returns results in the response | | Typical size | Hundreds of thousands to millions of rows | Hundreds to a few thousand rows | | Per-row error reporting | After the job completes | Immediately, in `BulkResponse.results` | | Retry semantics | Retry the upload; fix CSV and re-submit if the job fails | Idempotent per item | | Rate limit footprint | One request per file | One request per batch | ## Use `imports/*` when... * You are doing a **full-catalog refresh** from your ERP or PIM. * The data already lives in a CSV (or your pipeline can produce one cheaply). * Wall-clock latency does not matter - you can poll status. * You want minimal request overhead per row. Endpoints in this family: * [`POST /v1/imports/products`](/api-reference/imports/products) * [`POST /v1/imports/transactions`](/api-reference/imports/transactions) * [`POST /v1/imports/competitor_prices`](/api-reference/imports/competitor-prices) * [`POST /v1/imports/product_store`](/api-reference/imports/product-store) ## Use `/bulk` when... * You are syncing **deltas** from your source system on an event-driven schedule. * You want a single round trip and an immediate per-row outcome. * You can shape the payload as JSON (the records do not naturally come from a file). * Batch size is in the **hundreds to low thousands**. Endpoints in this family: * [`POST /v1/products/bulk`](/api-reference/products/bulk-upsert) * [`POST /v1/product_store/bulk`](/api-reference/product-store/bulk-upsert) * [`POST /v1/transactions/bulk`](/api-reference/transactions/bulk-upsert) * [`POST /v1/competitor_prices/bulk`](/api-reference/competitor-prices/bulk-upsert) ## Use single-record `POST /v1//{entity_id}` when... * You are reacting to a **single change** in your source system (one product updated, one transaction posted). * You want strong per-call status semantics (`201` vs `422` directly). * You need the full response body for the row you just wrote. ## A pragmatic pattern Most production integrations end up using two of the three: 1. **Daily** full-catalog `imports/products` to absorb all upstream changes. 2. **Event-driven** `products/bulk` or `POST /v1/products/{entity_id}` for low-latency updates between full syncs. This keeps Retailgrid eventually consistent on cheap infrastructure, with a fast path for the small slice of changes that actually need it. # Import competitor prices CSV Source: https://docs.retailgrid.io/api-reference/imports/competitor-prices POST /v1/imports/competitor_prices Upload a CSV file of competitor prices to Retailgrid for asynchronous import, returning a job ID to track ingestion progress. # Import Competitor Prices Csv Source: https://docs.retailgrid.io/api-reference/imports/import-competitor-prices-csv https://clientapi.retailgrid.io/openapi.json post /v1/imports/competitor_prices # Import Product Store Csv Source: https://docs.retailgrid.io/api-reference/imports/import-product-store-csv https://clientapi.retailgrid.io/openapi.json post /v1/imports/product_store # Import Products Csv Source: https://docs.retailgrid.io/api-reference/imports/import-products-csv https://clientapi.retailgrid.io/openapi.json post /v1/imports/products # Import Transactions Csv Source: https://docs.retailgrid.io/api-reference/imports/import-transactions-csv https://clientapi.retailgrid.io/openapi.json post /v1/imports/transactions # Import product store CSV Source: https://docs.retailgrid.io/api-reference/imports/product-store POST /v1/imports/product_store Upload a CSV of product store-level records to Retailgrid for asynchronous import, returning a job ID you can use to track ingestion progress. # Import products CSV Source: https://docs.retailgrid.io/api-reference/imports/products POST /v1/imports/products Upload a CSV file of products to Retailgrid for asynchronous import, returning a job ID you can use to track ingestion progress. # Import transactions CSV Source: https://docs.retailgrid.io/api-reference/imports/transactions POST /v1/imports/transactions Upload a CSV file of sales transactions to Retailgrid for asynchronous import, returning a job ID to track ingestion progress. # Raw Dbcsv Imported Source: https://docs.retailgrid.io/api-reference/internal-import-events/raw-dbcsv-imported https://clientapi.retailgrid.io/openapi.json post /v1/internal/import-events/raw-dbcsv-imported # Introduction Source: https://docs.retailgrid.io/api-reference/introduction The Retailgrid Public API lets you push and pull product, variant, transaction, and competitor data, plus export full grid contents - programmatically. The Retailgrid Public API is the programmatic counterpart to the in-app upload and export flows. Use it when you want to keep Retailgrid in sync with your source systems on a schedule, or feed enriched data back into your ERP and BI tools, instead of uploading and downloading CSVs by hand. ## Base URL ```text theme={null} https://clientapi.retailgrid.io ``` All endpoints live under `/v1`. Future breaking changes will land under a new prefix; minor additions will not change `/v1`. ## What you can do today * **Push catalog data** - create, patch, delete, and bulk-upsert products and product variants. * **Push transactions** - create, patch, delete, and bulk-upsert sales transactions. * **Push competitor prices** - create, patch, delete, and bulk-upsert observed competitor prices. * **Pull catalog and transactions** - list enriched products and sales transactions for ERP / BI ingestion. Includes custom columns added at import time. * **Pull approved prices** - read the finalized prices your team approved in Price Approval, to sync into your ERP, webshop, or POS. * **Export grids** - list grids in your account and pull the full row contents of any grid, including canonical, custom, and formula / calculated columns. * **Upload CSVs asynchronously** - submit a multipart CSV to `/v1/imports/*` and poll a job handle. * **Verify your API key** - sanity-check that your key works before hooking up a pipeline. Retailgrid keys the catalog at the SKU level. `item_id` is the required unique identifier for each item, and the key used to link transactions, store-level data, and competitor prices back to your catalog. `product_id` is an optional product-level code that groups related items / variants under one product. ## When to use the API vs other paths For continuous, machine-to-machine sync from your ERP, PIM, or warehouse - in or out. For one-off loads or when an analyst is driving. Same data, manual flow. For asking an AI assistant about your grids in plain language and acting on them interactively, rather than a scheduled sync. ## Conventions A few things are worth reading once before you start integrating: * [Authentication](/api-reference/authentication) - how Bearer keys work and where to issue one. * [Conventions](/api-reference/conventions) - bulk endpoints, async jobs, decimals as strings, timestamps in UTC. * [Imports vs bulk](/api-reference/imports-vs-bulk) - which endpoint family to pick for which job. * [Errors](/api-reference/errors) - status codes and the validation-error shape. ## Versioning The current version is `v1`. Additive changes (new fields, new endpoints, new optional parameters) ship without notice. Breaking changes will be announced in the [API changelog](/api-reference/changelog) and shipped under a new version prefix. # Get Job Source: https://docs.retailgrid.io/api-reference/jobs/get-job https://clientapi.retailgrid.io/openapi.json get /v1/jobs/{job_id} # Get job status Source: https://docs.retailgrid.io/api-reference/jobs/get-status GET /v1/jobs/{job_id} Check the progress, state, and results of an asynchronous Retailgrid job such as a bulk upsert or CSV import operation. # Bulk upsert product store records Source: https://docs.retailgrid.io/api-reference/product-store/bulk-upsert POST /v1/product_store/bulk Create or update many per-store product records in a single request, keyed on (item_id, store_id), to sync prices and stock. # Upsert a product store record Source: https://docs.retailgrid.io/api-reference/product-store/create POST /v1/product_store Create or update a single product store-level record - one item in one store, market, or channel - with its price, cost, stock, and status. # Delete a product store record Source: https://docs.retailgrid.io/api-reference/product-store/delete DELETE /v1/product_store/{entity_id} Permanently remove a product store-level record from Retailgrid by its entity ID. # List product store records Source: https://docs.retailgrid.io/api-reference/product-store/list GET /v1/product_store List and filter product store-level records - per-store, market, or channel prices, costs, and stock - for ERP or BI ingestion. # Patch a product store record Source: https://docs.retailgrid.io/api-reference/product-store/patch PATCH /v1/product_store/{entity_id} Update specific fields on an existing product store-level record without overwriting the full row, identified by entity ID. # Bulk Upsert Product Store Source: https://docs.retailgrid.io/api-reference/product_store/bulk-upsert-product-store https://clientapi.retailgrid.io/openapi.json post /v1/product_store/bulk # Delete Product Store Source: https://docs.retailgrid.io/api-reference/product_store/delete-product-store https://clientapi.retailgrid.io/openapi.json delete /v1/product_store/{entity_id} # Get Product Store Source: https://docs.retailgrid.io/api-reference/product_store/get-product-store https://clientapi.retailgrid.io/openapi.json get /v1/product_store/{entity_id} # List Product Store Source: https://docs.retailgrid.io/api-reference/product_store/list-product-store https://clientapi.retailgrid.io/openapi.json get /v1/product_store # Patch Product Store Source: https://docs.retailgrid.io/api-reference/product_store/patch-product-store https://clientapi.retailgrid.io/openapi.json patch /v1/product_store/{entity_id} # Upsert Product Store Source: https://docs.retailgrid.io/api-reference/product_store/upsert-product-store https://clientapi.retailgrid.io/openapi.json post /v1/product_store # Bulk upsert products Source: https://docs.retailgrid.io/api-reference/products/bulk-upsert POST /v1/products/bulk Create or update many products in Retailgrid in a single request, ideal for syncing large product catalogs from external systems. # Bulk Upsert Products Source: https://docs.retailgrid.io/api-reference/products/bulk-upsert-products https://clientapi.retailgrid.io/openapi.json post /v1/products/bulk # Create a product Source: https://docs.retailgrid.io/api-reference/products/create POST /v1/products/{entity_id} Create a single product record in Retailgrid for a specific entity, including its SKU, attributes, and category metadata. # Create Product Source: https://docs.retailgrid.io/api-reference/products/create-product https://clientapi.retailgrid.io/openapi.json post /v1/products/{entity_id} # Delete a product Source: https://docs.retailgrid.io/api-reference/products/delete DELETE /v1/products/{entity_id} Permanently remove a product from Retailgrid by its entity ID, useful for cleaning up discontinued or duplicated SKUs. # Delete Product Source: https://docs.retailgrid.io/api-reference/products/delete-product https://clientapi.retailgrid.io/openapi.json delete /v1/products/{entity_id} # Get Product Source: https://docs.retailgrid.io/api-reference/products/get-product https://clientapi.retailgrid.io/openapi.json get /v1/products/{entity_id} # List products Source: https://docs.retailgrid.io/api-reference/products/list GET https://clientapi.retailgrid.io/v1/products List enriched product records, including custom columns added at import time. Returns the product catalog visible to your API key. Each record includes canonical product fields plus a `custom` map of any custom columns that were added when products were imported. This is the read-side counterpart of [Bulk upsert products](/api-reference/products/bulk-upsert). Use it for ERP / BI ingestion where you need the current enriched state of the catalog. ## Authentication Bearer token. See [Authentication](/api-reference/authentication). ## Query parameters | Name | Type | Required | Description | | :- | :- | :- | :- | | `limit` | integer | No | Page size, default `100`, max `1000`. | | `cursor` | string | No | Opaque pagination cursor returned by a previous response. | | `product_id` | string | No | Filter to a single canonical product ID. | | `sku` | string | No | Filter to a single SKU. | | `updated_after` | string (ISO-8601, UTC) | No | Only return products updated at or after this timestamp. | ## Response ```json theme={null} { "data": [ { "product_id": "SKU-12345", "sku": "SKU-12345", "name": "Whole Milk 1L", "brand": "Arla", "category": "Dairy", "price": "1.49", "unit_cost": "0.92", "image_url": "https://cdn.example.com/sku-12345.jpg", "product_url": "https://www.example.com/products/sku-12345", "created_at": "2026-01-04T11:08:00Z", "updated_at": "2026-05-21T08:14:21Z", "custom": { "role": "KVI", "buyer": "E. Kirilov" } } ], "next_cursor": "eyJvZmZzZXQiOjEwMH0=" } ``` The `custom` map contains every custom column attached to the product during import - the keys are the column names you defined. Numeric values are returned as strings - see [Conventions](/api-reference/conventions). ## Example request ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/products?updated_after=2026-05-01T00:00:00Z&limit=500" \ -H "Authorization: Bearer rg_live_xxx" ``` ## Related * [Bulk upsert products](/api-reference/products/bulk-upsert) * [Conventions](/api-reference/conventions) * [Errors](/api-reference/errors) # List Products Source: https://docs.retailgrid.io/api-reference/products/list-products https://clientapi.retailgrid.io/openapi.json get /v1/products # Patch a product Source: https://docs.retailgrid.io/api-reference/products/patch PATCH /v1/products/{entity_id} Update specific fields on an existing product in Retailgrid without overwriting the full record, identified by its entity ID. # Patch Product Source: https://docs.retailgrid.io/api-reference/products/patch-product https://clientapi.retailgrid.io/openapi.json patch /v1/products/{entity_id} # API quickstart Source: https://docs.retailgrid.io/api-reference/quickstart From zero to your first product upserted via the API in under five minutes. This walks you through the full inbound and outbound flow: verify your key, create one product, bulk-upsert many, read enriched data back, and check a job status. By the end you will have working `curl` commands and a clear path to scale up to full catalog sync. ## 1. Get an API key Follow the steps in [Authentication](/api-reference/authentication) to generate a key from **Settings → Integrations**. Export it for the rest of this page: ```bash theme={null} export RETAILGRID_API_KEY="rg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` ## 2. Verify the key works ```bash theme={null} curl https://clientapi.retailgrid.io/v1/auth/verify \ -H "Authorization: Bearer $RETAILGRID_API_KEY" ``` A `200` with `"ok": true` means you are good to go. ## 3. Create one product Pick a product id you control - it is the stable identifier you will use to update or delete this product later. UUIDs are fine, but any string works. ```bash theme={null} PRODUCT_ID="11111111-2222-3333-4444-555555555555" curl -X POST "https://clientapi.retailgrid.io/v1/products/$PRODUCT_ID" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "product_id": "SKU-DEMO-001", "product_name": "Demo running shoe", "brand": "Acme", "manufacturer": "Acme Footwear", "product_type": "shoe", "status": "active" }' ``` A `201 Created` response returns the full product record, including the internal `id` Retailgrid assigned. ## 4. Bulk-upsert many products at once For more than a handful of records, prefer `/bulk`. It accepts up to a few thousand items in one request and returns per-item results. ```bash theme={null} curl -X POST "https://clientapi.retailgrid.io/v1/products/bulk" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [ {"product_id": "SKU-DEMO-002", "product_name": "Demo trail shoe", "brand": "Acme"}, {"product_id": "SKU-DEMO-003", "product_name": "Demo road shoe", "brand": "Acme"} ] }' ``` The response has one `BulkItemResult` per input row, so partial failures do not block the whole batch. See [Conventions](/api-reference/conventions) for details. ## 5. Read what you've loaded The API reads as well as writes. Pull enriched products (canonical fields plus any custom columns added at import) for ERP or BI ingestion: ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/products?updated_after=2026-05-01T00:00:00Z&limit=500" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" ``` The same pattern works for transactions: ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/transactions?updated_after=2026-05-01T00:00:00Z&limit=1000" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" ``` To export the full row contents of a grid - including custom and formula / calculated columns - list your grids first, then pull the rows: ```bash theme={null} # List grids curl -s "https://clientapi.retailgrid.io/v1/grids" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" # Pull rows from a grid curl -s "https://clientapi.retailgrid.io/v1/grids/grd_01HX2K8E4Y/rows?limit=5000" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" ``` All read endpoints support cursor pagination and an `updated_after` filter for delta pulls. See [Get grid rows](/api-reference/grids/get-rows) for the full column-selection options. ## 6. Async CSV uploads For full-catalog refreshes, use the CSV import endpoints. They return immediately with a `job_id`: ```bash theme={null} curl -X POST "https://clientapi.retailgrid.io/v1/imports/products" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" \ -F "file=@products.csv" ``` ```json theme={null} { "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b" } ``` Then poll for status: ```bash theme={null} curl "https://clientapi.retailgrid.io/v1/jobs/0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b" \ -H "Authorization: Bearer $RETAILGRID_API_KEY" ``` When `"status": "completed"`, the data is live in your Retailgrid grids. ## What's next * [Imports vs bulk](/api-reference/imports-vs-bulk) - decide which endpoint family fits your pipeline. * [Conventions](/api-reference/conventions) - the patterns every endpoint follows. * [Errors](/api-reference/errors) - what 422 responses look like and how to fix them. * [List products](/api-reference/products/list) and [Get grid rows](/api-reference/grids/get-rows) - the read-side reference. # Bulk upsert transactions Source: https://docs.retailgrid.io/api-reference/transactions/bulk-upsert POST /v1/transactions/bulk Create or update many sales transactions in a single Retailgrid request, ideal for syncing large batches of POS or order history data. # Bulk Upsert Transactions Source: https://docs.retailgrid.io/api-reference/transactions/bulk-upsert-transactions https://clientapi.retailgrid.io/openapi.json post /v1/transactions/bulk # Create a transaction Source: https://docs.retailgrid.io/api-reference/transactions/create POST /v1/transactions/{entity_id} Create a single sales transaction in Retailgrid for a specific entity, recording the line item, quantity, price, and date. # Create Transaction Source: https://docs.retailgrid.io/api-reference/transactions/create-transaction https://clientapi.retailgrid.io/openapi.json post /v1/transactions/{entity_id} # Delete a transaction Source: https://docs.retailgrid.io/api-reference/transactions/delete DELETE /v1/transactions/{entity_id} Permanently remove a sales transaction from Retailgrid by entity ID, useful for correcting erroneous or duplicate sales records. # Delete Transaction Source: https://docs.retailgrid.io/api-reference/transactions/delete-transaction https://clientapi.retailgrid.io/openapi.json delete /v1/transactions/{entity_id} # Get Transaction Source: https://docs.retailgrid.io/api-reference/transactions/get-transaction https://clientapi.retailgrid.io/openapi.json get /v1/transactions/{entity_id} # List transactions Source: https://docs.retailgrid.io/api-reference/transactions/list GET https://clientapi.retailgrid.io/v1/transactions List sales transactions for downstream ERP and BI ingestion. Returns sales transactions visible to your API key. Pair with [List products](/api-reference/products/list) to assemble a catalog-plus-sales view in your warehouse without going through the in-app CSV export flow. ## Authentication Bearer token. See [Authentication](/api-reference/authentication). ## Query parameters | Name | Type | Required | Description | | :- | :- | :- | :- | | `limit` | integer | No | Page size, default `1000`, max `10000`. | | `cursor` | string | No | Opaque pagination cursor returned by a previous response. | | `from_date` | string (ISO-8601, UTC) | No | Earliest transaction timestamp to include. | | `to_date` | string (ISO-8601, UTC) | No | Latest transaction timestamp to include. | | `product_id` | string | No | Filter to a single product. | | `store_id` | string | No | Filter to a single store. | | `updated_after` | string (ISO-8601, UTC) | No | Only return transactions updated at or after this timestamp - useful for incremental sync. | ## Response ```json theme={null} { "data": [ { "transaction_id": "txn_01HX9R5M", "product_id": "SKU-12345", "store_id": "STORE-007", "timestamp": "2026-05-21T16:42:09Z", "quantity": "2", "price": "1.49", "revenue": "2.98", "currency": "EUR", "is_promo": false, "updated_at": "2026-05-21T16:42:09Z" } ], "next_cursor": "eyJvZmZzZXQiOjEwMDB9" } ``` Numeric values are returned as strings - see [Conventions](/api-reference/conventions). ## Incremental pulls For downstream sync jobs, store the highest `updated_at` you've seen and pass it as `updated_after` on the next run. This is more efficient than re-reading the full window and lets you catch late-arriving corrections to existing transactions. ## Large windows For wide date ranges across high-volume accounts, narrow the window with `from_date` / `to_date` and page with `limit=10000`. Pulling many months in a single call is supported but slow and more likely to hit a retry. ## Example request ```bash theme={null} curl -s "https://clientapi.retailgrid.io/v1/transactions?from_date=2026-05-01T00:00:00Z&to_date=2026-05-22T00:00:00Z&limit=10000" \ -H "Authorization: Bearer rg_live_xxx" ``` ## Related * [Bulk upsert transactions](/api-reference/transactions/bulk-upsert) * [List products](/api-reference/products/list) * [Conventions](/api-reference/conventions) * [Errors](/api-reference/errors) # List Transactions Source: https://docs.retailgrid.io/api-reference/transactions/list-transactions https://clientapi.retailgrid.io/openapi.json get /v1/transactions # Patch a transaction Source: https://docs.retailgrid.io/api-reference/transactions/patch PATCH /v1/transactions/{entity_id} Update one or more fields on an existing sales transaction in Retailgrid without replacing the full record for the given entity. # Patch Transaction Source: https://docs.retailgrid.io/api-reference/transactions/patch-transaction https://clientapi.retailgrid.io/openapi.json patch /v1/transactions/{entity_id} # Usage & limits Source: https://docs.retailgrid.io/api-reference/usage-limits How Retailgrid meters API and MCP usage per organization, what happens when the budget is exhausted, and how to back off. Retailgrid meters usage so one pipeline — or one runaway loop — can't exhaust the resources behind everyone else's. Reads, writes, exports, and polling all count, across the [public API](/api-reference/introduction) and the [MCP connector](/settings/ai-connections) alike. ## What's metered, and at what grain Usage is accounted **per organization**. The budget is shared across every API key, every user, and the MCP connector, so spreading calls over more keys or more endpoints does not raise your ceiling — it all draws on the same organization budget. Usage is tracked along a few **dimensions**, each with its own `unit`: | Unit | What it counts | | :- | :- | | `requests` | Individual API requests. | | `calls` | MCP tool calls. | | `active_work` | Concurrent in-flight work (long operations and jobs). | | `bytes` | Data volume moved by an operation, e.g. a large export. | ## When the budget is exhausted An exhausted budget is rejected **before** any expensive processing, so you are not billed for work that never ran. The response is `429 Too Many Requests` with: * a `Retry-After` header, and * a JSON body whose `detail` reports exactly what was hit and when it clears. ```json theme={null} { "detail": { "code": "organization_usage_limit_exceeded", "dimension": "transactions_read", "unit": "requests", "limit": 100000, "remaining": 0, "request_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b", "reset_at": "2026-10-08T00:00:00Z", "retry_after": 3600 } } ``` `reset_at` is when the budget refills; `retry_after` is the same wait in seconds and mirrors the `Retry-After` header. Because the budget is shared, switching to another API key does **not** restore access — wait for the reset. ## How to behave well * **Honour `Retry-After`.** Back off for the stated interval rather than retrying immediately; a tight retry loop just burns the next window too. * **Don't fan out across keys** to dodge the limit — it's one organization budget. * **Prefer bulk and async endpoints** over many small calls (see [Imports vs bulk](/api-reference/imports-vs-bulk)); they move the same data for far fewer requests. * **Poll on a sane cadence.** For async jobs, every 5–10s for active jobs and 30–60s for queued ones is plenty (see [Conventions ▸ Async jobs](/api-reference/conventions)). ## If enforcement is unavailable If the usage-enforcement service itself is temporarily unavailable, the API returns `503` with `{ "detail": { "code": "usage_enforcement_unavailable" } }`. This is transient — retry after a short delay. ## Related * [Conventions](/api-reference/conventions) * [Errors](/api-reference/errors) * [Imports vs bulk](/api-reference/imports-vs-bulk) * [AI connections (MCP)](/settings/ai-connections) # Product updates Source: https://docs.retailgrid.io/changelog/product-updates See the latest Retailgrid releases, including new features, improvements, and bug fixes in each update cycle. Subscribe to product updates via [RSS](https://docs.retailgrid.io/changelog/product-updates/rss.xml), or watch the **What's new** feed inside Retailgrid. **Summary**: This cycle was about reach and unattended trust. Retailgrid now answers to your AI assistant over a read-only connector, grids can be shared with teammates instead of rebuilt, and scheduled pricing runs can approve and queue prices on their own — but only inside limits you set, and they email you what happened. Alongside that, grid creation got a faster scope step with a rolling competitor window, and you can now remove a competitor price that shouldn't count. ### Features * **Query your grids from an AI assistant** — connect Claude, ChatGPT, or another MCP client to Retailgrid over a read-only connection and ask about your catalog and grids in plain language, no exporting. See [Connect your AI assistant](/settings/connect-ai-assistant) and manage connected clients in [AI connections](/settings/ai-connections). * **Share grids with teammates** — grant named teammates in your account **view-only** or **edit** access to a grid, change it, or revoke it; shared grids appear alongside your own. See [Share a grid](/grids/share). * **Auto-approve prices in scheduled runs** — let a scheduled Price Optimization or Dynamic Pricing run automatically approve and queue the prices that clear a **minimum unit margin** and a **maximum price change** you set, while everything else waits in Price Approval. See [Schedule and automate pricing runs](/agents/automation). * **Email summary when a scheduled run finishes** — opt in per schedule to get one email when a run completes, with what changed and a link straight to the results in Grid. See [Schedule and automate pricing runs](/agents/automation). ### Improvements * **Rebuilt grid-creation scope step** — a pinned header keeps **Create grid** in reach, sections collapse behind live summaries, category/brand/competitor pickers search instead of listing everything, quick filters cover Active / in-stock / recently-selling, and live cards count SKUs, stores, competitors, and competitor coverage. See [Create grid](/grids/create-grid). * **Rolling competitor observation window** — set the competitor window as a rolling **Last 7 / 14 / 30 / custom** days that re-evaluates on every refresh, instead of fixed dates that go stale. See [Create grid](/grids/create-grid). * **Remove a competitor price** — delete a wrong or irrelevant competitor price straight from the Price Comparison popover, so it stops feeding the price range, market-data metrics, and optimization. The removal is permanent, and price monitoring re-collects the competitor on its next run if it's still configured. See [Remove a competitor price](/price-monitoring/exclude-competitor-price). * **Four new product fields** — **Long Description**, **Supplier**, **Manufacturer Code**, and **Supplier Code** are now first-class optional fields in the product data model, over CSV and the API. See [Products](/data-requirements/products). ### Bug Fixes * **Grid editing locks behave for private grids and same-user sessions** — concurrent-edit locks no longer block your own second session on a private grid, and the editing lease was shortened to one minute so a stale lock frees quickly. **Summary**: This cycle was about putting settings where they belong. Demand model parameters are account-level, not per-run, so they now live in their own **Modelling** tab instead of the Price Optimization dialog. The **Datasets** page stops pretending data only flows one way and splits into Inbound and Outbound. And duplicating a grid now carries the setup you spent time on, instead of just the rows. ### Features * **Seven inventory and pricing metrics** — Value of Inventory, Cost of Inventory, GMROI, Sell-through %, Inventory Turnover, Average Selling Price, and Realized Discount % joined the curated metrics library, in every timeframe. See [Metric columns](/grids/metrics). ### Improvements * **Modelling settings, admin only** — confidence thresholds, clustering, recommendation corridors, seasonality, GLM regularization, data filtering, and model freshness moved out of the Price Optimization **Advanced** tab into **Settings → Modelling**. They belong to the account's demand model rather than to one run, so a change applies to every future run and scheduled recalculation, and only Admins can make it. You can also rebuild the model on demand from there. See [Modelling](/settings/modelling). * **Datasets grouped by direction** — the Datasets page now separates **Inbound** data you load from **Outbound** data Retailgrid publishes back, and names the **Connector** behind each dataset (CSV, API, or Shopify) so it's obvious where a dataset came from. See [Datasets](/data-requirements/datasets). * **Duplicate a grid with its full configuration** — duplicating now copies the whole setup (added columns, formulas, metric columns, layout, saved views) onto a product scope you choose, instead of leaving you to rebuild it. See [Duplicate a grid](/grids/duplicate-grid). ### Bug Fixes * **Matched-data upload reports what it refused** — publishing a competitor scan no longer hides rejected rows or repeats work it had already finished. A delivery that is refused says so, and a publish already in flight blocks a second one instead of racing it. See [Competitor matching](/price-monitoring/competitor-matching). * **Selling items no longer vanish from the forecast without a reason** — an item left out of the forecast now reports why (no recommendation, low confidence, missing inputs, or no recent sales) instead of coming back blank. See [Sales forecast & price impact](/agents/sales-forecast). **Summary**: This cycle was about trusting what the grid tells you. A new pinned **totals row** rolls every calculated metric up over your exact filtered set — summing what should sum, averaging what should average, and recomputing ratios like margin and CPI correctly. Three pricing behaviors that quietly produced no output — a Minimum change limit, a one-by-one Price Relations ladder, and a Maximize Revenue run — now do what the rules say. ### Features * **Grid totals row** — a pinned row at the bottom of the grid aggregates every calculated metric over the exact rows your filters and scope select, recomputing as you filter or change the forecast horizon. Sums, averages, sales-weighted margins, and recomputed ratios (Gross Margin %, ASP, GMROI, CPI) each roll up correctly, and you can collapse it with a click. See [Totals row](/grids/totals-row). ### Improvements * **Cleaner grid and totals styling** — colored cell backgrounds and red/green text are gone from regular data rows; color accents now live only in the totals row, which reads as visually distinct from the last product row. ### Bug Fixes * **Price Change Limit (Minimum) now acts as a floor, not a block** — a Minimum-type limit applies after the target price is set, so it caps small moves instead of suppressing every price change. See the [Pricing rules guide](/agents/pricing-rules-guide). * **Price Relations (one by one) produces changes again** — a one-by-one ladder now emits gap-preserving proposals in production instead of returning zero changes. See the [Pricing rules guide](/agents/pricing-rules-guide). * **Maximize Revenue stays on the right side of the curve** — the revenue goal no longer returns a cost-floored price whose own forecast lowers revenue; price safety and the revenue optimum are handled separately. See [Price Optimization](/agents/price-optimization). * **Honest no-data on inventory metrics** — inventory metrics show a no-data state instead of a confident 0.00 or 100% when a product's stock history is missing. See the [Metrics glossary](/reference/metrics). **Summary**: This cycle was about control and trust. You can now split your team into **Admin** and **User** roles, tell Retailgrid when your prices already exclude tax so margin and markup come out right, and rely on forecast and price-impact numbers that no longer blow up on thin-history items. The Product Role agent also got the clean, editable result view every other agent already has. ### Features * **Team roles — Admin and User** — invite teammates as **Admin** (manage users, Organization defaults, and datasets) or **User** (build grids, run agents, work with data), change a role, or remove someone, all from the Users table. See [Organization](/settings/organization). * **Prices exclude tax** — a setting that tells Retailgrid your product prices already exclude tax, so margin and markup use the price as supplied instead of deriving a net-of-tax value. Built for US and Canadian retailers whose feeds are tax-exclusive. See [Account](/settings/account). ### Improvements * **Cleaner Product Role results** — the [Product Role Classification](/agents/product-roles) agent now writes the role as a colored single-select badge you can edit in place, and hides the technical columns (Role Confidence, Role Source, Product Role Details) by default, matching every other agent flow. ### Bug Fixes * **Elasticity kept inside a plausible range in forecasts** — extreme price-response coefficients no longer inflate the forecasted units, value, or gross-profit uplift shown in [Price Approval](/agents/price-approval) and the grid export. See [Sales forecast & price impact](/agents/sales-forecast). * **Accurate baseline on no-change items** — an unchanged price now forecasts close to the item's recent actual sales instead of overstating them, and dormant SKUs are no longer force-forecast. See [Sales forecast & price impact](/agents/sales-forecast). ### Technical Updates * Decoupled pricing-model parameter calculation from the [Price Optimization](/agents/price-optimization) agent, so model builds run independently for more reliable agent runs. No user-visible change beyond steadier runs. **Summary**: This cycle was about trusting the numbers at scale. The elasticity behind Dynamic Pricing and Price Optimization now leans on recent, real selling behavior and refuses to propose a price below cost or outside an item's proven trading range. Underneath, the Market Data grid, metrics, and imports moved onto BigQuery, so everything stays responsive on large catalogs. ### Improvements * **Smarter, safer elasticity** — the model now gives more weight to recent price changes and to how long each price actually sold, and every recommendation is bounded to the item's observed price range and kept above cost. [Dynamic Pricing](/agents/dynamic-pricing) and [Price Optimization](/agents/price-optimization) stop proposing increases a product has already proven will erode profit, and loss-leader pricing stays a deliberate choice rather than a side effect. * **The grid and pricing models now run on BigQuery** — the Market Data grid, metrics, and CSV / API imports run on BigQuery, so grids and imports stay responsive on large catalogs, and the pricing model input ceiling was raised to 10 million rows. See [Datasets](/data-requirements/datasets). * **Group names in Price Analysis** — the [Dynamic Pricing](/agents/dynamic-pricing) Price Analysis panel now shows your category and group names instead of internal IDs. * **More items earn a confidence tier** — lowered the evidence threshold so more product groups clear the High and Medium confidence gates in [Price Optimization](/agents/price-optimization). ### Bug Fixes * Fixed **newly-shown columns showing empty until you refresh or sort** — a column's values now appear immediately when you make a previously hidden column visible in the Market Data grid. See [Manage columns](/grids/manage-columns). * Fixed the **grid not refreshing after a price approval** — the grid and the [Price Approval](/agents/price-approval) queue now update after you approve and when you return to the tab. ### Technical Updates * Moved grid, metrics, transaction reads, and imports onto BigQuery, and replaced the pricing workflow heartbeat with durable, event-driven job dispatch for more reliable agent runs. No user-visible change beyond the improvements above. **Summary**: This cycle put more decision context in front of you before you commit a price. Every SKU can now be auto-classified into a product role, price approvals show the forecasted sales and profit impact of each change, and the app wears Retailgrid's new trademark brand. Under the hood, demand and elasticity accuracy got a round of fixes so those forecasts are trustworthy. ### Features * **Product Role Classification** — automatically tag every SKU with an explainable product role (KVI, Traffic Driver, Profit Driver, Niche, Tail, New) so pricing, promo, and monitoring can treat the SKUs that matter differently, without hand-maintained tags. See [Product roles](/agents/product-roles). * **Sales-impact forecasts in Price Approval** — each proposed price now shows the forecasted units, value, gross profit, and margin over a 7 / 30 / 90 / 180-day horizon, so you can judge the financial consequences before you approve. See [Review and approve price changes](/agents/price-approval) and [Sales forecast & price impact](/agents/sales-forecast). ### Improvements * **New trademark brand across the app** — the application now shows Retailgrid's refreshed, trademark-clean logo everywhere the brand appears. ### Bug Fixes * Fixed **top-selling products getting no elasticity at any tier**, so price optimization produces recommendations where they have the biggest profit impact. * **Recalibrated base demand** at item and group level so forecasts reflect each item's actual recent sales rate. * Fixed the **homepage grid row count** not matching the row count shown inside the grid. **Summary**: This cycle was about connecting Retailgrid to the systems around it and tightening store-level control. Shopify is now a first-class data source — connect a store and your products and sales sync in and refresh automatically, no CSV uploads. Store setup got bulk CSV import/export, competitors can be scoped to the exact stores, zones, and channels where they actually compete, and approved prices are now reachable through the public API. Alongside that, the pricing approval flow, the grid, and long-running jobs all got more livable. ### Features * **Shopify integration** — connect a Shopify store to import your products and sales history, with automatic scheduled and event-driven sync so data stays fresh without manual CSV uploads. See [Connect Shopify](/settings/shopify). * **Import and export stores via CSV** — create and update stores in bulk and download them back out, instead of adding each store by hand. See [Manage stores](/stores/manage-stores). * **Scope competitors to stores, zones, and channels** — assign each competitor to the locations where it actually competes, so pricing rules and gap metrics compare against the right set per store. See [Assign competitors to stores, zones, and channels](/stores/assign-competitors). * **Quick filters, saved in Views** — show, configure, add, and remove filters from a single Filters menu, and keep them as part of a saved [View](/grids/views). ### Improvements * **Override proposed prices in Price Approval** — replace an agent's proposed price with your own number inline, while still seeing the original suggestion and its margin impact. See [Review and approve price changes](/agents/price-approval). * **Per-zone price relations** — set the relationship between each pair of zones one by one in the [Dynamic Pricing](/agents/dynamic-pricing) rule UI, instead of a single global gap. * **Cancel running jobs** — stop a long-running import, agent run, or grid fill from the [Runs](/runs/overview) view. * **Recall generated Pricing Strategy decks** — generated decks now persist across sessions, so a refresh no longer loses the link. See [Pricing Strategy](/agents/pricing-strategy). * **Persistent column widths** — columns keep the widths you set across reloads, and auto-size is now an explicit action rather than forced on every load. ### API * **Fetch approved prices via the public API** — pull finalized pricing decisions into your ERP, webshop, or POS without a manual export. See the [API changelog](/api-reference/changelog). ### Bug Fixes * Fixed **CSV / Excel export capped at 100 rows** — exports now include the full filtered set. See [Export data](/grids/export). * Fixed **CSV export failing on the Datasets page**. See [Datasets](/data-requirements/datasets). * Fixed **Config by AI errors** in the Agents and Formula columns. See [Agents overview](/agents/overview). **Summary**: This cycle put pricing managers firmly in control. Agent-proposed prices now flow through a dedicated **price approval workflow** — review, adjust, bulk-approve, and publish in one click — and Price Optimization's output was redesigned around that flow. The grid got more livable too: search any product by any attribute, and stop worrying about losing work, because layout changes autosave automatically. ### Features * Added a **price approval workflow** — review, approve, reject, or override every price proposed by [Dynamic Pricing](/agents/dynamic-pricing) and [Price Optimization](/agents/price-optimization), with comments, filters, and bulk-approve, then publish in one click. See [Review and approve price changes](/agents/price-approval). * **Search by any attribute** in the grid — find a product by SKU, name, brand, EAN, supplier, category, or any custom field, without building a filter. See [Configure grid](/grids/configure-grid). ### Improvements * **Redesigned Price Optimization output** — recommendations now appear one value per cell, with a price type and effective date you set per recommendation and a clear explanation of why each price was proposed. See [Price Optimization](/agents/price-optimization). * **Grid autosave** — filtering, sorting, grouping, and column changes save automatically with a status-line confirmation; the "Leave site?" warning is gone. See [Configure grid](/grids/configure-grid). * **Store-level pricing refinements** — Sync Prices respects a chosen group column at the store-product level, and Price Relations can hold a structure across store attributes like pricing zones. See [Dynamic Pricing](/agents/dynamic-pricing). * **Price Optimization clusterization** — sparse-history SKUs borrow demand signal from clusters of similar items instead of dropping out. * **More room for the grid** — reclaimed vertical space so more product rows are visible at once. * **Aligned data model and API docs** on the Product vs SKU distinction — `item_id` is the SKU-level key, `product_id` groups related items. See [Datasets](/data-requirements/datasets). ### Bug Fixes * Fixed **grid list sorting** on the home page — the list now sorts predictably with a created-date default. See [Grids overview](/grids/overview). * Fixed **sorting and filtering on the image column**, with a placeholder for products that have no image. **Summary**: This cycle shipped store-level pricing end to end. You can now define your stores, markets, and channels in a dedicated Stores section, load per-store prices and costs by CSV or API, work in a SKU-Store grid view, and run pricing rules that respect each store's own competitor set and costs. Alongside it, Dashboards grew into a full editor — manual building and AI assistance on the same configuration — and the grid gained a time-series analytics panel that shows how any SKU's price, competitors, sales, and profit have moved over time. ### Features * Added **Stores management** — create and organize stores with country, currency, sales channel, and pricing zone, and group stores into pricing zones that share pricing logic. See [Manage stores](/stores/manage-stores). * Added a **SKU-Store grid view** — toggle between Product view (one row per product) and SKU-Store view (one row per product-store combination) with store columns, store-aware filters, and metrics computed per store. See [Product and SKU-Store grid views](/grids/sku-store-view). * **[Dynamic Pricing](/agents/dynamic-pricing)** now runs at the SKU-Store level — one recommended price per product-store combination, using each store's own competitor prices and costs, with rules targetable by country, channel, or pricing zone. * **Store-level data via the Public API** — ingest endpoints accept `store_id` alongside `product_id`, with `(product_id, store_id)` upsert semantics and full backward compatibility for product-level payloads. See the [API changelog](/api-reference/changelog). * Added a **SKU time-series analytics panel** in the grid — select a SKU to see Competitor Prices, Sales & Price, and Profit & Price over time (4–52 week windows, daily or weekly), with promo and below-cost markers and a profit-maximizing price view. See [SKU time-series analytics panel](/grids/sku-analytics-panel). * Added the **dashboard editor** — one place to build and modify dashboards, combining a manual builder (searchable widget picker, drag & drop layout, auto-save) with a **Dashboard Assistant** that adds, removes, or replaces widgets from a plain-language prompt. See [Dashboard editor](/dashboards/editor). ### Improvements * **Rules Based Pricing is now Dynamic Pricing** — same agent, same engine, clearer name. Existing configurations, runs, and output columns are unaffected, and old documentation links redirect. * Refreshed the **Dashboards home page** — built-in and custom dashboards in one list, with create, rename, and delete; built-in dashboards are now editable and can be reset to their defaults. See [Dashboards](/dashboards/overview). * Added **global dashboard filters** — Period, Category, Brand, and Competitor — applied consistently to every widget on the dashboard. * **Metrics are now computed at the SKU-Store grain** in BigQuery, so margin, price index, and competitor-gap figures reflect each store's own price, cost, and competitor set, with a product-level rollup for Product view. See the [Metrics glossary](/reference/metrics). **Summary**: This cycle made Retailgrid feel more programmable and more analytical at the same time. The Public API gained outbound read endpoints — Products, Transactions, and a full Grid export — closing the inbound/outbound asymmetry that integrators have asked about since v1. Dashboards picked up an AI creation flow so analysts can describe what they want to see in plain language. And the grid grouping experience finally feels sensible out of the box: each column type now picks a meaningful default aggregation, and all aggregation runs on the backend so the numbers are correct even at full grid scale. ### Features * Shipped read endpoints on the **[Public API](/api-reference/introduction)** — `GET /v1/products`, `GET /v1/transactions`, `GET /v1/grids`, and `GET /v1/grids/{grid_id}/rows` — for ERP and BI ingestion. The grid export includes canonical, custom, and formula/calculated columns. See the [API changelog](/api-reference/changelog) for the full list. * Added **AI-driven dashboard creation** — describe the dashboard you want in plain language from the [Create with AI](/dashboards/create-with-ai) entry point on the [Dashboards](/dashboards/overview) page, review the example prompts, and save the generated view. *(v1 — single dashboard per prompt.)* * Added **Conditional Color Highlighting** to grid columns — drive cell color from a formula result or value condition (e.g. `IF({Margin} > 20, "High Margin", "Low Margin")`), much like Excel conditional formatting. Configure it from the column header menu in [Add Column](/grids/manage-columns). ### Improvements * The **[Runs](/runs/overview)** list's **Type** column now shows the agent name (e.g. **Pricing Strategy**) instead of the raw action name. Easier to scan, matches how you think about your runs. * **Smart default aggregation per column type** in the grid — sales sum, prices and margins average, identifiers count. Override per column from the column menu when you need a different rollup. * **Agents UI** in [Rules Based Pricing](/agents/rules-based-pricing) — separated **Config by AI** from the configuration tabs so it has its own surface above the tabs, and gave the [Price Analysis panel](/agents/rules-based-pricing#read-the-result) a polish pass (consistent 2-decimal formatting, cleaner labels, a margin-impact card trio). ### Bug Fixes * **Grid grouping aggregation** is now computed on the backend, so rolled-up values (Sum, Average, Count, Min, Max) are correct and consistent at any grid size — including large grids where the previous frontend implementation was unreliable. **Summary**: This cycle made Retailgrid feel more responsive across three axes — language (a working **English / Finnish** switcher and a fully localized AG Grid chrome), data freshness (every grid now refreshes itself overnight, no manual replace needed), and observability (the in-app loader is now hard to miss). Under the hood, the static metrics that used to live in Cloud SQL now flow through BigQuery, so every metric — not just sales — accumulates a daily history. ### Features * Shipped **multi-language UI** powered by Crowdin. A language switcher in [Settings → Account](/settings/account) lets you pick **English** or **Finnish**; the entire app, including AG Grid headers, filter menus, and pagination, follows your selection. Your own data (product names, categories) is never translated. ### Improvements * **Nightly grid refresh**: every grid now refreshes automatically overnight with the latest data from your [Datasets](/data-requirements/datasets). Replaced datasets are picked up the next morning without any manual action. * The in-app **processing indicator** is now centered and more prominent — no more clicking around while a grid upload is still running. Only the agents that are actually in flight are shown in the loader. * [Pricing Strategy](/agents/pricing-strategy) runs now surface a **Download** button on the run progress card as soon as the deck is ready, so you can grab the Gamma link without drilling into [Runs](/runs/overview). * Static metrics like **Margin** and **Current Unit Margin** now keep a daily history in BigQuery alongside time-series metrics — enabling future analytics that compare how every [metric](/reference/metrics) has moved over time, not just sales. ### Bug Fixes * In the [Grid creation flow](/grids/create-grid), pressing **Back** after a mapping error now returns you to the **Mapping** step with your selections preserved, instead of dropping you on the legacy upload screen. The legacy `/upload` route was also removed. ### Technical Updates * All transactional tables now stream from Cloud SQL into BigQuery on an ongoing basis, consolidating analytics on a single warehouse. No user-visible change today; this is the foundation for upcoming time-series dashboarding. **Summary**: This cycle promoted the [Pricing Strategy](/agents/pricing-strategy) agent from prototype to product (real grid data, real account binding, full timeframe ladder), added Single Select / Status as a first-class grid column type, and tightened the Runs view with a default 7-day window and an active-jobs badge. ### Features * Promoted the **[Pricing Strategy](/agents/pricing-strategy)** agent to product: it now reads your **Account** company name and runs on real Grid data via BigQuery — no more placeholder numbers — and supports seven timeframes (last 7 / 30 / 90 days, QTD, YTD, T12M, Custom). * Added **Single Select / Status** as a column type in [Add Column](/grids/manage-columns), with auto-mapped colored pills — ideal for product roles, workflow states, and risk tiers. ### Improvements * The **[Runs](/runs/overview)** view now shows the **last 7 days** by default, with **Last 30 days** and **All time** controls when you need a wider window. * Added an **active-runs badge** to the Runs sidebar icon — a quick at-a-glance signal that something is still in flight. * Polished the **Price Analysis panel** in [Rules Based Pricing](/agents/rules-based-pricing): cleaner labels (**Start Price**, **Applied Rules**), consistent 2-decimal formatting, and a margin-impact card trio (new unit margin, pp change, relative change). **Summary**: This cycle shipped the public REST API for data ingestion, surfaced pricing-rule traceability in the right Insights Panel, added richer column-type support to the grid, introduced the Pricing Strategy agent, and cleaned up the MVP UI ahead of the v1 demo. ### Features * Shipped the **public [REST API v1](/api-reference/introduction)** for Products and Transactions data ingestion, so retailers can load and maintain data programmatically without manual CSV uploads. * Added a **Rules Based Price Insights Panel** — click any calculated rules-based price in the grid to see the pricing trace, applied rules, and any violated rules for that row. * Added **Analytical Metric** as a third column type in the Add Column flow, so you can pick from a curated list of pre-built [retail metrics](/reference/metrics) (margin, revenue, price index, elasticity). * Added the **[Pricing Strategy](/agents/pricing-strategy)** agent — generates an AI-powered pricing strategy presentation directly from your retail data. ### Improvements * Refreshed the [Agents](/agents/overview) selection UI: removed Credits / Buy Credits placeholders and per-agent pricing, regrouped agents to match the new structure. * Removed credits, billing, and Team placeholder sections from UI Settings ahead of MVP. ### Bug Fixes * Fixed the Group column disappearing from view after applying Group By in the grid. * Fixed columns (notably Product URL and Image URL) disappearing from the grid after running the [Rules-Based Pricing](/agents/rules-based-pricing) agent. **Summary**: This cycle focused on making [Rules-Based Pricing](/agents/rules-based-pricing) faster, more reliable, and easier to use. We shipped AI-assisted rule setup, reusable action configs, several UX improvements, and fixes for large-grid and session reliability issues. ### Features * Added **AI-powered rule creation**, so you can describe a pricing rule in plain language and review the generated setup before saving. * Added **save and load for action configurations**, making it easier to reuse pricing setups. * Improved rule setup with **context-aware column selection and search**, so only relevant fields are shown for each rule type. ### Improvements * Refreshed the Actions UI to better match the Retailgrid design system and improve consistency across the flow. * Moved advanced rule parameters behind a cleaner expandable section to reduce UI clutter. * Improved app availability so the product stays responsive instead of needing to wake up after inactivity. ### Bug Fixes * Fixed grid creation filters so they apply correctly and update previews as expected. * Fixed failures when working with very large grids, including datasets around 200k rows. * Fixed Rules-Based Pricing execution being blocked by another job. * Fixed rounding behavior in Rules-Based Pricing so rule order and rounding work together more predictably. * Added automatic logout after long inactivity for better session security and more reliable timeout behavior. ### Technical Updates * Reduced memory issues in Rules-Based Pricing runs on large datasets. * Prevented database connection exhaustion in production to improve backend stability. **Summary**: This cycle focused on making competitor pricing workflows much more usable and trustworthy. We shipped major Market Data UI improvements, added clearer pricing visuals, and fixed several pricing and data accuracy issues. ### Features * Added a new **competitor pricing view** in the grid, so you can compare [competitor prices](/data-requirements/competitors) directly in-row and open source links faster. * Added a visual **Price Range** column showing min/max competitor prices and where the current price sits in the market range. * Improved the [Columns panel](/grids/manage-columns) with grouped sections, search, expand/collapse, and bulk show/hide controls for faster navigation in large datasets. * Added column type icons to make editable, formula, and AI-generated columns easier to distinguish. ### Improvements * Moved the [Export](/grids/export) button to a more accessible position in the toolbar. * Renamed "Load Data" to "Upload Data" for clearer terminology. * Improved competitor matching by prioritizing Product ID before falling back to SKU. ### Bug Fixes * Fixed incorrect competitor price indicator colors so market position is shown accurately. * Fixed stale Zooplus competitor prices so the latest value is displayed. * Corrected competitor gap and undercut percentage calculations. * Fixed missing "Last updated" values in [Datasets](/data-requirements/datasets). * Improved rule-based pricing rounding so rounded prices stay within the feasible range. ### Technical Updates * Migrated production infrastructure to Google Cloud. * Set up profiling and telemetry foundations to improve performance analysis and debugging. **Summary**: This cycle shipped major usability improvements across pricing, data management, and grid workflows. You can now configure pricing rules visually, manage datasets directly in the app, and work with clearer grid controls and data signals. ### Features * **[Rules-based pricing](/agents/rules-based-pricing)**: Shipped a full rules configuration flow with support for multiple rule types, rule ordering, conditions, enable/disable controls, and action execution. * **[Datasets management](/data-requirements/datasets)**: Added a dedicated datasets page for uploading, viewing, downloading, replacing, and deleting imported files, with status tracking and row counts. ### Improvements * Added a derived **promo flag** (`is_promo`) to support filtering, rules, and downstream workflows involving promotional items. * Updated the grid to show **filtered vs total row count**, making it easier to understand active filters at a glance. * Made **[Add Column](/grids/manage-columns)** easier to access from the main menu. * Improved CSV import behavior by **removing automatic grid creation**, giving users more control during import flows. ### Bug Fixes * Fixed import behavior so CSV uploads no longer create grids automatically in the background. # Competitive Position dashboard Source: https://docs.retailgrid.io/dashboards/competitive-position Read the built-in Competitive Position dashboard - CPI, competitor moves, win/loss, and the category heatmap - to see where competitors are pricing against you. **Competitive Position** is one of the dashboards Retailgrid ships out of the box (see [Dashboards overview](/dashboards/overview)). It answers one question: **where are competitors pricing against you, and where should you react first?** Every tile reads live from your [datasets](/data-requirements/datasets) and follows the dashboard's global filters (Period, Store, Country, Zone, Category, Brand, Competitor). This page explains each tile and how to read it. For the underlying metric definitions, see the [Metrics glossary](/reference/metrics). ## KPI row - your overall price position The tiles across the top summarise price position at a glance, each with a prior-period comparison: * **CPI** - your competitive price index against the market, in three reads: **CPI (Min)** vs the cheapest competitor, **CPI (Mode)** vs the most common market price, and **CPI (Weighted)** weighted by sales value so the SKUs you actually sell count most. 100 = parity, under 100 = you're cheaper, over 100 = you're more expensive. * **Gap to cheapest competitor** - how far your price sits above or below the lowest competitor, in money and percent. * **SKUs above / below market** - how much of your assortment is priced over vs under the market reference right now. ## SKU Position Distribution A histogram of your SKUs bucketed by CPI, so you see the **shape** of your price position, not just the average. Bands split cleanly at parity (100): the mass to the left is where you're cheaper than the market, the mass to the right is where you're more expensive. Use it to spot a long tail of over-priced SKUs that a single average would hide. ## Competitor Price Moves A count of distinct competitor price changes in the selected period, with a prior-period comparison. This is your "how active is the market right now?" read - a spike means competitors are repricing and your position is drifting even if your own prices haven't changed. Widen the Period filter for a trend, narrow it to catch a fresh burst. ## Win / Loss vs top competitor A weekly split of how many SKUs you **win** (priced at or below) versus **lose** (priced above) against your primary competitor - the one that covers the most of your SKUs, picked automatically. Read the trend: a rising loss line means the competitor is pulling ahead on price across more of your range week over week. ## Competitor Move Heatmap A grid of competitor price moves broken down by **competitor x category**, so you can see *who* is repricing *where*. A hot row is a competitor on the move across many categories; a hot column is a category under pressure from several competitors at once. Use it to aim the next pricing pass at the categories taking the most heat. ## Make it your own The built-in dashboard is a starting point. Open it in the [dashboard editor](/dashboards/editor) to add, remove, or rearrange tiles, or describe a different view in plain language with [Create a dashboard with AI](/dashboards/create-with-ai). A built-in dashboard can always be reset to its default. ## Related * [Dashboards overview](/dashboards/overview) * [Create a dashboard with AI](/dashboards/create-with-ai) * [Metrics glossary](/reference/metrics) * [Competitor prices data spec](/data-requirements/competitors) # Create a dashboard with AI Source: https://docs.retailgrid.io/dashboards/create-with-ai Describe the analytical view you want in plain language and Retailgrid generates the dashboard for you. When the built-in [Dashboards](/dashboards/overview) don't answer the question you're asking, the **Dashboard Assistant** lets you describe the view you want and Retailgrid generates it from your data. The assistant lives as a panel inside the [dashboard editor](/dashboards/editor) - AI generation and manual editing work on the same dashboard configuration, so you can prompt, then fine-tune by hand, then prompt again. ## Start from a prompt 1. Open **Dashboards** from the left sidebar. 2. Create a new dashboard (or open an existing one). The dashboard editor opens with the **Dashboard Assistant** panel on the left. 3. Describe the view you want and send the prompt. The assistant can also generate or update the dashboard's **name** and **description** to match its content. ## Write a useful prompt The AI does better when your prompt names four things: 1. **The metric** - revenue, gross margin, price index, sales units, competitor undercut count. 2. **The time window** - last 7 / 30 / 90 days, this quarter, year over year. 3. **The dimension** - by category, by brand, by store, by competitor. 4. **The intent** - top-N, comparison vs. last period, trend over time, distribution. Good prompts: * *"Show me **gross margin %** for the last **90 days**, broken down by **category**, as a **trend** with one line per category."* * *"List the **top 50 SKUs by sales** where a **competitor is undercutting us right now**, with the gap in % and absolute terms."* * *"Compare **price index** **this quarter vs. last quarter** by **category**, sorted by largest change."* Weak prompts that produce vague results: * *"Show me pricing."* - no metric, no window, no dimension. * *"How are we doing?"* - no measurable signal. ## How the assistant applies changes The assistant reads your prompt **and the current dashboard configuration**, then works in one of three modes: * **Add** - extends the dashboard with additional widgets or filters. Widgets already on the dashboard are never duplicated; if everything relevant is already there, the assistant says so instead of adding noise. * **Remove** - takes widgets or filters off the dashboard. * **Replace** - swaps in a completely different dashboard when you ask for something new. Changes apply to the dashboard immediately - there's no separate apply step - and every response includes a summary of what was added, removed, or updated. The dashboard **auto-saves** as you go (you'll see *Saved · Just now* under the dashboard name). ## Iterate on the result Because the assistant and the manual builder share the same configuration, iteration is fluid: * **Re-prompt** - ask for the change directly (*"swap the trend chart for a bar chart by brand"*). The assistant modifies the existing dashboard rather than starting over. * **Edit by hand** - add or remove widgets from the widget picker, drag widgets between rows, adjust filters. See the [dashboard editor](/dashboards/editor) guide. ## Limitations * Widget vocabulary is bounded by the widget catalog and the metrics in your [Metrics glossary](/reference/metrics). Asking for a metric you don't have data for won't work. * Each dashboard holds one instance of each widget - the assistant won't add the same widget twice. * Filters are global to the dashboard (Period, Store, Country, Zone, Category, Brand, Competitor - store-level filters apply once store data is loaded); per-widget filters aren't supported. ## Related * [Dashboard editor](/dashboards/editor) * [Dashboards overview](/dashboards/overview) * [Datasets](/data-requirements/datasets) * [Metrics glossary](/reference/metrics) # Dashboard editor Source: https://docs.retailgrid.io/dashboards/editor Build and modify dashboards in one place - a manual builder with drag & drop and a widget picker, plus an AI assistant working on the same configuration. The **dashboard editor** is the single place to create and modify dashboards. It combines two panels working on the same dashboard configuration: * **Dashboard Builder** - direct manipulation: add widgets, arrange the layout, set filters. * **Dashboard assistant** - AI panel on the left: describe a change in plain language and it's applied to the dashboard. The panel can be hidden when you don't need it. Whatever one panel changes, the other sees - prompt first and fine-tune by hand, or build by hand and ask the assistant to extend it. The dashboard editor - Dashboard assistant on the left, builder with the Add widget tile on the right ## Open the editor From the [Dashboards](/dashboards/overview) page, create a new dashboard or open an existing one for editing. Built-in dashboards open in the editor too - they're fully editable (see below). ## Name and description Every dashboard has a required **name** and an optional **description**. Enter them manually, or let the [Dashboard assistant](/dashboards/create-with-ai) generate and update them to match the dashboard's content. ## Add and remove widgets * An **Add widget** tile is always visible. Click it to open the **widget picker** - a searchable list of every available widget, each with its title, description, and type (KPIs, chart, table). * Each widget on a dashboard is unique. Widgets already present are marked as added in the picker and can't be selected twice. * Remove a widget via its overflow menu (⋮) → **Delete**, with a confirmation step. The widget picker - searchable list of available widgets Widgets visualize the same filtered dataset from different angles - one dashboard is one analytical view, which is why duplicates and per-widget filters don't exist. ## Filters The filter bar above the dashboard content applies to **every widget**: * **Period** - always present. Presets from yesterday through last 7 / 30 / 90 / 180 days, last year, last 5 years, and all time. * **Filters** - attach **Store**, **Country**, **Zone**, **Category**, **Brand**, or **Competitor** filters next to the Period control. Change any filter and every widget refreshes with the new scope. ## Arrange the layout The layout is rows of widgets: * **Drag & drop** to reorder widgets within a row or move them between rows. * Drop a widget on the **bottom drop zone** to start a new row. * Widgets stretch to fill their cell; KPI cards and chart cards keep their natural heights, so the editor works best when similar widget types share a row. ## Auto-save The editor saves automatically as you work - watch for **Saved · Just now** beneath the dashboard name. There is no separate save step, including for changes made by the assistant. ## Built-in dashboards: edit and reset The built-in dashboards are editable like any other - widgets, filters, metadata, layout. Two guarantees keep them safe: * **Reset** restores the original built-in configuration at any time. * They can't be permanently deleted - a removed built-in dashboard is recreated from its default template. ## Related * [Dashboards overview](/dashboards/overview) * [Create a dashboard with AI](/dashboards/create-with-ai) * [Metrics glossary](/reference/metrics) # Dashboards overview Source: https://docs.retailgrid.io/dashboards/overview Pre-built and AI-generated analytical views over your Retailgrid data - pricing, competitor pressure, margin health - all in one place. **Dashboards** turn the data already in your grids into ready-to-read analytical views. Open **Dashboards** from the left sidebar to see the built-in dashboards Retailgrid ships with, plus any dashboard you or a teammate has created. Dashboards read from the same underlying [datasets](/data-requirements/datasets) your grids read from - no separate data load is needed. Refresh a dataset and every dashboard built on it follows. ## The Dashboards home page The Dashboards page lists every dashboard in one place - **Built-in Dashboards** and **My Dashboards**. From here you can: * **Open** any dashboard in place. Every chart is interactive - hover for the data point, click a legend entry to isolate it. * **Create** a new dashboard from the **New dashboard** tile - build it by hand or chat with the AI assistant in the [dashboard editor](/dashboards/editor). * **Rename** or **delete** a dashboard you created. The Dashboards home page - built-in dashboards and your own ## Built-in dashboards Retailgrid ships analyst-grade dashboards out of the box: * **Competitive Position** - where competitors are pricing aggressively against you: CPI variants, gap to the cheapest competitor, SKUs above and below market, and competitor price moves, with trend charts below. * **Margin Leakage by Category** - margin leakage by category versus competitor price index, so you can focus the next pricing pass on the categories that matter. Built-in dashboards are **editable** - open one in the [dashboard editor](/dashboards/editor) to change its widgets, filters, or layout. They can't be permanently deleted: a **Reset** action restores the original configuration at any time, and a removed built-in dashboard is automatically recreated from its default template. ## Filters Every dashboard has a global filter bar: * **Period** - presets from yesterday to all time; every widget follows the selected window. * **Store**, **Country**, **Zone**, **Category**, **Brand**, and **Competitors** filters narrow the scope - store-level filters become meaningful once [store-level data](/data-requirements/product-store-data) is loaded. Filters are global to the dashboard: change a filter and every widget refreshes with the new scope. There are no per-widget filters - a dashboard is one analytical view, and its widgets are different visualizations of the same filtered dataset. An open dashboard with the global filter bar and KPI widgets ## Create with AI When the built-in dashboards don't answer your question, describe what you want to see in plain language and Retailgrid generates a dashboard for you. The AI assistant also works on existing dashboards - it can add, remove, or replace widgets from a prompt inside the [dashboard editor](/dashboards/editor). Full walkthrough: [Create a dashboard with AI](/dashboards/create-with-ai). ## Refresh and freshness Dashboards read live from your datasets - no manual refresh step. The freshness signal on each dashboard matches the freshness of the underlying datasets: * Datasets refreshed overnight - dashboards reflect last night's data when you open them in the morning. * Replaced a dataset manually - the next dashboard load reflects the new data. If a chart looks stale, check the dataset on the [Datasets](/data-requirements/datasets) page. ## Sharing Dashboards are visible to everyone in your [Organization](/settings/organization). Anyone on your team can open them; only the creator (and admins) can rename or delete a custom dashboard. ## Related * [Dashboard editor](/dashboards/editor) * [Create a dashboard with AI](/dashboards/create-with-ai) * [Datasets](/data-requirements/datasets) * [Metrics glossary](/reference/metrics) * [Grids overview](/grids/create-grid) # Competitor prices Source: https://docs.retailgrid.io/data-requirements/competitors The Competitor Prices dataset allows you to upload, monitor, and analyze competitor pricing at SKU level over time. The **Competitor Prices** dataset allows you to upload, monitor, and analyze competitor pricing at SKU level over time. This dataset powers: * Competitive positioning analysis * Price gap and price index calculations * Dynamic pricing automation * Promo monitoring * AI elasticity and competitive pressure modeling Each row represents: > **One competitor price observation for one product at a specific point in time** ## Dataset structure Competitor Prices is a **time-series dataset**. You can upload: * Historical snapshots (6–24 months recommended) * Daily / weekly price updates * Automated feeds (scraping, Google Shopping, APIs) ## Data schema All required and optional fields are listed below. | Field Name | Required | Type | Format | Description | Alternative Names | Example | | :- | :- | :- | :- | :- | :- | :- | | `itemId` | ⬜ No\* | string | Text | Unique SKU-level identifier linked to your Products dataset | item\_id, sku\_id, product\_code | SKU-1001 | | `datetime` | ✅ Yes | datetime | YYYY-MM-DD HH:MM:SS | Date & time of price observation | date, timestamp, observed\_at, price\_date | 2026-02-22 14:35:00 | | `competitorName` | ✅ Yes | string | Text | Competitor / retailer name | competitor, seller, shop\_name, retailer | Bauhaus | | `regularPrice` | ✅ Yes | float | Dot separator | Competitor regular (non-discounted) price | price, list\_price, base\_price | 125 | | `promoPrice` | ⬜ No | float | Dot separator | Competitor promotional price (if applicable) | sale\_price, discounted\_price, promo\_price | 119.99 | | `currency` | ⬜ No | string | ISO 4217 | Currency code | curr, currency\_code | EUR | | `url` | ⬜ No | string | URL | Source product page | link, product\_url, source\_url | [https://bauhaus.fi/product123](https://bauhaus.fi/product123) | | `barcode` | ⬜ No | string | Text | EAN / UPC barcode | ean, ean13, upc, gtin | 5901234123457 | | `sku` | ⬜ No | string | Text | Competitor SKU | competitor\_sku, seller\_sku | SKU-1001 | | `status` | ⬜ No | string | Text | Lifecycle status (active / discontinued) | availability\_status, product\_status | active | | `stock` | ⬜ No | float | Numeric | On-hand inventory quantity (if available) | inventory, stock\_qty, quantity\_available | 125 | \* At least one identifier (`itemId`, `barcode`, or `sku`) must be present for product matching. `itemId` gives the highest match accuracy and is strongly recommended. You may add additional attributes as separate columns (CSV / XLSX upload only — see [Loading via the API](#loading-via-the-api)). ## Product matching Retailgrid matches each competitor price observation to a product in your Products dataset using this priority order: 1. `itemId` — used first when present. 2. `barcode` (EAN / UPC) — used as fallback when `itemId` is missing. 3. `sku` — used as a final fallback when neither `itemId` nor `barcode` matches. Provide all three when possible. Provide `itemId` at minimum — it gives the highest match accuracy and the fewest unmatched rows. Matching rules are configurable during dataset setup. Custom product codes and composite keys are also supported. Formatting tips: * Ensure consistent formatting between datasets. * Avoid leading/trailing spaces. * Keep barcode values numeric without formatting. ## Competitor scope By default every competitor here applies to all of your stores. If different stores, pricing zones, or sales channels face different rivals, scope each competitor to where it competes — see [Assign competitors to stores, zones, and channels](/stores/assign-competitors). Scoping drives which competitors per-store pricing rules and competitor-gap metrics use. ## Price interpretation logic Retailgrid applies the following logic: * If `promoPrice` exists → it is treated as the active selling price * If `promoPrice` is empty → `regularPrice` is used * If both exist → system calculates: * Discount % * Promo depth * Effective price * Price gap vs your price * Price index ## Currency handling * If all data is in one currency → no conversion required * If multiple currencies are present → configure FX conversion rules Recommendation:\ Upload competitor prices in the same currency as your product prices. ## Time-series behavior Retailgrid stores Competitor Prices historically. This enables: * Price change tracking * Promo frequency analysis * Volatility scoring * Price war detection * EDLP vs Hi-Lo classification * AI elasticity modeling Each upload should include a valid `datetime` value. Do not overwrite history. ## Data format requirements Supported formats: * CSV (recommended) * XLSX Encoding: * UTF-8 * Dot (.) as decimal separator Rules: * One row = one product × one competitor × one timestamp * Do not combine multiple competitors in one row * Do not store historical prices in multiple columns Correct example: | itemId | datetime | competitorName | regularPrice | | :- | :- | :- | :- | ## Loading via the API Competitor prices can also be loaded programmatically: `POST /v1/competitor_prices/bulk` for JSON records, or `POST /v1/imports/competitor_prices` for CSV import. JSON payloads use snake\_case field names that differ from the CSV columns: | CSV column | API field | | :- | :- | | itemId | `item_id` (optional) | | datetime | `observed_at` (required) | | competitorName | `competitor_name` (required) | | regularPrice | `regular_price` (required) | | promoPrice | `promo_price` | | url | `source_url` | | sku | `competitor_sku` | | stock | `stock_on_hand` | | barcode | `barcode` | | status | `status` | | currency | `currency` | Additional attributes as extra columns are supported for CSV / XLSX upload only. The JSON API rejects unknown fields — payloads containing fields outside the schema fail validation. ## Validation rules Upload will fail if: * `datetime`, `competitorName`, or `regularPrice` is missing * No product identifier (`itemId`, `barcode`, or `sku`) is present * `datetime` format is invalid * `regularPrice` is empty or non-numeric * Matching with Products dataset fails * Duplicate identical rows are detected ## Example row | itemId | datetime | competitorName | regularPrice | promoPrice | currency | url | barcode | sku | status | stock | | :- | :- | :- | :- | :- | :- | :- | :- | :- | :- | :- | | SKU-1001 | 2026-02-22 14:35:00 | Bauhaus | 125 | 119.99 | EUR | [https://bauhaus.fi/product123](https://bauhaus.fi/product123) | 5901234123457 | SKU-1001 | active | 125 | # Datasets Source: https://docs.retailgrid.io/data-requirements/datasets Manage the product, transaction, and competitor data you have uploaded to Retailgrid in one place. The **Datasets** page is where you manage all the data you've loaded into Retailgrid — product master data, transaction history, competitor prices, and product-store-level data — in one place. Open it from the **Datasets** icon in the left sidebar. Datasets page, grouped into Inbound and Outbound ## What you'll see Datasets are grouped by direction, so it's clear at a glance what is coming into Retailgrid and what is going back out. ### Inbound The data you load into Retailgrid. One row per dataset: | Dataset | What it holds | | :- | :- | | **Products** | Your product master data. | | **Transactions** | Your sales history. | | **Competitors** | Competitor price observations. | | **Store Prices** | Per-store prices, costs, and stock, for multi-store retailers. | Each row shows: | Column | What it shows | | :- | :- | | **Dataset** | The dataset name. | | **Status** | `Ready` once data is loaded, or `Empty` when nothing has been loaded for that type yet. | | **Last updated** | When the data was last replaced or refreshed. | | **Uploaded by** | The team member who loaded the current version. A dash means it wasn't loaded by a person, for example a scheduled sync. | | **Rows** | Row count after parsing. | | **Connector** | How the data got in - `CSV` for files uploaded through the UI, `API` for the public REST API, or `Shopify` for a connected Shopify store. | ### Outbound The data Retailgrid publishes back to your systems. Today this is your **Approved prices**: the prices your team signed off in [Price Approval](/agents/price-approval), on their way to a connected destination. An outbound row shows its publish state rather than a load state - `Pending` while approvals are waiting to go out, `Publishing` while a push is running, then `Published`, or `Partial success` / `Failed` when some or all of the batch didn't land. Pending approvals can be cancelled before they publish. The Outbound group only appears once you have a destination connected to publish to. See [Connect Shopify](/settings/shopify). ## Upload a new dataset Click **Upload data** in the top-right corner. The Upload Data modal opens; pick the dataset type (Products, Transactions, Competitors, Store Prices) and select your file. For the field requirements per dataset, see: * [Products](/data-requirements/products) * [Sales / Transactions](/data-requirements/sales) * [Competitor Prices](/data-requirements/competitors) * [Product store data](/data-requirements/product-store-data) — for multi-store retailers You can also load Products, Transactions, Competitor prices, and store-level data programmatically via the public Retailgrid REST API — see [Imports vs bulk](/api-reference/imports-vs-bulk). To sync from Shopify instead of uploading files, see [Connect Shopify](/settings/shopify). ## Replace, download, or delete a dataset Use the three-dot menu at the right of any dataset row to: * **Replace** the file — upload a fresh CSV or XLSX without changing the dataset's role. The new file replaces the previous version's rows and the row count updates accordingly. * **Download** the current file — useful for reviewing what's currently loaded or sharing with a colleague. * **Delete** the dataset. Replacing a dataset preserves its role (Products stays the Products dataset) and refreshes the data available to your existing grids and agents. Deleting a dataset removes its rows from Retailgrid. Grids that depended on those rows will become empty or partial. Replace rather than delete when you only want to refresh the data. ## Status values Inbound datasets carry a load state: | Status | Meaning | | :- | :- | | `Empty` | Nothing loaded for this dataset type yet. | | `Ready` | Data is loaded and available for use in grids and agents. | While an upload is being parsed, follow it in [Runs](/runs/overview) rather than on this page - that's where a failed import shows its error and the rows that caused it. Outbound datasets carry a publish state instead: `Pending`, `Publishing`, `Published`, `Partial success`, `Failed`, or `Cancelled`. ## When to upload vs replace * **First time a dataset type is loaded** — use **Upload data** and pick the type. * **Refreshing data you already loaded** (e.g. monthly product master refresh, daily competitor price feed) — use **Replace** on the existing row. This keeps grid bindings intact. Replaced datasets are picked up by the nightly grid refresh; existing grids show the new rows the next morning without any further action. * **Switching to a totally different schema** — delete and re-upload, then expect to remap fields. ## Related * [Products data requirements](/data-requirements/products) * [Sales / Transactions data requirements](/data-requirements/sales) * [Competitor Prices](/data-requirements/competitors) * [Product store data](/data-requirements/product-store-data) * [Quickstart](/get-started/quickstart) # Product store data Source: https://docs.retailgrid.io/data-requirements/product-store-data The Product Store-Level dataset allows you to provide product information at store, market, or sales-channel level. The **Product Store-Level** dataset allows you to provide product information at store, market, or sales-channel level. This dataset is required if: * You operate multiple physical stores * You sell across different markets (countries) * You manage multiple sales channels (Online / Marketplace / Wholesale) * Prices, costs, tax rates, or stock differ per store Each row represents: > **One product in one specific store (or market / channel)** *** ## When to use store-level data Use this dataset if: * Prices vary by store * Unit cost differs by warehouse or region * Stock is managed per store * Tax rates vary by country * Some products are active in certain stores only If all stores share identical product data, you may use only the Products dataset. *** ## Data schema | Field Name | Required | Type | Format | Description | Alternative Names | Example | | :- | :- | :- | :- | :- | :- | :- | | `item_id` | ✅ Yes | string | Text | Unique SKU-level identifier (must match `item_id` in the Products dataset) | itemId, sku\_id | SKU-1001 | | `store_id` | ✅ Yes | string | Text | Store / market / sales channel identifier | storeId, store\_code, market\_id, channel\_id, location\_id | FI-HELSINKI | | `price` | ✅ Yes | float | Dot separator | Current selling price | selling\_price, current\_price | 119.99 | | `unit_cost` | ⬜ No | float | Dot separator | Cost per unit | cost, purchase\_cost, landed\_cost | 75 | | `status` | ⬜ No | string | Text | Product status in this store (active / discontinued / out\_of\_stock) | availability\_status, lifecycle\_status | active | | `stock` | ⬜ No | float | Numeric | Available inventory units | inventory, stock\_qty, quantity\_available | 42 | | `regular_price` | ⬜ No | float | Dot separator | Non-discounted base price | list\_price, base\_price | 129.99 | | `tax_rate` | ⬜ No | float | Percentage (0–1 or 0–100) | VAT or sales tax rate | vat, vat\_rate, tax | 24 | | `currency` | ⬜ No | string | ISO 4217 | Currency code | currency\_code | EUR | | `price_effective_date` | ⬜ No | datetime | YYYY-MM-DD | Date when price becomes effective | effective\_from, valid\_from | 2026-02-01 | You may include additional attributes as separate columns (CSV / XLSX upload only — the JSON API rejects unknown fields). *** ## Matching logic * `item_id` must match the Products dataset * `store_id` must match a store defined in [Stores management](/stores/manage-stores) Matching can be configured using: * Exact match * Composite keys (item\_id + store\_id) * Custom identifiers *** ## Load by CSV or by API Store-level data can be loaded two ways: * **CSV / XLSX upload** — via the Datasets page, following the format requirements below. * **Public API** — push product-store records programmatically (`POST /v1/product_store/bulk` for JSON records, `POST /v1/imports/product_store` for CSV import), one record per product-store combination. The upsert key is `(item_id, store_id)`; `store_id` is required on product-store payloads — product-level records are managed via the Products endpoints instead. See the [API changelog](/api-reference/changelog) for details. API payloads differ from the CSV schema in a few places: * Required fields in API payloads: `item_id`, `sku`, `store_id`, `current_price`. Note that `sku` is required in API payloads even though it is not a CSV column, and the CSV `price` column is named `current_price` in the API. * `stock` is named `stock_on_hand` in the API. * API-only optional fields: `store_name`, `country_code`. Additional attributes as extra columns are supported for CSV / XLSX upload only. The JSON API rejects unknown fields — payloads containing fields outside the schema fail validation. *** ## Price interpretation logic Retailgrid applies the following logic: * If `regular_price` exists and `price` \< `regular_price` → product is considered on promotion * Margin is calculated as: ``` Margin = price - unit_cost Margin % = (price - unit_cost) / price ``` * If `tax_rate` is provided, gross/net calculations are supported *** ## Store / market / channel flexibility `store_id` can represent: * Physical store * Country * Warehouse * Online shop * Marketplace (Amazon, Zalando, etc.) * B2B vs B2C channel Retailgrid treats this as a **location dimension**. *** ## Currency handling If multiple stores use different currencies: * Currency must be provided * FX conversion rules must be configured Recommended:\ Keep price and cost in the same currency per row. *** ## Time handling This dataset is typically a **current snapshot**, not time-series. If `price_effective_date` is provided: * Future price simulations become possible * Scheduled price changes can be modeled *** ## Data format requirements Supported formats: * CSV (recommended) * XLSX Encoding: * UTF-8 * Dot (.) as decimal separator Rules: * One row = one product × one store * Do not duplicate identical product-store combinations * Use consistent store\_id formatting *** ## Validation rules Upload will fail if: * `item_id` is missing * `store_id` is missing * `price` is missing or non-numeric * Matching with Products dataset fails *** ## Example row | item\_id | store\_id | price | unit\_cost | status | stock | regular\_price | tax\_rate | currency | price\_effective\_date | | :- | :- | :- | :- | :- | :- | :- | :- | :- | :- | | SKU-1001 | FI-HELSINKI | 119.99 | 75 | active | 42 | 129.99 | 24 | EUR | 2026-02-01 | *** ## How Retailgrid uses store-level data This dataset powers: ### Margin analysis per store * Store-level profitability * Margin leakage detection ### Local price optimization * Dynamic pricing by store * Market-based pricing ### Assortment strategy * Store-specific availability * Stock-based pricing decisions ### Promotion strategy * Local promo activation * Regional discount benchmarking *** ## Advanced recommendations For best results: * Always provide `unit_cost` for margin optimization * Keep store identifiers consistent * Provide stock for AI-driven replenishment signals * Include tax\_rate for cross-country operations *** ## Related * [Manage stores](/stores/manage-stores) * [Product and SKU-Store grid views](/grids/sku-store-view) * [Datasets](/data-requirements/datasets) # Products Source: https://docs.retailgrid.io/data-requirements/products Upload your product master data including IDs, names, prices, costs, and attributes to power Retailgrid analytics. Product master data specification. Check the sample [**Sample\_products.csv**](https://drive.google.com/file/d/1Mj8DS0pTOdF747zNEAn4_5hCfxU10-Qo/view?usp=sharing). You can also load product data programmatically via the public **Retailgrid REST API**. API payloads use slightly different field names and required fields — see [Loading via the API](#10-loading-via-the-api) below. ## 1. Objective The Product Master dataset supports: * Linking transactions to products * Margin calculation (via unit\_cost) * Category-level analytics * Brand & assortment analysis * Elasticity modeling * Price corridor analysis Each row represents: > **One sellable item (SKU level)** ## 2. Referential integrity (critical) The following fields must match exactly with [**Transaction history**](/data-requirements/sales): | **Product File** | **Transaction File** | | :- | :- | | item\_id | item\_id | If `item_id` does not match, transactions cannot be linked. ## 3. Product master — unified specification | **Field Name** | **Required** | **Type** | **Format** | **Description** | **Example** | **Alternative Names** | | :- | :- | :- | :- | :- | :- | :- | | item\_id | ✅ Yes | string | Unique | Unique SKU-level identifier. Links transactions, store, and competitor data to this item. | SKU-1001 | item id, sku id, article id | | product\_id | ⬜ Optional | string | Text | Product-level code grouping related items / variants under one product. | P001 | product code, style code, product group code | | product\_name | ✅ Yes | string | Text | Full product name | Cordless Drill 18V Blue | SKU Name, Product Title, Product Variant Name | | price | ✅ Yes | decimal(12,2) | Dot separator | The actual selling price. Current Price | 129.99 | Current Price, Actual Price | | unit\_cost | ✅ Yes | decimal(12,2) | 2 decimals | Cost per unit at time of sale | 80.00 | Cost Price, Purchase Cost, Purchase Price | | sku | ⬜ Optional | string | Text | Stock Keeping Unit | SKU-1001 | SKU Code | | barcode | ⬜ Optional | string | EAN/UPC | Barcode value | 5901234123457 | EAN, UPC, GTIN | | brand | ⬜ Optional | string | Text | Brand name | Bosch | Brand Name, Label, Trademark | | manufacturer | ⬜ Optional | string | Text | Manufacturer name | Bosch | Producer, Mfg | | long\_description | ⬜ Optional | string | Text | Longer product description for context and matching. Does not replace product\_name. | 18V cordless drill with 2 batteries, charger and case | Long desc, Detailed description, Full description | | supplier | ⬜ Optional | string | Text | Supplier or vendor the item is sourced from | Northwind Supply | Supplier name, Vendor, Vendor name | | manufacturer\_code | ⬜ Optional | string | Text (leading zeros kept) | Manufacturer's own code for the item | 001234 | Manufacturer ID, Mfg code, Mfr code, MPN | | supplier\_code | ⬜ Optional | string | Text (leading zeros kept) | Supplier's own code for the item | 000567 | Supplier ID, Vendor code, Vendor ID | | product\_group | ⬜ Optional | string | Text | Logical product group | Drill-18V | Pricing group, Assortment Group, Product family | | status | ⬜ Optional | string | active / inactive | Product lifecycle status | active | Lifecycle Status, Availability status | | stock | ⬜ Optional | integer | ≥ 0 | Current stock level | 125 | Inventory level, Stock\_on\_hand, Available quantity | | product\_type | ⬜ Optional | string | Text | Product classification | Power Tools | Item type | | category1 | ⬜ Optional | string | Text | Top-level category | Tools | Department | | category2 | ⬜ Optional | string | Text | Subcategory | Power Tools | | | category3 | ⬜ Optional | string | Text | Subcategory level 3 | Drills | | | category4 | ⬜ Optional | string | Text | Subcategory level 4 | Cordless | | | category5 | ⬜ Optional | string | Text | Subcategory level 5 | 18V | | | image\_url | ⬜ Optional | string | URL | Product image URL | https\://… | | | product\_url | ⬜ Optional | string | URL | Product page URL | https\://… | | | rrp | ⬜ Optional | decimal(12,2) | 2 decimals | Recommended retail price | 129.99 | MSRP, List Price, Recommended Price | | regular\_price | ⬜ Optional | decimal(12,2) | Dot separator | The standard, non-discounted price of the product | 139.99 | Base price, Normal price, Standard price | | product\_created\_date | ⬜ Optional | date | YYYY-MM-DD | Product creation date | 2024-01-15 | Product Launch date | | price\_effective\_date | ⬜ Optional | date | YYYY-MM-DD | Price effective date | 2026-02-01 | Price start date, Price valid from | | cost\_effective\_date | ⬜ Optional | date | YYYY-MM-DD | Cost effective date | 2026-01-15 | Cost start date, Cost valid from | | size | ⬜ Optional | decimal or string | Numeric or text | Size value | 12 | Variant size | | uom | ⬜ Optional | string | Unit code | Unit of measure | pcs | Unit, Measurement unit | | tax\_rate | ⬜ Optional | decimal(5,4) | 0–1 | VAT rate (0.20 = 20%) | 0.20 | Tax rate, Vat percentage | | currency | ⬜ Optional | string(3) | ISO code | Currency | EUR | Currency code, Iso currency | ## 4. Alignment with transaction schema **4.1 Keys Mandatory alignment:** ```text theme={null} transaction.item_id = product.item_id ``` **4.2 Currency Consistency** If transactions include `currency`, product file must include `currency`. **4.3 VAT Consistency** If VAT is included in transaction prices: * Provide vat\_rate in product file * Clarify whether unit\_price in transactions is gross or net ## 5. Data format requirements **5.1 Dates** ```text theme={null} YYYY-MM-DD ``` Example: ```text theme={null} 2026-02-18 ``` **5.2 Numeric Fields** * Decimal separator: `.` * No currency symbols * No thousand separators ## 6. Custom attributes (extensibility model) You can add additional attributes as extra columns. Examples: * material * color * season * gender * warranty\_months * competitor\_reference * margin\_group * elasticity\_cluster **Rules for custom attributes** * Must not overwrite required column names * Must follow consistent naming * Must contain a single value per row * No nested JSON inside Excel cells * CSV / XLSX upload only — the JSON API rejects unknown fields (see [Loading via the API](#10-loading-via-the-api)) Example: | **item\_id** | **product\_name** | **material** | **color** | | :- | :- | :- | :- | | SKU-1001 | Tent 3P | Polyester | Green | ## 7. File format Accepted: * CSV (UTF-8 preferred) * XLSX **One row per SKU.** No pivot tables.\ No formulas.\ Raw data only. ## 8. Minimal product dataset (absolute minimum) If system limitations exist: ```text theme={null} item_id product_name price unit_cost ``` However, for pricing analytics it is strongly recommended to include: ```text theme={null} item_id product_name price unit_cost brand category 1–3 rrp regular_price vat_rate currency ``` ## 9. SQL schema example ```text theme={null} CREATE TABLE product_master ( item_id VARCHAR(64) PRIMARY KEY, product_id VARCHAR(64), product_name VARCHAR(255) NOT NULL, sku VARCHAR(64), barcode VARCHAR(64), brand VARCHAR(128), manufacturer VARCHAR(128), long_description TEXT, supplier VARCHAR(255), manufacturer_code VARCHAR(255), supplier_code VARCHAR(255), product_group VARCHAR(128), status VARCHAR(32), stock INT, product_type VARCHAR(128), category1 VARCHAR(128), category2 VARCHAR(128), category3 VARCHAR(128), category4 VARCHAR(128), category5 VARCHAR(128), image_url TEXT, product_url TEXT, size VARCHAR(64), uom VARCHAR(32), product_created_date DATE, rrp DECIMAL(12,2), regular_price DECIMAL(12,2), vat_rate DECIMAL(5,4), currency CHAR(3), price_effective_date DATE, cost_effective_date DATE ); ``` ## 10. Loading via the API The public REST API accepts the same data through `POST /v1/products/bulk` (JSON records) or `POST /v1/imports/products` (CSV import). JSON payloads use different names for some fields and a different required set. Required fields in API payloads: `item_id`, `sku`, `sku_name`, `current_price`, `unit_cost`. | CSV column | API field | Notes | | :- | :- | :- | | price | `current_price` | Required. There is no `price` field in the API. | | product\_name | `sku_name` | Required. The API also has an optional `product_name` for a product-level name. | | sku | `sku` | Required in API payloads (optional in CSV). | | stock | `stock_on_hand` | | | category1 – category5 | `category_1` – `category_5` | Underscored in the API. | | — | `color` | API-only optional field. | All other fields keep the same names as the CSV columns above, including the optional `long_description`, `supplier`, `manufacturer_code` and `supplier_code` fields. They are returned on every product read and accepted on create, PATCH and bulk upsert. When you PATCH a product, an omitted field keeps its current value. Send an explicit `null` to clear it. Custom attributes as extra columns are supported for CSV / XLSX upload only. The JSON API rejects unknown fields — payloads containing fields outside the schema fail validation. # Sales Source: https://docs.retailgrid.io/data-requirements/sales Upload transaction-level sales data to power analytics, elasticity modeling, and price optimization in Retailgrid. ## Transaction history data specification For sales analytics, elasticity modeling, and retail intelligence. Check the sample [**sample\_transactions**](https://drive.google.com/file/d/1Q6ew53Np9Bj8WG_NzVOBNxYL9n1UCqM9/view?usp=sharing). You can also load transaction data programmatically via the public **Retailgrid REST API**. API payloads use slightly different field names and required fields — see [Loading via the API](#11-loading-via-the-api) below. ## 1. Objective Use this dataset for: * Sales performance analytics * Price elasticity modeling * Demand curve estimation * Price optimization * Promotion impact analysis * Store performance benchmarking You must provide data at **transaction line level**. ## 2. Data granularity requirement Each row must represent: > **One product in one transaction** Correct: | **transaction\_id** | **item\_id** | **quantity** | | :- | :- | :- | | TRX\_001 | SKU-1001 | 2 | | TRX\_001 | SKU-1002 | 1 | Incorrect: * Daily aggregated totals * Store-level summaries * Pivoted exports ## 3. Transaction history — unified data specification | **Field Name** | **Required** | **Type** | **Format** | **Description** | **Example** | **Alternative Names** | | :- | :- | :- | :- | :- | :- | :- | | transaction\_id | ✅ Yes | string | Unique | Unique identifier of the transaction. Must be stable and not reused. | TRX\_000123 | order id, receipt id, invoice id, ticket id | | store\_id | ✅ Yes | string | Text | Identifier of store or sales location. | S001 | location id, branch id, outlet id | | item\_id | ✅ Yes | string | Text | Unique SKU-level identifier. Must match `item_id` in the Product Master file. | SKU-1001 | product id, sku id, article id | | transaction\_timestamp | ✅ Yes | datetime | YYYY-MM-DD HH:MM:SS | Date and time of transaction in ISO 8601 format. Preferably UTC. | 2026-02-18 14:22:05 | sale timestamp, order datetime, event time | | quantity | ✅ Yes | decimal(12,2) or integer | Positive for sales, negative for returns | Units sold (positive) or returned (negative). Must not be zero. | 3 / -1 | units, qty, quantity sold, units sold | | selling\_price | ✅ Yes | decimal(12,2) | Dot separator | Actual price paid per unit in that transaction (after discount). | 129.99 | unit price, final price, transaction price | | regular\_price | ⬜ Optional | decimal(12,2) | Dot separator | Standard non-discounted product price at time of sale. | 139.99 | base price, list price, shelf price | | unit\_cost | ⬜ Optional | decimal(12,2) | 2 decimals | Cost per unit at time of transaction (for margin calculation). | 80.00 | cost price, cogs unit, purchase cost | | discount\_rate | ⬜ Optional | decimal(5,4) | 0–1 | Discount applied as a rate (0.15 = 15%). | 0.15 | discount pct, promo rate | | sales\_value | ⬜ Optional | decimal(12,2) | Dot separator | Total transaction value per row. If provided, must equal `quantity × selling_price`. | 389.97 | revenue, line revenue, gross sales | | gross\_profit | ⬜ Optional | decimal(12,2) | Dot separator | Gross profit per row. If provided, must equal `(selling_price - unit_cost) × quantity`. | 149.97 | margin value, contribution margin | | stock\_after\_sale | ⬜ Optional | integer | ≥ 0 | Inventory level after transaction occurred. | 37 | stock on\_hand, inventory after | | currency | ⬜ Optional\* | string(3) | ISO 4217 | Currency code used for price fields. Required if multi-currency. | EUR | currency code | | tax\_rate | ⬜ Optional | decimal(5,4) | 0–1 | VAT/tax rate applied (0.20 = 20%). | 0.20 | vat rate, tax percentage | | transaction\_type | ⬜ Optional | string | sale / return | Type of transaction. If return, quantity must be negative. | sale | order type, operation type | ## 4. Return handling policy (mandatory if returns exist) Returns must be provided as separate transaction rows. ### **4.1 Format for returns** | **Field** | **Value** | | :- | :- | | transaction\_type | return | | quantity | Negative value | | selling\_price | Positive | | transaction\_timestamp | Actual return time | | item\_id | Same as original sale | ### **4.2 Example** **Original Sale** | **transaction\_id** | **item\_id** | **quantity** | **selling\_price** | **transaction\_type** | | :- | :- | :- | :- | :- | | TRX\_1001 | SKU-1001 | 2 | 129.99 | sale | **Return** | **transaction\_id** | **item\_id** | **quantity** | **selling\_price** | **transaction\_type** | | :- | :- | :- | :- | :- | | TRX\_1055 | SKU-1001 | -1 | 129.99 | return | ### **4.3 Important rules** * Do NOT delete original sale rows * Do NOT aggregate net values before export * Do NOT use positive quantity for returns * unit\_price must remain positive * Returns must reflect the actual return date ## 5. Data format requirements (strict) ### 5.1 Timestamp Must follow ISO 8601: ```text theme={null} YYYY-MM-DD HH:MM:SS ``` Example: ```text theme={null} 2026-02-18 14:22:05 ``` Timezone: * Preferably UTC * Or timezone must be clearly specified ### **5.2 Numeric fields** * Decimal separator: `.` * No thousand separators * No currency symbols * No localized formats Correct: ```text theme={null} 129.99 ``` Incorrect: ```text theme={null} 129,99 1 299.99 $129.99 ``` ### 5.3 Discount format Must be a rate: ```text theme={null} 0.15 = 15% 0.00 = no discount ``` Not: ```text theme={null} 15% -20 ``` ## 6. File format Accepted: * CSV (UTF-8 encoding) — preferred * XLSX No pivot tables\ No formulas\ Raw data only ## 7. Historical data requirements Minimum: * 6–12 months of history * Preferably 24 months Recommended minimum volume: * 10,000+ transaction rows per store ## 8. Data validation rules * quantity ≠ 0 * quantity > 0 for sales * quantity \< 0 for returns * unit\_price ≥ 0 * discount\_rate between 0 and 1 * transaction\_timestamp not null * item\_id must exist in product master file ## 9. SQL schema example ```text theme={null} CREATE TABLE transaction_history ( transaction_id VARCHAR(64) NOT NULL, store_id VARCHAR(32) NOT NULL, item_id VARCHAR(64) NOT NULL, transaction_timestamp TIMESTAMP NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(12,2) NOT NULL, unit_cost DECIMAL(12,2), discount_rate DECIMAL(5,4), stock_after_sale INT, currency CHAR(3), transaction_type VARCHAR(16), sales_channel VARCHAR(16) ); ``` ## 10. Minimal dataset (if system limitations exist) If the system cannot provide full structure, minimum required: ```text theme={null} transaction_id store_id item_id transaction_timestamp quantity selling_price ``` Returns must still follow negative quantity logic. ## 11. Loading via the API The public REST API accepts the same data through `POST /v1/transactions/bulk` (JSON records) or `POST /v1/imports/transactions` (CSV import), with two differences from the CSV schema: * There is no `transaction_id` field in the API. Use the optional `external_transaction_id` to carry your own transaction identifier — the API does not require one. * Required fields in API payloads: `item_id`, `store_id`, `transaction_timestamp`, `quantity`, `selling_price`. API-only optional fields: `sku`, `region`, `promo_type`, `zone_price`. The JSON API rejects unknown fields — payloads containing fields outside the schema fail validation. # Retailgrid Source: https://docs.retailgrid.io/get-started/overview Learn what Retailgrid is, who it's built for, and how it helps retailers organize, enrich, and optimize pricing data. Retailgrid is an AI-powered spreadsheet for retail — designed to help retailers organize, enrich, and optimize their data without enterprise complexity. This guide helps you reach value in **under 15 minutes**. ## What is Retailgrid? Retailgrid is a cloud-based retail analytics and pricing workspace that combines: * Structured data grid (Excel-like interface) * AI-powered enrichment * Elasticity & optimization models * Export-ready pricing decisions Built specifically for: * Pricing managers * E-commerce directors * Category managers * Retail founders * Analysts ## Who is Retailgrid for? Retailgrid is designed for **mid-market retailers**: * €10M–€500M revenue * E-commerce or omnichannel * 10,000–200,000 SKUs * Data exists, but is underutilized Retailgrid is especially valuable if you: * Manage pricing manually in Excel and the spreadsheets are starting to break * Lack elasticity visibility * Do not have an in-house data science team * Want AI-driven insights without enterprise overhead or a six-month implementation ## How Retailgrid works Retailgrid follows a simple framework: > **Upload → Build a grid → Optimize → Export** ## What's next? Get hands-on in 15 minutes with the [**Quickstart**](/get-started/quickstart), or jump straight into building your first workspace in [**Create Grid**](/grids/create-grid). # Quickstart Source: https://docs.retailgrid.io/get-started/quickstart Get started with Retailgrid in minutes by uploading your product CSV, mapping fields, and creating your first grid. Get from raw CSV to your first pricing-ready grid in minutes. ## Step 1. Create your account * Go to [**app.retailgrid.io**](https://app.retailgrid.io) * Click **Sign in with Google** (recommended), or enter your email and a password to sign up. * If you signed up with email and password, we'll send a verification email. Click the link to confirm your address, then sign in. Google sign-in completes immediately. You'll land on the **Grids** dashboard. Sign-in page at app.retailgrid.io ## Step 2. Click "Upload Data" From the top-right corner: * Click **Upload Data** * The **Upload Data** modal will open You'll see two upload options: * **Products** (required) * **Transactions** (optional but recommended for analytics) Upload Data modal with Products and Transactions options ## Step 3. Select your CSV file Upload your **Products CSV** file. Before uploading, ensure your file follows Retailgrid data requirements. See [Products data requirements](/data-requirements/products). ### Minimum required fields * `item_id` * `product_name` * `price` * `unit_cost` * `sku` (recommended) * `barcode` (optional but useful) After you select the file, Retailgrid parses it and prepares it for mapping. ## Step 4. Match your data to Retailgrid schema You'll enter the **Map Your Fields** screen. Here you: * Match your CSV column names to Retailgrid fields * Confirm required fields are correctly mapped * Validate data (green check = valid mapping) Retailgrid supports: * Products * Product Variants * Prices * Catalog attributes (brand, manufacturer, product group, status, etc.) Once you've mapped all required fields, proceed. Field mapping screen with CSV columns mapped to Retailgrid schema ## Step 5. Name your first grid and select products After processing: 1. Enter a **Grid Name** (e.g., "March Pricing Test") 2. Select the products you want included: * Filter by brand, type, status * Select all or specific SKUs 3. Click **Create grid** A Grid is a focused working environment - not just raw data, but the set of products you want to analyze, price, or optimize. Name your first grid and select products screen ## Step 6. Your grid is created Your Grid opens in grid view. You can now: * Filter and sort products * Edit prices * Add calculated columns * Apply rules * Run agents * Export results You're now ready to: * [Define product roles](/agents/product-roles) * Apply pricing rules * Analyze margins * Model scenarios * Track performance ## What's next? To get the full pricing workflow: * Upload **Transactions CSV** for sales analytics * Add competitor data * Configure pricing rules * Generate pricing strategy presentations # Configure grid Source: https://docs.retailgrid.io/grids/configure-grid Customize your grid layout by choosing visible columns, sorting, grouping, pinning, filtering, and saving views. Once your **Grid** is created, you can configure the grid to match your pricing workflow. Retailgrid gives you full control over: * Visible columns * Column order * Pinned columns * Sorting * Grouping * Saved views Below is how to configure your grid efficiently. Your grid layout — visible columns, order, sorting, grouping, and filters — **autosaves as you work**, with a confirmation in the status line at the top of the grid. There's no "Leave site? Changes you made may not be saved" prompt to worry about. **Views** (see step 7) are for naming and switching between distinct saved layouts, not for avoiding lost work. ## Search the grid Use the grid **search** box to find a product by *any* attribute — SKU, name, brand, EAN, supplier, category, or any custom field — without building a filter. Type a value and the grid narrows to matching rows instantly. Clear the search to return to the full set. ## 1. Select visible columns You can show or hide any column. ### How to do it 1. Click **Columns** in the top toolbar 2. Use the search field to find a column 3. Click the eye icon to: * Show column * Hide column 4. Toggle a whole section at once with the eye icon on its section header The Columns panel groups related columns into expandable sections, supports search, and lets you expand, collapse, and toggle each section. A visible-count indicator (e.g. "15 / 239 visible") shows how many columns are on, so you can keep the grid lean. ### Column type icons Each column header shows a small icon indicating its type: * **Editable** — manually entered values you can change row by row. * **Formula** — calculated from other columns; values update automatically. * **AI-generated** — populated by a Retailgrid AI action (e.g. enrichment, classification). ### Best practice Keep only what you need for the task.\ Example for pricing work: * Product Name * Price * Unit Cost * Margin * Competitor Price * Status Columns panel with grouped sections, search, and bulk show/hide controls ## 2. Order columns by drag and drop You can rearrange columns freely. ### How to do it * Click and hold a column header * Drag it left or right * Drop it where you want it ### Recommended layout Left (fixed reference): * Product Name * Product ID Middle (analysis): * Price * Unit Cost * Margin * Elasticity Right (attributes): * Brand * Category * Manufacturer Organize based on your workflow. ## 3. Pin columns Pinning keeps important columns visible while scrolling. ### How to pin 1. Click the three-dot menu on a column header 2. Select **Pin Column** 3. Select: * Pin Left * Pin Right ### When to use Pin Left: * Product Name * SKU Pin Right: * Status * Actions Pinning is essential when working with many pricing metrics. ## 4. Sort values You can sort any column. ### How to sort * Click column header to toggle ascending / descending\ OR * Use the column menu → Sort Ascending / Descending ### Example use cases * Sort by **Price Descending** → find premium items * Sort by **Unit Cost Descending** → detect cost risk * Sort by **Margin Ascending** → identify weak SKUs You can also **Clear Sort** from the column menu. The image column is sortable and filterable like any other column; products without an image show a placeholder so missing imagery is easy to spot. ## 5. Group products Grouping lets you analyze structured segments. ### How to group 1. Drag a column into the grouping area\ OR 2. Use the Group column You can group by **any** column — the loaded-data attributes stored in the grid and dynamic, AI-derived columns alike. Example groupings: * Group by Product Group * Group by Brand * Group by Category * Group by **Product Role** (or any other dynamic column) You'll see collapsible sections: * Oranges (2) * Blueberries (2) * Romaine (2) ### Why grouping matters * Compare pricing inside category * Spot inconsistencies * Review margin distribution by group Grouping turns a flat list into a structured analysis tool. ### Value aggregation per column When grouping is active, each numeric column rolls up to a single value per group. Retailgrid picks a sensible default based on the column's semantic type — **Sales** sums, **Price** and **Margin** average, **Product ID** counts. Aggregation runs on the backend so the rolled-up values stay correct and consistent across any grid size. To override the default, open the column header's three-dot menu → **Value Aggregation** and pick from `None`, `Sum`, `Average`, `Min`, `Max`, `Count`. Your override is per-column and persists with the grid view. ## 6. Filter columns Filtering allows you to narrow down products based on specific criteria — instantly focusing on what matters. The **Filters** button in the grid toolbar shows a count badge whenever one or more column filters are active, so you can tell at a glance that you're looking at a filtered subset rather than the whole grid. You can filter any column (numeric, text, status, etc.). ### How to apply a filter 1. Click the **filter icon** in the column header 2. Select a condition from the dropdown 3. Enter your filter value 4. The grid updates immediately ### Numeric filters (price, cost, margin, etc.) For numeric columns, you can use: * Greater than * Less than * Equals * Not equal * Between ### Example * Price **Greater than 2** * Unit Cost **Less than 3** * Margin **Below 20%** You can also combine conditions using: * **AND** * **OR** Example: * Price > 2 **AND** Price \< 5 This helps you isolate specific price corridors. ### Text filters (brand, product name, SKU, etc.) For text columns, you can filter by: * Contains * Equals * Starts with * Ends with ### Example * Brand equals *Arla* * Product Name contains *Greek* ## 7. Save view as default or create your own view Once your layout is configured, save it as a named view to switch back to later. (Your working layout already autosaves — named views are for keeping several distinct setups side by side.) ### How to save 1. Click **Views** in the toolbar 2. Enter a name (e.g., "Margin Analysis") 3. Click **Add** You can: * Keep Default view * Create multiple custom views * Switch between them instantly ### Example saved views | View Name | Purpose | | :- | :- | | Margin Review | Price, Cost, Margin columns | | Competitive View | Price, Competitor Price, CPI | | Operations | SKU, Barcode, Status | | Elasticity | Elasticity, Revenue, Price Δ | Saved views allow different teams (pricing, category, operations) to work in the same grid with different layouts. The Views panel, with the saved view list and the field for naming a new one ## Reset layout If needed, click **Reset** to restore the default configuration. # Create grid Source: https://docs.retailgrid.io/grids/create-grid Create a new grid by selecting products from your catalog to build a focused pricing and analysis workspace. A Grid in Retailgrid is your focused pricing workspace — a selected set of products where you analyze, optimize, and execute pricing decisions. ## What is a grid? A Grid is: * A filtered subset of your product catalog * A working environment for pricing and analysis * A place to run agents, apply rules, and export results You can create multiple grids for: * A specific brand (e.g., *Arla*) * A category (e.g., *Dairy*) * A campaign * A pricing experiment * A country or channel ## Step 1. Go to grid From the main navigation: * Click **Grids** * Click **+ New** (top right) You'll enter the **Manual grid** creation screen. ## Grids view Grids dashboard with existing grids and the New button in the top-right ## Step 2. Name your grid At the top: * Enter a clear, descriptive name\ Example: * `Arla` * `Dairy – Margin Optimization` * `March Price Test` The pinned header keeps a live counter of how many products are currently in scope, so you always see the size of the set you're building. ## Name your grid New-grid creation screen with the grid name field ## Step 3. Define your scope The scope panel is where you narrow your catalog down to the products this grid should hold. It's organized so the controls you reach for most stay in front of you, and the rest stay out of the way until you need them. **Open by default:** * **Grid grain** — whether a row is one product or one product-store combination. See [Product and SKU-Store grid views](/grids/sku-store-view). * **Search** — find products by name or ID. * **Quick filters** — three one-click toggles: **Status = Active**, **Stock > 0**, and **Sales > 0** (in the last 52 weeks). * **Category** and **Brand** — each shows the top values with a **"+N more"** search box, so an account with hundreds of categories or brands stays usable instead of listing everything at once. **Collapsed into accordions** (each shows a one-line summary of its current state, so you can see what's set without expanding it): * **Location** — narrow to specific stores or zones. * **Competitor context** — choose the competitors and the observation window the market-data metrics use (see below). * **Custom columns** — add columns to the grid as you build it. * **Advanced filters** — build precise row sets with **Add field filter** (on a product attribute) and **Add metric filter** (on a computed metric such as margin or sales), combined into nested `AND` / `OR` groups with **Add group**. **Live stat cards** at the top of the panel count the current scope as you refine it: **SKUs**, **Stores**, **Competitors**, and **Competitor Coverage %**. Watch them to sanity-check a filter before you commit — a combination that returns zero SKUs is easy to spot here. ## Define scope The scope panel - grid grain, quick filters, category and brand pickers, and live stat cards ## Step 4. Select products With your filters set, choose the products the grid will hold: * Select individual SKUs * Select all filtered results * Clear selection This defines exactly which products will be included in your grid. ## Step 5. Click "Create grid" The pinned header keeps the title, **Cancel**, and **Create grid** in view the whole time, so you never scroll to find the button. When your scope looks right: * Click **Create grid** * Retailgrid creates your grid and opens it ## Step 6. Start working in your grid Your new grid opens in spreadsheet-style grid format. From here you can: ### Analyze * Sort and filter * Compare price vs cost * Review margin gaps ### Modify * Edit prices directly * Add calculated columns * Apply bulk updates ### Execute * Use **Agents** * Run pricing rules * Export data * Prepare price updates ## Grid is ready Grid open in spreadsheet view with toolbar, columns, and product rows ## Change which products are in the grid The selection you made when creating the grid isn't permanent. Click **Scope** in the top-right of the grid toolbar to reopen the grid's definition and change it. The scope panel holds more than the product selection: * **Grid grain** - whether a row is one product or one product-store combination. See [Product and SKU-Store grid views](/grids/sku-store-view). * **Competitor context** - **Store** and **Zone** narrow both the grid's rows and its competitor metrics. **Competitors** and the **competitor observation window** narrow the metrics only, so you can ask "how did we sit against these two competitors" without changing which products are in the grid. The observation window is a **rolling lookback** — **Last 7**, **Last 14**, **Last 30**, or a **custom** number of days — re-evaluated against the current date every time the grid is filled or refreshed. A grid set to "Last 14 days" keeps showing only competitor prices observed in the last 14 days, and stays that way on later refreshes without editing the scope. Competitor-derived metrics (index, min/max, coverage) reflect the same windowed set as the visible prices. Grids created before this change keep their fixed **Observed from** / **Observed to** dates until you next edit the scope. * **Filters** - build the row set with **Add field filter** (on a product attribute) and **Add metric filter** (on a computed metric such as margin or sales). Combine them into nested groups with **Add group**, and set each group to `AND` or `OR`. * **Preview** - the matching row count and a sample of the rows, updating as you change the filters. Check it before you apply: a filter combination that returns zero rows is easier to spot here than in an empty grid. Applying a new scope refreshes the grid against it. ## How your grid stays fresh Every grid is refreshed automatically overnight with the latest data from your datasets. You don't need to trigger a manual refresh after replacing a file — the next night's run picks it up. * **Nightly refresh** — runs once per day during an off-hours window and covers every active grid. * **On-demand updates** — replace a dataset on the [Datasets page](/data-requirements/datasets) at any time; the change becomes visible in your grids on the next nightly refresh, or sooner if you manually run an agent against the grid. * **Per-grid status** — failed refreshes are visible in the [Runs](/runs/overview) section, where `system`-triggered nightly jobs appear alongside user-triggered runs. # Custom columns Source: https://docs.retailgrid.io/grids/custom-columns Add editable custom columns to any grid - text, number, currency, or formula. Custom columns let you extend a grid with your own data fields. You can add them manually, edit cell values inline, and choose how each column stores and displays its data. This page covers the API-level details of custom columns. For the in-app **Add Column** flow with screenshots and step-by-step walkthroughs, see [Add columns](/grids/manage-columns). ## Add a custom column Click **Add Column** in the grid toolbar to open the column stepper. The stepper walks you through two steps: 1. **Select a column type** - choose from Text, Number, Currency, or Formula. 2. **Configure the column** - set a name and any type-specific options, then click **Add Column**. The new column appears immediately in the grid. ## Column types | Type | `column_type` value | Description | | - | - | - | | Text | `custom_text` | Plain text field. Values are stored as strings. | | Number | `custom_number` | Numeric field. Values are stored as numbers. | | Currency | `custom_currency` | Numeric field displayed in the account's display currency (see [Currency](/settings/currency)). | | Formula | `custom_formula` | Dynamically computed from other columns. | ### Formula columns Formula columns reference other column values using expressions and display the result according to a **Display Format** you pick during setup: | Formatter | `formatter` value | Behavior | | - | - | - | | Text | `text` | Result is rendered as plain text. | | Number | `number` | Result is rendered as a number. | | Currency | `currency` | Result is rendered in the account's display currency. | Grouping and aggregation are not available for formula columns. ## Edit cell values Custom columns are editable directly in the grid. Click a cell and type to update its value. Press **Enter** to confirm and move to the next row. Retailgrid validates the value against the column's storage type before saving: | Storage type | Accepted values | Rejected values | | - | - | - | | `string` | Text, `null` | - | | `numeric` | Numbers, numeric strings, `null` | Booleans | | `integer` | Integers, integer strings, `null` | Booleans, floats with a fractional part | | `boolean` | `true`/`false`, `"true"`/`"1"`/`"yes"`, `null` | - | If the value cannot be coerced to the expected type, the API returns a `422` error and the cell reverts to its previous value. Clearing a cell (leaving it empty) stores `null` for all column types. ## API reference The internal API path segment is currently `/workbooks/` for historical reasons. The customer-facing concept is **Grid** everywhere in the product and the docs; the path segment will be renamed in a future API version. Both segments resolve to the same endpoint. ### Add a custom column ```http theme={null} POST /workbooks/{grid_id}/columns/add-custom ``` **Request body** ```json theme={null} { "name": "Margin", "column_type": "custom_formula", "formatter": "currency" } ``` | Field | Type | Required | Description | | - | - | - | - | | `name` | string | No | Column label. Defaults to `"Custom"`. | | `column_type` | string | No | One of `custom_text`, `custom_number`, `custom_currency`, `custom_formula`. Defaults to `custom_text`. | | `formatter` | string | No | Display format for `custom_formula` columns: `text`, `number`, or `currency`. Defaults to `text`. | **Response** ```json theme={null} { "column_id": 42, "column_key": "dyn:a1b2c3d4e5f6", "column_name": "Margin", "unique_id": "dyn:42", "type": "custom_formula", "formatter": "currency" } ``` ### Set a cell value ```http theme={null} PUT /workbooks/{grid_id}/data/cells ``` **Request body** ```json theme={null} { "row_id": 101, "column_id": 42, "value": 19.99 } ``` | Field | Type | Required | Description | | - | - | - | - | | `row_id` | integer | Yes | Must be greater than `0`. | | `column_id` | integer | Yes | Must be greater than `0`. | | `value` | string \| number \| boolean \| null | Yes | The new cell value. Pass `null` to clear the cell. | **Errors** | Status | Cause | | - | - | | `404` | Column not found in this grid. | | `422` | Value cannot be coerced to the column's storage type (e.g. passing a boolean to a numeric column). | ## Related * [Add columns](/grids/manage-columns) * [Formula columns](/grids/formula-columns) * [Currency](/settings/currency) # Duplicate a grid Source: https://docs.retailgrid.io/grids/duplicate-grid Copy a grid's full configuration onto a new set of products, so a setup you have tuned once can be reused. **Duplicate** copies a grid's configuration into a new grid. Everything you built up on the original comes across: the columns you added, your formula and metric columns, the layout, and the saved views. What you choose fresh is which products the copy covers. Use it when a grid's setup is worth more than the row set it currently holds. ## When to duplicate rather than create A new grid starts empty: no custom columns, no formulas, no metric columns, no views. If you have spent time building a working setup, duplicating carries it forward. Good reasons to duplicate: * **Same analysis, different category.** A margin-review setup that works for Dairy, pointed at Frozen. * **Same analysis, different market.** One country's grid copied for another. * **A safe place to experiment.** Duplicate, then try a different rule set or column layout without disturbing the grid your team works in. * **Period-over-period work.** A quarterly review grid duplicated for the new quarter. If you only need a different column layout on the same products, you want a saved [View](/grids/views), not a duplicate. ## Duplicate a grid 1. Open **Grids** in the left sidebar. 2. Find the grid you want to copy. Its row shows four actions on the right: rename, edit tags, **duplicate**, and remove. 3. Click **Duplicate Grid**. 4. Choose the scope for the copy - which products the new grid should cover. Keep the original's scope, or pick a different set the same way you would when [creating a grid](/grids/create-grid). 5. Confirm. The copy appears in the Grids list and its data is generated in the background. The Grids list - the row actions include Duplicate Grid ## What comes across The duplicate is a full configuration copy, not just a row set. That includes your added columns, formula columns, metric columns, column layout and widths, and saved views. What does not come across is history. The copy is a new grid, so it has its own [Runs](/runs/overview) history and its own price approvals. Approvals and runs stay with the original. ## While the copy is generating Generating the duplicate's data is a background job, so a large grid takes a moment to fill. Follow it in [Runs](/runs/overview). If generation fails, you do not have to start over. The grid offers a retry that re-runs the generation with the same configuration, which is the right move when the failure was transient. For a configuration problem, fix the source and duplicate again. ## Common pitfalls * **Duplicating before the original is finished.** Let the original's imports and refreshes settle first, or the copy is generated from a half-loaded grid. * **Forgetting to rename.** Two grids with near-identical names get confusing fast. Rename the copy as soon as it appears. * **Expecting agent configurations to follow automatically.** Saved agent configurations are managed in the agent dialog's Config Manager, and can be loaded onto any grid. See [Agents overview](/agents/overview). ## Related * [Grids overview](/grids/overview) * [Create grid](/grids/create-grid) * [Configure grid](/grids/configure-grid) * [Views](/grids/views) * [Runs](/runs/overview) # Export data Source: https://docs.retailgrid.io/grids/export Download your grid data as CSV or Excel files, with filters and sorting applied, for analysis or sharing. The **Export** feature allows you to download the data currently displayed in the grid for further analysis or sharing. Export modal with CSV and Excel format options ## Available formats Retailgrid supports two export formats: * **CSV** — best for data processing, integrations, and large datasets * **Excel (.xlsx)** — best for manual analysis, reporting, and sharing with colleagues ## How to export data 1. Click **Export** in the top-right of the grid toolbar. 2. Pick a format from the menu: * **CSV Export** * **Excel Export** 3. Retailgrid downloads the file to your computer. ## What gets exported The export includes the data exactly as it appears in the grid: * visible columns * applied filters * current sorting This allows you to export only the data you need. ## Example If you filter the grid to show **Grocery products with low margin**, the exported file will contain only those filtered results. # Formula columns Source: https://docs.retailgrid.io/grids/formula-columns Create calculated columns using formulas, functions, and AI to compute margins, revenue, and other pricing metrics. Formula Columns allow you to **calculate values dynamically** based on other columns. They are essential for pricing, analytics, and decision-making workflows. Formula column shown in a grid with calculated margin values ## 1. What is a formula column? A **Formula Column** automatically computes values using: * Other columns (e.g. `{Price}`, `{Unit Cost}`) * Functions (e.g. `SUM`, `IF`, `ROUND`) * Operators (`+`, `-`, `*`, `/`) Unlike standard columns, values are not manually entered - they are always calculated. Formula column add dialog ## 2. How to create a formula column ### Step 1: Add column * Click **Columns → Add Column** ### Step 2: Select type * Select **Formula** ### Step 3: Configure column #### 1. Name Give a clear business name: * `Margin %` * `Revenue` * `Price Index` #### 2. Display format Select how results are shown: | Format | Example | | :- | :- | | Number | 123 | | Currency | €123.45 | | Percentage | 25% | | Text | "High" | #### 3. Build formula You have three ways: ## 3. Build formulas ### 3.1 Manual input Type directly: ```text theme={null} ({Price} - {Unit Cost}) / {Price} ``` ### 3.2 Insert column references * Click **Insert column reference** * Select fields from the list Insert column reference picker Example: ```text theme={null} {Price} * {Quantity} ``` ### 3.3 Insert functions * Click **Insert function** * Select from the list Example: ```text theme={null} ROUND({Price}, 2) ``` ## Math and numeric functions Functions list in the formula editor ### `ROUND(value, digits)` Rounds a number to a specified number of decimals. **Example:** ```text theme={null} ROUND({Price}, 2) ``` ### `TRUNC(value, digits)` Truncates a number without rounding. **Example:** ```text theme={null} TRUNC({Price}, 0) ``` ### `PRODUCT(value1, value2, ...)` Multiplies values. **Example:** ```text theme={null} PRODUCT({Price}, {Quantity}) ``` ### `POWER(base, exponent)` Raises a number to a power. **Example:** ```text theme={null} POWER({Price}, 2) ``` ### `RAND()` Generates a random number between 0 and 1. **Example:** ```text theme={null} RAND() ``` ## Aggregation functions ### `SUM(range)` Returns sum of values. ```text theme={null} SUM({Price}) ``` ### `SUMIF(range, condition)` Conditional sum. ```text theme={null} SUMIF({Sales}, {Category} = "Electronics") ``` ### `MIN(range)` Returns smallest value. ```text theme={null} MIN({Price}) ``` ### `MAX(range)` Returns largest value. ```text theme={null} MAX({Price}) ``` ### `AVERAGE(range)` Returns average. ```text theme={null} AVERAGE({Price}) ``` ### `MEDIAN(range)` Returns median value. ```text theme={null} MEDIAN({Price}) ``` ## Counting functions ### `COUNT(range)` Counts numeric values. ```text theme={null} COUNT({Price}) ``` ### `COUNTA(range)` Counts non-empty values. ```text theme={null} COUNTA({Product Name}) ``` ### `COUNTBLANK(range)` Counts empty cells. ```text theme={null} COUNTBLANK({Price}) ``` ### `COUNTIF(range, condition)` Counts values matching condition. ```text theme={null} COUNTIF({Category}, "Electronics") ``` ## Logical functions ### `IF(condition, value_if_true, value_if_false)` Conditional logic. ```text theme={null} IF({Price} > 100, "High", "Low") ``` String outputs from `IF()` pair well with [Conditional color highlighting](/grids/manage-columns#5-conditional-color-highlighting). Classify a row in a formula column, then map each classification to a color in the column header menu. ## Text functions ### `CONCAT(value1, value2, ...)` Joins text values. ```text theme={null} CONCAT({Brand}, " ", {Product Name}) ``` ## Date and time functions ### `NOW()` Returns current date and time. ```text theme={null} NOW() ``` ### `TODAY()` Returns current date. ```text theme={null} TODAY() ``` ## Operators (core syntax) ### Arithmetic ```text theme={null} + addition - subtraction * multiplication / division ``` ### Comparison ```text theme={null} = equal != not equal > greater than < less than >= greater or equal <= less or equal ``` ## 4. Use AI to generate formulas ### Step 1: Enable AI * Toggle **AI ON** AI toggle in the formula editor ### Step 2: Describe logic Example inputs: * "Margin" * "Revenue = price times quantity" * "Discount percent" ### Step 3: Generate * Click **Generate by AI** The system will: * Create the formula * Validate the syntax ## 5. Validation and feedback After writing a formula: * **Formula is valid** - ready to use. * **Errors** - fix syntax or references. Common issues: * Missing `{}` around columns * Invalid function usage * Division by zero (logic issue) ## 6. Key concepts ### 6.1 Row-level calculation Formulas work **per row**. Example: ```text theme={null} {Price} * {Quantity} ``` Calculates revenue per product. ### 6.2 Column references Always use `{}`. Correct: ```text theme={null} {Price} ``` Wrong: ```text theme={null} Price ``` ### 6.3 Functions Used for: * Aggregation * Logic * Transformation Example: ```text theme={null} IF({Price} > 100, "High", "Low") ``` ## 7. Common use cases ### Pricing **Margin %** ```text theme={null} ({Price} - {Unit Cost}) / {Price} ``` **Price Gap vs Competitor** ```text theme={null} ({Price} - {Competitor Price}) / {Competitor Price} ``` ### Revenue **Revenue** ```text theme={null} {Price} * {Quantity} ``` ### Classification **Price Tier** ```text theme={null} IF({Price} > 100, "Premium", "Standard") ``` ### Promotions **Discount %** ```text theme={null} ({Original Price} - {Price}) / {Original Price} ``` ## 8. Best practices ### Naming * Use business terms, not technical ones * Prefer: * `Gross Margin %` * `Net Revenue` ### Simplicity * Avoid overly complex formulas * Break into multiple columns if needed ### Reusability * Build intermediate columns: * `Margin` * `Revenue` * Then reuse in other formulas ### Performance * Avoid heavy nested formulas on large datasets * Prefer simpler expressions ## 9. Limitations * Some column types do not support aggregation * Text fields cannot be used in numeric calculations * No multi-row / window functions (yet) ## 10. Recommended workflow 1. Ensure base data exists (Price, Cost, etc.) 2. Create core metrics: * Revenue * Margin 3. Add derived insights: * Classification * Flags 4. Refine with AI or manual tuning ## 11. Troubleshooting | Issue | Fix | | :- | :- | | Formula error | Check syntax | | Empty results | Check null values | | Wrong numbers | Validate input columns | | Not updating | Check column references | # Add columns Source: https://docs.retailgrid.io/grids/manage-columns Add custom standard, formula, single-select, or analytical-metric columns to your grid for notes, calculations, status pills, margin analysis, and more. The **Add Column** feature allows you to create custom fields directly in Retailgrid. You can add manual columns for notes or inputs, single-select columns with auto-colored value pills, calculated columns that automatically compute values using formulas, or analytical-metric columns that draw from a curated library of pre-built retail metrics. This makes Retailgrid flexible for pricing analysis, margin calculations, data enrichment, status tracking, and operational workflows. Add Column toolbar button ## 1. How to add a column ### Step 1: Open the Add Column menu * Click **Add Column** in the top toolbar. ## 2. Select column type You will see four options: Column type picker (four options) ### Standard column * Create a column with a fixed data type * Used for: * Notes * Flags * Manual inputs * Tags ### Single Select / Status * Create a column whose values come from a fixed set of choices, each rendered as a colored pill * Colors are mapped automatically and consistently per value * Used for: * Product roles (KVI, Core, Niche, Seasonal) * Workflow states (Draft, In Review, Approved) * Risk tiers (Low, Medium, High) ### Formula column * Create a column calculated from other columns * Used for: * Margin * Revenue * Price differences * KPIs ### Analytical metric * Pick a single pre-built retail metric (margin, revenue, price index, elasticity, etc.) and choose its timeframe. * Best for dropping in one extra metric without leaving the Add Column flow. * For adding several metrics at once - each with its own timeframe - use the dedicated [Metrics picker](/grids/metrics) in the top toolbar instead. Select **Standard** for manual input, **Single Select** for status / categorical pills, **Formula** for custom calculations, or **Analytical metric** for ready-made retail KPIs. ## 3. Standard column Standard column configuration form ### 3.1 Column name * Enter a clear name (e.g. `Margin`, `Discount %`, `Notes`) * Limit: 100 characters ### 3.2 Column type / display format Select how values should be interpreted: | Type | Use Case | | :- | :- | | Text | Notes, labels | | Number | Quantities, counts | | Currency | Prices, revenue | | Percentage | Margin, discount | | Date | Events, timestamps | | Toggle | Yes/No flags | ### 3.3 Column color (optional) * Assign a color for quick visual grouping * Useful for: * Custom fields * KPI highlighting * Workflow states ## 4. Single Select / Status column The Single Select column type renders each value as a colored pill, with colors mapped automatically and consistently to values. Status column with auto-colored pills ### 4.1 How it works * Type or pick a value in the cell. Existing values are offered in a dropdown; new values are accepted on the fly. * Each unique value is assigned a color from a fixed palette. The same value always gets the same color across the grid - so KVI is always blue, Core is always orange, and so on. * The mapping persists across reloads. You don't configure the colors manually. ### 4.2 When to use it over Standard - Text Use Single Select when the value space is bounded and you want at-a-glance grouping. Use Standard - Text for free-form notes where every cell is unique. Good Single Select use cases: * **Product role** (KVI, Core, Niche, Seasonal, Promo) * **Workflow status** (Draft, In Review, Approved, Rejected) * **Risk / priority tiers** (Low, Medium, High) * **Source channel** (Online, Store, Both) ### 4.3 New values When you type a value that hasn't appeared in the column before, Retailgrid adds it to the dropdown and assigns the next available color. There's no separate "manage values" screen - just typing creates the value. ## 5. Conditional color highlighting Conditional color highlighting drives a cell's color from a formula result or a value condition - similar to conditional formatting in Excel. Use it to make at-a-glance grids: green for high-margin SKUs, red for products below the margin floor, blue for KVIs. Conditional color highlighting configuration UI ### 5.1 When to use it Conditional highlighting is most useful when the value that drives the color is **derived** rather than set by a person. If a column already stores a bounded set of values you maintain by hand (KVI / Core / Niche), prefer a **Single Select / Status** column - the colors come for free and the pill UI is clearer than a tinted cell. Pick conditional highlighting when: * The color should follow a formula result that updates as inputs change (e.g. margin tiers). * The same condition needs to apply across many cells or rows. * The classification logic lives in a formula that you might iterate on later. ### 5.2 How to configure 1. Open the column header's three-dot menu on the column whose colors you want to drive. 2. Select **Conditional color highlighting**. 3. Map each value or formula result to a color. Retailgrid offers the same palette used by Single Select columns. 4. Preview the result on the visible rows, then save. You can drive the color from either a value column (e.g. a string column with `"High Margin"` and `"Low Margin"`) or directly from a formula column. The most common pattern is the formula route - use an `IF()` to classify a row, then map the classification to a color. ### 5.3 Worked example - margin tiers Add a formula column called `Margin Tier`: ```text theme={null} IF({Margin} > 0.20, "High Margin", IF({Margin} > 0.10, "Mid", "Low Margin")) ``` Then open **Conditional color highlighting** on `Margin Tier` and map: * `High Margin` → green * `Mid` → grey * `Low Margin` → red Every row now shows its margin tier at a glance, and the colors update automatically whenever `{Margin}` changes. ### 5.4 Interaction with Single Select Conditional color highlighting works on any column type. If you apply it to a Single Select column, the conditional rules override the automatic Single Select palette for the values you map. Values you don't map keep their automatic Single Select color. # Metrics Source: https://docs.retailgrid.io/grids/metrics Add curated, system-maintained retail KPIs to your grid in any timeframe — sales, value, profit, and margin per period. **Metrics** are pre-built retail KPIs that Retailgrid maintains for you per product. Pick the ones you want from the **Metrics** picker in the top toolbar, choose a timeframe, and they appear as new columns in your grid. The values stay up to date as your transactional data refreshes — you don't compute or refresh them by hand. Use Metrics when you want sales, profit, or margin context on every row without writing a formula. ## Open the Metrics picker In any grid: 1. Click **Metrics** in the top toolbar (between **Add Column** and **Agents**). 2. The Metrics side picker opens with the full metrics library. 3. Search by name, tick the metrics you want, set a timeframe per metric. 4. Click **Show *n* metrics** at the bottom of the picker - the label counts what you've ticked, so it reads **Show 3 metrics** when three are selected. **Reset** clears the selection. Metrics side picker with metric list and per-metric timeframe selectors The selected metrics are added to the grid as new columns. Each column name includes the timeframe suffix — `Sales Units (7D)`, `Gross Margin % (30D)`, and so on. ## Available metrics | Metric | What it means | | :- | :- | | **Sales Units** | Total quantity of items sold in the timeframe. | | **Sales Value (excl. VAT)** | Net revenue excluding VAT in the timeframe. | | **Sales Value (incl. VAT)** | Gross revenue including VAT in the timeframe. | | **Gross Profit (excl. VAT)** | Profit after cost of goods sold, excluding VAT. | | **Gross Profit (incl. VAT)** | Profit including VAT in sales value. | | **Gross Margin %** | Profitability ratio relative to sales value. | | **Unit Margin** | Profit per unit sold. | | **Unit Margin %** | Margin relative to selling price. | | **Markup %** | Margin over cost, on the ex-VAT price. | | **Realized Discount %** | How far the selling price sits below the regular price, ex-VAT. | | **Value of Inventory** | Stock on hand valued at the ex-VAT selling price. | | **Cost of Inventory** | Stock on hand valued at unit cost. | | **GMROI** | Gross margin return on inventory investment. | | **Sell-through %** | Share of available stock sold in the timeframe. | | **Inventory Turnover** | How many times stock turns over in the timeframe. | | **Average Selling Price** | Realized average price per unit sold. | Additional retail-pricing metrics (price index, elasticity-derived metrics, market position) appear in the picker as your data matures — they need transactions and competitor prices to be useful. ## Timeframes Each metric can be added in any of these timeframes: * **7D** — trailing 7 days. * **30D** — trailing 30 days. * **90D** — trailing 90 days. * **180D** — trailing 180 days. You can add the **same metric in multiple timeframes** — e.g. `Sales Units (7D)` and `Sales Units (30D)` side by side — to compare short-term and longer-term behavior. Metrics are computed off your loaded [Transactions](/data-requirements/sales) data. If a product has no transactions in the chosen window, the cell is blank rather than zero. ## Show, hide, and remove metric columns Once added, metric columns behave like any other column in the [Columns panel](/grids/manage-columns): * Open **Columns** in the toolbar. * Expand the **Metrics** group — it lists every metric-timeframe combination available on this grid. It is by far the largest group in the panel, because it holds each metric once per timeframe, so use the panel's search rather than scrolling it. * Click the eye icon next to any metric to show or hide it. * **Hide All** on the group collapses the whole panel without losing the metric definitions. Columns panel with the Metrics group expanded, showing metric-timeframe entries To permanently remove a metric column, hide it from the Columns panel. Re-adding it later from the **Metrics** picker re-creates the column instantly — no recompute needed. ## Metrics vs Add Column → Analytical metric Metrics and the **Analytical metric** option in [Add Column](/grids/manage-columns) draw from the same curated KPI library. They differ in entry point and ergonomics: * **Metrics** (top toolbar) — a side picker built for adding several KPIs at once, each with its own timeframe, in a single confirm step. Recommended for setting up a new grid for analysis. * **Add Column → Analytical metric** — the single-metric flow inside the column creation dialog. Useful when you're already in the Add Column flow and want to drop in one more metric without leaving it. Both flows produce the same column shape and feed into the same Metrics group in the Columns panel. ## When to use Metrics vs Formula columns Use **Metrics** when: * The KPI you want is in the curated library (sales, value, profit, margin). * You want a value per timeframe and you want it to stay current automatically. * You're doing portfolio or category analysis and need consistent definitions across grids. Use [**Formula columns**](/grids/formula-columns) when: * You want a custom calculation that combines specific columns in your grid (e.g. `{Price} - {Competitor Price}`). * You need an `IF` / classification expression that isn't a standard retail KPI. * You're computing something that only makes sense in the context of this particular grid. The two compose well — add a metric column for `Sales Units (30D)`, then write a formula column on top of it (e.g. revenue concentration, share of sales). ## Common pitfalls * **Blank metric cells** — the product has no transactions in the chosen window. Either widen the timeframe or accept the gap (it's signal, not a bug). * **No metrics in the picker** — your account has no transactional data loaded yet. Upload Transactions on the [Datasets](/data-requirements/datasets) page or via the [API](/api-reference/imports/transactions). * **Metric definitions don't match your finance reporting** — Retailgrid's curated definitions are intentionally conservative (Gross Margin %, Sales Value excl. VAT, etc.). If your team uses a different convention, model it with a [Formula column](/grids/formula-columns) rather than reinterpreting the metric value. ## Related * [Add columns](/grids/manage-columns) * [Formula columns](/grids/formula-columns) * [Sales / Transactions data requirements](/data-requirements/sales) * [Metrics glossary](/reference/metrics) * [Datasets](/data-requirements/datasets) # Grids overview Source: https://docs.retailgrid.io/grids/overview The Grids home page - where your working grids live, and where you create, open, and organize them. A **grid** is your focused pricing workspace - a selected set of products where you analyze, optimize, and act on pricing. The **Grids** home page is where all your grids live and where you start a new one. Open it from the **Grids** entry in the left sidebar (it's also where you land after signing in). ## The Grids home page The home page lists every grid in your [Organization](/settings/organization) in a table. Each row is one grid, with its name, the number of products it contains, who created it, and when it was last updated. The list is **sortable** - click a column header to sort by name, size, or last-updated, and the list keeps a sensible **created-date** default so your newest grids surface first. Sorting is stable between visits, so the list looks the same each time you open it. ## What you can do here * **Open** a grid - click its name to jump into the grid view. * **Create** a new grid - click **+ New** to start the [grid creation flow](/grids/create-grid) and pick products and a name. * **Search** for a grid by name with the search box above the table. Each row also carries four actions on the right: | Action | What it does | | :- | :- | | **Rename Grid** | Change the grid's name in place. | | **Edit Tags** | Add or remove tags, so a long list stays organized and filterable. | | **Duplicate Grid** | Copy the grid's full configuration onto a scope you choose. See [Duplicate a grid](/grids/duplicate-grid). | | **Remove Grid** | Delete the grid. The underlying datasets are not touched. | ## How grids relate to your data A grid is a view over the [datasets](/data-requirements/datasets) you've loaded - it doesn't hold its own copy of the data. Refresh a dataset and the grids built on it pick up the new rows on the next nightly refresh. Deleting a grid never deletes the underlying data. ## Next steps * [Create a grid](/grids/create-grid) - build your first workspace. * [Configure grid](/grids/configure-grid) - columns, sorting, grouping, filters, and views. * [Agents overview](/agents/overview) - run pricing rules and optimization against a grid. # Share a grid Source: https://docs.retailgrid.io/grids/share Give teammates in your account view-only or edit access to a grid, change it, or revoke it - without rebuilding grids or trading exports. A grid is normally visible only to the person who created it. Sharing lets you give named teammates in the same account access to the grid you built, so they work from the same server-backed data instead of rebuilding it or trading exports. Existing grids stay private until you explicitly share them. ## Who can share, and what they can grant Any grid owner — whether an Organization User or an Admin — can share a grid they own. Open the **Share** dialog, find a named, active teammate in the same account, and give them one of two access levels; you can change or revoke it later. The Share dialog - search a teammate and set their access level Grid permissions are separate from account roles. Being an Organization Admin does not bypass a grid's permissions, and a grid grant never gives someone account-admin abilities. Sharing across accounts is not possible. ## Access levels ### View-only A viewer can: * Read the same server-backed grid data, columns, and the grid's saved scope and filter presets. * Download data exports. * Sort, filter, arrange columns, and switch views locally. These stay personal to them — nothing is synced back to the grid or to you. A viewer cannot edit cells, columns, metadata, or scope; save action configurations; duplicate the grid; run, refresh, or cancel computations; approve or register prices; or change sharing. Their exploring saves no state on the server. ### Edit An editor can do everything an owner can **except** delete the grid or manage its access grants. Existing organization permission checks still apply on top — for example, actions that need an account permission still need it. If an editor duplicates the grid, the copy belongs to them and starts private, with no inherited sharing. ## Grant, change, or revoke access Only the owner can grant, change, or revoke access, and only the owner can delete a shared grid. As an owner you select an eligible teammate and give them view-only or edit access, adjust that level later, or revoke it. Shared grids appear alongside your own on the [Grids home page](/grids/overview), showing the owner and your current access level, so it's clear which grids are yours and which were shared with you. ## How revocation and lifecycle behave * **Changes apply on the next request.** Revoking access, or downgrading an editor to view-only, takes effect on the recipient's next action — including from a session they already have open. The UI reflects their effective access, and direct API calls enforce the same limits. * **A run already accepted may finish.** Jobs a user started before a change may complete; anything after uses their current access. Nothing is retroactively cancelled. * **Inactive teammates lose access but keep their grant.** A deactivated recipient can't reach the grid; their grant is stored and resumes if they're reactivated. * **Deactivating the owner doesn't remove teammates' access.** Their existing access continues. ## Related * [Grids overview](/grids/overview) * [Organization](/settings/organization) * [Configure grid](/grids/configure-grid) # SKU time-series analytics panel Source: https://docs.retailgrid.io/grids/sku-analytics-panel See how a SKU's price, competitors, sales, and profit move over time - without leaving the grid or exporting data. The grid shows a rich point-in-time snapshot per SKU. The **time-series analytics panel** adds the history: select a SKU and a full-width panel docks at the bottom of the grid, charting how that SKU's price, competitor prices, sales, and profit have moved over time. Use it to answer the questions a snapshot can't: is that competitor price a temporary promo or a sustained reprice? How did sales react to the last price move? Where does the profit-maximizing price sit? ## Open and control the panel * **Open** - select a SKU in the grid; the panel docks at the bottom, using the full screen width. * **Hide / restore** - one click on the collapse toggle in the panel header fully hides the panel; one click restores it. * **Resize** - drag the handle to change the panel height. The header shows which SKU the panel is bound to; selecting a different row re-binds the charts. ## Timeframe and aggregation Two global controls apply to every chart tab: * **Timeframe** - 4, 12, 26, or 52 weeks. * **Aggregation** - Daily or Weekly. Longer timeframes with weekly aggregation read best for trend questions; short timeframes with daily aggregation are right for promo post-mortems. Each tab also offers more than one layout - use the icons at the bottom of the panel to switch between the chart view and the tab's alternative views (described per tab below). ## The three tabs ### Competitor prices Your price as a flat baseline with competitor prices as stepped lines, plus a **My cost** reference line. Hover any point for the per-competitor gap on that date. Competitor prices tab - my price baseline, stepped competitor lines, and a per-date tooltip Dot markers flag two situations worth attention: * **Promo-driven moves** - a competitor price change flagged as a promotion. * **Below-cost prices** - a competitor priced below your cost. The tab's table view adds two reads below the chart: * **Recent price changes** - every competitor move in the window, with the old and new price and the gap vs yours. * **Current ranking** - where your price sits against each competitor right now, with a trend sparkline per competitor. Competitor prices tab - Recent price changes and Current ranking This is the tab for "is this a promo or a reprice?" ### Sales & price Units sold as bars, with your price and the market average price as lines over the same period. The tooltip surfaces **Sales value**, **Gross profit**, and **Gross margin %** per period. Sales & price tab - units sold bars with my price and market average price lines The summary view adds KPI cards on top of the chart - total units sold, your average price, market average price, the average market gap, and the best week by units. Sales & price tab - summary view with KPI cards above the chart This is the tab for "what did that price change do to volume and margin?" ### Profit & price Total profit by period, with your price and your cost as reference lines. Hover any bar for the profit, price, and cost on that date. Profit & price tab - profit bars with price and cost reference lines This is the tab for "are we above or below the profit peak?" ## Data requirements The panel reads per-SKU daily history. For full fidelity you need: * Price snapshots and units sold per day ([Sales data](/data-requirements/sales)) * Competitor price snapshots with a promo flag ([Competitor data](/data-requirements/competitors)) * Landed cost per SKU ([Products data](/data-requirements/products)) SKUs with thin history chart what's available - a SKU loaded last week won't fill a 52-week window. ## Related * [Metrics glossary](/reference/metrics) * [Competitor data](/data-requirements/competitors) * [Sales data](/data-requirements/sales) * [Dynamic Pricing](/agents/dynamic-pricing) # Product and SKU-Store grid views Source: https://docs.retailgrid.io/grids/sku-store-view Work at the right granularity - one row per product, or one row per product-store combination with store-scoped prices, costs, and metrics. With store-level data loaded, a grid can display rows at two levels of granularity: * **Product view** - one row per product, with aggregated fields where applicable. The right level for catalog-wide work. * **SKU-Store view** - one row per product-store combination. The right level when prices, costs, or competitors differ per store. Not to be confused with [Views](/grids/views), which save and restore grid *layouts* (columns, filters, sorting). This page is about row granularity. ## Switch between views Row granularity is set by **Grid grain**, in the **Scope** panel. Click **Scope** in the top-right of the grid toolbar, set **Grid grain**, and the grid changes granularity in place - no page reload. See [Set the grid's scope](/grids/create-grid#change-which-products-are-in-the-grid) for the rest of that panel. Your selection is persisted per workbook and session, so a grid you work at SKU-Store grain stays that way when you come back. The SKU-Store grain is available once store-level data is loaded - see [Product store data](/data-requirements/product-store-data) and [Manage stores](/stores/manage-stores). ## What changes in SKU-Store view Every row is one product in one store. Alongside the product columns you get the store columns: * **Store** (identifier) * **Country**, **Currency**, **Channel**, **Pricing zone** * **Store price** - the price in that store, plus store-scoped cost and stock when loaded A product active in 12 stores shows as 12 rows, each carrying that store's own values. ## Filters and sorting Filters and sorts work in both views and respect the current mode: * Store-scoped filters (by country, channel, pricing zone, store) apply in SKU-Store view. * Switching views preserves applied filters where they still make sense. When a filter can't be carried across (e.g. a store filter going back to Product view), the grid warns you instead of silently dropping it. ## Metrics per store In SKU-Store view, [metrics](/reference/metrics) are computed at the SKU-Store grain: each row's margin, price index, and competitor gap use that store's own price, cost, and competitor set. In Product view you see the product-level rollup of the same metrics. This matters wherever stores genuinely differ - a SKU can be comfortably profitable in one market and underwater in another, and only the SKU-Store view shows that split. ## Pricing agents at the SKU-Store level [Dynamic Pricing](/agents/dynamic-pricing) runs at the SKU-Store level when your grid carries store-level data: one recommended price per product-store combination, computed against each store's own competitor prices and costs. Rules can target store attributes (country, channel, pricing zone) so different stores get different guardrails. ## Performance SKU-Store view multiplies row counts (products × stores), and the grid is built to stay responsive on typical catalogs - tens of thousands of product-store rows. If you work with very large combinations, filter to the stores you're actively pricing. ## Related * [Manage stores](/stores/manage-stores) * [Product store data](/data-requirements/product-store-data) * [Dynamic Pricing](/agents/dynamic-pricing) * [Metrics glossary](/reference/metrics) * [Views (saved layouts)](/grids/views) # Totals row Source: https://docs.retailgrid.io/grids/totals-row Read the pinned totals row that aggregates every calculated metric over your exact filtered set. The **totals row** is a pinned row at the bottom of the grid that rolls every calculated metric column up over the rows you're actually looking at. It always reflects the active filters, scope, and forecast horizon, so you can see the overall sales and margin impact of a repricing without exporting first. ## Show or hide the totals row Toggle the totals row with a click. The grid remembers its collapsed or expanded state per user, so it opens the way you left it. ## It follows your filters Retailgrid computes the totals on the backend over the exact filtered and scoped row set - not just the rows currently loaded on screen. Filter to a category, a brand, or any slice and the totals recompute for that slice. Change the forecast horizon and the forecast columns re-total to match. A manual export of the same filtered rows reproduces every total. ## How each metric is aggregated Not every metric should be added up. A price can be averaged; a margin percentage can't - averaging ratios gives the wrong answer. The totals row aggregates each column by its correct semantics: * **Summed** - count- and money-style quantities: Sales Units, Sales Value, Gross Profit, Stock, Cost of Inventory, Value of Inventory, average daily sales, and the forecasted and change (Δ) columns. * **Averaged** - per-unit and rate columns: Price, Cost, RRP, Regular Price, Proposed Price, Tax Rate %, Elasticity, Price Change %, competitor prices, and custom number columns. * **Recomputed from totals** - ratios are rebuilt from their aggregated parts, never averaged: Gross Margin % (total gross profit ÷ total sales value), Average Selling Price, Run Rate (days), GMROI, and CPI (Min / Mode). * **Sales-weighted** - Unit Margin % and New Unit Margin % are weighted by each row's sales value, so high-volume SKUs count most. * **Count or share** - Is Promo shows a count of promo items; Cheapest Flag shows the share of products where you're the cheapest. A few columns stay blank in the totals row where a correct total isn't available from the visible data - Cost Coverage %, Sell-through %, Inventory Turnover, and Realized Discount %, along with text, date, and status columns. ## Read it at a glance The totals row is styled to stand apart from your data. Regular grid rows are plain - no colored backgrounds and no red or green text - so color accents appear only in the totals row, and it reads as clearly separate from the last product row. ## Related * [Metrics glossary](/reference/metrics) * [Configure grid](/grids/configure-grid) * [Views](/grids/views) * [Export data](/grids/export) # Views Source: https://docs.retailgrid.io/grids/views Save and switch between custom grid layouts to quickly access different column, filter, and sorting configurations. Views panel with saved view list **Views** allow you to save and quickly switch between different table layouts in Retailgrid. A view stores how the grid is configured, including: * visible columns * column order * filters, including quick filters * sorting * grouping This helps you avoid rebuilding the same setup every time and allows teams to work with different analytical perspectives. ## Creating a view 1. Configure the grid (columns, filters, sorting). 2. Click **Views** in the toolbar. 3. Enter a name for the view. 4. Click **Add**. The layout is now saved and can be reused anytime. ## Managing views Inside the **Views** panel you can: * **Rename** a view * **Duplicate** a view to create variations * **Save** the current layout under a name — your live grid edits autosave continuously, so saving a view is about capturing a named layout you can switch back to, not about avoiding lost work * **Delete** a view if it is no longer needed ## Quick filters Beyond the per-column filter icons in the grid header, a single **Filters** menu lets you show, configure, add, and remove **quick filters** - the filters you reach for most often - from one place. Quick filters are saved as part of a view, so a saved view captures not just which columns are visible but the exact filtered slice your team works from. ## Best practices * Use clear names like **Pricing Analysis**, **Promo Monitoring**, or **Margin Control**. * Keep the **Default view** simple. * Duplicate views when creating variations for different tasks. **Example:**\ A *Pricing Review* view might include columns like Price, Competitor Price, Margin %, Sales, and Elasticity to help pricing managers quickly identify opportunities. # Competitor matching Source: https://docs.retailgrid.io/price-monitoring/competitor-matching Validate the latest competitor scan before you price against it - coverage, match quality, blind spots, and price sanity. **Competitor matching** is where you check a competitor scan before you trust it. Open it from the left sidebar. The page reads one thing: the latest scan published for your organization, with the observed date range in the subtitle. Competitor matching - the headline figures and the Coverage tab ## Why validate a scan first Competitor prices drive your Price Match rules, your CPI, and every competitor-gap metric. A scan with thin coverage or bad matches moves prices quietly, and it is hard to spot after the fact. Validating takes a few minutes and is cheaper than unwinding a bad repricing. Run through this page whenever a new scan lands, and before any pricing pass that leans on competitor data. ## The headline figures Six tiles across the top tell you how much of your catalog the scan can actually support a decision on: | Tile | What it counts | | :- | :- | | **Catalog SKUs** | The size of your product master, as the denominator for everything else. | | **SKUs with a competitor price** | How much of the catalog the scan reached at all, and how much still has nothing. | | **Competitor price points** | Total matched observations, and the average number of competitors per covered SKU. | | **Repricing-ready SKUs** | Direct matches at high confidence. This is the set you can actually reprice against. | | **SKUs needing review** | The best match sits below the confidence line. Worth a look before you rely on them. | | **SKUs with no match** | No competitor price at all. The **Blind spots** tab breaks these down. | The gap between **SKUs with a competitor price** and **Repricing-ready SKUs** is the number to watch. Coverage tells you the scan found something; repricing-ready tells you it found something you can price against. ## Filter the scan Four filters sit across the top of the page: | Filter | What it narrows to | | :- | :- | | **Products** | Active products only, or your whole catalog. | | **Cost** | Rows with a unit cost above zero. | | **Price** | Rows with a selling price above zero. | | **Stock** | Rows with stock above zero. | The filters are global to the page. Every figure and the row table are computed from the same filtered pass over the scan, so no two tabs can show you conflicting answers. Start unfiltered to see the raw scan. Narrow to active, in-stock, priced products when you want the view that matches what you actually sell. ## Coverage - how much of the catalog was reached * **Overall catalog coverage** - the share of SKUs with at least one competitor price. Coverage is counted per SKU, not per match: a product found at four sellers is one covered SKU. * **Coverage by competitor** - each competitor's reach across the whole catalog, with the share of its matches that are direct and its average confidence. Sort by **Highest**, **Lowest**, or **Size**. These bars do not add up to overall coverage, because one SKU can be found at several sellers. * **Comparison depth** - how many competitors each covered SKU has. This is the tile that tells you whether you have a market price or just a data point. One competitor is enough to spot a gap; three or more is the band a pricing decision can lean on. ## Match quality - how much to trust the matches Competitor matching - the Match quality tab * **Confidence distribution** - every match bucketed by how sure the matcher was, in bands (under 70, 70-79, 80-89, 90-94, 95 and above). The bands are the matcher's own, so a bar means the same thing here as in the delivered file. * **Direct vs substitute** - whether the price belongs to the same product (**Direct**) or to an analogue that merely serves the same purpose (**Substitute**). Repricing against a substitute carries a risk a direct match does not. Check this number before treating coverage as usable. * **Matches by method** - the share of each matching method that clears high confidence: `barcode`, `manufacturer_code`, `fuzzy`, and `llm`. Barcode and manufacturer-code matches are near-certain and usually scarce; fuzzy and LLM matches are plentiful and need the confidence check. In markets where few products carry a barcode, expect the volume to sit in the lower-precision methods. ## Blind spots - where you have nothing Competitor matching - the Blind spots tab * **Where the risk sits** - the catalog sorted by what can be done with it: no competitor price at all, only substitute matches, a single competitor only, best match below the confidence line, and direct matches at high confidence. Each band is measured against the whole catalog and a SKU can appear in more than one, so the bands do not sum to 100%. * **Category blind spots** - the worst-covered departments, ranked by what is worth acting on. Departments below a hundred SKUs are excluded: one uncovered product out of three reads as 0% coverage and would head the list without costing anything. * **Manufacturer blind spots** - brands and ranges your competitors barely carry. Use this tab to decide where to point the next scan, not to judge the current one. ## Price sanity - catching bad matches through their prices Competitor matching - the Price sanity tab A price check is also a matching check. Products whose prices differ tenfold are rarely the same product, so the tails are where bad matches surface. * **CPI distribution** - the competitor's price divided by yours, in bins from under 0.50 to over 2.00. A healthy scan clusters around 1.00. Look at the tails first. * **Is the unit comparable?** - whether both sides sell the same quantity. Averaging a five-pack against a single unit produces a number that looks like a price gap and is really a units mistake. Only comparable packs feed the CPI distribution above. * **Median CPI by competitor** - who is systematically cheaper, and by how much. A median far from 1.00 is worth a second look at that competitor's matches before you read it as a pricing position. ## All matches - the rows behind the figures Competitor matching - the All matches tab The row-level table, one row per match: | Column | What it shows | | :- | :- | | **SKU** | Your identifier for the product. | | **Our product** | Your product name. | | **Competitor** | Which competitor the price came from. | | **Their listing** | The competitor's own product title, so you can judge the match by eye. | | **Confidence** | How sure the matcher was. | | **Type** | `Direct` or `Substitute`. | | **Method** | Which method produced the match. | | **Their price** | The competitor's price. | | **CPI** | Their price divided by yours. | | **Unit** | Whether the pack sizes are comparable. | Search by SKU, product, or competitor, and filter to a single competitor. Both run on the server, so the response stays bounded on a scan holding tens of thousands of rows. Pages are capped at 200 rows. This is the tab to open when a figure above looks wrong. Search into the extreme CPI values and read the listings: a bad match is usually obvious once you see the competitor's own title next to yours. ## Read the empty states honestly The page distinguishes two situations that need opposite reactions from you: * **Nothing published yet.** No scan has been delivered for your organization. The page says so rather than drawing a dashboard of zeros, because zeros would be indistinguishable from a catalog nobody has matched. * **The scan could not be loaded.** The source behind the scan was unreachable. That is an incident, not an empty account. Retry, and if it persists reach out through the in-app messenger. ## Publish a new scan **Update** publishes a new scan and replaces the current one. You can give it either an already-**matched** file - each row already carries the `item_id` it belongs to - or a **raw competitor export**, which Price Radar matches to your catalog for you before it publishes, so you no longer have to link the rows yourself first. A delivery carries three choices: * **Coverage** - whether the file is a **full** sweep of your catalog or a **partial** one. This decides what the delivery is allowed to retire. * **Covered competitors** - on a partial delivery, which competitors the file speaks for. Competitors outside that list keep their existing prices. * **Allow large removal** - an explicit confirmation, required when the delivery would retire an unusually large share of existing matches. It exists so a truncated file cannot silently wipe your competitor set. Conversion, validation, and publication run in the background, so the page stays responsive while a large file is processed. You can follow progress on the page while it runs. ## Common pitfalls * **Reading coverage as readiness.** A high coverage number with most SKUs resting on a single competitor is not a market price. Check **Comparison depth** before you act. * **Ignoring the substitute count.** A substitute match is a different product. Treating it as a direct one is how a price ends up anchored to the wrong item. * **Trusting a competitor whose median CPI is far from 1.00.** That usually means its matches are wrong, not that it is cheap. * **The figures are read-only.** The rows are a delivery artifact, replaced wholesale by the next delivery. There is nothing to correct here that the next scan would not overwrite. Fix the source data or the matching instead. * **One delivery at a time.** A second publish is refused while one is already in flight for your organization. Wait for the first to finish rather than retrying. * **A partial delivery that names no competitors** retires nothing and adds only what it carries. Name the competitors the file covers so stale matches for those competitors are cleared. ## Related * [Price Monitoring](/agents/price-monitoring) - discover competitor prices with AI search * [Competitor prices data spec](/data-requirements/competitors) * [Assign competitors to stores, zones, and channels](/stores/assign-competitors) * [Competitive Position dashboard](/dashboards/competitive-position) * [Metrics glossary](/reference/metrics) # Remove a competitor price Source: https://docs.retailgrid.io/price-monitoring/exclude-competitor-price Delete a wrong or irrelevant competitor price from the Price Comparison popover so it stops feeding the price range, market-data metrics, and optimization. Sometimes a competitor price in your grid is simply wrong for the decision: a mismatched product, a clearance or bundle price, or a market you don't compete in. Left in, it still sets the min or max of the **Price Range** and still feeds the optimizer, so the recommendation is skewed and the range reads as noise. You can remove such a price directly from the grid so it stops counting. ## Remove a price from the Price Comparison popover Open the **Price Comparison** popover from a product's **Price Range** cell. It lists the competitor prices behind that range, each with the competitor, the price, and when it was observed. Use the delete (trash) icon next to a price to remove it. The Price Comparison popover, opened from a Price Range cell, with a delete icon on each competitor price Retailgrid asks you to confirm first, because the removal is permanent. The confirmation dialog before a competitor price is deleted ## What removal does Deleting removes **every price collected for that competitor on that product**. Once removed: * the price no longer feeds the Price Range min / max / median, the market-data metrics, or optimization for that product, and * it is gone everywhere that competitor's price appeared for the product — every grid, dashboard, and presentation. Removal is permanent and cannot be undone. There is no restore. ## It comes back if monitoring still tracks it Removing a price deletes what has already been collected; it does not stop future collection. If [Price Monitoring](/agents/price-monitoring) is still configured to track that competitor for the product, its next run collects the competitor again. So deleting is the right move for a one-off bad observation you want out of today's numbers. When a competitor is *consistently* mismatched or irrelevant for a product, fix it at the source instead, so the bad price doesn't return — correct the match, or stop tracking that competitor for that product. See [Competitor matching](/price-monitoring/competitor-matching) to check a match, and [Assign competitors to stores, zones, and channels](/stores/assign-competitors) to change what is tracked. ## Related * [Competitor matching](/price-monitoring/competitor-matching) * [Competitor prices data spec](/data-requirements/competitors) * [Metrics glossary](/reference/metrics) # Metrics glossary Source: https://docs.retailgrid.io/reference/metrics Reference for every analytical metric Retailgrid computes - definitions, formulas, and when each one matters. This page is the reference for every metric Retailgrid computes. Use it to confirm what a column means before you act on it. Metrics are available as **Analytical metric** columns (added via **Add Column** or the **Metrics** toolbar button) and as outputs of agents like Dynamic Pricing. ## Metric grain When store-level data is loaded, metrics are computed at the **SKU-Store** grain: each store's KPIs (margin, price index, competitor gap) use that store's own price, cost, and competitor set. In the grid's Product view, a product-level rollup of the same metrics is shown. See [Product and SKU-Store grid views](/grids/sku-store-view). ## Margin metrics ### Margin Absolute monetary margin per unit, gross of tax. ```text theme={null} Margin = price - unit_cost ``` Use it for: quick eyeballing of which SKUs make money. ### Margin % Margin as a percentage of selling price. ```text theme={null} Margin % = (price - unit_cost) / price ``` Use it for: comparing margin across SKUs at different price points. ### New Unit Margin % Net-of-tax margin on the price calculated by Dynamic Pricing. ```text theme={null} New Unit Margin % = (Rules Based Price / (1 + Tax Rate) - Unit Cost) / (Rules Based Price / (1 + Tax Rate)) ``` Use it for: seeing the margin you'd land at if you accepted the agent's recommended price. ### Unit Margin % change Percentage-point change between current Margin % and New Unit Margin %. ```text theme={null} Unit Margin % change = New Unit Margin % - current Margin % ``` Use it for: spotting the rules that move margin the most (positive or negative). ## Competitive metrics ### Price Index (PI) Your price relative to a single competitor or competitor reference. ```text theme={null} Price Index = price / competitor_price * 100 ``` 100 = parity, less than 100 = you're cheaper, greater than 100 = you're more expensive. ### CPI (Min) **CPI** (Competitive Price Index) compares your price to a competitor reference; the variants below differ only in which competitor price they benchmark against. Competitive price index against the cheapest competitor. ```text theme={null} CPI (Min) = price / min(competitor_price) * 100 ``` Use it for: tracking how aggressive the cheapest competitor is per SKU. ### CPI (Mode) Competitive price index against the most common competitor price. ```text theme={null} CPI (Mode) = price / mode(competitor_price) * 100 ``` The mode often represents the "market price" better than the min, because outlier-cheap competitors don't dominate it. ### CPI (Weighted) Competitive price index weighted by each SKU's sales value, so the SKUs you actually sell dominate the index. ```text theme={null} CPI (Weighted) = Σ(sales_value × CPI) / Σ(sales_value) ``` Use it for: the single best summary of overall price position across a category or the whole grid - high-volume SKUs count most. This is the CPI variant surfaced on the built-in Competitive Position dashboard. ### Price Gap Absolute monetary gap to a competitor reference. ```text theme={null} Price Gap = price - competitor_price ``` Use it for: understanding the literal euros / dollars between you and a competitor. ### Cheapest Flag Boolean. `true` if your price is the cheapest among the available competitors, otherwise `false`. ```text theme={null} is_cheapest = price <= min(competitor_price) ``` Use it for: filtering grids to "where am I winning on price." ### Price Range Visual column showing the min/max of competitor prices and where the current price sits in that range. This is a UI affordance rather than a numeric metric - use it for at-a-glance market positioning, then drill in with CPI variants for precision. ## Inventory & realized-pricing metrics These metrics show how much of the list price you actually realize and how much capital is tied up in stock. Prices here are **ex-VAT** (`price_ex_vat = price / (1 + tax_rate)`). The inventory metrics need a `stock` value on your Products data; the sell-through, turnover, and GMROI metrics need Transactions. When a product has no usable stock history in the selected window, the stock-based metrics (Value of Inventory, Cost of Inventory, and the stock-dependent productivity metrics) show a **no-data** state instead of a `0.00` or `100%` figure. This keeps a real zero distinct from missing stock. ### Markup % Margin over cost, on the ex-VAT price. ```text theme={null} Markup % = (price_ex_vat - unit_cost) / unit_cost ``` ### Realized Discount % How far the current selling price sits below the regular (list) price - the discount you're actually giving. ```text theme={null} Realized Discount % = (regular_price_ex_vat - price_ex_vat) / regular_price_ex_vat ``` ### Value of Inventory Stock on hand valued at the ex-VAT selling price. ```text theme={null} Value of Inventory = stock * price_ex_vat ``` ### Cost of Inventory Stock on hand valued at unit cost - the capital tied up in stock. ```text theme={null} Cost of Inventory = stock * unit_cost ``` ### Inventory-productivity metrics The picker also exposes **GMROI**, **Sell-through %**, **Inventory Turnover**, and **Average Selling Price**, computed from your Transactions over the selected window - gross margin return on inventory investment, the share of available stock sold, how many times stock turns over, and the realized average price per unit. Each needs enough sales history in the window to be meaningful; a product with no transactions in the window shows blank. ## Refresh cadence Most metrics are computed in BigQuery and refresh in near real-time as your underlying data (Products, Transactions, Competitors) changes. Specifically: * **Margin / Margin %** refresh whenever `price` or `unit_cost` changes on a product. * **CPI variants and Price Index** refresh whenever the relevant Competitors dataset row updates. * **Dynamic Pricing output columns** (Rules Based Price, New Unit Margin %, Unit Margin % change) update on demand when the Dynamic Pricing agent runs. Static metrics like margin, unit cost, and price also keep a daily history in BigQuery alongside the time-series metrics. That history isn't surfaced as its own grid column, but you can see how a SKU's price, competitors, sales, and profit have moved over time in the [SKU time-series analytics panel](/grids/sku-analytics-panel). If a metric column looks stale, replace the source dataset on the [Datasets page](/data-requirements/datasets) - that triggers a downstream refresh. ## Related * [Add columns](/grids/manage-columns) * [Dynamic Pricing](/agents/dynamic-pricing) * [Product and SKU-Store grid views](/grids/sku-store-view) * [Competitor Prices data spec](/data-requirements/competitors) # Runs overview Source: https://docs.retailgrid.io/runs/overview Track every asynchronous job - CSV imports, agent runs, optimization, dataset processing - from one place. The **Runs** section in the left sidebar shows every asynchronous job Retailgrid is processing or has finished processing for you. Open it to see what's running, what succeeded, and what failed. Runs list with date filter ## What shows up in Runs Any job that takes more than a moment runs asynchronously. The Runs section captures: * **CSV / XLSX imports** - kicked off when you upload a dataset. * **Agent runs** - Dynamic Pricing, Price Optimization, Pricing Strategy, Product Roles, Price Monitoring, and Base Price Performance. * **Dataset rebuilds** - triggered when you replace a dataset. * **Bulk operations** - large multi-grid actions where applicable. The Runs page is the single source of truth for "what is the system doing right now?" and "what happened to that thing I clicked Run on five minutes ago?" ## Time window By default the Runs list shows runs from the **last 7 days**. Switch to **Last 30 days** or **All time** with the buttons above the list. Most teams stay on the 7-day default - older runs rarely need attention and the shorter window keeps the list scannable. ## Active runs badge The Runs icon in the left sidebar shows a small badge with the number of currently running jobs (queued + running). The badge clears once everything finishes, so it's a quick at-a-glance signal for "is anything still in flight?". ## Read the Runs list Each row is one run: | Column | What it shows | | :- | :- | | **Type** | The agent that started the run (Dynamic Pricing, Pricing Strategy, etc.) or a system label (Import, Dataset rebuild) for non-agent jobs. | | **Triggered by** | The user who started it (or `system` for scheduled jobs). | | **Started** | When the run entered the queue. | | **Status** | Current state - see the table below. | | **Duration** | How long the run took (or has been running). | | **Target** | The grid or dataset the run is acting on. | ## Status values | Status | What it means | | :- | :- | | `Queued` | The run is waiting for a worker to pick it up. | | `Running` | A worker is processing the run. | | `Succeeded` | The run finished and wrote results back to the target. | | `Partial` | The run completed but skipped some rows (e.g. validation failures on a subset). Open the run to see which rows. | | `Failed` | The run errored out. The run detail shows the error and the inputs that produced it. | | `Canceled` | A user canceled the run before it finished. | ## Drill into a run Click any row to open the run's detail view: * **Inputs** - the configuration the run was started with. * **Logs** - what the worker did, step by step. * **Output** - the columns or artifacts the run produced (and links back to the grid where they live). * **Errors** - if any rows failed validation or rule execution, the row IDs and reasons. ## Retry a failed run Failed runs show a **Retry** action in the row's three-dot menu. Retry re-runs with the same inputs - useful when the failure was transient (timeout, infrastructure hiccup). For configuration errors, edit the source (rule config, dataset) and trigger a fresh run from the original surface (Agents modal, Datasets page). ## Cancel a running run Long-running jobs can be canceled from the same three-dot menu. Cancellation is best-effort: workers stop at the next checkpoint, so you may see a few seconds of work continue before the status flips to `Canceled`. ## Related * [Datasets](/data-requirements/datasets) * [Agents overview](/agents/overview) * [Price Monitoring](/agents/price-monitoring) # Account Source: https://docs.retailgrid.io/settings/account Manage your personal Retailgrid account - profile, login method, and personal preferences. Your **account** is your personal Retailgrid identity. Open it from the **gear (Settings)** icon in the left sidebar. ## Profile Profile settings page The **Profile** page shows: * **First name** and **Last name** - your display name (visible to teammates as the owner of grids and triggered runs). * **Email** - the address you signed in with. It can't be changed for Google accounts. * **Currency** - your default display currency. * **Company Name** - the company shown on generated artifacts such as [Pricing Strategy](/agents/pricing-strategy) decks. * **Prices exclude tax** - when on, Retailgrid uses **Price as supplied** for margin and markup instead of deriving a net-of-tax price. Turn this on if your product prices already exclude tax (common for US and Canadian retailers). Changing it marks workbooks for recalculation. * **Preferences** - **Timezone** and interface **Language** (English or Finnish). Edit these fields in place and click **Save Changes**. ## Password If you signed up with email and password, you can change your password from the **Security** tab. Enter your current password and a new one to update it. Forgot your password? Use the **Forgot password?** link on the [sign-in page](https://app.retailgrid.io) to request a reset email. If you sign in with Google, manage your password through Google. The Security tab shows that password change is unavailable for Google accounts. ## Language Retailgrid currently supports **English** and **Finnish** as interface languages. Switch between them from the **Language** control in the Profile section above. Your selection follows you across the whole app: * Navigation, dialogs, and action labels. * AG Grid chrome - column header menus, filter conditions, pagination. * Date and number formatting on metric columns and run timestamps. Your own data - product names, categories, dataset content - is never translated. Only the surrounding UI changes. ## Account vs Organization Retailgrid distinguishes two layers: * **Account** - your individual identity. Everyone on your team has their own. * **Organization** - the workspace your team shares. Grids, datasets, and agents live at the Organization level. You can belong to one Organization at a time. To switch organizations or invite teammates, see [Organization](/settings/organization). ## Related * [Organization](/settings/organization) * [Modelling](/settings/modelling) * [Integrations](/settings/integrations) * [Currency](/settings/currency) * [Session and security](/settings/session) # AI connections Source: https://docs.retailgrid.io/settings/ai-connections Connect an AI client to Retailgrid over MCP, see what it can read and do, and disconnect it at any time. **AI connections** lists the AI clients you have connected to Retailgrid, and lets you cut any of them off. Retailgrid exposes an MCP endpoint, so an AI assistant can work with your catalog and your grids in your own words, without you exporting anything. Open it from **Settings** in the left sidebar, then **AI connections**. Settings - AI connections, with no clients connected yet AI connections is rolled out per organization. If the tab is missing or the endpoint is not yet available to you, ask us to switch it on for your account and we will send you the endpoint to use. ## What an AI client can do A connected client can both **read** your data and — with your explicit approval on each write — **act on it**. It always works with your own sign-in, so it can only ever do what you could do in the app yourself. **Read** — look at your data and reason about it: | Capability | What it reads | | :- | :- | | **Catalog** | Pages through your organization's products, or reads a single product by its identifier. | | **Grids** | Lists the grids you own and reads selected columns from them. | | **Price proposals & approval queue** | Reads an agent run's proposed prices, the Price Approval queue, and the forecasted impact of a filtered selection. | | **Product roles** | Reads how your catalog is classified into product roles. | | **Job status** | The status of a job you started. It starts no work of its own. | | **Transaction export** | Starts a durable CSV snapshot of your transactions and returns short-lived download links, so you can pull a bounded extract through the assistant. | **Act — with your consent** — each of these asks you to confirm before it changes anything: | Capability | What it can do | | :- | :- | | **Run Price Optimization** | Start (or cancel) a Price Optimization → Dynamic Pricing → Price Approval run on a grid you own. | | **Build and edit grids** | Create or duplicate a grid, add or edit columns, and set cell values. | | **Approve and publish prices** | Approve, reject, or override proposed prices, comment on a row, and publish approved prices — the same actions as the Price Approval screen. | | **Pin product roles** | Override the role on a grid row. | Write actions are never silent. The client asks for your explicit consent before each one, and nothing changes until you confirm. A client still only ever gets the access **you** have — it can't reach your teammates' grids or do anything your account can't. Two limits are worth knowing before you ask an assistant a question and wonder why the answer looks thin: * **Grids are per person, not per organization.** A client connected to your account sees the grids you own. Your teammates' grids stay private to them. * **Live formula columns are not readable.** A client can read canonical and stored columns from a grid; formula columns that compute on the fly are not exposed. If you want an assistant to see a calculation, materialize it rather than leaving it as a live formula. Paged reads reflect live data rather than a frozen snapshot, so a long walk through a large catalog can see values change underneath it. For a stable, point-in-time extract of your transactions, ask the assistant to start a **transaction export** (above), which snapshots the data and hands back download links. For scheduled, repeatable extraction into another system, use the [public API](/api-reference/introduction) instead. ## Connect a client The connection starts in the AI client, not here. In broad terms: 1. Add Retailgrid as an MCP server in your client, using the endpoint we provide for your organization. 2. The client sends you to Retailgrid to sign in, if you are not already signed in. 3. Retailgrid shows you a consent screen naming the client that is asking for access. Read it, then approve or decline. 4. On approval the client is connected, and it appears in the list on this page. Because consent is tied to your own sign-in, a client only ever gets the access **you** have. Connecting a client does not grant it anything your account cannot already reach, and it does not expose your teammates' grids. ## Disconnect a client Find the client in the list and disconnect it. Access stops immediately: the client's existing authorization is revoked rather than left to expire. If you want it back later, connect again from that client. Disconnecting is not destructive and does not affect your data. Disconnect when: * You are no longer using that assistant. * You installed a client on a device you no longer have. * You are not sure what a listed client is. Disconnect first and reconnect deliberately. ## Good practice * **Review the list periodically.** Treat it like any other list of things holding access to your data. * **Connect one client at a time** and check the list afterwards, so every entry is one you recognize. * **Write actions need your consent.** A client can run agents and approve or publish prices, but only after you confirm each action — it can't change anything on its own or in the background. * **Prefer the API for integrations.** This endpoint is built for interactive assistants. Scheduled syncs into an ERP or BI tool belong on the [public API](/api-reference/introduction), which is stable, paginated for extraction, and scoped to an organization key rather than one person. ## Related * [Integrations](/settings/integrations) * [API introduction](/api-reference/introduction) * [Account](/settings/account) * [Organization](/settings/organization) # Connect your AI assistant Source: https://docs.retailgrid.io/settings/connect-ai-assistant Add Retailgrid to Claude, ChatGPT, or another MCP client so you can ask about your grids — and, with your approval, act on them — in plain language. Retailgrid exposes an **MCP** endpoint, so an AI assistant can read your catalog and your grids and — with your explicit approval on each action — also do work for you, all in your own words and without exporting anything. This page walks through connecting a client. For everything the connection can read and do, and how to manage or disconnect clients, see [AI connections](/settings/ai-connections). AI connections is rolled out per organization. If it isn't available to you yet, ask us to switch it on and we'll send you the endpoint to use. ## Connect a client The connection starts in your AI client, not in Retailgrid. In broad terms: 1. **Add Retailgrid as an MCP server** in your client, using the endpoint we provide for your organization. 2. **Sign in.** The client sends you to Retailgrid to sign in if you aren't already. 3. **Approve the consent screen.** Retailgrid shows you which client is asking for access. Read it, then approve or decline. Because consent is tied to your own sign-in, the client only ever gets the access you already have — it can't see your teammates' grids. 4. The client connects and appears in the list on the [AI connections](/settings/ai-connections) page. The exact steps for adding an MCP server differ by client — follow your client's instructions for Claude, ChatGPT, Cursor, or another MCP-capable tool, using the Retailgrid endpoint at the server-URL step. ## Try a few questions first Once connected, confirm it works with a few plain-language questions about your own data: * "Show me all overpriced SKUs." * "What is the CPI min price for Category X?" * "List my KVI products." The assistant answers from the same data and permissions you'd see in the grid. Once you trust the reads, you can also ask it to **do** things — for example: * "Run a profit-maximizing Price Optimization on my Dairy grid." * "Approve every proposed price under a 5% change and publish them." Actions like these ask you to confirm before anything changes (see below). ## Good to know * The connection can **read and act**. It reads your data, and — only after you confirm each action — can run a Price Optimization, edit a grid, or approve and publish prices. Nothing is changed in the background. * **Every write asks first.** A client can't change a price, run an agent, or edit a grid without your explicit consent on that action, and it only ever acts with your own permissions. * A client sees **the grids you own**, not your teammates'. * For scheduled syncs into an ERP or BI tool, use the [public API](/api-reference/introduction) instead — it's built for extraction, paginated, and scoped to an organization key rather than one person. ## Related * [AI connections](/settings/ai-connections) * [API introduction](/api-reference/introduction) * [Integrations](/settings/integrations) # Currency Source: https://docs.retailgrid.io/settings/currency How Retailgrid resolves currency for prices, costs, and metrics. Retailgrid supports multi-currency setups. This page explains how a currency value is chosen for any given price or cost. ## Resolution order For any price, cost, or transaction, Retailgrid evaluates currency in this order: 1. **Product-level currency** - if `currency` is populated on the Products row, that value wins. 2. **Account-level (Organization) default** - if the row's currency is empty, the Organization's default currency is used. The backend resolves currency once per record. There are no hardcoded defaults - if neither layer is set, the value is treated as "currency unknown" and metrics that depend on currency comparison are flagged. ## Setting the Organization default Open **Settings -> Organization** and pick a default currency from the list (ISO 4217 codes). This becomes the fallback for any row missing a `currency` field. ## Multi-currency workflows If your products are sold in multiple currencies: * Provide `currency` on every Products row that differs from the Organization default. * Configure FX rates in the Organization settings so cross-currency comparisons (CPI, Price Gap against a competitor in a different currency) resolve correctly. If you don't set FX rates, Retailgrid won't auto-convert - it will surface the mismatch instead of silently using a wrong rate. ## What the frontend does The frontend only formats and displays. It doesn't merge or select currencies. If you see a currency formatted oddly, the issue is upstream in the data, not in the UI. ## Related * [Organization](/settings/organization) * [Products data spec](/data-requirements/products) * [Sales / Transactions data spec](/data-requirements/sales) # Getting help Source: https://docs.retailgrid.io/settings/getting-help How to reach the Retailgrid team - in-app messenger, what to expect, and where to file feedback. When something doesn't behave the way you expect, or you need a hand setting up data, Retailgrid has a built-in support channel. ## In-app messenger The **chat bubble** in the bottom-right corner of every Retailgrid page opens an Intercom-powered messenger. Click it to: * Ask a question - "where do I set the company name?" * Report something that's not working. * Send feedback on a feature. Messages are read by the Retailgrid team during business hours. Outside business hours your message is queued and answered as soon as someone is online. ## What to include To get a fast, useful answer: * **What you were trying to do** - "I'm setting up Dynamic Pricing for our wine grid." * **What happened** - "When I click Run, the page reloads and nothing happens." * **What you expected** - "I expected to see a Rules Based Price column." * **A link** - the URL of the grid or page you're on. The Retailgrid team can usually open it directly. For data questions (mismatched currencies, weird metric values), include a screenshot of the row in question. ## Status and incidents If the app feels broken across the board, check the chat bubble - if there's an active incident, the team will pin a notice in the messenger. Confirmed widespread issues are also acknowledged via email to Organization admins. ## Feedback and feature requests The same messenger handles "this would be useful" requests. Mark the message as feedback so the team can route it correctly - it doesn't need to be triaged as a support ticket. ## Related * [Account](/settings/account) * [Session and security](/settings/session) # Integrations Source: https://docs.retailgrid.io/settings/integrations Connect external systems to Retailgrid — import from Shopify and issue a key for the public REST API. The **Integrations** tab is where you connect Retailgrid to the systems around it. Open it from the **gear (Settings)** icon in the left sidebar, then **Integrations**. ## Shopify Connect a Shopify store to sync your products and sales history into Retailgrid — no manual CSV uploads. Once linked, Shopify becomes a live data source for your Products and Transactions [datasets](/data-requirements/datasets), and it keeps them current on its own. * **Connect** — enter your **Shop domain** (`your-store.myshopify.com`), then click **Connect Shopify** to install the Retailgrid app for that store and authorize access. The card shows the connection status, **Not connected** until you link a store. * **Check the connection** — once linked, the card shows the **Connected shop**, the **Connected at** timestamp, and the **Granted scopes** Retailgrid holds. **Refresh** re-reads that state, which is the first thing to try if a sync looks wrong. * **Automatic sync** — after the first import, Retailgrid refreshes the data on a schedule and in response to Shopify events (product and order changes), so you don't re-import by hand. * **Manage** — reconnect or disconnect the store from the same card at any time. For the full walkthrough — install, what syncs, refresh cadence, and verifying the data — see [Connect Shopify](/settings/shopify). ## API access Issue a Bearer key for the public Retailgrid REST API. 1. Click **Generate API Key**. 2. Copy the key when it is shown — treat it like a password. 3. Use it as a Bearer token on requests to the public API. See [API authentication](/api-reference/authentication). Only **one active key per account** is supported. Generating a new key replaces the previous one, so any integration using the old key must be updated. An API key grants programmatic access to your Organization's data. Store it in a secrets manager, never commit it to source control, and regenerate it immediately if it may have leaked. ## Related * [Connect Shopify](/settings/shopify) * [Account](/settings/account) * [Datasets](/data-requirements/datasets) * [API authentication](/api-reference/authentication) * [Imports vs bulk](/api-reference/imports-vs-bulk) # Modelling Source: https://docs.retailgrid.io/settings/modelling Account-level demand model parameters that Price Optimization and every scheduled recalculation read - admin only. **Modelling** holds the parameters behind your account's demand model. [Price Optimization](/agents/price-optimization) and every scheduled recalculation read these values. They are not set per run. Open it from **Settings** in the left sidebar, then **Modelling**. The tab is marked **Admin only** and is visible to Admins. Settings - Modelling, the account-level demand model parameters These parameters belong to the account, not to one optimization run. Change them once and every future run and recalculation picks them up. If you are looking for the per-run settings (optimization goal, scope, input columns), those live in the [Price Optimization](/agents/price-optimization) agent dialog. ## Who can change them Only Admins. The permission is enforced on the server, not just by hiding the tab, so a **User** cannot reach these values by any route. See [roles](/settings/organization#roles). Keep the list of Admins short. A change here moves every price recommendation the account produces from that point on. ## The parameter groups Each group is collapsed by default. Expand the one you need. | Group | What it controls | | :- | :- | | **Clustering** | When a brand inside a leaf subcategory gets its own cluster. Lower values keep smaller brands separate but can make group elasticity less stable. | | **Confidence thresholds** | How strict a fit has to be to earn a High or Medium confidence label, expressed as R2, MAPE, and bin-count thresholds. | | **Recommendation corridors** | How far a recommended price may move from the current price, per confidence tier. | | **GLM** | Regularization strength of the elasticity fit. | | **Seasonality** | How category granularity is chosen, and how the seasonal curve and outliers are detected, smoothed, and projected forward. | | **Data filter and binning** | Which rows are kept, and how prices are grouped into bins before fitting. | | **Model freshness** | How and when the model rebuilds itself. | The defaults are deliberately conservative. Change one group at a time and watch what it does to confidence coverage and recommendation size before you move the next one. ## Model freshness This group keeps the model current independently of Price Optimization, so a run never waits on a stale model. * **Refresh trigger** - rebuild on a **schedule**, or **when relevant data updates**. * **Frequency** - **Weekly** or **Daily**, when the trigger is scheduled. * **Time (UTC)** - when the scheduled rebuild starts. The panel shows the timestamp of the last successful model calculation and whether it was automatic or manual, so you can tell at a glance how fresh the model behind your recommendations is. ### Rebuild the model now **Recalculate now** starts a rebuild immediately, using your saved tuning values. If a rebuild is already running, the request joins that one instead of queueing a second. A manual rebuild needs at least one grid to build from. On a freshly provisioned account, create a grid first. Use it after a meaningful change here, or after loading a large batch of new sales history, when you do not want to wait for the next scheduled run. ## What a change affects Saved values apply to the **next** Price Optimization run and to every scheduled recalculation. Prices you have already approved are not affected, and nothing about your live prices changes when you save. So a change here is safe to make and easy to reason about: it changes future recommendations only. ## Common pitfalls * **Loosening confidence thresholds to get more recommendations.** It works, but the extra recommendations are the ones the model is least sure about. Widen the corridors before you lower the bar. * **Changing several groups at once.** You lose the ability to attribute the change in output to any one of them. * **Expecting an immediate effect on the grid.** Saving does not re-price anything. Run Price Optimization again, or trigger a recalculation. ## Related * [Price Optimization](/agents/price-optimization) * [Organization](/settings/organization) * [Sales forecast and price impact](/agents/sales-forecast) * [Runs](/runs/overview) # Organization Source: https://docs.retailgrid.io/settings/organization Configure your Retailgrid workspace - company name, defaults, and team members. The **Organization** is the workspace your team shares. Grids, datasets, and agents are scoped to it. Anyone with an account in your Organization can see and act on the same data. Manage the people in it from **Settings → Users** in the sidebar. ## Company name Set the company name that shows up on generated artifacts (Pricing Strategy decks, exports). It lives on your [Profile](/settings/account) page, under **Company Name**. The [Pricing Strategy](/agents/pricing-strategy) agent reads this name for the deck cover and narrative, so set it before running the agent on a freshly provisioned Organization. Your display currency and timezone are also set per person on the Profile page. See [Account](/settings/account) and [Currency](/settings/currency). ## Team members The Organization is shared across the people you invite. Each teammate has their own [Account](/settings/account) but works inside the same Organization. Settings - Users, with the invite form, the Organization users table, and Invitations The **Organization users** table shows everyone in your Organization: | Column | What it shows | | :- | :- | | **User** | The teammate's email address, with their role beneath it. | | **Registration type** | How they sign in - `Email and password`, or Google. | | **Status** | `Active`, or inactive for an account that has been deactivated. | | **Actions** | Role and access actions for that teammate. Your own row is marked **Current user**, since you can't act on yourself. | ### Invite a teammate 1. Open **Settings → Users**. 2. Enter the teammate's **Email address** in the form at the top of the page. 3. Pick a **Role** (see roles below). It defaults to **User**. 4. Click **Invite user**. The teammate receives an email with a link to set up their account and pick a sign-in method (Google or email). Invitations that haven't been accepted yet live in the **Invitations** section further down the page, not in the users table. Use the **Invitation status** filter there to switch between pending and other invitations. ### Roles Retailgrid has two customer-facing roles inside an Organization: | Role | What they can do | | :- | :- | | **Admin** | Everything a User can do, plus invite and manage teammates, tune the account's demand model in [Modelling](/settings/modelling), and delete datasets. | | **User** | Create and edit grids, run agents, upload and replace datasets, build dashboards. | Most teammates are Users. Keep the Admin role small - typically the founder, the analytics lead, and a backup. The role also changes what Settings shows. A **User** sees **Profile** and **Security** only. An **Admin** additionally sees **Integrations**, **Users**, and **Modelling**. The restriction is enforced on the server, so a User cannot reach an Admin-only page by its URL. ### Change a teammate's role 1. Find the teammate's row in the **Organization users** table. 2. Use the role action in the **Actions** column. 3. Pick the new role and confirm. You can't change your own role to User if you're the only Admin. Promote another teammate to Admin first. ### Remove a teammate 1. Find the teammate's row in the **Organization users** table. 2. Use the remove action in the **Actions** column. 3. Confirm the action. When you remove a teammate: * Their account loses access to your Organization immediately. * Their grids, saved views, and dashboards stay in the Organization and remain visible to remaining teammates. * Their historical runs are preserved with their name attached - the audit trail is intact. If a teammate has only forgotten their password rather than left the team, ask them to use the password-reset flow on the sign-in page rather than removing and re-inviting them. ## Related * [Account](/settings/account) * [Modelling](/settings/modelling) * [Currency](/settings/currency) * [Session and security](/settings/session) # Session and security Source: https://docs.retailgrid.io/settings/session How Retailgrid handles login sessions, inactivity, and auto-logout. Retailgrid manages your session automatically. This page is for understanding what to expect. ## Session lifetime Once you sign in, your session stays active across browser tabs and reloads. There's no per-tab session. ## Auto-logout on inactivity After a long period of inactivity, Retailgrid signs you out automatically and redirects to the login page. The threshold is set at the Organization level. If you were in the middle of editing a grid, your saved state (views, selections, configurations) is preserved - you'll come back to where you were after re-authenticating. ## App availability The Retailgrid app is configured to stay always-on, so you don't see "wake-up" lag after a quiet period. If the app is unreachable, that's an incident, not normal behavior - reach out via the in-app messenger (see [Getting help](/settings/getting-help)). ## Privacy and Terms Retailgrid's Privacy Policy and Terms of Service are linked on the login screen and in the app footer. ## Related * [Getting help](/settings/getting-help) # Connect Shopify Source: https://docs.retailgrid.io/settings/shopify Connect a Shopify store to sync your products and sales history into Retailgrid, with automatic refresh — no manual CSV uploads. Connecting **Shopify** makes your store a live data source in Retailgrid. Your products and sales history sync in automatically, and Retailgrid keeps them current without CSV uploads. Set it up once from **Settings → Integrations**. ## What syncs * **Products** — your Shopify products flow into the [Products dataset](/data-requirements/products). * **Transactions** — your Shopify orders flow into the [Transactions dataset](/data-requirements/sales). Your own data — product names, categories, and anything you edit in the grid — is never overwritten. Shopify is a source, not the owner of your grids. ## Connect a store 1. Open the **gear (Settings)** icon in the left sidebar, then **Integrations**. 2. On the **Shopify** card, start the connection and install the Retailgrid app for your store. 3. Authorize the access Retailgrid requests to read your products and orders. 4. The card switches from **Not connected** to connected, and the first import begins. The first import runs as a job — track it in [Runs](/runs/overview) and confirm the data lands on the [Datasets](/data-requirements/datasets) page. ## Automatic refresh After the first import, you don't re-import by hand: * **Scheduled sync** keeps products and transactions current on a regular cadence. * **Event-driven sync** picks up changes as they happen in Shopify — product edits and new orders — so the data in Retailgrid tracks your store. Synced data follows the same incremental behavior as the rest of Retailgrid: only what changed is updated, not the whole store every time. ## Manage the connection From the **Shopify** card on the Integrations tab you can: * **Reconnect** — re-authorize if access expired or you moved to a different store. * **Disconnect** — stop syncing. Data already imported stays in your datasets; it simply stops refreshing from Shopify. ## Verify the data Open the [Datasets](/data-requirements/datasets) page. Your Products and Transactions datasets show **Origin: Shopify**, with the row count and last-updated time from the most recent sync. ## Related * [Integrations](/settings/integrations) * [Datasets](/data-requirements/datasets) * [Products data requirements](/data-requirements/products) * [Sales / Transactions data requirements](/data-requirements/sales) * [Runs](/runs/overview) # Assign competitors to stores, zones, and channels Source: https://docs.retailgrid.io/stores/assign-competitors Scope each competitor to the stores, zones, or channels where it actually competes, so pricing rules and gap metrics use the right competitor set per location. **Competitor assignment** controls which competitors apply where. By default a competitor you load in the [Competitor prices](/data-requirements/competitors) dataset is available everywhere; assignment narrows it to the specific **stores**, **pricing zones**, or **sales channels** where that competitor actually competes. Use it when different markets or channels face different rivals — so store-level pricing rules and competitor-gap metrics compare against the right set. ## When to use it * Different competitors per country or market. * A competitor that only matters online, or only in B2B. * Pricing zones that share a competitive set distinct from the rest of your stores. * Use the global default instead when one competitor set applies to every store — no assignment needed. ## Before you start * Define your stores, zones, and channels first on the [Stores](/stores/manage-stores) page. * Load competitor observations via the [Competitor prices](/data-requirements/competitors) dataset or the API. Assignment scopes competitors you already have; it doesn't create them. ## Assign a competitor Competitor assignment lives on the **Competitors** tab of the [Stores](/stores/manage-stores) page: 1. Open **Stores** and switch to the **Competitors** tab. 2. Find the competitor you want to scope (use the competitor search). 3. Assign it to the stores, pricing zones, or channels where it competes. 4. Save. The assignment takes effect on the next metric refresh and the next agent run. Assign at the **zone** or **channel** level when several stores share the same rivals — every store in the zone or channel inherits the set, so you don't repeat the assignment per store. ## How assignment is used * **Pricing rules** — competitor-based rules in [Dynamic Pricing](/agents/dynamic-pricing) (Price Match, parity, undercut) evaluate against the competitors assigned to each product-store combination, so a rule scoped to one channel matches that channel's competitors. * **Metrics** — per-store competitive metrics (Price Index, CPI, Price Gap, Cheapest Flag) compute against the assigned set, so a store's gap reflects the rivals it actually faces. See the [Metrics glossary](/reference/metrics). * **Grid** — in [SKU-Store view](/grids/sku-store-view), competitor columns reflect each store's assigned competitors. ## Default behavior A competitor with no explicit assignment stays **global** — it applies to every store, zone, and channel. Assigning a competitor anywhere scopes it to the assigned set only. Review your global competitors once you start assigning, so a rival meant for one market doesn't keep skewing gap metrics everywhere. ## Common pitfalls * **Double-counting after first assignment** — once you scope some competitors, any you leave global still apply everywhere. Decide per competitor whether it's global or scoped. * **Empty competitor set for a store** — if a store has no assigned competitors and none are global, its competitive metrics come back blank. That's signal, not a bug. * **Assigned but no data** — assignment scopes *which* competitors apply; it doesn't backfill prices. A competitor with no observations for a SKU still shows no gap for that SKU. ## Related * [Manage stores](/stores/manage-stores) * [Competitor prices](/data-requirements/competitors) * [Dynamic Pricing](/agents/dynamic-pricing) * [Product and SKU-Store grid views](/grids/sku-store-view) * [Metrics glossary](/reference/metrics) # Manage stores Source: https://docs.retailgrid.io/stores/manage-stores Define and organize the stores, markets, and channels behind store-level pricing - country, currency, sales channel, and pricing zones. **Stores management** is where you define the stores your store-level pricing runs against. Open **Stores** from the main navigation - four tabs cover the full setup: **Stores**, **Zones**, **Channels**, and **Competitors**. A store in Retailgrid is a **location dimension**, not just a physical shop. A store can represent: * A physical store * A country or market * A warehouse * An online shop or marketplace (Amazon, Zalando, etc.) * A B2B vs B2C channel If prices, costs, tax rates, or stock differ across any of these, model them as separate stores and load a [Product store data](/data-requirements/product-store-data) dataset. Stores management - the Stores tab with name, external ID, country, currency, zone, and channel per store ## Create a store 1. Open **Stores** from the main navigation. 2. Click **Add store**. 3. Fill in the store's attributes: * **Name** - must be unique. Use consistent, readable names. * **External ID** - the identifier your data uses for this store (e.g. `NE-TOR`). `storeId` values in your data loads match against this. * **Country** - drives tax and market context. * **Currency** - the currency this store prices in. Must be a valid currency code. * **Zone** - the pricing zone this store belongs to (see below). * **Channel** - the sales channel (e.g. Online, Offline, B2B). 4. Save. The store appears in the list with its key attributes. Retailgrid validates name and currency on save, and rejects a duplicate name or an invalid currency with a specific error. ## Edit a store Use **Edit** on any row to change a store's attributes. Changes persist immediately and are reflected everywhere the store is used - the [SKU-Store grid view](/grids/sku-store-view), rule targeting in [Dynamic Pricing](/agents/dynamic-pricing), and store-level [metrics](/reference/metrics). ## The default store Every organization always has at least one store, and exactly one of them is the **designated default**. The default store is protected — it can't be deleted or left unset, so there is always a store for data loads and store-level pricing to fall back on. Apart from that protection it behaves like any other store. You can **transfer** the default designation to another store from the Stores tab at any time; the previously-default store then becomes a normal store. New organizations start with a default store already in place, so there is nothing to set up before your first import. ## Pricing zones A **pricing zone** is a group of stores that share pricing logic - for example, all eurozone stores, or all urban locations. * Define zones on the **Zones** tab and assign stores to them. * In [Dynamic Pricing](/agents/dynamic-pricing), a rule can target a pricing zone, so one margin floor or parity rule covers every store in the zone without repeating the rule per store. Zones keep rule sets small when you operate many stores with a handful of distinct pricing policies. Stores management - the Zones tab grouping stores into pricing zones ## Channels The **Channels** tab groups your stores into sales channels - Online, Offline, B2B, marketplace, and so on. Like zones, channels are a targeting dimension: filter the grid by channel, or scope a Dynamic Pricing rule to just your online stores. Stores management - the Channels tab grouping stores into sales channels ## Competitors The **Competitors** tab is where you scope which competitors apply to which stores, zones, and channels. Search your loaded competitors and assign each one to the locations where it actually competes, so pricing rules and competitor-gap metrics compare against the right set per location. See [Assign competitors to stores, zones, and channels](/stores/assign-competitors) for the full workflow. Stores management - the Competitors tab for scoping competitors to stores, zones, and channels ## Find stores The store list is searchable and filterable by **country**, **zone**, and **channel** - useful once the list grows past a screenful. ## Import and export stores via CSV For accounts with many stores, set them up in bulk instead of one at a time: * **Export** - download your current stores as a CSV from the **Stores** tab. Useful as a template, or as a backup before a bulk change. * **Import** - upload a CSV to create or update stores in bulk, including their zone and channel assignments. Rows match existing stores by **External ID**; a row with a new External ID creates a new store. Bulk import is the fastest way to stand up a multi-store account and to keep zones and channels in sync when your store list changes. ## How store configuration is used Everything defined here feeds the rest of the app: * **Data loading** - `storeId` values in your [Product store data](/data-requirements/product-store-data) uploads (CSV or API) must match a store defined here. * **Grid** - the [SKU-Store view](/grids/sku-store-view) shows one row per product-store combination, with the store's country, currency, channel, and pricing zone as columns. * **Pricing rules** - [Dynamic Pricing](/agents/dynamic-pricing) emits one recommended price per product-store combination and can scope rules by store attributes. * **Competitors** - assign competitors to stores, zones, or channels so each location's pricing rules and gap metrics use the right competitor set. See [Assign competitors to stores, zones, and channels](/stores/assign-competitors). * **Metrics** - margin, price index, and competitor-gap metrics compute per store, using each store's own price, cost, and competitor set. ## Related * [Product store data](/data-requirements/product-store-data) * [Product and SKU-Store grid views](/grids/sku-store-view) * [Assign competitors to stores, zones, and channels](/stores/assign-competitors) * [Dynamic Pricing](/agents/dynamic-pricing) * [Metrics glossary](/reference/metrics)