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:

  1. One list: {"regional_availability": ["region_1", "region_3"]}
  2. 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 only description and html as 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 in description. 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.