Get started · 10 minutes · four paths

Your first authorized
call in 10 minutes.

Connect an agent. Create an app. Attach a policy. Trigger a call. Every step shows the exact command for the path that fits how you work — CLI, MCP, TypeScript SDK, or Python SDK. Pick once; it sticks across steps.

No credit card1k decisions free/moSelf-host coming
step 01

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. SDK users pass it to createIntentClient(). The key says who is asking — policy still decides every call.

  • · Issued from /app/api-keys
  • · Visible exactly once
  • · Revocable per-key, per-app
agent.ts · fail-closed by default
import { createIntentClient } from '@auto-nomos/sdk';

const client = createIntentClient({
  controlPlaneUrl: process.env.NOMOS_CONTROL_URL!,
  apiKey: process.env.NOMOS_API_KEY!,
});
step 02

Create an App.

An App is one agent's identity inside your org. It carries a DID for signing UCANs, a default policy, and its own API keys. The name shows up in every audit row forever — name it accurately.

  • · One DID per agent
  • · Mode: dynamic or static
  • · Rotate keys without breaking the App
creates an App + an API key in one block
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 — only returned once
step 03

Attach a policy.

Without a policy, every authorize denies — fail-closed by design. The fastest path: the starter template github:read-only. Edit it later or build your own in the visual policy builder.

cedar · template github:read-only
permit (
  principal,
  action == Action::"github:issue:list",
  resource is GithubRepo
)
when { principal.app == resource.allowed_app };
two calls — policy from template, then attach
const policy = await client.policies
  .createFromTemplate({
    template: 'github:read-only',
    name: 'GitHub read',
  });

await client.apps.attachPolicy({
  appId: app.id,
  policyId: policy.id,
});
step 04

Trigger your first call.

Authorize gives you a UCAN. Hand that UCAN to the PDP and it makes the real GitHub call on your behalf. Your agent never holds the OAuth token — every step lands in the audit chain.

/app/audit · receipts after step 04
↪ chained
  • 14:22:11allow/github/issue/list (proxy)ae71…1c7b
  • 14:22:10allow/github/issue/list (authorize)1c7b…09f4
  • 14:22:09initapp.created · Inbox triage bot09f4…0000
Every row links to the row above via prevHash. Daily roots are Ed25519-signed and replayable via @auto-nomos/audit-verify.
authorize + fetch — same two HTTP calls, less plumbing
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());