Product Schema
A product is one piece of content: an article, a video, a page. This page lists what Miso accepts, and how to shape it.
The schema validates only product_id, but a record with only an id is of no
use to Answers. The fields below are what Miso works from.
To send the records, see Integrating Your Data.
Send these on every record
| Field | Type | Why Miso needs it |
|---|---|---|
product_id |
string | Your id for the content. It is the key: an upload with an id that exists overwrites that record. Interactions and answers point back with it. |
title |
string | The strongest short signal for retrieval, and the text a reader sees in a citation. |
url |
string | Where the citation links to. Without it an answer can cite the article but not send anyone to it. |
html or description |
string | The body. This is the text Miso answers from. Send html when you have it, because the tags carry the structure. |
published_at |
ISO-8601 date or datetime | Drives recency. Without it Miso cannot tell new from old, and "latest" questions get stale answers. |
Send these when you have them
| Field | Type | What it buys you |
|---|---|---|
authors |
array of strings | Author filters and "by X" questions. One name per entry, not one joined string. |
cover_image |
string | The thumbnail in your UI and in Dojo Sandboxes. |
categories |
array of arrays | Your hierarchy. See below. |
tags |
array of strings | Flat labels, for filtering and boosting. |
Everything else in the schema is optional: type, language, updated_at,
brand, rating, original_price, and more. Send what you have.
How Miso reads each field
This decides where your text belongs.
| Treated as | Fields | What happens |
|---|---|---|
| Text | html, description |
Tokenized, and read for meaning. Term frequency matters here. |
| Feature or keyword | Everything else | Used to filter, boost, and rank. Not read as prose. |
| Display only | url, cover_image |
Not used for training. They render the result. |
So: put the words in html or description. Put the facts in their own
fields.
Prefer html when you have it. Miso's parser reads the tags to understand
the structure of your document — headings, paragraphs, lists, and tables. That
structure tells Miso which passage answers a question. Send description when
plain text is all you hold.
Categories are hierarchical
categories is an array of arrays. Each inner array runs from broad to
narrow, and a product can sit in more than one:
{
"categories": [["News", "Markets", "Interest rates"], ["Analysis"]],
"tags": ["central-bank", "mortgages"]
}
Use categories for a taxonomy with levels. Use tags for flat labels.
A flat list is rejected, not merely flattened. "categories": ["News", "Markets"] returns 422 with data.0.categories/0 is invalid. Each entry must
itself be an array, even when the path has one level: [["News"]].
Custom attributes
custom_attributes is a free-form object for everything specific to your
business. Miso handles hundreds of them, so send all the metadata you hold.
A key must be a string. A value can be a boolean, a string or array of strings,
a number or array of numbers, an array of objects, or null.
{
"product_id": "art-20260612-rates",
"title": "Central bank signals 2026 cuts",
"custom_attributes": {
"section": "markets",
"premium": true,
"read_minutes": 6,
"item_source": "WIRE",
"alternative_langs": ["en", "zh"],
"series": {
"name": "Rate Watch",
"part": 4
}
}
}
Why the design matters: every attribute becomes a filter
Whatever you send is queryable by its path, as custom_attributes.<key>. That
is how filters (fq) and boosts (boost_fq) reach your own data:
fq: custom_attributes.premium:"false"
boost_fq: custom_attributes.item_source:WIRE AND tags:markets
See Filter Syntax (fq) for the operators, ranges, and the two mistakes that return nothing instead of an error.
Nested objects work with a dotted path — custom_attributes.series.name. You
can combine your attributes with the standard fields in one expression.
This is the reason to think about shape before you send. If a value must be filtered on, it needs its own key or its own path.
Shape the fields for the updates you make
Take products that are available in some regions only. Two ways to model it:
- One list:
{"regional_availability": ["region_1", "region_3"]} - One field per region:
{"region_1_availability": true, "region_2_availability": false}
Option 1 reads better and filters cleanly. Option 2 is easier for a partial update, because you send only the regions that changed. Your Miso solutions architect can help you decide.
Put the front end's fields in there too
If your UI needs a rating score or a badge to render a result, send it as a custom attribute. It comes back with the result, so your page does not need a second request.
Warning: text belongs in
description. Miso reads onlydescriptionandhtmlas text. Every other field, including every custom attribute, is treated as a feature or a keyword. If your summary has several sections, put the whole text indescription. Do not create one custom attribute per section — that text will not be searched as prose.
Naming rules
Custom attribute keys are not restricted: a leading _ and a dot both pass
validation. Avoid the dot anyway. It is the path separator in a filter, so
custom_attributes.a.b cannot tell your key a.b from a nested field b
inside a.
The top level is strict. Miso rejects any field it does not define:
{
"errors": true,
"message": "None of the records were inserted…",
"data": [
"data.0 is invalid. The attribute must not contain additional properties"
]
}
Everything of your own belongs in custom_attributes.
