# Mythos SDK > Mythos lets users buy credits once and spend them across apps. The Mythos SDK (Node: @mythos-work/sdk, Python: mythos-sdk) handles launch, sessions, charging and LLM billing. Start with a quickstart; use createMythos() — never the 0.0.x primitives. --- # Introduction Source: https://docs.mythos.work/ # Introduction Mythos is a platform where users buy credits once and spend them across apps. Producers integrate the Mythos SDK to make their apps launchable and billable from the Mythos marketplace. The SDK handles launch token exchange, secure sessions, charge confirmation, wallet charging, LLM billing, and the routes Mythos uses to verify an integration. You work with `createMythos()` in Node or `create_mythos()` in Python instead of wiring those protocols yourself. ## Integrate in 3 steps 1. Install the package and run the scaffold command. 2. Wire the SDK's handler or router into one route. 3. Confirm in the browser, then call `charge` on the server for a billable action. ## Pick your quickstart - [Next.js App Router](https://docs.mythos.work/getting-started/quickstart-nextjs-app) - [Next.js Pages Router](https://docs.mythos.work/getting-started/quickstart-nextjs-pages) - [Express](https://docs.mythos.work/getting-started/quickstart-express) - [FastAPI](https://docs.mythos.work/getting-started/quickstart-fastapi) ## Packages | Package | Runtime | |---|---| | `@mythos-work/sdk` | Node.js 20+, Next.js, and Express | | `mythos-sdk` | Python 3.11+ and FastAPI | ## Next steps - [Install the SDK](https://docs.mythos.work/getting-started/install) - [Launch sessions](https://docs.mythos.work/concepts/launch-sessions) - [Use with AI agents](https://docs.mythos.work/ai-agents) --- # Install the SDK Source: https://docs.mythos.work/getting-started/install # Install the SDK Install the SDK, then let its CLI scaffold the integration files. ## Node.js / TypeScript ```bash npm i @mythos-work/sdk ``` **Runtime:** Node.js 20 or newer. ## Python ```bash pip install "mythos-sdk[fastapi,llm]" ``` **Runtime:** Python 3.11+. ## Scaffold Run this from the application root: ```bash npx @mythos-work/sdk init ``` The command detects Next.js App Router, Next.js Pages Router, Express, or FastAPI. It never overwrites files or edits `next.config.*` or `main.py`; follow the printed instructions for those manual lines. ## Next steps - [Next.js App Router quickstart](https://docs.mythos.work/getting-started/quickstart-nextjs-app) - [Express quickstart](https://docs.mythos.work/getting-started/quickstart-express) - [FastAPI quickstart](https://docs.mythos.work/getting-started/quickstart-fastapi) - [Configuration](https://docs.mythos.work/reference/configuration) --- # Quickstart — Next.js (App Router) Source: https://docs.mythos.work/getting-started/quickstart-nextjs-app Zero to a billed charge in 5 steps. ## 1. Install and scaffold ```bash npm i @mythos-work/sdk npx @mythos-work/sdk init ``` `init` creates the files below and adds env placeholders to `.env.local`. Or create them by hand: ## 2. Environment ```bash # .env.local MYTHOS_LISTING_ID=your-listing-id # from the Mythos dashboard # openssl rand -base64 32 (≥ 32 chars) MYTHOS_SESSION_SECRET= # Mythos dashboard → Settings → Mythos key MYTHOS_KEY= ``` ## 3. Server: one file, one route Missing `MYTHOS_KEY` is soft in 0.5.0; static listing launches remain silent, while dynamic registration, handshake and callbacks warn once per process. It will be required in 0.6.0. Update deployment env after key regeneration. See the [binding matrix](https://docs.mythos.work/reference/configuration). | Troubleshooting code | Fix | |---|---| | `PRODUCER_MISMATCH` | The `MYTHOS_KEY` in this deployment belongs to a different Producer, or was regenerated. Copy the current key from the dashboard and redeploy. | ```ts // lib/mythos.ts import { createMythos } from '@mythos-work/sdk'; export const mythos = createMythos(); ``` ```ts // app/api/mythos/[...mythos]/route.ts import { mythos } from '../../../../lib/mythos'; export const { GET, POST } = mythos.handlers; ``` ```js // next.config.js — add these rewrites async rewrites() { return [ { source: '/.well-known/mythos-handshake', destination: '/api/mythos/handshake' }, { source: '/.well-known/mythos-listing-registered', destination: '/api/mythos/listing-registered' }, ]; } ``` ## 4. Charge credits ```ts // app/api/calculate/route.ts import { MythosError } from '@mythos-work/sdk'; import { mythos } from '@/lib/mythos'; export async function POST(req: Request) { const { a, b } = await req.json(); try { await mythos.charge(req, { credits: 1, reason: 'calculate' }); return Response.json({ success: true, data: { result: a + b } }); } catch (err) { if (err instanceof MythosError) return Response.json({ success: false, code: err.code }, { status: err.httpStatus }); throw err; } } ``` ## 5. Page ```tsx 'use client'; import { useMythos } from '@mythos-work/sdk/react'; export default function Calculator() { const { status, session, fetch, confirmCharge, relaunch } = useMythos(); if (status === 'loading') return
Loading…
; if (status === 'standalone') returnOpen this app from Mythos.
; if (status === 'expired') return ; if (status === 'error') returnSomething went wrong.
; async function calculate() { const { approved } = await confirmCharge({ credits: 1, reason: '1 + 2' }); if (approved) await fetch('/api/calculate', { method: 'POST', body: JSON.stringify({ a: 1, b: 2 }) }); } return ; } ``` ## Check it ```bash npx @mythos-work/sdk doctor ``` Then launch your listing from the Mythos dashboard. --- # Quickstart — Next.js (Pages Router) Source: https://docs.mythos.work/getting-started/quickstart-nextjs-pages Zero to a billed charge in 5 steps. ## 1. Install and scaffold ```bash npm i @mythos-work/sdk npx @mythos-work/sdk init ``` ## 2. Environment ```bash # .env.local MYTHOS_LISTING_ID=your-listing-id # openssl rand -base64 32 (≥ 32 chars) MYTHOS_SESSION_SECRET= # Mythos dashboard → Settings → Mythos key MYTHOS_KEY= ``` ## 3. Server: one file, one route Missing `MYTHOS_KEY` is soft in 0.5.0; static listing launches remain silent, while dynamic registration, handshake and callbacks warn once per process. It will be required in 0.6.0. Update deployment env after key regeneration. See the [binding matrix](https://docs.mythos.work/reference/configuration). | Troubleshooting code | Fix | |---|---| | `PRODUCER_MISMATCH` | The `MYTHOS_KEY` in this deployment belongs to a different Producer, or was regenerated. Copy the current key from the dashboard and redeploy. | ```ts // lib/mythos.ts import { createMythos } from '@mythos-work/sdk'; export const mythos = createMythos(); ``` ```ts // pages/api/mythos/[...mythos].ts import { pagesHandler } from '@mythos-work/sdk/next'; import { mythos } from '../../../lib/mythos'; export default pagesHandler(mythos); ``` ```js // next.config.js — add these rewrites async rewrites() { return [ { source: '/.well-known/mythos-handshake', destination: '/api/mythos/handshake' }, { source: '/.well-known/mythos-listing-registered', destination: '/api/mythos/listing-registered' }, ]; } ``` ## 4. Charge credits ```ts // pages/api/calculate.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { MythosError } from '@mythos-work/sdk'; import { mythos } from '../../lib/mythos'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await mythos.charge(req, { credits: 1, reason: 'calculate' }); res.json({ success: true, data: { result: req.body.a + req.body.b } }); } catch (err) { if (err instanceof MythosError) return res.status(err.httpStatus).json({ success: false, error: err.message, code: err.code }); throw err; } } ``` ## 5. Page ```tsx import { useMythos } from '@mythos-work/sdk/react'; export default function Calculator() { const { status, fetch, confirmCharge, relaunch } = useMythos(); if (status === 'loading') returnLoading…
; if (status === 'standalone') returnOpen this app from Mythos.
; if (status === 'expired') return ; if (status === 'error') returnSomething went wrong.
; async function calculate() { const { approved } = await confirmCharge({ credits: 1, reason: '1 + 2' }); if (approved) await fetch('/api/calculate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ a: 1, b: 2 }) }); } return ; } ``` ## Check it ```bash npx @mythos-work/sdk doctor ``` Then launch your listing from the Mythos dashboard. --- # Quickstart — Express Source: https://docs.mythos.work/getting-started/quickstart-express Zero to a billed charge in 5 steps. ## 1. Install and scaffold ```bash npm i @mythos-work/sdk express npx @mythos-work/sdk init ``` ## 2. Environment ```bash # .env MYTHOS_LISTING_ID=your-listing-id # openssl rand -base64 32 (≥ 32 chars) MYTHOS_SESSION_SECRET= # Mythos dashboard → Settings → Mythos key MYTHOS_KEY= ``` Node does not read `.env` on its own — step 5 starts the server with `--env-file`. In production, set these in your host's environment instead. ## 3. Server Missing `MYTHOS_KEY` is soft in 0.5.0; static listing launches remain silent, while dynamic registration, handshake and callbacks warn once per process. It will be required in 0.6.0. Update deployment env after key regeneration. See the [binding matrix](https://docs.mythos.work/reference/configuration). | Troubleshooting code | Fix | |---|---| | `PRODUCER_MISMATCH` | The `MYTHOS_KEY` in this deployment belongs to a different Producer, or was regenerated. Copy the current key from the dashboard and redeploy. | ```ts // server.ts import express from 'express'; import { createMythos, MythosError } from '@mythos-work/sdk'; import { mythosExpress } from '@mythos-work/sdk/express'; const app = express(); const mythos = createMythos(); app.use(express.json()); app.use(mythosExpress(mythos)); app.post('/api/calculate', async (req, res, next) => { try { await mythos.charge(req, { credits: 1, reason: 'calculate' }); res.json({ success: true, data: { result: req.body.a + req.body.b } }); } catch (err) { if (err instanceof MythosError) return res.status(err.httpStatus).json({ success: false, error: err.message, code: err.code }); next(err); } }); app.listen(3000); ``` ## 4. Page ```html ``` ## 5. Run and check it ```bash node --env-file=.env server.js # Node ≥ 20.6 (compile server.ts first, or use tsx --env-file=.env server.ts) npx @mythos-work/sdk doctor ``` Already using `dotenv`? `import 'dotenv/config'` at the top of `server.ts` works too. Then launch your listing from the Mythos dashboard. --- # Quickstart — FastAPI Source: https://docs.mythos.work/getting-started/quickstart-fastapi Zero to a billed charge in 5 steps. ## 1. Install ```bash pip install "mythos-sdk[fastapi,llm]" "fastapi[standard]" npx @mythos-work/sdk init ``` ## 2. Environment ```bash # .env MYTHOS_LISTING_ID=your-listing-id # openssl rand -base64 32 (≥ 32 chars) MYTHOS_SESSION_SECRET= # Mythos dashboard → Settings → Mythos key MYTHOS_KEY= ``` uvicorn does not read `.env` on its own — step 5 starts it with `--env-file`. In production, set these in your host's environment instead. ## 3. Server Missing `MYTHOS_KEY` is soft in 0.5.0; static listing launches remain silent, while dynamic registration, handshake and callbacks warn once per process. It will be required in 0.6.0. Update deployment env after key regeneration. See the [binding matrix](https://docs.mythos.work/reference/configuration). | Troubleshooting code | Fix | |---|---| | `PRODUCER_MISMATCH` | The `MYTHOS_KEY` in this deployment belongs to a different Producer, or was regenerated. Copy the current key from the dashboard and redeploy. | `init` created `mythos_setup.py` (`mythos = create_mythos()`); import it in `main.py`: ```python from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from mythos_sdk import MythosError from mythos_setup import mythos app = FastAPI() app.include_router(mythos.router) @app.post("/api/calculate") async def calculate(request: Request): body = await request.json() try: await mythos.charge(request, credits=1, reason="calculate") return {"success": True, "data": {"result": body["a"] + body["b"]}} except MythosError as err: return JSONResponse({"success": False, "error": str(err), "code": err.code}, status_code=err.http_status) ``` Defining `mythos = create_mythos()` directly in `main.py` is equivalent. ## 4. Page ```html ``` ## 5. Run and check it ```bash uvicorn main:app --env-file .env python -m mythos_sdk doctor ``` Then launch your listing from the Mythos dashboard. --- # Browser client Source: https://docs.mythos.work/getting-started/browser-client # Browser client Choose the browser entry point that fits your application; each exposes the same session-aware client. | Application | Import | |---|---| | React | `import { useMythos } from '@mythos-work/sdk/react'` | | Any bundled page | `import { initMythos } from '@mythos-work/sdk/client'` | | No bundler | The global ` ``` ## Statuses | Status | Meaning | What your UI should do | |---|---|---| | `loading` | Session initialization is in progress | Show a loading state | | `mythos` | A Mythos launch session is active | Enable Mythos billing | | `standalone` | The app was opened outside Mythos | Use your own auth or paywall | | `expired` | The launch session expired | Embedded clients request a relaunch automatically; offer `relaunch()` as a manual fallback | | `error` | Initialization failed | Show `error` and a retry path | ## Automatic relaunch `initMythos({ autoRelaunch: false })` disables the default one-time `mythos:relaunch` request to the embedding Mythos dashboard on expiry. For manual recovery, display a `relaunch()` button when status is `expired`. Standalone pages never post a relaunch. The client schedules a silent session refresh before identity expiry and checks again when a background tab becomes visible. ## Transport The client uses an HTTP-only cookie first, so session credentials are normally unavailable to JavaScript. If the browser blocks cookies, the SDK falls back to its managed transport. Your code never reads, parses, or stores Mythos tokens in `localStorage` or `sessionStorage`. This keeps transport details inside the SDK while preserving the same API in embedded and standalone browsers. Use `m.fetch` (or the `fetch` returned by `useMythos`) like native `fetch`; it automatically attaches the current session. Before a billable action, call `confirmCharge({ credits, reason })` for a fixed price, or `confirmCharge({ kind: 'llm', reason })` for LLM calls — those are usage-based (provider cost plus your margin, settled after the response), so they take no credits. --- # Deploy checklist Source: https://docs.mythos.work/getting-started/deploy-checklist # Deploy checklist Check every item in the deployed environment, not only on your laptop. - [ ] `MYTHOS_SESSION_SECRET` is at least 32 characters and is set in the host environment, such as Vercel. - [ ] `MYTHOS_KEY` is copied from Dashboard → Settings → Mythos key into the host environment. Missing is soft in 0.5.0 (static listing launches remain silent), required in 0.6.0. Update env and redeploy after key regeneration. - [ ] `MYTHOS_LISTING_ID` is set to the listing ID from the Mythos dashboard. - [ ] Express/FastAPI: variables are set in the host environment. `.env` files are not read in production unless your start command loads them (`node --env-file=.env`, `uvicorn --env-file .env`). - [ ] The app uses HTTPS; the session cookie requires `Secure`. - [ ] `https://your-app.example/.well-known/mythos-handshake` reaches the SDK handler. - [ ] `npx @mythos-work/sdk doctor` passes every check. After deployment, run a test launch from the Mythos dashboard and complete one billed action. --- # How it works Source: https://docs.mythos.work/getting-started/how-it-works # How it works Mythos launches a Consumer into your app with a short-lived, signed launch token. The SDK exchanges it once, establishes the app session, and exposes session data to your server and browser code. ## Launch flow ```mermaid sequenceDiagram participant Consumer participant Mythos participant App Consumer->>Mythos: Launch listing Mythos->>App: Redirect with launch token App->>Mythos: Exchange token once Mythos-->>App: Session and billing identity App-->>Consumer: Session-ready UI Consumer->>App: Approve billable action App->>Mythos: Charge credits ``` 1. The Consumer launches your listing from Mythos. 2. Mythos redirects the browser to your app with a signed, single-use token. 3. The browser client and server handler exchange that token for a secure session. 4. `getSession` returns the Consumer and listing context, or `null` when the app is opened standalone. 5. The browser calls `confirmCharge` before a billable action. 6. The server calls `charge` after approval, or uses `llm` for gateway-routed model billing. ## Publish handshake Before a listing goes live, Mythos requests `/.well-known/mythos-handshake`. The handler exposed by `mythos.handlers`, `pagesHandler`, `mythosExpress`, or `mythos.router` answers that check. The optional listing-registered callback lets an app persist dynamically created listing IDs. The SDK does all of this for you. Start with the [quickstart for your framework](https://docs.mythos.work/getting-started/quickstart-nextjs-app) instead of implementing token exchange or metering yourself. --- # Verify your integration Source: https://docs.mythos.work/getting-started/verify-integration # Verify your integration Run the SDK doctor after wiring the integration: ```bash npx @mythos-work/sdk doctor # FastAPI projects can also use: python -m mythos_sdk doctor ``` Doctor validates configuration, API reachability, listing IDs, route wiring, Next.js rewrites, and the installed SDK version. Fix every failed check before deploying. ## Dashboard test launch 1. Start or deploy your app over HTTPS. 2. Open the listing in the Mythos dashboard and run a test launch. 3. Confirm the UI reaches the `mythos` status. 4. Approve and complete one billed action. 5. Confirm the charge appears in Mythos. If the launch fails, run doctor in the same environment as the deployed app and use [Troubleshooting](https://docs.mythos.work/resources/troubleshooting). --- # Standalone mode Source: https://docs.mythos.work/recipes/standalone-mode # Standalone mode The same app can work both inside and outside Mythos. In the browser, `status === 'standalone'` means the page was not launched through Mythos. On the server, `await mythos.getSession(req) === null` means the same thing. In that branch, use your existing authentication and paywall instead of Mythos billing. ```ts const session = await mythos.getSession(req); if (!session) return handleWithYourOwnAuth(req); ``` For LLM calls, provide an OpenAI fallback for standalone users: ```ts const client = await mythos.llm(req, { apiKey: process.env.OPENAI_API_KEY, fallback: ownOpenAiClient }); ``` Without a session and without `fallback`, `llm()` throws `LLM_SESSION_REQUIRED` or `LLM_IDENTITY_REQUIRED`. --- # LLM chat Source: https://docs.mythos.work/recipes/llm-chat # LLM chat Ask for approval in the browser, then route the completion through the Mythos LLM gateway on the server. LLM cost is usage-based — provider cost plus your margin, settled after the response — so the dialog shows no amount and you pass no credits. ```ts const { approved } = await confirmCharge({ kind: 'llm', reason: 'chat' }); if (approved) await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ message }) }); ``` ```ts const client = await mythos.llm(req, { apiKey: process.env.OPENAI_API_KEY }); const completion = await client.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: message }], }); const billing = mythos.billing(completion); return Response.json({ completion, billing }); ``` `mythos.billing(completion)` returns Mythos billing metadata when the completion contains it, otherwise `null`. --- # Fixed-price charge Source: https://docs.mythos.work/recipes/fixed-price-charge # Fixed-price charge Confirm the price in the browser before starting a fixed-price action. ```ts const idempotencyKey = crypto.randomUUID(); const { approved, consentId } = await confirmCharge({ credits: 2, reason: 'export' }); if (approved) await fetch('/api/export', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ idempotencyKey, consentId }), }); ``` Send the same client-generated UUID and consent ID to the server. Reuse the UUID whenever the client retries the same action so retries never double-charge. ```ts const { idempotencyKey, consentId } = await req.json(); await mythos.charge(req, { credits: 2, reason: 'export', idempotencyKey, consentId }); ``` --- # Session expiry Source: https://docs.mythos.work/recipes/session-expiry # Session expiry When `useMythos` or `initMythos` reports `status === 'expired'`, stop billable actions and offer a relaunch. ```tsx if (status === 'expired') { return ; } ``` `relaunch()` asks the Mythos dashboard to launch the listing again. Outside the dashboard, explain that the user must reopen the app from Mythos. Until backend issue #179 ships, fixed charges return `SESSION_EXPIRED` about five minutes after launch. Handle that error as an expired session and offer the same relaunch action. --- # Token types Source: https://docs.mythos.work/concepts/token-types # Token types Mythos uses distinct signed tokens for publishing, launching, and dynamic listing registration. The high-level SDK handlers validate each token in the correct route and never expose raw token handling to your application code. | Token | Purpose | SDK route | |---|---|---| | Handshake | Proves the app is reachable before publishing | `/.well-known/mythos-handshake` | | Launch | Starts a single-use Consumer session | `/api/mythos/session` | | Listing registered | Delivers a dynamically created listing ID | `/.well-known/mythos-listing-registered` | Your browser code uses `useMythos()` or `initMythos()`, and server code uses the handlers returned by `createMythos()` or the router returned by `create_mythos()`. Do not parse or move these tokens yourself. See [Launch sessions](https://docs.mythos.work/concepts/launch-sessions) and [Dynamic listing IDs](https://docs.mythos.work/concepts/dynamic-listing-ids). --- # Launch sessions Source: https://docs.mythos.work/concepts/launch-sessions # Launch sessions A launch session connects a Consumer, a listing, and server-side billing context. ## Session lifecycle 1. A Consumer launches a listing from Mythos. 2. The SDK's browser client initializes against `/api/mythos/session`. 3. The SDK validates and consumes the launch once, then establishes secure transport. 4. Server routes read the session with `getSession` and perform charges with `charge`. 5. The browser calls the session route ahead of identity-token expiry; the SDK refreshes its identity and rewrites the cookie. 6. At hard expiry the browser reports `expired` and, when embedded, requests a new dashboard launch automatically. ## Lifetime and refresh The launch token is single-use and must be consumed within its five-minute launch window. Once consumed on the updated backend, its session has an **absolute eight-hour lifetime**. The sealed session cookie expires at that boundary. The LLM identity token remains short-lived (normally 30 minutes), but the browser revisits `/api/mythos/session` five minutes before its expiry to obtain a new token. A server-side `llm()` call can also refresh inline; that refresh does not rewrite the browser cookie, so create an `llm()` client per request. The session response includes `data.session` (public fields only), `sessionToken` (SDK-managed transport), `expiresAt`, and `refreshAt` (ISO timestamps; `refreshAt` is null without an identity token). Do not expose the identity token to the browser. When a background tab becomes visible, the browser checks whether refresh is overdue. A refresh call omits the original `?lt=` launch token. ```text Dashboard launch → SDK session route → consume → sealed cookie (up to 8 h) Browser timer/visibility → SDK session route → backend refresh → new cookie Server llm() → backend refresh if due → one request-scoped LLM client Hard expiry → SESSION_EXPIRED → embedded browser requests dashboard relaunch ``` On an older backend without `/sessions/:jti/refresh`, the SDK keeps the current session without failing the refresh request; the session ends at its original identity expiry. Pre-0.4 cookies need one relaunch because sealed Node and Python cookies now use the shared `v1.` format. Legacy low-level encoding primitives are unchanged. ## Session shape | Field | Description | |---|---| | `userId` | Consumer's Mythos user ID | | `email` | Consumer email | | `displayName` | Consumer display name | | `listingId` | Listing that was launched | | `sessionJti` | Session identifier managed by the SDK | ```ts const session = await mythos.getSession(req); if (!session) return handleStandaloneRequest(req); ``` `null` means the app was opened standalone, not that initialization failed. See [Standalone mode](https://docs.mythos.work/recipes/standalone-mode). ## Browser transport The SDK prefers an HTTP-only session cookie and manages a fallback when cookies are blocked. Application code must not store Mythos tokens in browser storage. Use the session-aware `fetch` returned by [the browser client](https://docs.mythos.work/getting-started/browser-client). --- # Usage metering Source: https://docs.mythos.work/concepts/usage-metering # Usage metering Bill a Consumer only after they approve the price and the server performs the billable action. ## Fixed-price flow 1. The browser calls `confirmCharge({ credits, reason })`. 2. If `approved` is true, the browser sends the action through the SDK's session-aware `fetch`. 3. The server calls `mythos.charge(req, { credits, reason, idempotencyKey, consentId })`. 4. The SDK returns `chargeId` and `sessionMeteredTotal`. ```ts const { approved, consentId } = await confirmCharge({ credits: 1, reason: 'calculate' }); if (approved) await fetch('/api/calculate', { method: 'POST', body: JSON.stringify({ consentId }), }); ``` ```ts await mythos.charge(req, { credits: 1, reason: 'calculate', consentId }); ``` Credits must be integers. Use a stable, client-generated `idempotencyKey` for retries of the same action; see [Fixed-price charge](https://docs.mythos.work/recipes/fixed-price-charge). ## LLM billing Use `mythos.llm` to route an OpenAI client through the Mythos gateway, then inspect the completion with `mythos.billing`. See [LLM chat](https://docs.mythos.work/recipes/llm-chat). --- # Dynamic listing IDs Source: https://docs.mythos.work/concepts/dynamic-listing-ids # Dynamic listing IDs Dynamic listing IDs let one deployment accept listings created after the app was deployed. Set `MYTHOS_KEY` from Dashboard → Settings → Mythos key before accepting auto-registration. The SDK checks the same `pkh` binding on callback, handshake and launch tokens; a mismatch is HTTP 401 and never reaches the registration callback. Without a key, a resolver or callback emits a once-per-process startup warning in 0.5.0. The key will be required in 0.6.0. See the [binding matrix and exact warning](https://docs.mythos.work/reference/configuration). ## Node Provide a resolver and a persistence callback when creating the SDK instance: ```ts export const mythos = createMythos({ resolveListingIds: async () => listingStore.getIds(), onListingRegistered: async (listingId) => listingStore.add(listingId), }); ``` `resolveListingIds` is optional and returns every listing ID accepted by this deployment. `onListingRegistered` is optional; setting it enables the listing-registered callback route. ## Python ```python mythos = create_mythos( resolve_listing_ids=listing_store.get_ids, on_listing_registered=listing_store.add, ) app.include_router(mythos.router) ``` Persist callbacks idempotently because Mythos may retry delivery. When the resolver supplies at least one ID, `MYTHOS_LISTING_ID` and `MYTHOS_LISTING_IDS` are optional. --- # `createMythos` Source: https://docs.mythos.work/reference/create-mythos # `createMythos` `createMythos()` is the high-level Node server API. Python provides the equivalent `create_mythos()` factory. ## Create a client ```ts import { createMythos } from '@mythos-work/sdk'; export const mythos = createMythos({ resolveListingIds: async () => ['listing-id'], onListingRegistered: async (listingId) => saveListingId(listingId), }); ``` Both options are optional. `resolveListingIds` provides dynamic listing IDs; `onListingRegistered` enables the listing-registered callback route. ```python from mythos_sdk import create_mythos mythos = create_mythos( resolve_listing_ids=get_listing_ids, on_listing_registered=save_listing_id, ) ``` ## `handlers` / `router` `mythos.handlers` contains web-standard `GET` and `POST` handlers for Next.js App Router. ```ts export const { GET, POST } = mythos.handlers; ``` Pages Router uses `pagesHandler(mythos)` from `@mythos-work/sdk/next`. Express uses `mythosExpress(mythos)` from `@mythos-work/sdk/express`. ```python app.include_router(mythos.router) ``` The Node handlers serve: - `GET /api/mythos/session` - `GET /api/mythos/handshake` - `GET|POST /api/mythos/listing-registered` when `onListingRegistered` is set Next.js maps the well-known paths with [the required rewrites](https://docs.mythos.work/getting-started/quickstart-nextjs-app). Express also serves `/.well-known/mythos-handshake` and `/.well-known/mythos-listing-registered` directly. The Python router serves `/api/mythos/session` and the two `/.well-known/mythos-*` routes directly; it does not expose `/api/mythos/listing-registered`. ## `getSession` / `get_session` ```ts const session = await mythos.getSession(req); ``` Returns `{ userId, email, displayName, listingId, sessionJti }` for a Mythos launch, or `null` when opened standalone. `req` may be a web `Request`, `NextApiRequest`, or Express request; it only needs `headers`. ```python session = await mythos.get_session(request) ``` Returns `MythosSession | None`. ## `charge` ```ts const result = await mythos.charge(req, { credits: 1, reason: 'calculate', idempotencyKey, consentId, }); ``` Returns `{ chargeId, sessionMeteredTotal }`. `reason`, `idempotencyKey`, and `consentId` are optional. ```python result = await mythos.charge( request, credits=1, reason="calculate", idempotency_key=idempotency_key, consent_id=consent_id, ) ``` ## `llm` ```ts const client = await mythos.llmLoading...
; if (status === 'standalone') return{error?.message}
; const { approved, consentId } = await confirmCharge({ credits: 1, reason: 'calculate' }); if (approved) await fetch('/api/calculate', { method: 'POST' }); // LLM chat: usage-based, no credits const { approved: chatApproved } = await confirmCharge({ kind: 'llm', reason: 'chat' }); ``` Non-React bundled apps import `initMythos` from `@mythos-work/sdk/client`. Unbundled pages use: ```html ``` ## Server Billing ```ts import OpenAI from 'openai'; await mythos.charge(req, { credits: 1, reason: 'calculate' }); const fallback = new OpenAI({ apiKey: process.env.PRODUCER_OPENAI_API_KEY }); const client = await mythos.llm(req, { apiKey: process.env.PRODUCER_OPENAI_API_KEY, fallback }); const billing = mythos.billing(completion); ``` Python uses `await mythos.charge(request, credits=1, reason="calculate")`, `await mythos.llm(request, api_key=producer_api_key)`, and `mythos.billing(completion)`. ## Environment | Variable | Requirement | |---|---| | `MYTHOS_SESSION_SECRET` | Required, at least 32 characters. Generate with `openssl rand -base64 32`. | | `MYTHOS_KEY` | Copy from Dashboard → Settings → Mythos key. Missing is a soft warning for auto-registration in 0.5.0; required in 0.6.0. | | `MYTHOS_LISTING_ID` or `MYTHOS_LISTING_IDS` | Required unless using `resolveListingIds` / `resolve_listing_ids`. | | `MYTHOS_API_URL` | Optional; defaults to `https://api.mythos.work`. | | `MYTHOS_DEBUG=1` | Optional verbose diagnostics. | ```dotenv # openssl rand -base64 32 (>= 32 chars) MYTHOS_SESSION_SECRET= # Mythos dashboard → Settings → Mythos key MYTHOS_KEY= ``` ### Producer binding (Node and Python) `MYTHOS_KEY` is a Mythos-generated, non-secret identifier (`mk_` + 32 base64url characters). Keep it in server deployment configuration. Regenerating it requires updating the environment and redeploying. Startup rejects a malformed key with `MythosConfigError('MYTHOS_KEY is malformed. Copy it again from the Mythos dashboard.')`. Launch, handshake (`handshake-check`) and callback (`listing_registered`) tokens use the same `pkh`: base64url without padding of SHA-256 of UTF-8 bytes of the **full key string**, including `mk_`. The backend omits `pkh` when the owner has no key. Literal hash fixture (not a valid deployment key): `mk_test` → `7i08_9pmD6r4prkeKSsCnOFaCAduf8cz4ogjmhGlpG0`. | Key | Token `pkh` | Result | |---|---|---| | Set | Matches hash(key) | Accept | | Set | Different or missing | Reject with `ProducerMismatchError` / `PRODUCER_MISMATCH`, HTTP 401 | | Unset | Anything | Accept; warn once per process for launches outside static `MYTHOS_LISTING_ID(S)`, handshake or callback validation | Static listing launches without a key are safe legacy configuration and remain silent in 0.5.0. Configuring a resolver or registration callback without a key warns at startup (counts as the once). `MYTHOS_KEY` will be required in 0.6.0. Exact warning, with `[mythos]` prefix: > MYTHOS_KEY is not set: this app accepts Listings auto-registered by any Mythos Producer. Copy your key from the Mythos dashboard into MYTHOS_KEY. It will be required in 0.6.0. Session mismatch response: HTTP 401 `{"success":false,"error":"This app is bound to a different Mythos Producer. Check MYTHOS_KEY.","code":"PRODUCER_MISMATCH"}`. Handshake and callback: HTTP 401 `{"error":"Producer mismatch"}`; never invoke the registration callback on mismatch. Legacy middleware keeps its existing invalid-token 401 because `ProducerMismatchError` extends `InvalidLaunchTokenError`. Mismatch logs contain `