#Saved charts
GET /saved-charts
List the organization's saved charts across every dashboard. Filter by dashboardId to read one dashboard's charts, or use the dashboard's detail endpoint to get them in reading order.
Requires the analytics:read permission.
Query parameters
limitnumberThe number of records to return.
cursorstringA pagination cursor. Fetch the next page by passing the nextCursor value from the previous response.
sortname | -name | createdAt | -createdAtThe sort order. Prefix a field with - to sort descending. Defaults to -createdAt.
filterstringA JSON-encoded filter document, for example {"createdAt":{"$gte":"2025-01-01T00:00:00Z"}}. Filterable fields: id, dashboardId, name, resource, chartType, createdAt. See Filtering for the syntax.
curl \
"https://your-store.meisei.app/api/saved-charts" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { getSavedCharts } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getSavedCharts()require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
page = meisei.saved_charts.listfrom meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
page = meisei.saved_charts.list(){
"data": [
{
"id": "A4Ws1igz",
"dashboardId": "A4Ws1igz",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": "...",
"groupBy": "...",
"granularity": "...",
"filter": "...",
"limit": "...",
"lastDays": "..."
},
"layout": {
"x": "...",
"y": "...",
"w": "...",
"h": "..."
},
"invalidReason": "string",
"feedCreatedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}
],
"nextCursor": "string"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the saved chart.",
"idCodec": "sqid"
},
"dashboardId": {
"type": "string",
"description": "The dashboard the chart belongs to, for its whole life.",
"idCodec": "sqid"
},
"name": {
"type": "string",
"description": "The title shown above the chart."
},
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"chartType": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"query": {
"type": "object",
"properties": {
"measures": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
},
"description": "The measures to report, by name. They have to be ones the resource offers."
},
"groupBy": {
"description": "The dimensions to group by, by name, at most three and at most one of them a date. Omit for a single total row.",
"type": "array",
"items": {
"type": "string"
}
},
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
"filter": {
"description": "The filter document to apply, in the shape the resource's list endpoint takes. It is validated against the fields that screen allows.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"limit": {
"description": "How many distinct group-by combinations to return, from 1 to 500.",
"type": "integer",
"minimum": 1,
"maximum": 500
},
"lastDays": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"description": "Only count records from the last this many days, up to now. It applies to `timeField` like `from` does, and an explicit `from` takes its place."
}
},
"required": ["measures"],
"additionalProperties": false,
"description": "What the chart asks for. Send it to `/analytics/query` alongside `resource` to draw it."
},
"layout": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The column the chart starts at, on a 12 column grid."
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The row the chart starts at."
},
"w": {
"type": "integer",
"minimum": 2,
"maximum": 12,
"description": "How many columns the chart spans, at least 2."
},
"h": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "How many rows tall the chart is. One row renders as 90 pixels."
}
},
"required": ["x", "y", "w", "h"],
"additionalProperties": false,
"description": "Where the chart sits on the dashboard's grid."
},
"invalidReason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the chart can no longer run, or null when it is fine. Definitions are validated on write, so this only fills in when a resource, measure or dimension the chart names was removed afterward."
},
"feedCreatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the chart's data link was made, or null when it has none. The link itself is only shown once, when it is made."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was saved."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was last changed."
}
},
"required": [
"id",
"dashboardId",
"name",
"resource",
"chartType",
"query",
"layout",
"invalidReason",
"feedCreatedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false
},
"description": "The page of records, in the requested sort order."
},
"nextCursor": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Pass this back as `cursor` to fetch the next page. Null once the last page has been returned."
}
},
"required": ["data", "nextCursor"],
"additionalProperties": false
}POST /saved-charts
Save a chart onto a dashboard. The query document is validated against what the resource can measure and group by, its filter against the fields that resource's list endpoint allows, and the chart type against the shape the query returns, so a chart that would error every time it is opened is rejected here instead. Omit layout to append the chart at the bottom of the dashboard.
Requires the analytics:write permission.
Body parameters
dashboardIdstringrequiredThe dashboard the chart goes on. It cannot be moved to another one later.
namestringrequiredThe title to show above the chart.
resourcereviews | review-answers | orders | customers | products | loyalty-members | loyalty-activity | loyalty-rewardsrequiredThe resource to aggregate. Call /analytics/schema for what each one offers.
chartTypestat | line | area | bar | donut | stackedBar | groupedBar | tablerequiredHow the chart is drawn. It has to suit the query it is saved with: a donut needs one counting measure grouped by one non-date dimension, a stat needs no grouping at all, and table always fits.
queryobjectrequiredWhat the chart asks for.
layoutobjectWhere the chart sits on the dashboard's grid. Omit it to append the chart at the bottom, half width.
curl \
-X POST \
"https://your-store.meisei.app/api/saved-charts" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"dashboardId": "string",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": [
"string"
],
"groupBy": [
"string"
],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
},
"layout": {
"x": 0,
"y": 0,
"w": 0,
"h": 0
}
}'import { client } from "@meisei/api/admin/client"
import { postSavedCharts } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postSavedCharts({
body: {
dashboardId: "string",
name: "string",
resource: "reviews",
chartType: "stat",
query: {
measures: ["string"],
groupBy: ["string"],
granularity: "hour",
filter: "...",
limit: 0,
lastDays: 0,
},
layout: {
x: 0,
y: 0,
w: 0,
h: 0,
},
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.create(
dashboard_id: "string",
name: "string",
resource: "reviews",
chart_type: "stat",
query: {
measures: ["string"],
group_by: ["string"],
granularity: "hour",
filter: "...",
limit: 0,
last_days: 0,
},
layout: { x: 0, y: 0, w: 0, h: 0 },
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.create(
dashboard_id="string",
name="string",
resource="reviews",
chart_type="stat",
query={
"measures": ["string"],
"group_by": ["string"],
"granularity": "hour",
"filter": {},
"limit": 0,
"last_days": 0,
},
layout={"x": 0, "y": 0, "w": 0, "h": 0},
){
"id": "A4Ws1igz",
"dashboardId": "A4Ws1igz",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": ["string"],
"groupBy": ["string"],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
},
"layout": {
"x": 0,
"y": 0,
"w": 0,
"h": 0
},
"invalidReason": "string",
"feedCreatedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the saved chart.",
"idCodec": "sqid"
},
"dashboardId": {
"type": "string",
"description": "The dashboard the chart belongs to, for its whole life.",
"idCodec": "sqid"
},
"name": {
"type": "string",
"description": "The title shown above the chart."
},
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"chartType": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"query": {
"type": "object",
"properties": {
"measures": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
},
"description": "The measures to report, by name. They have to be ones the resource offers."
},
"groupBy": {
"description": "The dimensions to group by, by name, at most three and at most one of them a date. Omit for a single total row.",
"type": "array",
"items": {
"type": "string"
}
},
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
"filter": {
"description": "The filter document to apply, in the shape the resource's list endpoint takes. It is validated against the fields that screen allows.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"limit": {
"description": "How many distinct group-by combinations to return, from 1 to 500.",
"type": "integer",
"minimum": 1,
"maximum": 500
},
"lastDays": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"description": "Only count records from the last this many days, up to now. It applies to `timeField` like `from` does, and an explicit `from` takes its place."
}
},
"required": ["measures"],
"additionalProperties": false,
"description": "What the chart asks for. Send it to `/analytics/query` alongside `resource` to draw it."
},
"layout": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The column the chart starts at, on a 12 column grid."
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The row the chart starts at."
},
"w": {
"type": "integer",
"minimum": 2,
"maximum": 12,
"description": "How many columns the chart spans, at least 2."
},
"h": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "How many rows tall the chart is. One row renders as 90 pixels."
}
},
"required": ["x", "y", "w", "h"],
"additionalProperties": false,
"description": "Where the chart sits on the dashboard's grid."
},
"invalidReason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the chart can no longer run, or null when it is fine. Definitions are validated on write, so this only fills in when a resource, measure or dimension the chart names was removed afterward."
},
"feedCreatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the chart's data link was made, or null when it has none. The link itself is only shown once, when it is made."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was saved."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was last changed."
}
},
"required": [
"id",
"dashboardId",
"name",
"resource",
"chartType",
"query",
"layout",
"invalidReason",
"feedCreatedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The new saved chart."
}POST /saved-charts/preview
Check a chart definition without saving it. It runs exactly the validation POST /saved-charts runs, and reports which chart types can draw the query, so a type that does not fit is caught before anything is written. Nothing is stored and nothing is recorded.
Requires the analytics:read permission.
Body parameters
resourcereviews | review-answers | orders | customers | products | loyalty-members | loyalty-activity | loyalty-rewardsrequiredThe resource to aggregate. Call /analytics/schema for what each one offers.
chartTypestat | line | area | bar | donut | stackedBar | groupedBar | tableThe chart type you are thinking of. Leave it off to only ask which types fit.
queryobjectrequiredWhat the chart would ask for.
curl \
-X POST \
"https://your-store.meisei.app/api/saved-charts/preview" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": [
"string"
],
"groupBy": [
"string"
],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
}
}'import { client } from "@meisei/api/admin/client"
import { postSavedChartsPreview } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postSavedChartsPreview({
body: {
resource: "reviews",
chartType: "stat",
query: {
measures: ["string"],
groupBy: ["string"],
granularity: "hour",
filter: "...",
limit: 0,
lastDays: 0,
},
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.preview(
resource: "reviews",
chart_type: "stat",
query: {
measures: ["string"],
group_by: ["string"],
granularity: "hour",
filter: "...",
limit: 0,
last_days: 0,
},
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.preview(
resource="reviews",
chart_type="stat",
query={
"measures": ["string"],
"group_by": ["string"],
"granularity": "hour",
"filter": {},
"limit": 0,
"last_days": 0,
},
){
"valid": true,
"reason": "string",
"chartTypes": ["stat"],
"recommendedChartType": "stat"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"valid": {
"type": "boolean",
"description": "Whether this definition would be accepted by `POST /saved-charts` as it stands."
},
"reason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why it would be refused, in the words the save would use, or null when it is fine."
},
"chartTypes": {
"type": "array",
"items": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"description": "Every chart type that can draw this query, best first. Empty when the query itself is the problem, since a query that cannot run has no shape to draw."
},
"recommendedChartType": {
"anyOf": [
{
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
{
"type": "null"
}
],
"description": "The chart type that suits this query best, or null when the query cannot run."
}
},
"required": ["valid", "reason", "chartTypes", "recommendedChartType"],
"additionalProperties": false,
"description": "What the save would make of this definition."
}GET /saved-charts/{id}
Get a single saved chart by ID.
Requires the analytics:read permission.
Path parameters
idstringrequiredThe ID of the saved chart.
curl \
"https://your-store.meisei.app/api/saved-charts/A4Ws1igz" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { getSavedChartsById } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getSavedChartsById({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.retrieve("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.retrieve("A4Ws1igz"){
"id": "A4Ws1igz",
"dashboardId": "A4Ws1igz",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": ["string"],
"groupBy": ["string"],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
},
"layout": {
"x": 0,
"y": 0,
"w": 0,
"h": 0
},
"invalidReason": "string",
"feedCreatedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the saved chart.",
"idCodec": "sqid"
},
"dashboardId": {
"type": "string",
"description": "The dashboard the chart belongs to, for its whole life.",
"idCodec": "sqid"
},
"name": {
"type": "string",
"description": "The title shown above the chart."
},
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"chartType": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"query": {
"type": "object",
"properties": {
"measures": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
},
"description": "The measures to report, by name. They have to be ones the resource offers."
},
"groupBy": {
"description": "The dimensions to group by, by name, at most three and at most one of them a date. Omit for a single total row.",
"type": "array",
"items": {
"type": "string"
}
},
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
"filter": {
"description": "The filter document to apply, in the shape the resource's list endpoint takes. It is validated against the fields that screen allows.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"limit": {
"description": "How many distinct group-by combinations to return, from 1 to 500.",
"type": "integer",
"minimum": 1,
"maximum": 500
},
"lastDays": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"description": "Only count records from the last this many days, up to now. It applies to `timeField` like `from` does, and an explicit `from` takes its place."
}
},
"required": ["measures"],
"additionalProperties": false,
"description": "What the chart asks for. Send it to `/analytics/query` alongside `resource` to draw it."
},
"layout": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The column the chart starts at, on a 12 column grid."
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The row the chart starts at."
},
"w": {
"type": "integer",
"minimum": 2,
"maximum": 12,
"description": "How many columns the chart spans, at least 2."
},
"h": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "How many rows tall the chart is. One row renders as 90 pixels."
}
},
"required": ["x", "y", "w", "h"],
"additionalProperties": false,
"description": "Where the chart sits on the dashboard's grid."
},
"invalidReason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the chart can no longer run, or null when it is fine. Definitions are validated on write, so this only fills in when a resource, measure or dimension the chart names was removed afterward."
},
"feedCreatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the chart's data link was made, or null when it has none. The link itself is only shown once, when it is made."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was saved."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was last changed."
}
},
"required": [
"id",
"dashboardId",
"name",
"resource",
"chartType",
"query",
"layout",
"invalidReason",
"feedCreatedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The requested saved chart."
}PUT /saved-charts/{id}
Rename a saved chart, change how it is drawn, or replace what it asks for. The resource it aggregates and the dashboard it belongs to cannot be changed, and its position is written through the dashboard's layout endpoint. The whole definition is revalidated, so a new query the saved chart type cannot draw is rejected.
Requires the analytics:write permission.
Path parameters
idstringrequiredThe ID of the saved chart to update.
Body parameters
namestringThe title shown above the chart.
chartTypestat | line | area | bar | donut | stackedBar | groupedBar | tableHow the chart is drawn. It still has to suit the query. Omit to leave it alone.
queryobjectReplaces what the chart asks for, wholesale. The resource cannot be changed. Omit to leave it alone.
curl \
-X PUT \
"https://your-store.meisei.app/api/saved-charts/A4Ws1igz" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"chartType": "stat",
"query": {
"measures": [
"string"
],
"groupBy": [
"string"
],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
}
}'import { client } from "@meisei/api/admin/client"
import { putSavedChartsById } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await putSavedChartsById({
path: { id: "A4Ws1igz" },
body: {
name: "string",
chartType: "stat",
query: {
measures: ["string"],
groupBy: ["string"],
granularity: "hour",
filter: "...",
limit: 0,
lastDays: 0,
},
},
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.update(
"A4Ws1igz",
name: "string",
chart_type: "stat",
query: {
measures: ["string"],
group_by: ["string"],
granularity: "hour",
filter: "...",
limit: 0,
last_days: 0,
},
)from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.update(
"A4Ws1igz",
name="string",
chart_type="stat",
query={
"measures": ["string"],
"group_by": ["string"],
"granularity": "hour",
"filter": {},
"limit": 0,
"last_days": 0,
},
){
"id": "A4Ws1igz",
"dashboardId": "A4Ws1igz",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": ["string"],
"groupBy": ["string"],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
},
"layout": {
"x": 0,
"y": 0,
"w": 0,
"h": 0
},
"invalidReason": "string",
"feedCreatedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the saved chart.",
"idCodec": "sqid"
},
"dashboardId": {
"type": "string",
"description": "The dashboard the chart belongs to, for its whole life.",
"idCodec": "sqid"
},
"name": {
"type": "string",
"description": "The title shown above the chart."
},
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"chartType": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"query": {
"type": "object",
"properties": {
"measures": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
},
"description": "The measures to report, by name. They have to be ones the resource offers."
},
"groupBy": {
"description": "The dimensions to group by, by name, at most three and at most one of them a date. Omit for a single total row.",
"type": "array",
"items": {
"type": "string"
}
},
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
"filter": {
"description": "The filter document to apply, in the shape the resource's list endpoint takes. It is validated against the fields that screen allows.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"limit": {
"description": "How many distinct group-by combinations to return, from 1 to 500.",
"type": "integer",
"minimum": 1,
"maximum": 500
},
"lastDays": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"description": "Only count records from the last this many days, up to now. It applies to `timeField` like `from` does, and an explicit `from` takes its place."
}
},
"required": ["measures"],
"additionalProperties": false,
"description": "What the chart asks for. Send it to `/analytics/query` alongside `resource` to draw it."
},
"layout": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The column the chart starts at, on a 12 column grid."
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The row the chart starts at."
},
"w": {
"type": "integer",
"minimum": 2,
"maximum": 12,
"description": "How many columns the chart spans, at least 2."
},
"h": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "How many rows tall the chart is. One row renders as 90 pixels."
}
},
"required": ["x", "y", "w", "h"],
"additionalProperties": false,
"description": "Where the chart sits on the dashboard's grid."
},
"invalidReason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the chart can no longer run, or null when it is fine. Definitions are validated on write, so this only fills in when a resource, measure or dimension the chart names was removed afterward."
},
"feedCreatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the chart's data link was made, or null when it has none. The link itself is only shown once, when it is made."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was saved."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was last changed."
}
},
"required": [
"id",
"dashboardId",
"name",
"resource",
"chartType",
"query",
"layout",
"invalidReason",
"feedCreatedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The updated saved chart."
}DELETE /saved-charts/{id}
Delete a saved chart from its dashboard. The deletion is recorded in the audit log.
Requires the analytics:write permission.
Path parameters
idstringrequiredThe ID of the saved chart to delete.
curl \
-X DELETE \
"https://your-store.meisei.app/api/saved-charts/A4Ws1igz" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { deleteSavedChartsById } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteSavedChartsById({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.delete("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.delete("A4Ws1igz"){
"id": "A4Ws1igz",
"dashboardId": "A4Ws1igz",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": ["string"],
"groupBy": ["string"],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
},
"layout": {
"x": 0,
"y": 0,
"w": 0,
"h": 0
},
"invalidReason": "string",
"feedCreatedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the saved chart.",
"idCodec": "sqid"
},
"dashboardId": {
"type": "string",
"description": "The dashboard the chart belongs to, for its whole life.",
"idCodec": "sqid"
},
"name": {
"type": "string",
"description": "The title shown above the chart."
},
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"chartType": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"query": {
"type": "object",
"properties": {
"measures": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
},
"description": "The measures to report, by name. They have to be ones the resource offers."
},
"groupBy": {
"description": "The dimensions to group by, by name, at most three and at most one of them a date. Omit for a single total row.",
"type": "array",
"items": {
"type": "string"
}
},
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
"filter": {
"description": "The filter document to apply, in the shape the resource's list endpoint takes. It is validated against the fields that screen allows.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"limit": {
"description": "How many distinct group-by combinations to return, from 1 to 500.",
"type": "integer",
"minimum": 1,
"maximum": 500
},
"lastDays": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"description": "Only count records from the last this many days, up to now. It applies to `timeField` like `from` does, and an explicit `from` takes its place."
}
},
"required": ["measures"],
"additionalProperties": false,
"description": "What the chart asks for. Send it to `/analytics/query` alongside `resource` to draw it."
},
"layout": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The column the chart starts at, on a 12 column grid."
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The row the chart starts at."
},
"w": {
"type": "integer",
"minimum": 2,
"maximum": 12,
"description": "How many columns the chart spans, at least 2."
},
"h": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "How many rows tall the chart is. One row renders as 90 pixels."
}
},
"required": ["x", "y", "w", "h"],
"additionalProperties": false,
"description": "Where the chart sits on the dashboard's grid."
},
"invalidReason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the chart can no longer run, or null when it is fine. Definitions are validated on write, so this only fills in when a resource, measure or dimension the chart names was removed afterward."
},
"feedCreatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the chart's data link was made, or null when it has none. The link itself is only shown once, when it is made."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was saved."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was last changed."
}
},
"required": [
"id",
"dashboardId",
"name",
"resource",
"chartType",
"query",
"layout",
"invalidReason",
"feedCreatedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The deleted saved chart."
}GET /saved-charts/{id}/data
Read a saved chart's rows, the same ones /analytics/query returns for its definition, so another tool can show what the dashboard shows from one address. from and to narrow it to a time range, which is how a BI tool's time picker reaches it. Answers are kept for up to a minute, and a request answered from that copy costs 1 rather than 5.
Requires the analytics:read permission.
Costs 5 rate limit tokens instead of the usual one. See Rate limits.
Path parameters
idstringrequiredThe ID of the saved chart.
Query parameters
fromstring (date-time)Only count records at or after this instant, as an ISO 8601 date and time. It applies to timeField, beside the filter, and a grouped date's series then starts here.
tostring (date-time)Only count records before this instant, as an ISO 8601 date and time. It applies to timeField, beside the filter, and a grouped date's series then ends here.
timeFieldstringThe date from and to apply to, by dimension name. Defaults to the date in groupBy, or to createdAt when nothing is grouped by a date.
timezonestringThe time zone to bucket date dimensions in, as an IANA name such as America/New_York. Defaults to the organization's own time zone.
includeOtherstringSet to false to leave out the Other row the chart collapses its smaller values into, so every value is a real one. With no limit on the chart, every combination comes back.
formatjson | csvAnswer as csv rather than json: one header row naming the columns, then one line per row. Sending Accept: text/csv does the same.
curl \
"https://your-store.meisei.app/api/saved-charts/A4Ws1igz/data" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { getSavedChartsByIdData } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await getSavedChartsByIdData({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.data("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.data("A4Ws1igz"){
"data": ["..."],
"meta": {
"resource": "reviews",
"timezone": "string",
"granularity": "hour",
"dimensions": [
{
"name": "...",
"label": "...",
"type": "...",
"association": "...",
"valueLabels": "..."
}
],
"measures": [
{
"name": "...",
"label": "...",
"type": "...",
"description": "..."
}
],
"truncated": true,
"otherLabel": "string"
}
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
]
},
"description": "One result row: the grouped dimension values and the measure values, keyed by the names you asked for."
},
"description": "The result rows, ordered by the grouped dimensions."
},
"meta": {
"type": "object",
"properties": {
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"timezone": {
"type": "string",
"description": "The time zone date dimensions were bucketed in, resolved from the request or the organization."
},
"granularity": {
"anyOf": [
{
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
{
"type": "null"
}
],
"description": "The bucket size used, or null when nothing was grouped by a date."
},
"dimensions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name to put in the query document's `groupBy`."
},
"label": {
"type": "string",
"description": "A short human label for a chart legend or axis."
},
"type": {
"type": "string",
"enum": ["time", "number", "string", "boolean"],
"description": "The kind of value the dimension takes. A `time` dimension is bucketed by the query's `granularity`."
},
"association": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "The related resource the dimension reads through, or null for a column on the resource itself."
},
"valueLabels": {
"anyOf": [
{
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
{
"type": "null"
}
],
"description": "A display label for each value an enumerated dimension takes, keyed by the value the rows carry, or null when the values are shown as they are."
}
},
"required": ["name", "label", "type", "association", "valueLabels"],
"additionalProperties": false
},
"description": "The dimensions the rows are grouped by, in order."
},
"measures": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name to put in the query document's `measures`."
},
"label": {
"type": "string",
"description": "A short human label for a chart legend or axis."
},
"type": {
"type": "string",
"enum": ["integer", "number"],
"description": "Whether the measure counts whole things."
},
"description": {
"type": "string",
"description": "What the measure counts."
}
},
"required": ["name", "label", "type", "description"],
"additionalProperties": false
},
"description": "The measures each row carries, in order."
},
"truncated": {
"type": "boolean",
"description": "Whether the cardinality cap bit. When true, one row carries the collapsed remainder and reads `Other` for each grouped dimension."
},
"otherLabel": {
"type": "string",
"description": "The value the collapsed remainder row reads for each grouped dimension."
}
},
"required": [
"resource",
"timezone",
"granularity",
"dimensions",
"measures",
"truncated",
"otherLabel"
],
"additionalProperties": false,
"description": "What the query resolved to, for labeling and charting."
}
},
"required": ["data", "meta"],
"additionalProperties": false
}POST /saved-charts/{id}/feed
Make a data link for a saved chart: an address that answers with the chart's rows to anyone who has it, for a spreadsheet or BI tool that cannot send an API key. The token is returned once, here, and making a new link turns the old one off. Making one is recorded in the audit log.
Requires the analytics:write permission.
Path parameters
idstringrequiredThe ID of the saved chart.
curl \
-X POST \
"https://your-store.meisei.app/api/saved-charts/A4Ws1igz/feed" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { postSavedChartsByIdFeed } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await postSavedChartsByIdFeed({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.feed.create("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.feed.create("A4Ws1igz"){
"token": "string",
"url": "string",
"createdAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"token": {
"type": "string",
"description": "The data link's token. It is the whole credential and is not shown again, so keep it somewhere safe."
},
"url": {
"type": "string",
"description": "The address another tool reads the chart's rows from. Add `?format=csv` for CSV."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the link was made."
}
},
"required": ["token", "url", "createdAt"],
"additionalProperties": false,
"description": "The new data link."
}DELETE /saved-charts/{id}/feed
Turn off a saved chart's data link. Anything still reading it gets a 404 from then on. Turning one off is recorded in the audit log.
Requires the analytics:write permission.
Path parameters
idstringrequiredThe ID of the saved chart.
curl \
-X DELETE \
"https://your-store.meisei.app/api/saved-charts/A4Ws1igz/feed" \
-H "Authorization: Bearer sk_YOUR_SECRET_KEY"import { client } from "@meisei/api/admin/client"
import { deleteSavedChartsByIdFeed } from "@meisei/api/admin"
client.setConfig({
baseUrl: "https://your-store.meisei.app/api",
headers: { Authorization: "Bearer sk_YOUR_SECRET_KEY" },
})
const { data, error } = await deleteSavedChartsByIdFeed({
path: { id: "A4Ws1igz" },
})require "meisei"
meisei = Meisei::Client.new(
secret_key: "sk_YOUR_SECRET_KEY",
base_url: "https://your-store.meisei.app/api",
)
result = meisei.saved_charts.feed.delete("A4Ws1igz")from meisei import Client
meisei = Client(
secret_key="sk_YOUR_SECRET_KEY",
base_url="https://your-store.meisei.app/api",
)
result = meisei.saved_charts.feed.delete("A4Ws1igz"){
"id": "A4Ws1igz",
"dashboardId": "A4Ws1igz",
"name": "string",
"resource": "reviews",
"chartType": "stat",
"query": {
"measures": ["string"],
"groupBy": ["string"],
"granularity": "hour",
"filter": "...",
"limit": 0,
"lastDays": 0
},
"layout": {
"x": 0,
"y": 0,
"w": 0,
"h": 0
},
"invalidReason": "string",
"feedCreatedAt": "2025-01-15T09:30:00Z",
"createdAt": "2025-01-15T09:30:00Z",
"updatedAt": "2025-01-15T09:30:00Z"
}{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the saved chart.",
"idCodec": "sqid"
},
"dashboardId": {
"type": "string",
"description": "The dashboard the chart belongs to, for its whole life.",
"idCodec": "sqid"
},
"name": {
"type": "string",
"description": "The title shown above the chart."
},
"resource": {
"type": "string",
"enum": [
"reviews",
"review-answers",
"orders",
"customers",
"products",
"loyalty-members",
"loyalty-activity",
"loyalty-rewards"
],
"description": "The resource to aggregate. Call `/analytics/schema` for what each one offers."
},
"chartType": {
"type": "string",
"enum": ["stat", "line", "area", "bar", "donut", "stackedBar", "groupedBar", "table"],
"description": "How the chart is drawn. It has to suit the query it is saved with: a `donut` needs one counting measure grouped by one non-date dimension, a `stat` needs no grouping at all, and `table` always fits."
},
"query": {
"type": "object",
"properties": {
"measures": {
"minItems": 1,
"type": "array",
"items": {
"type": "string"
},
"description": "The measures to report, by name. They have to be ones the resource offers."
},
"groupBy": {
"description": "The dimensions to group by, by name, at most three and at most one of them a date. Omit for a single total row.",
"type": "array",
"items": {
"type": "string"
}
},
"granularity": {
"type": "string",
"enum": ["hour", "day", "week", "month", "quarter", "year"],
"description": "The bucket size for a date dimension. Defaults to `day`. Ignored when nothing in `groupBy` is a date."
},
"filter": {
"description": "The filter document to apply, in the shape the resource's list endpoint takes. It is validated against the fields that screen allows.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"limit": {
"description": "How many distinct group-by combinations to return, from 1 to 500.",
"type": "integer",
"minimum": 1,
"maximum": 500
},
"lastDays": {
"type": "integer",
"minimum": 1,
"maximum": 3650,
"description": "Only count records from the last this many days, up to now. It applies to `timeField` like `from` does, and an explicit `from` takes its place."
}
},
"required": ["measures"],
"additionalProperties": false,
"description": "What the chart asks for. Send it to `/analytics/query` alongside `resource` to draw it."
},
"layout": {
"type": "object",
"properties": {
"x": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The column the chart starts at, on a 12 column grid."
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991,
"description": "The row the chart starts at."
},
"w": {
"type": "integer",
"minimum": 2,
"maximum": 12,
"description": "How many columns the chart spans, at least 2."
},
"h": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "How many rows tall the chart is. One row renders as 90 pixels."
}
},
"required": ["x", "y", "w", "h"],
"additionalProperties": false,
"description": "Where the chart sits on the dashboard's grid."
},
"invalidReason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the chart can no longer run, or null when it is fine. Definitions are validated on write, so this only fills in when a resource, measure or dimension the chart names was removed afterward."
},
"feedCreatedAt": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
],
"description": "When the chart's data link was made, or null when it has none. The link itself is only shown once, when it is made."
},
"createdAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was saved."
},
"updatedAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "When the chart was last changed."
}
},
"required": [
"id",
"dashboardId",
"name",
"resource",
"chartType",
"query",
"layout",
"invalidReason",
"feedCreatedAt",
"createdAt",
"updatedAt"
],
"additionalProperties": false,
"description": "The saved chart, with no data link."
}