Authentication
Two keys. Which one to use, and where each belongs.
Every Miso request carries an API key. Which key you use decides what the request can reach, so start here before you write any integration code.
You get two keys per environment:
| Key | Where it belongs | What it reaches |
|---|---|---|
| Secret | Your server | Every endpoint, including uploads and deletes. |
| Publishable | The browser | POST /v1/interactions, and read-only calls. |
The secret key trusts the caller completely, so it never leaves your back end. The publishable key travels to the browser, so Miso limits what it can do.
Which hostname? Browser code calls
https://api-edge.askmiso.com, which is behind a CDN. Server code callshttps://api.askmiso.com. Both take the same keys. See Two hostnames.
API Keys
Miso uses API keys to authenticate requests. You can view and manage your API keys in the Dojo Dashboard.
Each environment has its own set of keys: one secret key and one publishable key. Pass the appropriate key with every API request.
Environments
There are three environments in Miso:
- Playground — A read-only tutorial environment pre-loaded with sample data. Use this to explore Miso's APIs without affecting your own data.
- Development — For staging, QA, and experimentation. Use this environment to test your integration before going live.
- Production — Your live environment. Use this for all requests that serve real users.
Secret API Key
The secret API key grants full access to all Miso API endpoints, including data ingestion and engine queries.
Keep this key private. Never expose it in client-side code or public repositories. If the key is compromised, revoke it in the Dojo Dashboard and generate a new one.
You can pass the secret key in either of two ways:
As a request header:
X-API-KEY: YOUR_SECRET_KEY
As a query parameter:
GET /v1/recommendation/user_to_products?api_key=YOUR_SECRET_KEY
Publishable API Key
The publishable API key is intended for use in front-end code (for example, browser JavaScript). Use it to stream interactions from the browser, and to retrieve read-only search, recommendation and answer results.
Most of what it reaches works for a signed-out visitor, on an anonymous_id.
You prove who the reader is only when the request claims a user_id. Send
a signed JWT for that. The reader's own history, under
/v1/ask/user_history/…, always needs one.
Below is the signed-in path. A signed-out visitor skips steps 1 to 3 and sends the publishable key on its own.
Pass the publishable key as a query parameter:
POST /v1/interactions?api_key=YOUR_PUBLISHABLE_KEY
JWT (user authentication)
An API key says which app is calling. A JWT says which reader. Send one
when a request speaks for a signed-in person, so Miso can trust the user_id
even though the call comes from a browser holding a publishable key.
Where it matters:
-
The reader's own history. Every
POST /v1/ask/user_history/…endpoint requires an authenticated user. With a publishable key and no token, they answer401 Authenticated user_id is required. -
Answers tied to a person.
POST /v1/ask/questionshonours the token and takes theuser_idfrom it. -
Metering. The token can also carry
user_type, so the reader's tier comes from your server and a page cannot change it. See Metering anduser_typebelow.
A secret key already carries that trust, so server-side calls need no token.
Sign the token
HS256, and the signing secret is your Secret API Key — the one in Dojo for that environment. There is no separate JWT secret to configure, and no RS256 or JWKS. Rotating the secret key invalidates every token signed with the old one.
Mint tokens on your server, where the secret already lives. Keep them short, 15 minutes to an hour, and refresh as the reader keeps browsing.
import jwt from "jsonwebtoken";
const token = jwt.sign(
{
user_id: "user-123",
user_type: "subscriber", // optional, for metering
exp: Math.floor(Date.now() / 1000) + 15 * 60,
},
process.env.MISO_SECRET_API_KEY, // your Secret API Key
{ algorithm: "HS256" }
);
import jwt, time
token = jwt.encode(
{
"user_id": "user-123",
"user_type": "subscriber", # optional, for metering
"exp": int(time.time()) + 900,
},
MISO_SECRET_API_KEY,
algorithm="HS256",
)
The claims
| Claim | Notes |
|---|---|
user_id |
The reader. Preferred. |
sub |
The same job, for tokens you already issue. user_id wins when both appear. |
user_type |
The reader's tier, for metering. |
exp |
Unix seconds. Checked when present. Recommended on every token. |
nbf |
Checked when present. |
iat, iss, aud |
Accepted, and not validated. |
Any other claim rides along. On POST /v1/ask/questions the extras merge into
the request metadata, so a subscription tier in the token reaches your
analytics. Put nothing secret in a token. It travels to the browser, and
anyone holding it can read it.
Metering and user_type
Metering gives each reader tier its own question
allowance, and user_type names the tier. Put it in the token next to
user_id, so the tier comes from your server. In the following token, we mark
the reader as a subscriber:
{ "user_id": "user-123", "user_type": "subscriber", "exp": 1789500000 }
The values are anonymous, registered, free, subscriber, paid and
internal. Anything else is 401 Invalid JWT token: unknown user_type ….
A reader can be a subscriber today and lapse tomorrow, so mint the token on
each visit, after your own session check, and keep exp short.
NOTE: The request body also accepts
{ "user_type": "subscriber" }. If you send it and the token carriesuser_typetoo, the two must match, or the request is401 Token does not match the user_type.
Send the token
Either way works, and the header wins when you send both:
POST /v1/ask/questions?api_key=YOUR_PUBLISHABLE_KEY
Authorization: Bearer <jwt>
Content-Type: application/json
{ "question": "What changed in the 2026 budget?" }
Or as a jwt_token field in the request body:
POST /v1/ask/questions?api_key=YOUR_PUBLISHABLE_KEY
Content-Type: application/json
{ "question": "What changed in the 2026 budget?", "jwt_token": "<jwt>" }
Leave user_id and user_type out of the body and Miso takes them from the
token. Send a field both ways and the two must match.
Miso strips the token from the request before anything is logged.
What comes back when it fails
| Status | Message | Cause |
|---|---|---|
401 |
Invalid JWT token |
Bad signature, or a malformed token. |
401 |
JWT token expired (exp=…) |
exp is in the past. |
401 |
JWT token not yet valid |
nbf is in the future. |
401 |
Token does not match the user |
The body user_id differs from the token's. |
401 |
Token does not match the user_type |
The body user_type differs from the token's. |
401 |
Invalid JWT token: unknown user_type … |
The user_type claim is outside the allowed list. |
401 |
Authenticated user_id is required |
The endpoint needs a reader, and no token arrived. |
Next
- API Overview — conventions shared by every endpoint.
- Errors & Rate Limits — what a
401and a403mean here. - User History — the browser flow, with a signed JWT.
