Quickstart
Connect an agent, create an app, attach a policy, and trigger your first authorized call — pick the path that fits your stack.
This is the ten-minute path from zero to "an agent called GitHub through Nomos and the receipt is in the audit chain." Four access modes are first-class — pick the one that matches how you'll actually use Nomos:
- CLI —
@auto-nomos/cli. Best for scripting, CI, and one-off shell use. - MCP —
@auto-nomos/mcp-server. The path Claude Desktop, Cursor, and Claude Code take. Zero code on your side. - SDK · TS —
@auto-nomos/sdk. What your TypeScript / Node codebase calls when you're writing the agent yourself. - SDK · Py —
auto-nomos-sdkon PyPI. Mirrors the TS surface for LangGraph / CrewAI / AutoGen orchestrators.
Pick a tab in any step below; your choice is remembered across steps.
Before you start
- A Nomos account (sign up at the dashboard — takes 30 seconds).
- An API key from /app/api-keys (NOMOS_API_KEY=nk_live_…). Visible once on creation.
- Node 18+ if you're using the CLI, MCP server, or TypeScript SDK. Python 3.10+ for the Python SDK.
1. Connect an agent
Every agent identifies itself to Nomos with an API key. CLI users export it
once. MCP clients put it in their config JSON. SDK users pass it to
createIntentClient(). That key tells the control plane who is asking — it
does not grant access on its own; policy still decides every call.
import { createIntentClient } from '@auto-nomos/sdk';
const client = createIntentClient({
controlPlaneUrl: process.env.NOMOS_CONTROL_URL!,
apiKey: process.env.NOMOS_API_KEY!,
});
2. Create an App
An App is one agent's identity inside your organization. It has a DID (used to sign UCANs), an optional Cedar policy, and zero or more API keys. The App name shows up in every audit row forever — name it accurately.
const app = await client.apps.create({
name: 'Inbox triage bot',
mode: 'dynamic',
});
const key = await client.apps.keys.create({ appId: app.id });
// store key.value somewhere — it's only returned once
Prefer the dashboard?
Every step here also works in the web UI. Apps → Create App issues the same App and key. The dashboard is the path used by your teammates who don't write code.
3. Attach a policy
Without a policy, every authorize denies — fail-closed by design. The fastest
path is the starter template github:read-only, which lets the agent list
issues, read repo metadata, and nothing else. Edit it later or build your own
in the visual policy builder.
const policy = await client.policies.createFromTemplate({
template: 'github:read-only',
name: 'GitHub read',
});
await client.apps.attachPolicy({
appId: app.id,
policyId: policy.id,
});
The Cedar that template ships is short enough to read:
permit (
principal,
action == Action::"github:issue:list",
resource is GithubRepo
)
when { principal.app == resource.allowed_app };
4. Trigger your first call
Now hit /v1/authorize, get a UCAN back, then use it against the PDP to
actually call GitHub. The PDP holds the decrypted OAuth token — your agent
never sees it.
const grant = await client.authorize({
command: '/github/issue/list',
resource: { provider: 'github', owner: 'acme', repo: 'app' },
ttlSeconds: 300,
});
if (grant.decision !== 'allow') throw new Error(grant.reason);
const issues = await fetch(
`${process.env.NOMOS_PDP_URL}/github/issue/list?owner=acme&repo=app`,
{ headers: { authorization: `Bearer ${grant.ucan}` } },
).then((r) => r.json());
Verify it worked
/app/auditshows two new rows: theauthorizedecision and the/github/issue/listproxy call.- The receipt drawer on the proxy row shows
prevHashthat matches the hash of the authorize row above it. - "Download proof" gives you a JSON bundle
@auto-nomos/audit-verifycan verify offline against your signed daily root.
You're live
You've shipped an end-to-end authorized call: agent identified, policy checked, action proxied, receipt recorded — without your code ever holding a GitHub token. Same shape works for every integration in the catalog.