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 calls https://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 answer 401 Authenticated user_id is required.
  • Answers tied to a person. POST /v1/ask/questions honours the token and takes the user_id from 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 and user_type below.

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 carries user_type too, the two must match, or the request is 401 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