Integrating Your Data

Miso answers from the data you send it. This page covers what to send, how to send it, and how to check the result.

Data management in Dojo is available to customers only.

What Miso accepts

Your content catalog. The articles, opinion pieces, and other content your readers consume. Answers are built from this data. Send transcripts for video and audio too.

Most teams send the whole catalog first, then exclude what must stay out of Answers. This way, nobody has to guess what Miso holds. You can upload or delete at any time. Miso does not charge for indexing.

Databases and lists (optional). Useful when you want an exact value instead of a passage from an article. For example, profile pages for investment funds, design agencies, supermarkets, or hospitals. Ranked lists such as a Top 50 or an award shortlist also work well.

Users and interactions (optional). Data about your readers and what they read. Miso is a privacy-first platform, and many customers send hashed ids. Do not send PII.

How to send it

Method Use it when
WordPress plugin Your site runs on WordPress. It syncs for you.
Data API You want control, and your CMS can call an API.
SFTP You already export files. Miso reads the drop and loads it.
Crawler You cannot export at all. Miso reads your public pages.

All four keep Miso in near real-time sync with your catalog. See Ingest Methods to choose between them. Ask your Miso representative to set up WordPress, SFTP, or the crawler. The rest of this page covers the API.


Upload with the Data API

The Data APIs upload and manage your data. They accept bulk inserts, and they satisfy GDPR and CCPA, because a reader's data can be deleted.

Use your secret key, from a server. A publishable key reaches POST /v1/interactions and nothing else. See Authentication.

Send about 100 records per call. Larger batches risk a timeout.

Products — your content

curl -X POST "https://api.askmiso.com/v1/products" \
  -H "X-Api-Key: YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      {
        "product_id": "art-20260612-rates",
        "title": "Central bank signals 2026 cuts",
        "url": "https://example.com/markets/rates",
        "cover_image": "https://example.com/img/rates.jpg",
        "authors": ["Jane Reporter"],
        "categories": [["News", "Markets"]],
        "published_at": "2026-06-12T09:00:00Z",
        "html": "<p>Full article body…</p>",
        "custom_attributes": { "section": "markets", "premium": true }
      }
    ]
  }'

Users — optional, hashed ids only

curl -X POST "https://api.askmiso.com/v1/users" \
  -H "X-Api-Key: YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      { "user_id": "u-10293", "subscription_tier": "premium" }
    ]
  }'

Interactions — what readers do

curl -X POST "https://api.askmiso.com/v1/interactions" \
  -H "X-Api-Key: YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      {
        "type": "product_detail_page_view",
        "user_id": "u-10293",
        "product_ids": ["art-20260612-rates"],
        "timestamp": "2026-06-12T10:15:00Z"
      }
    ]
  }'

The Miso SDK sends interactions for you. Upload them yourself only when you built your own front end. See SDK Overview.


Uploads are asynchronous

A product or user upload does not write your records before it answers. Miso accepts the batch, queues it, and returns a task id at once:

{
  "message": "success",
  "data": {
    "task_id": "eyJpdiI6IktXZ2Voc0UwSzZSd1pKMWYySjJZNXc9PSIsImRhdGEi…"
  }
}

The task id is an opaque string of about 140 characters. Store it as it comes.

A 200 here means "accepted", not "indexed". The records reach the index a moment later. Treat the task id as your receipt.

Check the task

Poll the status endpoint with the task id:

curl "https://api.askmiso.com/v1/products/_status/TASK_ID" \
  -H "X-Api-Key: YOUR_SECRET_KEY"
import time, requests

def wait_for(task_id, key, timeout=300):
    url = f"https://api.askmiso.com/v1/products/_status/{task_id}"
    headers = {"X-Api-Key": key}
    deadline = time.time() + timeout
    while time.time() < deadline:
        body = requests.get(url, headers=headers).json()
        if body.get("message") != "pending":
            return body
        time.sleep(2)
    raise TimeoutError(task_id)
