Kumi Commerce — Kumi Bundle Builder for Shopify
Effective date: 2026-10-12
Last updated: 2026-11-12
Kumi Commerce ("we", "us") is a sole proprietorship based in Tokyo, Japan. We build and operate apps for the Shopify App Store. This policy covers the app Kumi Bundle Builder and the website kumicommerce.com.
| Operator | Kumi Commerce (sole proprietorship) |
| Location | Tokyo, Japan |
| Website | https://kumicommerce.com |
| Contact for privacy and data protection | [email protected] |
There is one person with administrative access to our systems: the owner of Kumi Commerce.
We use these words throughout this policy and in our Terms of Service.
| Term | What we mean by it |
|---|---|
| App | The Shopify app "Kumi Bundle Builder", including its admin interface, its theme app blocks, its Cart Transform function, its discount function for quantity breaks and the backend that serves them. |
| Merchant | The business that installs the App on its Shopify store. If you are reading this because you run a store, you are the Merchant. |
| Store | The Shopify store on which the App is installed, identified by its *.myshopify.com domain. |
| Customer | A person who shops on a Merchant's Store. Customers do not have accounts with us and do not interact with us directly. |
| Personal data | Any information relating to an identified or identifiable natural person. Shopify treats order information — including order totals and order identifiers — as customer personal data, and so do we, even when it carries no name or email address. |
| Controller | The party that decides why and how personal data is processed. |
| Processor | A party that processes personal data on a Controller's behalf and on its instructions. Under the CCPA the comparable term is "service provider". |
| Sub-processor | A third party we engage to process personal data for us as part of running the App. |
| Protected customer data | Shopify's term for customer and order data that an app may only access after Shopify approves the app's request. |
| Processing | Anything done with personal data: receiving, reading, storing, using, transmitting, deleting. |
For personal data that reaches us because the App is installed on a Store — orders, store configuration, bundle definitions — the Merchant is the Controller and we are the Processor. We process that data only to provide the App to that Merchant and to bill the Merchant correctly. We do not decide to use it for anything else, we do not sell it, and we do not use it to build profiles, advertise, or train machine-learning models.
The contractual terms that govern this relationship are in Part B (Data Processing Agreement) of our Terms of Service. Installing the App accepts them.
For a small amount of data we are the Controller ourselves: the record that a Store installed or uninstalled the App, which plan it is on, and our billing records. We need these to run our business, to invoice correctly, and to know whether the App is working.
The Merchant remains responsible for its own privacy notice to its own Customers, for having a lawful basis to process its Customers' data, and for the instructions it gives us.
This is the complete list. Nothing is processed that is not described here.
| Data | Why | Source |
|---|---|---|
Store myshopify.com domain | Identifies the Store. It is the key every other record hangs on. | Shopify, at install |
| Shopify API access token, token scope and expiry, refresh token | Lets the App call the Shopify Admin API for that Store. | Shopify OAuth |
| Install timestamp, uninstall timestamp | To know whether the App is active, and to measure how many Merchants keep it. | Our own records |
| Plan key, trial end date, onboarding completion timestamp | To apply the right plan and show the right onboarding. | Our own records |
| Billing events: event type, plan, amount, currency, timestamp | To reconcile charges and to measure revenue and cancellations. | Shopify's Partner API (the subscription Shopify holds for the Store), and our own records |
| Usage counters: a metric name, a month, a count | To enforce monthly limits on features that cost us money per use. | Our own records |
Review request records: which milestone the Store reached (the first order through the App, or US$100 in sales through the App), when we first saw it, how many times we asked Shopify to show its review prompt, when it was last shown, and the result code Shopify returned (for example success or already-reviewed) | To ask for a review only at those milestones, never more often than Shopify allows, and never again after the Merchant has reviewed the App or turned review requests off. | Our own records, and Shopify's review prompt in the admin |
| App preferences: whether the Merchant turned off review requests, and when the Merchant read the notice that quantity break sales count toward the plan | To respect that choice, and to show the notice once before the first quantity break is created. | The Merchant's choices in the App |
How we learn which plan a Store is on. Shopify does not send us a webhook when a subscription starts, changes or is cancelled. Instead, when a Merchant opens the App's admin pages we ask Shopify's Partner API which subscription it currently holds for that Store: at most once every five minutes per Store while a paid plan is confirmed, at most once a minute otherwise, and again right after the Merchant chooses a plan or when the previous request failed. The request sends Shopify its own identifier for the Store and the identifier of our app, and nothing else — no order data and no Customer data — and the answer is the plan, its price, its billing period and its trial end date. We write a billing event only when that answer differs from what we had recorded. The remaining billing event is written by us: the cancellation we record when the App is uninstalled. Free trials are decided by Shopify, not by us; we only copy the trial end date out of Shopify's answer.
Our session table also contains columns for a staff user's ID, first name, last name and email address, because the Shopify session-storage library we use defines them. The App uses offline access tokens only, so these columns are never populated. Our billing-event table likewise has a column for a Shopify subscription identifier that nothing ever writes to. We mention both because they exist in the schema, not because they hold data.
| Data | Why |
|---|---|
| Product IDs, variant IDs, product titles, product image URLs | To let the Merchant pick which products belong in a bundle, and to show them in the admin and in the storefront block. |
| Bundle configuration: bundle key, title, parent product and variant, required quantity, allowed products, discount type and value, enabled flag, display order | This is the Merchant's own configuration. It is what the App exists to store. |
| Whether a component product has become unavailable, and when we last checked | So a bundle does not offer a product a Customer cannot actually buy. |
| Quantity break settings: the name (which is also the discount name Customers see), the earlier names Shopify accepted for its discount, which of those names another automatic discount also uses (and how many recorded orders and how much we removed from the current month for each, and whether the Merchant dismissed that notice), the products or collections it applies to, how quantities are counted, the tiers, the combination settings, whether it is on, the ID of the Shopify discount that runs it, the currency of amount tiers, when it last reached Shopify and the type of the last error, and whether its discount was deleted in Shopify | This is the Merchant's own configuration for quantity breaks. The earlier names let the App recognise orders placed under a name that has since changed; names another discount also uses are no longer counted. |
This is business data about products. It is not personal data, but it is the Merchant's data and we treat it with the same care.
Optional permission for quantity breaks. Bundles do not need it. If a Merchant chooses to use quantity breaks, the App asks Shopify for one optional permission, write_discounts (which includes reading discounts), and only when the Merchant selects Allow in the App. The App uses it to create, update, turn on, turn off and delete its own automatic discounts and their settings, to read their status, and to read the titles and types of the Store's other automatic discounts so that it can warn about a discount with the same name and stop counting that name. If the Merchant does not allow it, or removes it later, bundles keep working.
After the Merchant allows it, the App subscribes the Store to Shopify's discounts/create, discounts/update and discounts/delete webhooks. It uses them for its own quantity break discounts: to take a discount turned off or deleted in Shopify off the product page tier table, to pause again an amount tier set in an earlier store currency, and to remember that a discount was deleted so that a reinstall does not recreate it. For any other discount it only compares the title with the names of the Merchant's quantity breaks, so that it can stop counting a name another automatic discount uses. It reads the discount's ID and title. If the permission is removed, Shopify stops sending these webhooks until the Merchant allows it again.
Store currency changes. The App subscribes to Shopify's shop/update webhook. From it the App reads only the Store's currency, so that it can turn off quantity breaks whose amounts were set in a different currency and set their discount to apply nothing until the currency matches again (Shopify would otherwise apply those amounts in the new currency). The rest of that payload, which includes the Store owner's contact details, is neither read nor stored.
The App subscribes to Shopify's orders/create, orders/updated, orders/cancelled and refunds/create webhooks using the read_orders scope. We use them for exactly one purpose: to measure how much revenue the Merchant earned through bundles and quantity breaks built with the App, and to reduce that figure when an order is refunded, edited or cancelled, because our pricing tiers are based on it.
We also show the Merchant these same totals in the analytics section on the App's home page; this is the same measurement, not a separate use of the data.
Shopify delivers the full order payload to our endpoint. From that payload we read only:
| Read from each order | Why |
|---|---|
Order identifier (admin_graphql_api_id, or the numeric id) | To avoid counting the same order twice. Shopify can deliver the same webhook more than once: it retries a failed delivery up to 8 times over about 4 hours. |
Order timestamp (processed_at, falling back to created_at) | To decide which monthly period the order belongs to. |
| Order currency code | To get the decimal places right and to convert to USD later. |
For each line item: line item ID, variant ID, quantity and current_quantity, unit price (and price_set.shop_money), discount allocations (and total_discount / total_discount_set) | To work out the discounted value of the bundle lines and the quantity break lines only, and not the rest of the cart. current_quantity is the quantity after edits and refunds, and it is the one that decides the figure we record. The line item ID lets us match a later refund to the same line without reading the order again. |
The refunds already present in the order payload: for each, the refunded line item IDs and the refunded quantities (refunds[].refund_line_items[]) | So that an order edit does not also subtract a refund that the refunds/create webhook has already subtracted. We read counts here, not money. |
| For a refund webhook: the refunded line item IDs and their refunded subtotals | To subtract a refund from the revenue we previously recorded. |
| The order's discount applications — for each, its type and its title — that the line items' discount allocations refer to | To recognise a line discounted by one of the Merchant's quantity breaks. Shopify records an automatic discount on an order by its title and gives no discount ID, so we compare the title with the quantity breaks' current and earlier names. |
For an order with a line discounted by a quantity break, we also ask Shopify once whether the App currently has the optional permission to manage discounts (this reads the App's own permissions, not the order). Quantity break lines are counted only if it does, and only if it also did when the order was placed. For that we keep, for the Store, the times the permission was removed (including by uninstalling) and granted again. This is about the App's permission, not about a person.
In addition we make an Admin GraphQL request that reads only order.lineItems[].id and order.lineItems[].lineItemGroup.variantId. Shopify records a bundle's components as separate order lines, and this is how we learn which lines belong to a bundle the App created — so it has to be asked before we can tell whether an order contains a bundle at all. In practice that means:
orders/updated traffic — a shipment, a tag change — makes no request.We do not store anything from this request except the bundle line item IDs and bundle variant IDs listed below. The request returns no customer fields.
We then store only:
| Stored per attributed order | Note |
|---|---|
| Order identifier | |
| Amount, in the Store's currency, in minor units | Only the portion attributed to the App, not the order total |
| Currency code | |
| USD-converted amount | May be empty until a daily job converts it |
| Attribution reason | An internal label, e.g. bundle or quantity_break |
| Order timestamp, and the time we recorded it | |
| The Shopify line item IDs, and for each the bundle variant ID or the quantity break the revenue was attributed to (with a short fingerprint of the quantity break name it matched) and the amount that line added | So that a later refund or order edit can be matched to the same lines without reading the order again, and so that lines recorded under a quantity break name that another discount turns out to use can be removed. These are Shopify object identifiers, our own record IDs and amounts, not customer data. |
| A record of any later reduction we made because a quantity break name was shared with another discount: the name, the quantity breaks, when that discount started, the amount before and after, and the line item IDs removed | So that the reduction can be checked later. |
We do not read, request, store or transmit a Customer's name, email address, phone number, shipping address, billing address, IP address, or payment details. We have not requested access to any of Shopify's protected customer fields. The order payload Shopify sends us does contain some of those fields; we parse the fields listed above, and the rest of the payload is discarded with the request and never written to storage.
We are nevertheless explicit that this is personal data: an order identifier and an order amount can be linked back to a person by the Merchant and by Shopify. We do not claim to hold "no personal data".
An order is only recorded if at least one of its line items belongs to a bundle the Merchant configured in the App, or was discounted by one of the Merchant's quantity breaks. Orders unrelated to the App are read, found not to match, and not stored.
| Data | Why |
|---|---|
Webhook receipts: Shopify's webhook ID, the topic, when we received it, when we finished it, and — if it failed — the type of the error, such as Error or a database error code | Shopify retries webhooks. Without this we would count the same order several times, or silently lose one. We record the type of the error and never the error message, because a message can quote an order identifier or the Store domain, and this record outlives the Store (section 11). |
| Server logs containing the Store domain, the webhook topic, order identifiers and attributed amounts | To diagnose failures. These are the same values described above; no Customer identifiers are logged. |
When Shopify sends us a customers/data_request, customers/redact or shop/redact webhook, we keep a record so that we can show what we received and what we did.
Shopify's payloads for these requests contain the Customer's email address and phone number. We strip them before writing anything down. What we keep is:
The email address and phone number are not stored, not logged and not forwarded.
When a Merchant opens the App's Status page or dashboard, the App reads, through the Admin API and without changing anything in Shopify: the number of orders the Store received in the last 7 days (a count, not the orders themselves); the Store's bundle features, currency and time zone; the published theme's product templates and section groups, to see where the App's blocks are placed; the App's own functions and its access scopes; and, if the Merchant allowed the App to manage discounts, the App's own discounts, the titles and types of the Store's other active or scheduled automatic discounts, and whether the products in quantity breaks are active. When the dashboard announces the first order through the App, it reads that order's name (for example #1001) to show it to the Merchant. None of these values is stored, except one: when another automatic discount uses one of a quantity break's names, the App records that name as no longer counted and removes the orders recorded under it in the current calendar month (section 4.2; Terms section 6.2).
The App's storefront block is a Shopify theme app block. The bundle information it shows (which products may be selected, how many are required, what the discount is) is published by us into a Shopify app-data metafield, and Shopify renders it into the page. The block makes no request to our servers, so visiting a Merchant's product page sends us nothing: no IP address, no page view, no identifier.
The quantity breaks block shows the tiers from a copy of the quantity break settings that we publish into the same kind of app-data metafield. It also makes no request to our servers.
Bundle discounts are calculated by a Shopify Cart Transform function, and quantity break discounts by a Shopify discount function. Both run inside Shopify's infrastructure as pure functions. They have no network access and cannot send anything to us.
Where we act as Processor, the Merchant is responsible for having a legal basis; we process on the Merchant's instructions, which are given through the App's settings and through our Terms of Service.
Where we act as Controller (section 4.1), our legal bases under the GDPR are:
| Third party | What it does | What it receives |
|---|---|---|
| Shopify Inc. (Canada / USA) | The platform the App runs on, and the source of all order and store data. | Shopify is overwhelmingly the source rather than a recipient, and it is not a sub-processor of ours: the Merchant contracts with Shopify directly, and that relationship is governed by Shopify's own terms and privacy policy. We send five kinds of request back to Shopify. (1) The Admin GraphQL request described in section 4.3, which asks Shopify about an order Shopify already holds. (2) A Partner API request asking which subscription Shopify holds for the Store (section 4.1), which sends Shopify its own identifiers for the Store and for our app. (3) Admin GraphQL writes that create and maintain the hidden parent product for each bundle and register the cart transform: these send the bundle name, the list of products you chose, the quantity and the discount you entered in the App. (4) If the Merchant uses quantity breaks, Admin GraphQL writes that create, update, turn on, turn off and delete the App's automatic discounts and publish the tier table: these send the quantity break name, the products or collections, the tiers and the combination settings entered in the App. (5) The read-only checks in section 4.6. Requests (1), (2) and (5) send Shopify nothing it did not already hold; requests (3) and (4) send Shopify the settings entered in the App, and no Customer data. |
Frankfurter (api.frankfurter.app), publishing European Central Bank reference rates | Supplies the exchange rate used to convert an order amount to USD for tier calculation. | A three-letter currency code and a date. Nothing else. No Store domain, no order identifier, no amount, no personal data of any kind. We call it from a daily background job, never from a webhook. |
| Render Services, Inc. (United States; our backend and database run in Render's Frankfurt, Germany region) | Runs the App's backend and stores everything in section 4. | All data described in section 4. |
Our backend and our managed PostgreSQL database both run on Render (Render Services, Inc.) in Render's Frankfurt, Germany region. Render encrypts database storage at rest with AES-256 and encrypts connections to it in transit. We use no other provider to run the App's backend or to store Merchant or Customer data.
Our codebase contains two optional integrations that are switched off and not in use:
Neither is reachable in the App: no part of the App calls them, and the credentials they require are not configured. No Merchant or Customer data has been sent to Anthropic or to Sentry. If we turn either of them on, we will update this policy first — stating exactly what is sent, with the sub-processor named — and not after.
We operate from Japan. The data described in section 4 is processed by us in Japan and by our hosting provider, Render, in Frankfurt, Germany.
The contractual commitments covering these transfers are in Part B of our Terms of Service.
What we actually do:
https://.We do not claim that our security is perfect or that a breach is impossible. No single measure described above is a guarantee. If a breach affects a Merchant's data, we will notify that Merchant without undue delay and, in any event, within 48 hours of becoming aware of it, with what we know at the time.
| Event | What happens |
|---|---|
Merchant uninstalls the App (app/uninstalled) | We immediately delete the Store's sessions, including the Shopify access token. We keep the Store's bundle and quantity break configuration so that a reinstall does not lose the Merchant's work, and we record the uninstall. Shopify itself deletes the automatic discounts the App created for quantity breaks. |
48 hours after uninstall (shop/redact, sent by Shopify) | We delete everything for that Store: bundle definitions and their components, quantity break settings, attributed orders, monthly revenue totals, billing events, usage counters, review request records, app preferences, privacy-request records, the Store record itself, and any remaining sessions. |
Customer deletion request (customers/redact) | We hold no Customer personal data beyond what is described in section 4.3, so there is nothing about a named Customer to erase. We record that the request arrived and was handled. We cannot delete individual order totals on request, because they are the basis of the Merchant's own billing; they are deleted with everything else at shop/redact. |
Customer data request (customers/data_request) | We respond to the Merchant. Because we hold no Customer name, email, phone or address, there is nothing to disclose about an individual beyond the fact that the App holds no data about them. |
Two things survive a shop/redact, and we would rather say so than be imprecise:
A Merchant can ask us to delete its data sooner by emailing [email protected].
If you are a Merchant: you may ask us for a copy of the data we hold about your Store, ask us to correct it, ask us to delete it, or object to our processing of it. Email [email protected]. We will answer within 30 days. We may ask you to prove you control the Store.
If you are a Customer of a store that uses this App: the Merchant is the Controller of your data, so please contact the store you bought from. Shopify's and the Merchant's processes handle your request; the Merchant can instruct us as part of it, and Shopify's customers/redact and customers/data_request webhooks reach us automatically. We will act on them as described in section 11.
Under the GDPR you have the rights of access, rectification, erasure, restriction, portability and objection, and the right to complain to your supervisory authority. Under the CCPA/CPRA, California residents have the rights to know, delete, correct and opt out of sale or sharing; we do not sell or share personal data, so there is nothing to opt out of, and we do not discriminate against anyone who exercises a right.
We have not appointed an EU or UK representative under Article 27 GDPR. We are a sole proprietorship whose processing of EEA personal data is occasional and limited to the categories in section 4; if that changes, we will appoint one and name them here.
The App is a business tool for store operators. We do not knowingly process data about children, and we do not collect data directly from any individual.
If we change what we process, why we process it, or who processes it for us, we will update this page and change the "Last updated" date. For changes that materially affect Merchants — in particular, adding a sub-processor — we will notify installed Merchants by email at the address Shopify provides for the Store, at least 30 days before the change takes effect, so that a Merchant who objects can uninstall before it does.
Questions about this policy, or any request about your data:
Kumi Commerce, Tokyo, Japan — https://kumicommerce.com