# Store reports and costs

> The API operations about store reports and costs, each with its input, its output and an example.

The operations about store reports and costs. Each one is a `POST` with a JSON object as its body: [Making a request](/docs/api#start) has the rules they share.

## Which ads earned sales

`POST /api/v1/store.attribution` · Read

Spend and the orders it earned, by channel, campaign, ad set or ad (level), for a period: spend, ROAS, the platform's own ROAS, orders, revenue, CPA, NC-ROAS, new customer orders, impressions, clicks and more per row, with the comparison period. Get the siteId from sites.list first. A visit is matched to an ad by the utm_id of its ad click first, then by campaign, ad set and ad names. Rows come sorted by spend, then revenue. Amounts are whole minor units (cents) of one currency, named in labels.currency; spend in another currency is listed in otherCurrencySpend, costs from the cost settings in another currency in otherCurrencyCosts, and neither is added in. Every number names what it covers in its scope: orders_paid (Orders paid in the period, before refunds), credited (Orders credited by the chosen model), credited_estimated (Credited by the chosen model; view credit is an estimate from daily platform totals), platform_reported (As the ad platform reports it), spend_synced (Spend as synced or uploaded, in the account currency), cost_settings (From the cost settings and cost lines of this site), refunds_dated (Refunds dated in the period). Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |
| `level` | "channel" \| "campaign" \| "adset" \| "ad" | optional | The level of the rows: channel (the default), campaign, adset or ad. |
| `search` | string, 1 to 100 characters | optional | Keep the rows whose name contains this text, ignoring case. 1 to 100 characters. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `level` | "channel" \| "campaign" \| "adset" \| "ad" | always | The level the rows are at: channel, campaign, adset or ad. |
| `rows` | array of object | always | One row per channel, campaign, ad set or ad, sorted by spend and then by revenue, highest first. key names the row, parent the key of the row above it, ids the platform's own ids. hasSpend false means traffic no spend row matched (an email flow), hasTraffic false means spend no visit matched. metrics are the numbers of the period, compare those of the comparison period or null. spendMinor, impressions, clicks, ctr (clicks over impressions), cpcMinor and cpmMinor come from the ad platform, null where there is no spend. orders and revenueMinor are credited by the model, newOrders and newRevenueMinor the part from first orders of new customers; under linear a row holds its share of each order, so orders can be a fraction. roas is revenueMinor over spendMinor, ncRoas newRevenueMinor over spendMinor, cpaMinor spend per order and costPerNewCustomerMinor spend per new customer order; platformConversions, platformRevenueMinor and platformRoas are what the platform itself reports, viewConversions its purchases after a view. estimated is true when some credit came from a view model, which spreads daily platform view totals over orders. |
| `totals` | object | always | Credit to the ad platforms and every spend row: roas is the blended ROAS, cpaMinor, ncRoas and costPerNewCustomerMinor match the Store tiles; mer, storeRevenueMinor and storeOrders cover every order in the period, credited or not. |
| `unmatched` | object | always | Spend at this level that matched no visit: its amount in minor units and the number of rows it came from. |
| `otherCurrencySpend` | array of object | always | Spend in a currency other than the report's: listed here by currency, in minor units of that currency, and never added into any number of this answer. |
| `availableColumns` | array of string | always | The column keys that have a value in at least one row at this level, such as spend, roas or platform_roas. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.attribution \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store attribution --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_attribution
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store attribution` for one flag per field.

## Store costs

`POST /api/v1/store.costs` · Read

The costs the Profit report is computed with: the cost rules (shipping per order, payment fees, a default product cost), the unit cost of each product and the other cost lines. Get the siteId from sites.list first. Every cost is in one currency, named in currency. It still answers while the workspace's dashboards are locked.

Needs a token with the Read permission. Keeps answering while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `settings` | object | always | The cost rules: currency (the currency of every cost of the site, null until the first cost is saved), shippingPerOrderMinor (what shipping one order costs the store), paymentFeeBps and paymentFeeFixedMinor (the payment fee of an order: basis points of its amount, 290 is 2.9%, plus a fixed amount) and defaultCogsBps (the product cost of a line whose product has none, in basis points of its price). |
| `products` | array of object | always | Each product cost: its id, the store it sells through (provider), the product id there and its name, the unit cost in minor units of currency, where it came from (form, csv or seed) and when it last changed (milliseconds since 1970 UTC). |
| `lines` | array of object | always | The other costs of the site: id, label, kind, amount, currency, startsOn, endsOn and updatedAt, as store.createCostLine answers them. |
| `currency` | string | always | The currency of every cost: the one pinned by the first cost write, or the currency of the site's orders before that. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.costs \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store costs --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_costs
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store costs` for one flag per field.

## Add a cost line

`POST /api/v1/store.createCostLine` · Write

Adds a cost the store carries besides product cost, shipping, fees and ad spend: rent and software per month, a photoshoot once, or returns handling as a share of revenue. Get the siteId from sites.list first. A site holds at most 100 cost lines. Each call adds a line, so sending it twice adds two. Profit changes for every period the line covers.

Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `label` | string, 1 to 80 characters | required | What the cost is, 1 to 80 characters: Rent, Shopify plan, Returns handling. |
| `kind` | "monthly" \| "one_off" \| "percent_of_revenue" | required | monthly: an amount per month, spread over the days it covers. one_off: an amount once, on startsOn. percent_of_revenue: a share of the revenue of the days it covers. |
| `amount` | integer, 0 to 1000000000 | required | Minor units of the site's cost currency for monthly and one_off; basis points for percent_of_revenue (150 is 1.5%, at most 10000). |
| `startsOn` | string | required | The first day the cost counts, YYYY-MM-DD; the day of a one_off cost. |
| `endsOn` | string \| null | required | The last day it counts, inclusive, YYYY-MM-DD, or null for no end. Must be null for one_off and may not be before startsOn. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The cost line id. |
| `label` | string | always | What the cost is, as the dashboard shows it. |
| `kind` | "monthly" \| "one_off" \| "percent_of_revenue" | always | monthly: a cost per month, spread over its days. one_off: once, on startsOn. percent_of_revenue: a share of the revenue of the days it covers. |
| `amount` | number | always | Minor units for monthly and one_off; basis points (1 is 0.01%) for percent_of_revenue. |
| `currency` | string | always | The currency of the amount: the site's cost currency. |
| `startsOn` | string | always | The first day it counts, YYYY-MM-DD; the day of a one_off cost. |
| `endsOn` | string \| null | always | The last day it counts, inclusive, or null when it has no end. Always null for one_off. |
| `updatedAt` | number | always | When it last changed, milliseconds since 1970 UTC. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.createCostLine \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","label":"example","kind":"monthly","amount":0,"startsOn":"2026-10-01","endsOn":"2026-10-01"}'
```

**CLI**

```bash
clearsaga store createCostLine --input '{"siteId":"site_abc123def456","label":"example","kind":"monthly","amount":0,"startsOn":"2026-10-01","endsOn":"2026-10-01"}'
```

**MCP**

```text
Tool: store_create_cost_line
Arguments: {"siteId":"site_abc123def456","label":"example","kind":"monthly","amount":0,"startsOn":"2026-10-01","endsOn":"2026-10-01"}
```

Run `clearsaga help store createCostLine` for one flag per field.

## Creative report

`POST /api/v1/store.creatives` · Read

Every ad the platforms report for a site, with its thumbnail, format, status, ad set and campaign, and its spend, credited orders and revenue, ROAS, CTR and the platform's own numbers for a period. Get the siteId from sites.list first. Spend uploaded as a CSV has no ads, so it is not here. Amounts are whole minor units (cents) of one currency, named in labels.currency; spend in another currency is listed in otherCurrencySpend, costs from the cost settings in another currency in otherCurrencyCosts, and neither is added in. Every number names what it covers in its scope: orders_paid (Orders paid in the period, before refunds), credited (Orders credited by the chosen model), credited_estimated (Credited by the chosen model; view credit is an estimate from daily platform totals), platform_reported (As the ad platform reports it), spend_synced (Spend as synced or uploaded, in the account currency), cost_settings (From the cost settings and cost lines of this site), refunds_dated (Refunds dated in the period). Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |
| `platform` | "meta" \| "google_ads" \| "tiktok" \| "pinterest" \| "amazon_ads" | optional | Only the ads of this platform: meta, google_ads, tiktok, pinterest or amazon_ads. Left out, every platform. |
| `search` | string, 1 to 100 characters | optional | Keep the rows whose name contains this text, ignoring case. 1 to 100 characters. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `rows` | array of object | always | One row per ad the platforms report: its ids, name, headline, format, status, thumbnail (an https address, or an inline SVG on the demo), ad set and campaign, when the platform last reported it (updatedAt), its numbers for the period and the comparison period, and spend per bucket (spark). spendMinor, impressions, clicks, ctr (clicks over impressions), cpcMinor and cpmMinor come from the ad platform, null where there is no spend. orders and revenueMinor are credited by the model, newOrders and newRevenueMinor the part from first orders of new customers; under linear a row holds its share of each order, so orders can be a fraction. roas is revenueMinor over spendMinor, ncRoas newRevenueMinor over spendMinor, cpaMinor spend per order and costPerNewCustomerMinor spend per new customer order; platformConversions, platformRevenueMinor and platformRoas are what the platform itself reports, viewConversions its purchases after a view. estimated is true when some credit came from a view model, which spreads daily platform view totals over orders. |
| `totals` | object | always | The totals of every row. |
| `platforms` | array of "meta" \| "google_ads" \| "tiktok" \| "pinterest" \| "amazon_ads" | always | The ad platforms that reported at least one ad. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.creatives \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store creatives --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_creatives
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store creatives` for one flag per field.

## New customers

`POST /api/v1/store.customers` · Read

New and returning customers of a period: their orders and revenue, the repeat rate and the days to a second order, weekly or monthly cohorts with how many came back, and lifetime value by the product and by the channel of the first order. Get the siteId from sites.list first. An order is new when it is its customer's first, by the customer key the store sends; without one it is unknown. The model decides the channel of a first order. Amounts are whole minor units of the currency in labels. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `split` | array of object | always | Orders of the period by customer: new (the customer's first order ever), returning, and unknown (the order carries no customer key), with revenue, customers and the average order. |
| `series` | array of object | always | New and returning orders and revenue per bucket of the period. |
| `repeatRate` | number \| null | always | Of the customers whose first order is in the period, the share with two orders or more, a fraction. |
| `medianDaysToSecond` | number \| null | always | The median number of days from a first order to the second, over those customers. |
| `cohortGrain` | "week" \| "month" | always | Whether cohorts are weeks (while the site's first order is under 180 days old) or months. |
| `cohorts` | array of object | always | The last 8 weekly or 6 monthly cohorts of new customers, whatever the period: start (YYYY-MM-DD, a Monday or the first of a month), the customers, then per period from the first one the customers who ordered and their revenue. Periods after today are 0. |
| `ltvByFirstProduct` | array of object | always | Lifetime value by the product of the first order: customers, orders per customer, revenue per customer over all time, repeat rate and the average first order. |
| `ltvByFirstChannel` | array of object | always | The same by the channel the model credits with the first order. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.customers \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store customers --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_customers
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store customers` for one flag per field.

## Orders and their channels

`POST /api/v1/store.orders` · Read

The orders of a period, newest first, each with the channels its journey touched and the one the model credits, and whether the customer was new or returning. Get the siteId from sites.list first. It pages by cursor: the answer holds limit rows (20 unless set, 100 at most) and nextCursor; send that as cursor for the next, older page, until it is null. There is no comparison. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | Accepted and ignored: this list has no comparison. |
| `cfrom` | string | optional | Accepted and ignored: this list has no comparison. |
| `cto` | string | optional | Accepted and ignored: this list has no comparison. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |
| `cursor` | string, at most 200 characters | optional | Where to continue: the nextCursor of the page before. Leave it out for the newest orders. |
| `limit` | integer, 1 to 100 | optional | Rows per page, 1 to 100. 20 when left out. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `rows` | array of object | always | The orders of the period, newest first: when it was paid (milliseconds since 1970 UTC), the order number at the store and a short label, the amount in minor units, the product and number of items, whether the customer was new or returning (unknown without a customer key), every channel the journey touched oldest first, the channel the model credits, and estimated when that credit came from platform-reported views. |
| `nextCursor` | string \| null | always | Send this as cursor to get the next, older page. Null on the last page. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.orders \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store orders --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_orders
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store orders` for one flag per field.

## Channel overlap

`POST /api/v1/store.overlap` · Read

How the channels of a period share their orders: per channel the orders whose journey touched it, per pair of channels the orders that touched both, and how many orders touched one channel, several, or none. Get the siteId from sites.list first. An order that touched several channels counts fully in each, so the channel rows add up to more than the store's orders. Amounts are whole minor units of the currency in labels. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `channels` | array of object | always | Per channel, the orders whose journey touched it at least once and their revenue. An order that touched three channels counts fully in each. |
| `pairs` | array of object | always | Per pair of channels, the orders whose journey touched both and their revenue. |
| `singleChannelOrders` | number | always | Orders whose journey touched one channel. |
| `multiChannelOrders` | number | always | Orders whose journey touched two channels or more. |
| `unattributedOrders` | number | always | Orders with no recorded visit. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.overlap \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store overlap --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_overlap
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store overlap` for one flag per field.

## Profit report

`POST /api/v1/store.profit` · Read

What the store kept in a period: gross sales, then refunds, product cost, shipping, payment fees, ad spend and the other costs, down to contribution and net profit, with margin, MER and the same lines per bucket. Get the siteId from sites.list first. The costs come from the site's cost rules, product costs and cost lines (store.costs); coverage says how many order lines had a product cost, since a missing cost overstates profit. Tax is inside gross, as the store charged it. Amounts are whole minor units (cents) of one currency, named in labels.currency; spend in another currency is listed in otherCurrencySpend, costs from the cost settings in another currency in otherCurrencyCosts, and neither is added in. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | Accepted and passed on: nothing in this report depends on the model. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `lines` | array of object | always | The profit lines in order, each in minor units with what it covers (scope): gross, refunds, cogs (product cost), shipping, payment_fees, ad_spend, custom_costs, contribution (gross minus refunds, product cost, shipping, fees and ad spend) and net_profit (contribution minus the other costs). previousMinor is the comparison period, delta the change. |
| `marginRatio` | number \| null | always | Net profit over gross minus refunds, a fraction; null when that is not positive. |
| `contributionRatio` | number \| null | always | Contribution over gross minus refunds, a fraction; null when that is not positive. |
| `mer` | number \| null | always | Every order's revenue over all ad spend in the period; null without spend. |
| `series` | array of object | always | One point per bucket of the period: gross, costs (product cost, shipping, fees and the other costs spread over their days), ad spend and net, in minor units. |
| `customCosts` | array of object | always | What each cost line of the site came to in the period. |
| `coverage` | object | always | How complete the costs are: order lines with a product cost out of all lines, up to 20 products without one (most lines first), and whether a shipping and a payment fee rule are set. Missing costs overstate profit. |
| `otherCurrencySpend` | array of object | always | Spend in a currency other than the report's: listed here by currency, in minor units of that currency, and never added into any number of this answer. |
| `otherCurrencyCosts` | array of object | always | Costs from the cost settings (cost lines, and the fixed shipping and fee per order) in a currency other than the report's: listed here by currency, in minor units of that currency, and never added into net profit or any other number of this answer. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.profit \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store profit --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_profit
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store profit` for one flag per field.

## Revenue reconciliation

`POST /api/v1/store.reconciliation` · Read

Checks, day by day, that the revenue the model credits to every channel (including not attributed) adds up to the revenue the store took. Get the siteId from sites.list first. ok is true when every day matches; days lists each day with the store's revenue, the credited revenue and the difference, in whole minor units of the currency in labels. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `ok` | boolean | always | True when credited revenue equals the store's revenue on every day of the period. |
| `maxDriftMinor` | number | always | The largest difference of one day, in minor units. |
| `days` | array of object | always | Per day (YYYY-MM-DD in the site's time zone): the store's revenue, the revenue credited to every channel, the part credited to no channel, and the difference. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.reconciliation \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store reconciliation --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_reconciliation
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store reconciliation` for one flag per field.

## Save cost rules

`POST /api/v1/store.saveCostSettings` · Write

Changes the cost rules of a site: what shipping one order costs, the payment fee (a share of the order plus a fixed amount) and the product cost of a line whose product has none. Get the siteId from sites.list first. Only the rules sent change. Amounts are minor units of the site's cost currency, which the first cost write pins to the currency of its orders. Profit changes for every past period. Sending the same rules twice changes nothing.

Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `shippingPerOrderMinor` | integer, 0 to 100000000 | optional | What shipping one order costs the store, in minor units, 0 to 100000000. |
| `paymentFeeBps` | integer, 0 to 2000 | optional | The payment fee as a share of the order, in basis points: 290 is 2.9%. 0 to 2000. |
| `paymentFeeFixedMinor` | integer, 0 to 100000 | optional | The fixed part of the payment fee per order, in minor units: 30 is $0.30. 0 to 100000. |
| `defaultCogsBps` | integer, 0 to 10000 | optional | The product cost of a line whose product has no unit cost, in basis points of its price: 3500 is 35%. 0 to 10000. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `settings` | object | always | The cost rules as saved. The cost rules: currency (the currency of every cost of the site, null until the first cost is saved), shippingPerOrderMinor (what shipping one order costs the store), paymentFeeBps and paymentFeeFixedMinor (the payment fee of an order: basis points of its amount, 290 is 2.9%, plus a fixed amount) and defaultCogsBps (the product cost of a line whose product has none, in basis points of its price). |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.saveCostSettings \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store saveCostSettings --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_save_cost_settings
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store saveCostSettings` for one flag per field.

## Set product costs

`POST /api/v1/store.setProductCosts` · Write

Saves the unit cost of up to 500 products of a site, each by its product id at the store. A product that already has a cost gets the new one. Get the siteId from sites.list first. Costs are minor units of the site's cost currency. A site holds at most 5,000 product costs. Sending the same costs twice changes nothing.

Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `costs` | array of object, 1 to 500 items | required | The products and their unit costs, 1 to 500. |

**Fields of `costs`**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `provider` | "stripe" \| "paddle" \| "shopify" \| "polar" \| "creem" \| "mollie" \| "revolut" \| "whop" \| "revenuecat" \| "superwall" \| "manual" \| "demo" | optional | Where the product is sold: shopify unless set. The orders of a Shopify store carry its product ids. Default: `"shopify"`. |
| `productId` | string, 1 to 200 characters | required | The product id at that store, as the orders carry it, such as 8215530291462. |
| `productName` | string, at most 200 characters | optional | The product name, for the dashboard. Optional. |
| `unitCostMinor` | integer, 0 to 1000000000 | required | What one unit costs the store, in minor units of the site's cost currency. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `written` | number | always | How many product costs were saved: one per distinct product of the input. |
| `products` | array of object | always | The product costs as saved. Each product cost: its id, the store it sells through (provider), the product id there and its name, the unit cost in minor units of currency, where it came from (form, csv or seed) and when it last changed (milliseconds since 1970 UTC). |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.setProductCosts \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","costs":[{"productId":"ID","unitCostMinor":0}]}'
```

**CLI**

```bash
clearsaga store setProductCosts --input '{"siteId":"site_abc123def456","costs":[{"productId":"ID","unitCostMinor":0}]}'
```

**MCP**

```text
Tool: store_set_product_costs
Arguments: {"siteId":"site_abc123def456","costs":[{"productId":"ID","unitCostMinor":0}]}
```

Run `clearsaga help store setProductCosts` for one flag per field.

## Store summary

`POST /api/v1/store.summary` · Read

The store's numbers for a period in one answer: revenue, orders, average order, new customers, the returning share, refunds, net profit and margin, ad spend, blended ROAS, MER, CPA and cost per new customer, then the same kind of tiles for each ad platform, each with its change and a sparkline. Get the siteId from sites.list first. Amounts are whole minor units (cents) of one currency, named in labels.currency; spend in another currency is listed in otherCurrencySpend, costs from the cost settings in another currency in otherCurrencyCosts, and neither is added in. Every number names what it covers in its scope: orders_paid (Orders paid in the period, before refunds), credited (Orders credited by the chosen model), credited_estimated (Credited by the chosen model; view credit is an estimate from daily platform totals), platform_reported (As the ad platform reports it), spend_synced (Spend as synced or uploaded, in the account currency), cost_settings (From the cost settings and cost lines of this site), refunds_dated (Refunds dated in the period). reconciliation says whether credited revenue adds up to the store's revenue every day. The store reports take no filter. Answers 402 workspace_locked while the workspace's dashboards are locked.

Needs a token with the Read permission. Answers 402 while the dashboards of the workspace are locked.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `range` | "today" \| "yesterday" \| "30m" \| "12h" \| "24h" \| "week" \| "7d" \| "30d" \| "60d" \| "90d" \| "mtd" \| "ytd" \| "12mo" \| "all" \| "custom" | optional | The period: today, yesterday, 30m, 12h, 24h, week, 7d, 30d, 60d, 90d, mtd, ytd, 12mo, all. Left out, the workspace default. For dates send from and to instead. |
| `from` | string | optional | First day of a custom period, YYYY-MM-DD in the site's time zone, sent with to. |
| `to` | string | optional | Last day of a custom period, inclusive, YYYY-MM-DD, sent with from. |
| `compare` | "prev" \| "week" \| "month" \| "quarter" \| "year" \| "custom" \| "off" | optional | prev (the default), week, month, quarter, year, custom (with cfrom and cto) or off. |
| `cfrom` | string | optional | First day of a custom comparison, YYYY-MM-DD. |
| `cto` | string | optional | Last day of a custom comparison, inclusive, YYYY-MM-DD. |
| `currency` | string | optional | The currency to report, ISO 4217 such as USD. Left out, the one with the most orders. |
| `model` | "last_click" \| "last_click_views" \| "clicks_views" \| "last_touch" \| "first_touch" \| "linear" | optional | The attribution model: last_click (the default), last_click_views, clicks_views, last_touch, first_touch or linear. The two view models also credit orders no click explains from the platforms' daily purchases after a view, an estimate. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `serverNow` | number | always | The clock the period was resolved with: the start of the next whole minute, milliseconds since 1970 UTC. |
| `serverTime` | number | always | When the answer was built, milliseconds since 1970 UTC. |
| `window` | object | always | The period the numbers cover: from and to in milliseconds since 1970 UTC (to is exclusive), the time zone, a label and the bucket size. |
| `compare` | object \| null | always | The comparison period in the same form, or null when there is none. |
| `rawFrom` | number \| null | always | The earliest time detailed data is kept from, or null when nothing was trimmed. |
| `truncated` | boolean | always | True when a filtered answer could only cover the part of the period that still has detailed data. |
| `labels` | object | always | What the report covers: the period and the comparison period as the dashboard names them, the currency of every amount (ISO 4217; amounts are whole minor units), the attribution model and the lookback in days. |
| `hasOrders` | boolean | always | False when the site has no order at all, in which case every number is empty. |
| `hasSpend` | boolean | always | False when no ad spend was ever synced or uploaded for the site: the spend, ROAS, MER and CPA tiles then have no value. |
| `hasCosts` | boolean | always | False while the site has no cost rule, product cost or cost line: net profit and margin then leave out every cost but ad spend. |
| `store` | array of object | always | The tiles of the Store section, by key: revenue, orders, aov, new_customers, returning_share, refunds, net_profit, margin, ad_spend, blended_roas (revenue credited to an ad platform over all spend), mer (all revenue over all spend), cpa, cost_per_new_customer. Each tile: key, label, unit (money in minor units, count, ratio as a fraction, decimal such as a ROAS of 2.4, days), value and previous (the comparison period; null when there is none), delta (the change as a fraction of the absolute previous value, new when previous was 0, null when it cannot be computed), deltaInverted (true for costs, where a drop is good), scope (what the number covers) and spark (one point per bucket of the period, oldest first). |
| `platforms` | array of object | always | One section per ad platform with spend ever, in a fixed order: hasSpend says whether it has spend in this period, and its tiles are spend, roas, platform_roas, orders, revenue, cpa, nc_roas, impressions, clicks, ctr, cpc, cpm, platform_conversions and view_conversions, in the same form as store. |
| `otherCurrencySpend` | array of object | always | Spend in a currency other than the report's: listed here by currency, in minor units of that currency, and never added into any number of this answer. |
| `otherCurrencyCosts` | array of object | always | Costs from the cost settings (cost lines, and the fixed shipping and fee per order) in a currency other than the report's: listed here by currency, in minor units of that currency, and never added into net profit or any other number of this answer. |
| `reconciliation` | object | always | Whether the revenue credited to every channel adds up to the store's revenue on every day of the period: ok, the number of days checked and the largest difference found, in minor units. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.summary \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456"}'
```

**CLI**

```bash
clearsaga store summary --input '{"siteId":"site_abc123def456"}'
```

**MCP**

```text
Tool: store_summary
Arguments: {"siteId":"site_abc123def456"}
```

Run `clearsaga help store summary` for one flag per field.

## Change a cost line

`POST /api/v1/store.updateCostLine` · Write

Replaces the label, kind, amount and days of one cost line of a site, by its id from store.costs. Get the siteId from sites.list first. Every field is sent; the answer is the line as saved. An id the site does not have answers 404 with detail cost_line. Profit changes for every period the line covers.

Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `id` | string, at least 1 characters | required | The cost line id, from store.costs. |
| `label` | string, 1 to 80 characters | required | What the cost is, 1 to 80 characters: Rent, Shopify plan, Returns handling. |
| `kind` | "monthly" \| "one_off" \| "percent_of_revenue" | required | monthly: an amount per month, spread over the days it covers. one_off: an amount once, on startsOn. percent_of_revenue: a share of the revenue of the days it covers. |
| `amount` | integer, 0 to 1000000000 | required | Minor units of the site's cost currency for monthly and one_off; basis points for percent_of_revenue (150 is 1.5%, at most 10000). |
| `startsOn` | string | required | The first day the cost counts, YYYY-MM-DD; the day of a one_off cost. |
| `endsOn` | string \| null | required | The last day it counts, inclusive, YYYY-MM-DD, or null for no end. Must be null for one_off and may not be before startsOn. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `id` | string | always | The cost line id. |
| `label` | string | always | What the cost is, as the dashboard shows it. |
| `kind` | "monthly" \| "one_off" \| "percent_of_revenue" | always | monthly: a cost per month, spread over its days. one_off: once, on startsOn. percent_of_revenue: a share of the revenue of the days it covers. |
| `amount` | number | always | Minor units for monthly and one_off; basis points (1 is 0.01%) for percent_of_revenue. |
| `currency` | string | always | The currency of the amount: the site's cost currency. |
| `startsOn` | string | always | The first day it counts, YYYY-MM-DD; the day of a one_off cost. |
| `endsOn` | string \| null | always | The last day it counts, inclusive, or null when it has no end. Always null for one_off. |
| `updatedAt` | number | always | When it last changed, milliseconds since 1970 UTC. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.updateCostLine \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","id":"STORE_ID","label":"example","kind":"monthly","amount":0,"startsOn":"2026-10-01","endsOn":"2026-10-01"}'
```

**CLI**

```bash
clearsaga store updateCostLine --input '{"siteId":"site_abc123def456","id":"STORE_ID","label":"example","kind":"monthly","amount":0,"startsOn":"2026-10-01","endsOn":"2026-10-01"}'
```

**MCP**

```text
Tool: store_update_cost_line
Arguments: {"siteId":"site_abc123def456","id":"STORE_ID","label":"example","kind":"monthly","amount":0,"startsOn":"2026-10-01","endsOn":"2026-10-01"}
```

Run `clearsaga help store updateCostLine` for one flag per field.

## Upload product costs

`POST /api/v1/store.uploadProductCosts` · Write

Saves product costs from a CSV file: a header row with product_id (or id), an optional product_name, and unit_cost (or cost) in major units such as 26.00. Get the siteId from sites.list first. Rows that cannot be read are skipped and listed with their line number. A currency column, when present, must match the site's cost currency. Uploading the same file twice changes nothing.

Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `csv` | string, 1 to 1000000 characters | required | The file's text, at most 1,000,000 characters and 20,000 rows. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `rows` | number | always | The data rows of the file, without the header. |
| `written` | number | always | The rows that were saved. |
| `skipped` | array of object | always | Each row that was not saved: its line number in the file (the header is line 1) and why. |
| `currency` | string \| null | always | The currency the amounts were read in. |
| `from` | string \| null | always | The first day of the file, YYYY-MM-DD; null for a file without dates. |
| `to` | string \| null | always | The last day of the file, YYYY-MM-DD; null for a file without dates. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.uploadProductCosts \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","csv":"example"}'
```

**CLI**

```bash
clearsaga store uploadProductCosts --input '{"siteId":"site_abc123def456","csv":"example"}'
```

**MCP**

```text
Tool: store_upload_product_costs
Arguments: {"siteId":"site_abc123def456","csv":"example"}
```

Run `clearsaga help store uploadProductCosts` for one flag per field.

## Upload ad spend

`POST /api/v1/store.uploadSpend` · Write

Saves ad spend from a CSV file, for platforms that are not connected: date, platform (meta, google_ads, tiktok, pinterest or amazon_ads), campaign, optional ad_set and ad, spend in major units, and optional impressions, clicks, conversions, conversion_value and view_conversions. Get the siteId from sites.list first. An upload replaces the uploaded spend of the same platforms over the days the file covers, so uploading a corrected file twice is safe. Rows that cannot be read are skipped and listed.

Needs a token with the Write permission.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteId` | string | required | The site id, from sites.list. |
| `csv` | string, 1 to 1000000 characters | required | The file's text, at most 1,000,000 characters and 20,000 rows. |
| `currency` | string | required | The currency the ad account bills in, ISO 4217 such as USD. Spend in another currency than the store's is listed beside the reports and never added in. |

### Output

| Field | Type | Present | Description |
| --- | --- | --- | --- |
| `rows` | number | always | The data rows of the file, without the header. |
| `written` | number | always | The rows that were saved. |
| `skipped` | array of object | always | Each row that was not saved: its line number in the file (the header is line 1) and why. |
| `currency` | string \| null | always | The currency the amounts were read in. |
| `from` | string \| null | always | The first day of the file, YYYY-MM-DD; null for a file without dates. |
| `to` | string \| null | always | The last day of the file, YYYY-MM-DD; null for a file without dates. |

### Example

**curl**

```bash
curl -s -X POST https://clearsaga.com/api/v1/store.uploadSpend \
  -H "Authorization: Bearer $CLEARSAGA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"site_abc123def456","csv":"example","currency":"USD"}'
```

**CLI**

```bash
clearsaga store uploadSpend --input '{"siteId":"site_abc123def456","csv":"example","currency":"USD"}'
```

**MCP**

```text
Tool: store_upload_spend
Arguments: {"siteId":"site_abc123def456","csv":"example","currency":"USD"}
```

Run `clearsaga help store uploadSpend` for one flag per field.