async function waitFor(taskId, key, timeoutMs = 300000) {
  const url =
    `https://api.askmiso.com/v1/products/_status/${taskId}`;
  const headers = { "X-Api-Key": key };
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const body = await (await fetch(url, { headers })).json();
    if (body.message !== "pending") return body;
    await new Promise((r) => setTimeout(r, 2000));
  }
  throw new Error(`timeout: ${taskId}`);
}

While Miso works, the status is pending:

{
  "message": "pending"
}

When the batch is written, the shape changes completely:

{
  "took": 9,
  "errors": false,
  "data": [],
  "code": 200
}
Field What it means
errors false when every record was written. Read this, not the HTTP status.
code 200, or the code of the first record that failed.
data Messages about the records. Empty when nothing went wrong.
took How long the write took, in seconds.
Response What it means
{"message": "pending"} Miso is still writing the batch. Poll again.
A body with errors and code The batch is finished.
404 The task id is unknown, or it expired.

Allow at least 60 seconds. A single-record batch against production takes about 51 seconds to move from pending to finished. Poll every 2 to 5 seconds, and set a timeout in minutes, not seconds.

The record becomes readable at the same moment the status resolves, so GET /v1/products/{product_id} is an equally good check for one known id.

A delete is faster, and its status carries a different data:

{
  "took": 0,
  "errors": false,
  "data": {
    "deleted": {
      "count": 1,
      "list": ["art-20260612-rates"]
    }
  },
  "code": 200
}

That one resolved in 11 seconds, against 51 for an insert. Afterwards GET /v1/products/{product_id} returns 404 {"message": "not found"}.

Which calls are asynchronous

Endpoint Behavior
POST /v1/products Asynchronous. Returns task_id.
POST /v1/users Asynchronous. Returns task_id. Poll /v1/users/_status/{task_id}.
POST /v1/interactions Synchronous. It answers immediately, so you can call it on every click.
POST /v1/products/_delete Asynchronous. Returns "message": "deleted" and a task_id.

Some apps are configured for synchronous uploads. Those return the per-record items in the first response, with no task id. Write your client to accept both: if data.task_id is present, poll. If data.items is present, you are already done.

Validate before you write

Add dry_run=1 to check a batch against the schema without writing it. Use it in your build, and on the first batch of a new pipeline.

curl -X POST "https://api.askmiso.com/v1/products?dry_run=1" \
  -H "X-Api-Key: YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": [ … ] }'

The product schema

Every field Miso accepts, what it is for, and how to design your custom attributes: see Product Schema.


Read your data

In Dojo, you can see everything you uploaded in a visual dashboard.

You can also read it through the API:

curl "https://api.askmiso.com/v1/products/PRODUCT_ID" \
  -H "X-Api-Key: YOUR_SECRET_KEY"

Delete your data

In Dojo, you can delete single records, or everything in your environment.

Through the API, get the ids first, then delete in bulk:

curl "https://api.askmiso.com/v1/products/_ids" \
  -H "X-Api-Key: YOUR_SECRET_KEY"
curl -X POST "https://api.askmiso.com/v1/products/_delete" \
  -H "X-Api-Key: YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "product_ids": ["PRODUCT_1", "PRODUCT_2"] } }'

Deletes are events, not a sync. Miso does not compare your catalog against its index. If an article is removed on your side and no delete reaches Miso, the old version stays and keeps serving. Send the delete.


Troubleshooting

422 — schema validation

A failed upload is almost always a schema error. One bad record fails the whole batch, and Miso writes nothing. Read the data field to find each problem:

{
  "errors": true,
  "message": "No records inserted - see the `data` field.",
  "data": [
    "data.0.product_ids is invalid. Expected 'array' or 'null'.",
    "data.0.timestamp is invalid. Expected 'date-time' format."
  ]
}

data.0 is the first record in the batch you sent. Correct the records that the response names, then send the batch again. Miso logs these errors too, so your Miso solutions engineer can help.

The data is wrong

Review it in Dojo. You can delete a record, or upload a corrected version over it. Miso deduplicates on product_id during upload and keeps the newest version.

Other errors

Code What it means
401 The key is missing or wrong.
403 You used a publishable key. Products and users need the secret key.
429 Too many requests. Slow down, then retry.