Installation
Three steps: create an API key, install the package, and call init() (or just set an env var —
init() is optional).
1. Create an API key
Open Settings → API keys (/settings/api-keys in the app), find your environment, and click
Generate (or Rotate, if it already has a key), then Generate key. Copy the key when it’s
shown — you won’t see the full key again. The setup wizard’s first screen also has a Generate API
key button. See Workspace settings for how environments and keys
relate. Only owners and admins can see, create or rotate environment keys.
Rotate an environment key
Each environment has exactly one active key. Rotate opens a dialog with two choices:
- Rotate (24h grace) — creates the new key and lets the old one keep working for 24 hours, so you can swap it into your deployments without dropping data. The old key then shows as “expires” with its time on the page.
- Expire old key now (compromised) — creates the new key and stops the old one working immediately. Use it when a key leaked. Anything still using the old key fails until you update it.
Either way, the new key is shown once: copy it before you close the dialog.
Personal keys
For your own scripts and laptop, create a personal key instead of sharing the environment key.
Open Profile (/profile in the app) and use Personal API keys → Create key:
- Give it a name (for example
laptop), choose Full access or Read-only, and choose when it expires: never, in 30 days, in 90 days or in 1 year. A personal key is scoped to the environment you are working in. - The key is shown once. Copy it before you close the dialog.
- The list shows each key’s prefix, when it was last used and when it expires. You can rename a key and change its access later.
- Revoke a key from the same list. A confirmation names the key, and anything using it stops working immediately. This cannot be undone. Expired and revoked keys move to a “revoked or expired” group.
Personal keys are the only keys you can revoke yourself. An environment key is replaced by rotating it (see above), which needs an owner or admin.
2. Install
Python 3.11 or newer is required (or Node.js 18.19+ / 20.6+ for TypeScript). On Python 3.10
or older, pip install morse-ai stops with “No matching distribution found for morse-ai” and
does not say why. Only pip install -v morse-ai shows the real reason: `requires-python
=3.11
. Check withpython3 —version, and usepython3.11` or newer.
Python:
pip install morse-aiTypeScript:
npm install @morsehq-dev/sdkAdapters are peer dependencies in TypeScript — install only the SDKs you actually use. In Python,
optional extras add framework-specific adapters — see Frameworks for the full
list ("morse-ai[anthropic]", "morse-ai[langgraph]", "morse-ai[openai_agents]", or "morse-ai[all]").
Quote the package name whenever it has brackets: zsh, the default shell on macOS, otherwise stops
with no matches found.
3. Initialize
The simplest path is zero-code: set MORSE_API_KEY in your environment and don’t call init() at
all. Every public SDK function lazily auto-initializes from the env var on first use.
export MORSE_API_KEY=your-morse-keyOr call init() explicitly at startup if you want to pass the key directly, override the
endpoint, or configure redaction:
import morse_ai
morse_ai.init() # reads MORSE_API_KEY from your environmentimport * as morse from "@morsehq-dev/sdk";
morse.init(); // reads MORSE_API_KEY from your environmentBoth SDKs make init() optional, and either resolves the key from MORSE_API_KEY when you don’t
pass one. They differ when no key is set: Python’s init() raises ValueError, while TypeScript
logs a one-time warning and sends nothing (every SDK call becomes a silent no-op).
In Python, init() resolves api_key from the argument first, then MORSE_API_KEY; if neither is set it
raises ValueError. It resolves the ingest endpoint from an explicit endpoint arg, then
MORSE_ENDPOINT, then the default https://api.morsehq.dev/api/v1/traces/batch.
Which MORSE_ENDPOINT forms are valid. In Python, both the bare host
(https://api.morsehq.dev) and the full ingest URL (https://api.morsehq.dev/api/v1/traces/batch)
work: the SDK appends /api/v1/traces/batch when it is missing. In TypeScript the value is used as
given for the JSON batch calls (record(), track(), logs), so set the full ingest URL there; spans
are sent over OTLP to the same host. Most setups leave MORSE_ENDPOINT unset; set it only if you
were given a different Morse API host.
Setting MORSE_DISABLED=1 makes init() (and every submission call) a no-op — useful for local
dev or CI.
Verify
Run the first snippet from the Quickstart (Python or TypeScript). It makes one real model call through a wrapped client, so the trace shows a model, token counts and a cost. Then check the Traces list on the dashboard for the run.
A run with no model call, no tool calls and a duration of 100 ms or less can be hidden from the default
Traces list as low-signal. If you run a bare
with morse_ai.run("my-agent"): block as a test, click Show them to see it. It has no cost, so it does not produce a finding.
When you need flush()
Both SDKs send spans in the background in batches, and both try to send what is left when the
process ends normally. That is why a short script that never calls flush() often still delivers
its traces. Call flush() yourself (morse_ai.flush() in Python, await morse.flush() in
TypeScript) when the normal exit path may not run:
- Serverless and short-lived handlers (Lambda, Cloud Functions, Vercel): the runtime can freeze the process as soon as your handler returns, before the background send happens. Flush before you return.
- Hard exits:
os._exit(), a process killed bySIGKILLor by a container stop that does not let it finish, and in Nodeprocess.exit()or an uncaught exception. The exit hook does not run on these. - Forked workers: a child process that exits without a normal interpreter shutdown.
- When you need to know the data is sent now, for example before you read the trace in the app from the same script.
For an ordinary script or service that ends by returning from main (Python) or by the event loop
emptying (Node), the SDK sends what is left on exit and flush() is a safe extra. On exit the SDK
waits a bounded time, so an unreachable API does not hang your process, and it prints a warning when
spans were dropped.
Related
- Overview — what the SDK does and its non-invasive guarantee.
- Frameworks — install commands and snippets for LangChain, LangGraph, OpenAI Agents, Anthropic, and Claude Agent SDK.
- set_context() — attach cost-attribution dimensions to a trace.