# 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') return

Open this app from Mythos.

; if (status === 'expired') return ; if (status === 'error') return

Something 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') return

Loading…

; if (status === 'standalone') return

Open this app from Mythos.

; if (status === 'expired') return ; if (status === 'error') return

Something 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.llm(req, { apiKey: process.env.OPENAI_API_KEY, fallback: ownOpenAiClient, baseURL: 'https://api.openai.com/v1', timeout: 30_000, }); ``` Returns an OpenAI client routed through the Mythos gateway, typed as your own `OpenAI` (pass it as the type argument; `fallback` should be the same type). `fallback` is used when there is no Mythos session; all options shown are optional. Browser-side, LLM calls are confirmed with `confirmCharge({ kind: 'llm', reason })` — no credits, because the cost is usage-based. ```python client = await mythos.llm(request, api_key="...", fallback=own_openai_client) ``` Python LLM support requires `pip install "mythos-sdk[llm]"`. ## `billing` ```ts const billing = mythos.billing(completion); ``` Returns the completion's Mythos metadata, including `mythos_charge_credits`, `mythos_cost_microunits`, `mythos_pricing_source`, and optionally `mythos_billing_status`, or `null` when no billing metadata is present. ```python billing = mythos.billing(completion) ``` --- # Errors Source: https://docs.mythos.work/reference/errors # Errors Every SDK error is a `MythosError`. Node errors expose `.code` and `.httpStatus`; Python errors expose `.code` and `.http_status`. | Code | HTTP | Meaning | |---|---:|---| | `CONFIG_ERROR` | 500 | Bad or missing environment configuration; raised at startup by `createMythos()` | | `INVALID_LAUNCH_TOKEN` | 401 | Invalid launch token | | `LISTING_NOT_REGISTERED` | 401 | Launch audience is not registered with this app | | `PRODUCER_MISMATCH` | 401 | This app is bound to a different Mythos Producer. Check MYTHOS_KEY. | | `TOKEN_ALREADY_CONSUMED` | 401 | Launch token was reused | | `SESSION_REQUIRED` | 401 | `charge` was called without a session | | `SESSION_EXPIRED` | 401 | Session expired | | `INSUFFICIENT_FUNDS` | 402 | Wallet balance is too low | | `SESSION_NOT_FOUND` | 404 | Session does not exist | | `INVALID_USAGE` | 400 | Invalid charge input, such as non-integer credits | | `UPSTREAM_ERROR` | 502 | Mythos returned an upstream error | | `MYTHOS_UNREACHABLE` | 503 | Mythos could not be reached | | `LLM_SESSION_REQUIRED` | — | `llm()` has no session and no fallback | | `LLM_IDENTITY_REQUIRED` | — | `llm()` has no identity token and no fallback | ## Node mapping Test launches report only the [diagnostic codes and messages](https://docs.mythos.work/resources/troubleshooting#test-launch-diagnostics). Real sessions never report. The diagnostic request contains exactly `code`, `step`, and `sdk_version`; reporting failures and its two-second timeout do not alter app responses. `charge()` and `llm()` rethrow the original `MythosError` unchanged. `ProducerMismatchError` extends `InvalidLaunchTokenError` in both languages, preserving legacy invalid-token handling. Producer binding applies to launch, handshake and callback tokens; see the [configuration matrix](https://docs.mythos.work/reference/configuration). | Endpoint | HTTP | Mismatch body | |---|---|---| | Session (`handleSession` / Python session route) | 401 | `{"success":false,"error":"This app is bound to a different Mythos Producer. Check MYTHOS_KEY.","code":"PRODUCER_MISMATCH"}` | | Handshake | 401 | `{"error":"Producer mismatch"}` | | Listing callback | 401 | `{"error":"Producer mismatch"}`; registration callback is not invoked | | Legacy `requireLaunchToken` / `require_launch_token` | 401 | Existing invalid-token response | The backend surfaces `Producer mismatch` to the Producer; preserve this exact copy. Logs contain only the endpoint's `producer mismatch` message and `listingId`, never the key, `pkh` or token. ```ts if (err instanceof MythosError) res.status(err.httpStatus).json({ success: false, error: err.message, code: err.code }); ``` ## Python mapping ```python if isinstance(err, MythosError): return JSONResponse({"success": False, "error": str(err), "code": err.code}, status_code=err.http_status) ``` --- # Configuration Source: https://docs.mythos.work/reference/configuration # Configuration `createMythos()` and `create_mythos()` read the same environment variables. | Variable | Required | Description | |---|---|---| | `MYTHOS_SESSION_SECRET` | Yes | Session secret of at least 32 characters. Generate with `openssl rand -base64 32`. | | `MYTHOS_KEY` | Soft in 0.5.0; required in 0.6.0 | Copy from Dashboard → Settings → Mythos key. Binds tokens to this Producer. | | `MYTHOS_LISTING_ID` | Yes* | One listing ID from the Mythos dashboard. | | `MYTHOS_LISTING_IDS` | Yes* | Comma-separated listing IDs. | | `MYTHOS_API_URL` | No | Mythos API origin. Defaults to `https://api.mythos.work`. | | `MYTHOS_DEBUG` | No | Set to `1` for verbose `[mythos]` logs. | \*A listing environment variable is not required when `resolveListingIds` or `resolve_listing_ids` supplies at least one ID. Set secrets in the deployment host's environment, not only in a local env file. See the [deploy checklist](https://docs.mythos.work/getting-started/deploy-checklist). ```dotenv # openssl rand -base64 32 (>= 32 chars) MYTHOS_SESSION_SECRET= # Mythos dashboard → Settings → Mythos key MYTHOS_KEY= ``` ## Producer binding (0.5.0) The Mythos-generated key is a non-secret identifier, `mk_` plus 32 base64url characters. Copy it into server deployment configuration; regenerating it requires updating the environment and redeploying. This prevents another Producer from auto-registering their Listing against your app and earning through it. Launch, handshake (`purpose: handshake-check`) and listing callback (`purpose: listing_registered`) tokens carry the same `pkh`: base64url without padding of SHA-256 of the UTF-8 bytes of the **full key string**, including `mk_`. The backend omits the claim when the owner has no key. Literal conformance vector (hash fixture, not a valid deployment key): ```text MYTHOS_KEY=mk_test pkh=7i08_9pmD6r4prkeKSsCnOFaCAduf8cz4ogjmhGlpG0 ``` | `MYTHOS_KEY` | Token `pkh` | Result | |---|---|---| | Set | Equals hash(key) | Accept | | Set | Different or missing | Reject with `ProducerMismatchError`, `PRODUCER_MISMATCH`, HTTP 401 | | Unset | Anything | Accept; warn once per process if a launch Listing is outside static `MYTHOS_LISTING_ID(S)`. Handshake and callback validation without a key also warn once. | Static `MYTHOS_LISTING_ID(S)` launches without a key are safe legacy configuration and remain silent in 0.5.0. A configured `resolveListingIds` / `resolve_listing_ids` or `onListingRegistered` / `on_listing_registered` without a key warns at startup, counting as the once. Missing keys are a soft warning in 0.5.0; **`MYTHOS_KEY` will be required in 0.6.0**. Exact warning (with the `[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. A malformed configured key makes `createMythos()` / `create_mythos()` throw `MythosConfigError` with `MYTHOS_KEY is malformed. Copy it again from the Mythos dashboard.` Deploy backend P1 before configuring a key: older tokens with no `pkh` are rejected when a key is set. Mismatch responses and exact error copy are in [Errors](https://docs.mythos.work/reference/errors). Mismatch logs contain `: producer mismatch` with `{ listingId }` only. Never log the key, `pkh`, or token. --- # CLI Source: https://docs.mythos.work/reference/cli # CLI The Node package provides `init` for scaffolding, `doctor` for diagnosis, and `agents` for coding-agent setup. Python provides the same server-side diagnosis through its module entry point. ## `init` ```bash npx @mythos-work/sdk init ``` The command detects Next.js App Router, Next.js Pages Router, Express, or FastAPI. It creates only missing integration files, adds missing env placeholders, prints required manual wiring, then runs doctor. It never overwrites an existing file and never edits `next.config.*` or `main.py`. ## `doctor` ```bash npx @mythos-work/sdk doctor python -m mythos_sdk doctor ``` Doctor reads process environment over `.env.local`, then `.env`, and performs these checks in order: | Check | Failure or warning fix | |---|---| | `MYTHOS_SESSION_SECRET` is set | `Set MYTHOS_SESSION_SECRET (≥32 chars): openssl rand -base64 32` | | `MYTHOS_KEY` format and presence | `✔ MYTHOS_KEY is set`, `✖ MYTHOS_KEY is malformed`, or a `⚠` missing-key warning. Copy from Dashboard → Settings → Mythos key. Missing is not a hard failure in 0.5.0; required in 0.6.0. | | Express/FastAPI config is not only in `.env` | Warning: `MYTHOS_* found only in .env — make sure your start command loads it (node --env-file=.env … / uvicorn --env-file .env)`. Next.js loads `.env` itself, so it never gets this warning. | | Secret length is at least 32 | `MYTHOS_SESSION_SECRET is N chars; regenerate with openssl rand -base64 32 (and update your host's env, e.g. Vercel)` | | `MYTHOS_LISTING_ID` or `MYTHOS_LISTING_IDS` has an ID | Warning: `MYTHOS_LISTING_ID not set — fine only if your code passes resolveListingIds to createMythos()` | | `MYTHOS_API_URL` is an HTTP(S) URL | `MYTHOS_API_URL must be an http(s) URL` | | Mythos API JWKS is reachable | `Cannot reach {api} (). Check MYTHOS_API_URL / network.` | | Every configured listing exists and is published | `Listing {id} not found or not published on {api}. Check the ID and that the listing is published.` Other failures report `run-info returned {status}`. | | Framework route is wired | `Missing Mythos route. Run: npx @mythos-work/sdk init` | | Next.js well-known rewrites exist | `Add the /.well-known rewrites to next.config (see https://docs.mythos.work/getting-started/quickstart-nextjs-app)` | | Node SDK version is at least 0.2.0 | `Upgrade: npm i @mythos-work/sdk@latest` | Python doctor checks server environment, key format, API reachability and listing publication. A listing ID warning does not fail the command because dynamic listing resolution may provide IDs at runtime. A missing-key warning is also soft in 0.5.0. See [configuration](https://docs.mythos.work/reference/configuration) for the exact warning and silent static-listing launch behavior. ## `agents` ```bash npx @mythos-work/sdk agents ``` Installs the `integrate-mythos-sdk` skill for Claude Code (`.claude/skills`), Codex and Devin (`.agents/skills`) and Cursor (`.cursor/skills`), and adds a short `` section to the root `AGENTS.md` (read by Codex and Devin). It never overwrites existing files and appends the `AGENTS.md` section only once. See [Use with AI agents](/ai-agents). ## What doctor cannot check Doctor cannot detect a backend missing an LLM identity key because that failure only appears when a session is consumed. The SDK logs it as `[mythos] session: consume returned no LLM identity token`. --- # Use with AI agents Source: https://docs.mythos.work/ai-agents # Use with AI agents Give an agent one canonical source instead of asking it to infer the integration from package internals. ## Documentation sources - Full site text: [https://docs.mythos.work/llms-full.txt](https://docs.mythos.work/llms-full.txt) - Node package: `node_modules/@mythos-work/sdk/AGENTS.md` - Python wheel: `site-packages/mythos_sdk/AGENTS.md` - Agent skill (npm): `node_modules/@mythos-work/sdk/skills/integrate-mythos-sdk/SKILL.md` `llms-full.txt` follows the public sidebar order and excludes all Advanced 0.0.x primitives. ## Set up your coding agent Run once in your project: ```bash npx @mythos-work/sdk agents ``` It never overwrites existing files, and installs the same `integrate-mythos-sdk` skill everywhere: | Tool | What it reads | Where it lands | |---|---|---| | Claude Code | Agent skill | `.claude/skills/integrate-mythos-sdk/SKILL.md` | | Cursor | Agent skill | `.cursor/skills/integrate-mythos-sdk/SKILL.md` | | Codex | Agent skill + `AGENTS.md` | `.agents/skills/integrate-mythos-sdk/SKILL.md`, root `AGENTS.md` | | Devin | Agent skill + `AGENTS.md` | `.agents/skills/integrate-mythos-sdk/SKILL.md` (Devin's recommended path), root `AGENTS.md` | The root `AGENTS.md` gets a short `` section pointing at the full instructions in the installed package. An existing `AGENTS.md` keeps its content; the section is appended once. ## Copy-paste prompt > Integrate Mythos using only the instructions in node_modules/@mythos-work/sdk/AGENTS.md. Run npx @mythos-work/sdk doctor until it passes. For Python-only tooling, replace the final command with `python -m mythos_sdk doctor`. --- # Code examples Source: https://docs.mythos.work/resources/code-examples # Code examples Start with the complete quickstart for your framework: | Framework | Quickstart | |---|---| | Next.js App Router | [Open](https://docs.mythos.work/getting-started/quickstart-nextjs-app) | | Next.js Pages Router | [Open](https://docs.mythos.work/getting-started/quickstart-nextjs-pages) | | Express | [Open](https://docs.mythos.work/getting-started/quickstart-express) | | FastAPI | [Open](https://docs.mythos.work/getting-started/quickstart-fastapi) | For browser-only examples, compare [`useMythos`, `initMythos`, and the script build](https://docs.mythos.work/getting-started/browser-client). The [recipes](https://docs.mythos.work/recipes/fixed-price-charge) cover standalone mode, LLM chat, fixed-price billing, and session expiry. --- # Mock integration apps Source: https://docs.mythos.work/resources/mock-integration-apps # Mock integration apps These reference implementations demonstrate the high-level API end to end. | Stack | Repository | API demonstrated | |---|---|---| | Next.js (Pages Router) | [mythos-sdk-node-js-mock-integration-app](https://github.com/Mythoswork/mythos-sdk-node-js-mock-integration-app) | `createMythos`, `mythos.handlers` via `pagesHandler`, `useMythos`, `mythos.charge`, `mythos.llm` | | FastAPI | [mythos-sdk-python-mock-integration-app](https://github.com/Mythoswork/mythos-sdk-python-mock-integration-app) | `create_mythos`, `mythos.router`, `mythos.charge`, `mythos.llm` | Use the mock apps to inspect complete project structure and test a real dashboard launch. For a new integration, copy the smaller [framework quickstart](https://docs.mythos.work/resources/code-examples) rather than copying an entire mock application. The mocks also include test-harness routes (`harness/*`) that real apps do not need. ## Callback and handshake wiring This is the part integrators most often get wrong. You enter two URLs on the Listing: | Listing field | Enter | Mythos calls | |---|---|---| | Launch URL | The page users open, e.g. `https://app.example.com` | Loads it in an iframe with `?lt=`, and before publish sends `GET /.well-known/mythos-handshake?lt=…` | | Callback URL (optional) | The **full** endpoint URL, e.g. `https://app.example.com/.well-known/mythos-listing-registered` | `POST ?lt=…` when the Listing is created, exactly as entered (no path is appended) | Where each mock wires it: | | Next.js mock | FastAPI mock | |---|---|---| | Register the callback | `lib/mythos.ts`: `createMythos({ resolveListingIds: getListingIds, onListingRegistered: addListingId })` | `main.py`: `create_mythos(resolve_listing_ids=get_listing_ids, on_listing_registered=add_listing_id)` | | Store Listing IDs | `lib/listing-ids-store.ts`: Redis (Upstash) in production, local file in dev | `listing_ids_store.py` | | Serve the routes | `pages/api/mythos/[...mythos].ts` (`pagesHandler(mythos)`) plus `next.config.ts` rewrites from `/.well-known/mythos-handshake` and `/.well-known/mythos-listing-registered` to `/api/mythos/*` | `app.include_router(mythos.router)` serves `/.well-known/mythos-handshake`, `/.well-known/mythos-listing-registered` and `/api/mythos/session` directly | Notes: - The callback is only useful together with `resolveListingIds` / `resolve_listing_ids`. With a static `MYTHOS_LISTING_ID`, leave Callback URL empty. - The SDK returns HTTP 200 once your `onListingRegistered` resolves. Keep that function idempotent. - On Vercel and other serverless hosts, the callback and session routes run in separate instances. Store IDs in shared storage, as the Next.js mock does with Redis. - A Launch URL with a path (`https://app.example.com/calc`) gets its handshake at `https://app.example.com/calc/.well-known/mythos-handshake`. --- # Troubleshooting Source: https://docs.mythos.work/resources/troubleshooting # Troubleshooting Start every diagnosis with: ```bash npx @mythos-work/sdk doctor ``` FastAPI projects can also run `python -m mythos_sdk doctor`. Fix every failed check before debugging application code. ## Test launch diagnostics Test launches report diagnostics; real sessions never do. Reports contain exactly `code`, `step`, and `sdk_version`, with step `verify_launch`, `consume`, `session`, `meter`, or `llm`. Background reports have a two-second timeout and cannot change your app's response. The browser handshake includes `sdk_version` even on localhost. SDK-side session failures in a test launch show a fixed overlay titled **Mythos test launch failed**, with the matching message, code, and **Reload** button. This applies only to `CONFIG_ERROR`, `PRODUCER_MISMATCH`, `INVALID_LAUNCH_TOKEN`, `LISTING_NOT_REGISTERED`, `MYTHOS_UNREACHABLE`, and `UPSTREAM_ERROR`. | Code | Message | |---|---| | `CONFIG_ERROR` | The app's Mythos SDK is misconfigured. Check MYTHOS_SESSION_SECRET and MYTHOS_KEY. | | `PRODUCER_MISMATCH` | MYTHOS_KEY belongs to a different Mythos account or was regenerated. | | `INVALID_LAUNCH_TOKEN` | The app rejected the launch token. Check MYTHOS_API_URL points at this Mythos environment. | | `LISTING_NOT_REGISTERED` | This listing isn't registered with the app yet. Save the listing again or set MYTHOS_LISTING_ID. | | `TOKEN_ALREADY_CONSUMED` | The launch token was used twice. Reload to start a new test launch. | | `SESSION_EXPIRED` | The test session expired. Start a new test launch. | | `MYTHOS_UNREACHABLE` | The app couldn't reach Mythos. Check its network access and MYTHOS_API_URL. | | `UPSTREAM_ERROR` | Mythos returned an unexpected error. Try again. | | `SESSION_REQUIRED` | The app called charge() or llm() without a Mythos session. | | `INSUFFICIENT_FUNDS` | Charge failed for funds. This shouldn't happen in a test launch; report it. | ## Other symptoms | Error or symptom | Fix | |---|---| | `MYTHOS_KEY is malformed` | Copy the full `mk_` plus 32 base64url characters from Dashboard → Settings → Mythos key. | | `MYTHOS_KEY is not set` warning | Set the dashboard key in the host environment. This is soft in 0.5.0 and required in 0.6.0; static listing launches without a key remain silent. | | `SESSION_NOT_FOUND` | Relaunch to establish a current session. | | `INVALID_USAGE` | Send integer credits and valid charge options. | | `LLM_SESSION_REQUIRED` / `LLM_IDENTITY_REQUIRED` | Launch through Mythos or supply an LLM fallback. | | Handshake is 404 | Wire the handler and make `/.well-known/mythos-handshake` reachable. | | Works locally but not after deploy | Set env vars in the host and rerun doctor in production. | See the [full error reference](https://docs.mythos.work/reference/errors) and [deploy checklist](https://docs.mythos.work/getting-started/deploy-checklist). --- # Security Source: https://docs.mythos.work/resources/security # Security The high-level SDK keeps launch credentials and billing identity out of application code. - Use `createMythos()` or `create_mythos()` instead of parsing launch tokens. - Never store Mythos tokens in `localStorage` or `sessionStorage`. - Keep `MYTHOS_SESSION_SECRET` server-side, at least 32 characters, and set it in the deployment host. - Set `MYTHOS_KEY` from Dashboard → Settings → Mythos key to bind launch, handshake and callback tokens to your Producer. Missing is soft in 0.5.0; required in 0.6.0. Static listing launches without a key remain silent. Update deployment env after regeneration. - Mismatch logs include only the endpoint and `listingId`; never log the key, `pkh`, or token. See [Producer binding](https://docs.mythos.work/reference/configuration). - Serve production apps over HTTPS because the session cookie requires `Secure`. - Call `confirmCharge` before every billable action and call `charge` only on the server. - Reuse a client-generated `idempotencyKey` when retrying the same fixed-price action. - Map `MythosError` to its provided HTTP status instead of exposing internal errors. The SDK verifies signed launch data, enforces single-use launch exchange, and manages cookie-first session transport. See [Browser client](https://docs.mythos.work/getting-started/browser-client) and [Errors](https://docs.mythos.work/reference/errors). --- # Glossary Source: https://docs.mythos.work/resources/glossary # Glossary These terms describe the Mythos launch and billing model used throughout the SDK docs. | Term | Definition | |---|---| | **Producer** | App developer who integrates the SDK and lists an app on Mythos | | **Consumer** | User who buys credits and spends them across Mythos apps | | **Listing** | A published app entry in the Mythos marketplace | | **Launch session** | Secure context connecting a Consumer, listing, and billing identity | | **Standalone** | App state when it was opened outside a Mythos launch | | **Charge confirmation** | Consumer approval returned by `confirmCharge` before a billable action | | **Consent ID** | Approval identifier that can be passed to the server charge | | **Idempotency key** | Client-generated UUID reused for retries of one fixed-price action | | **Session metered total** | Running credit total returned by `charge` | | **LLM gateway** | Mythos-routed OpenAI-compatible client returned by `mythos.llm` | | **Well-known route** | Standard integration endpoint under `/.well-known/` | --- # AGENTS.md # Integrate Mythos Mythos lets users buy credits once and spend them across apps. The SDK handles launch tokens, sessions, billing, and browser transport for Producer apps. ## Rules - Use `createMythos()` in Node or `create_mythos()` in Python. - Never parse `lt`, set Mythos cookies, or call `/consume` or `/meter` directly. - Never store Mythos tokens yourself. Do not use `localStorage` or `sessionStorage`; the SDK owns transport. - Always call browser `confirmCharge` before a billable action, then call server `charge` after approval. - LLM calls are usage-based: `confirmCharge({ kind: 'llm', reason })` takes no credits; billing settles after the response. - Express/FastAPI do not load `.env` automatically; Next.js does. Make sure the process actually loads the env file. - Map `MythosError` with `.httpStatus`; Python uses `.http_status`. - Sessions last up to 8 h and refresh automatically; never cache `llm()` clients across requests. - Test launches report diagnostics; real sessions never do. Reports contain only `code`, `step`, and `sdk_version`; they are best-effort background calls with a two-second timeout. ## Pick Your Framework ### Next.js App Router ```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; ``` Add both well-known rewrites shown under Next.js Pages Router. ### Next.js Pages Router Create `lib/mythos.ts` as above, then: ```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 async rewrites() { return [ { source: '/.well-known/mythos-handshake', destination: '/api/mythos/handshake' }, { source: '/.well-known/mythos-listing-registered', destination: '/api/mythos/listing-registered' }, ]; } ``` ### Express ```ts // server.ts import express from 'express'; import { createMythos } 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)); ``` Run with `node --env-file=.env server.js` (Node ≥ 20.6), or set the variables in the host environment. ### FastAPI ```python # main.py from fastapi import FastAPI from mythos_sdk import create_mythos app = FastAPI() mythos = create_mythos() app.include_router(mythos.router) ``` Run with `uvicorn main:app --env-file .env` (needs `fastapi[standard]`), or set the variables in the host environment. ## URLs Mythos Calls The Producer enters two URLs on the Listing. Once the SDK is mounted as shown above, it serves every route below. Do not write these handlers yourself. | Listing field | Enter | Mythos calls | Served by | |---|---|---|---| | Launch URL | The page users open, e.g. `https://app.example.com` | Loads it in an iframe with `?lt=`; the page's browser client then calls `/api/mythos/session` | `useMythos()` / `initMythos()` + `mythos.handlers` / `mythos.router` | | (derived from Launch URL) | Nothing | `GET /.well-known/mythos-handshake?lt=…` before publish | Next.js rewrite → `/api/mythos/handshake`; `mythosExpress`; `mythos.router` | | Callback URL (optional) | The **full** endpoint URL, e.g. `https://app.example.com/.well-known/mythos-listing-registered` | `POST ?lt=…` when the Listing is created. The URL is used exactly as entered; no path is appended | `createMythos({ onListingRegistered })` / `create_mythos(on_listing_registered=…)` | - The handshake path is appended to the full Launch URL. A Launch URL with a path (`https://app.example.com/calc`) is checked at `https://app.example.com/calc/.well-known/mythos-handshake`. - The callback must return HTTP 200. The SDK does that after `onListingRegistered(listingId)` resolves. Make the callback idempotent (upsert, not append). - The callback only matters with `resolveListingIds` / `resolve_listing_ids`: store the ID in `onListingRegistered` and return the stored IDs from the resolver. With a static `MYTHOS_LISTING_ID`, leave Callback URL empty. - On serverless hosts (Vercel), the callback and session routes may run in different instances. Store Listing IDs in shared storage (Redis/DB), not memory or `/tmp`. - Callback URL must be HTTPS in production. ## Reference Implementations These complete working apps run on the current SDK. Read them for real wiring, especially the callback: | Stack | Repository | Look at | |---|---|---| | Next.js (Pages Router) | https://github.com/Mythoswork/mythos-sdk-node-js-mock-integration-app | `lib/mythos.ts` (`onListingRegistered` + `resolveListingIds`), `lib/listing-ids-store.ts` (Redis with file fallback), `pages/api/mythos/[...mythos].ts`, `next.config.ts` rewrites, `pages/calculator.tsx` (`useMythos`), `pages/api/calculate.ts` (`mythos.charge`), `pages/api/chat.ts` (`mythos.llm`) | | FastAPI | https://github.com/Mythoswork/mythos-sdk-python-mock-integration-app | `main.py` (`create_mythos(on_listing_registered=…)`, `app.include_router(mythos.router)`, `mythos.charge`, `mythos.llm`), `listing_ids_store.py` | Copy patterns, not whole apps. The mocks also include test-harness routes (`harness/*`) that real apps do not need. ## Browser React apps use the shared client without a Provider: ```tsx import { useMythos } from '@mythos-work/sdk/react'; const { status, session, error, fetch, confirmCharge, relaunch } = useMythos(); ``` Handle every state before rendering billable UI: ```tsx if (status === 'loading') return

