User History
User History saves every question a signed-in reader asks. The reader can reopen past conversations. Miso can also tell the reader when it has a fresh answer to an earlier question.
Two capabilities, one API:
- User History — Miso saves each question a signed-in user asks and groups the follow-ups into conversation threads. You can list, reopen, rename, and delete them.
- Answer Updates — a user can subscribe to a thread. Miso then watches that topic. When Miso finds important new content, it appends a fresh follow-up answer and raises an unread indicator.
Availability. Miso enables User History and Answer Updates per app. To turn them on for your app, contact your Miso representative.
All endpoints use POST. Pass the API key as ?api_key=YOUR_KEY or in the
X-Api-Key header. Base URL: https://api.askmiso.com.
When the feature is off
User history is enabled per app. Until Miso turns it on, every endpoint on this
page returns 403, and no history is recorded:
{
"message": "user history is not enabled for this app"
}
Ask your Miso representative to enable it. A 403 here is a setting, not your
key: the same key reaches the rest of the API.
How records are created
You do not write history yourself. Miso does it automatically. When a signed-in
user asks a question through the Answer API
(POST /v1/ask/questions), Miso records it in the background.
- Only the first question of a conversation becomes a history row (a thread). Miso attaches the follow-ups to that thread and moves it to the top of the list.
- Miso records only questions from an authenticated user. It never saves an anonymous question to history.
Exclude a question from history
To keep a question out of history, pass log_user_history: false on
POST /v1/ask/questions. Miso then does not save that question. Use this for a
per-question "private mode" toggle. The default is true.
{
"user_id": "alice",
"question": "How do interest rate cuts affect mortgages?",
"log_user_history": false
}
This controls only the saved history for the user. Your usage analytics do not change.
Authentication
Every User History endpoint needs your API key and an authenticated user. You authenticate the user in one of two ways. The method depends on which key you use.
| Key type | Where the user comes from | JWT |
|---|---|---|
| Publishable key (browser) | the JWT | Required |
| Secret key (server) | user_id in the request body |
Optional |
-
Secret key (server-side). The key authenticates the caller. You pass
user_idin the body, and Miso trusts it. -
Publishable key (browser). This key is safe to put in the browser. It must
carry a signed JWT that proves who the user is. Pass the JWT as
Authorization: Bearer <token>(preferred) or in ajwt_tokenbody field. Sign the JWT with your app's secret key and the HS256 algorithm. Itsuser_id(orsub) claim is the authenticated user.
If a request supplies user_id in both the JWT and the body, the two values
must match. If they do not match, Miso returns 401.
Create a browser JWT (server-side)
Sign a short-lived token on your server with your secret key. Then send the token to the browser.
import hmac, hashlib, base64, json, time
def b64(d): return base64.urlsafe_b64encode(d).rstrip(b"=").decode()
secret = "YOUR_SECRET_API_KEY" # sign with your app's SECRET key
header = b64(json.dumps({"alg": "HS256", "typ": "JWT"}).encode())
claims = {"user_id": "alice", "exp": int(time.time()) + 3600}
payload = b64(json.dumps(claims).encode())
signing = f"{header}.{payload}".encode()
sig = b64(hmac.new(secret.encode(), signing, hashlib.sha256).digest())
jwt = f"{header}.{payload}.{sig}"
Read and manage history
A thread is a root question plus the follow-ups that hang off it. The root
question id is the thread_id. Reading a thread is two calls: …/thread
for the ids, then /v1/ask/answers for the answers in one round trip.
List a user's threads
POST /v1/ask/user_history/list
| Field | Type | Default | Notes |
|---|---|---|---|
user_id |
string | required | The signed-in user. |
rows |
integer | 100 |
Page size, 1–200. |
start |
integer | 0 |
Pagination offset. |
before |
datetime | — | Return only threads before this time (cursor paging). |
The response is an object, and it tells you whether another page exists:
{
"message": "success",
"data": {
"threads": [
{
"id": "aGlzdG9yeS0x",
"time": "2026-06-12T10:15:00Z",
"question_id": "0e1f2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"title": "How do interest rate cuts affect mortgages?",
"subscribed": true,
"has_new": false
}
],
"has_more": true,
"start": 0,
"rows": 20
}
}
curl -X POST "https://api.askmiso.com/v1/ask/user_history/list" \
-H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "alice",
"rows": 20
}'
const res = await fetch("https://api.askmiso.com/v1/ask/user_history/list", {
method: "POST",
headers: {
"X-Api-Key": KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: "alice",
rows: 20
}),
});
const data = (await res.json()).data;
import requests
res = requests.post(
"https://api.askmiso.com/v1/ask/user_history/list",
headers={"X-Api-Key": KEY},
json={
"user_id": "alice",
"rows": 20
},
)
data = res.json()["data"]
| Field | What it is |
|---|---|
id |
An opaque row id. Pass it to the delete endpoint. |
title |
The thread's display name. Render it as the row label. Miso writes it from the first question, and rename replaces it. |
question_id |
The thread's root question. Use it to open the thread or fetch the answer. |
time |
The last activity. The list shows the most recent first. |
subscribed |
Whether this thread subscribes to Answer Updates. |
has_new |
The thread-level unread indicator. An unseen update is ready. |
Read a thread
POST /v1/ask/user_history/thread
Returns the question_id values in a thread, in order. The root question comes
first, then the follow-ups.
| Field | Type | Default | Notes |
|---|---|---|---|
thread_id |
UUID | required | The thread's root question id. |
user_id |
string | — | If you supply it, Miso confirms that the user owns the thread. |
rows |
integer | 30 |
Page size (max 100). |
after |
UUID | — | Cursor — return the questions after this id. |
order |
string | asc |
asc = root → newest. desc = newest → root. |
To open a long thread at the newest answer, send order: "desc". The first
page then holds the most recent questions, and after pages backwards
toward the root. This is what a chat view wants: render the newest turn at
once, and fetch the older ones as the reader scrolls up.
{
"message": "success",
"data": {
"question_ids": [
"0e1f2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
],
"has_more": false
}
}
Paging. To get the next page, pass the last question_id of the current
page as after. Repeat until has_more is false.
Fetch answers in bulk
POST /v1/ask/answers
After you have the question_id values of a thread, fetch all their answers in
one request. This is faster than one call per question. This endpoint reads
existing answers only. It does not re-run the answer pipeline.
| Field | Type | Notes |
|---|---|---|
question_ids |
UUID[] | The ids to fetch (1–50). |
curl -X POST "https://api.askmiso.com/v1/ask/answers" \
-H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"question_ids": [
"0e1f2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
]
}'
const res = await fetch("https://api.askmiso.com/v1/ask/answers", {
method: "POST",
headers: {
"X-Api-Key": KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
question_ids: [
"0e1f2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
]
}),
});
const data = (await res.json()).data;
import requests
res = requests.post(
"https://api.askmiso.com/v1/ask/answers",
headers={"X-Api-Key": KEY},
json={
"question_ids": [
"0e1f2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
]
},
)
data = res.json()["data"]
{
"message": "success",
"data": [
{
"question_id": "0e1f2a3b-…",
"question": "…",
"answer": "…",
"sources": []
},
null
]
}
-
The
dataarray matches your input.data[i]corresponds toquestion_ids[i]. -
An id that does not exist, or does not belong to your app, returns
null. As a result, answers never leak across apps. -
Each entry matches the
dataofGET /v1/ask/questions/{question_id}/answer(see Answer response fields). - To render a whole thread, use this endpoint instead of many single fetches. It makes one round trip, not many.
Rename a thread
POST /v1/ask/user_history/thread/rename
| Field | Type | Notes |
|---|---|---|
thread_id |
UUID | The thread's root question id. |
user_id |
string | Required. |
title |
string | The new display title (max 1000 chars). |
Miso echoes the new name:
{
"message": "success",
"data": {
"question_id": "0e1f2a3b-4c5d-6e7f-8a9b-0c1d2e3f4a5b",
"title": "Rocky Linux — renamed"
}
}
The list endpoint then returns it as each row's title. If the thread does
not exist, the call returns 404.
Miso titles a thread from its first question. If the question is longer than about 60 characters, Miso generates a short title.
Delete specific threads
POST /v1/ask/user_history/delete
| Field | Type | Notes |
|---|---|---|
question_ids |
UUID[] | The question_id values from the list endpoint. |
user_id |
string | Required. |
{
"user_id": "alice",
"question_ids": ["7dc689d2-0e1f-4a3b-8c5d-6e7f8a9b0c1d"]
}
The opaque id on each row also works, in an ids array. Send a row id
inside question_ids and the call returns 422, because that field takes a
UUID.
Read deleted_count and compare it with the number of ids you sent:
{
"message": "success",
"data": {
"deleted_count": 2
}
}
Delete all history
POST /v1/ask/user_history/delete_all
| Field | Type | Notes |
|---|---|---|
user_id |
string | Required. |
Every delete is a soft delete. Miso can reverse it. But the record disappears from every read immediately.
Putting it together
A typical front-end flow:
-
On load — call
…/user_history/listfor the sidebar. Read each thread'shas_newfield for its own indicator, andhas_moreto page. -
Open a thread — call
…/threadwith itsthread_id, then…/answersto fetch every answer in one call. - Let users manage history —
…/rename,…/delete, and…/delete_all.
Next
Answer Updates is the next step. It covers how a reader subscribes to a thread, how Miso appends a fresh answer when the story moves on, and how the indicators clear.