Loading...

; if (status === 'standalone') return ; if (status === 'expired') return ; if (status === 'error') 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 `: producer mismatch` and `{ listingId }` only. Never log the key, `pkh`, or token. ## Verify Run `npx @mythos-work/sdk doctor` (Python: `python -m mythos_sdk doctor`) and fix every ✖ before finishing. ## Error Codes | Code | HTTP | Meaning | |---|---:|---| | `CONFIG_ERROR` | 500 | Missing or invalid configuration | | `INVALID_LAUNCH_TOKEN` | 401 | Invalid launch token | | `LISTING_NOT_REGISTERED` | 401 | Launch audience is not registered with this app | | `PRODUCER_MISMATCH` | 401 | The `MYTHOS_KEY` in this deployment belongs to a different Producer, or was regenerated. Copy the current key from the dashboard and redeploy. | | `TOKEN_ALREADY_CONSUMED` | 401 | Launch token reused | | `SESSION_REQUIRED` | 401 | Billable action has no session | | `SESSION_EXPIRED` | 401 | Session expired | | `INSUFFICIENT_FUNDS` | 402 | Wallet balance too low | | `SESSION_NOT_FOUND` | 404 | Session not found | | `INVALID_USAGE` | 400 | Invalid credits or usage | | `UPSTREAM_ERROR` | 502 | Unexpected Mythos response | | `MYTHOS_UNREACHABLE` | 503 | Mythos cannot be reached | | `LLM_SESSION_REQUIRED` / `LLM_IDENTITY_REQUIRED` | 500 | LLM session or identity missing |