Your agents, from your own code
Run your Nagent agents from a script, a backend, Claude Code, Cursor or any MCP client. A run from outside is governed exactly as one inside Nagent: the same knowledge, tools, autonomy level and approvals, and spend through your budget.
These are the developer docs. Setting up and running your AI teams in the workspace itself is covered in the platform docs.
Quickstart
- Create an API key in Nagent at Settings, API & MCP (steps below).
- Run an agent:
curl -X POST https://nagent.ai/api/v1/agents/nora/runs \
-H "Authorization: Bearer $NAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"input": "Research retail-1.example and draft a first email"}'The response is the finished run: its output, the actions it took, and any it is waiting on a person to approve.
Create an API key
- Sign in to Nagent and open
https://nagent.ai/admin/settings/api(Settings, API & MCP). Viewing it needssettings:read; creating a key needssettings:write. - Choose Create key. Name it after what will use it, for example Zapier production or Cursor, Priya.
- Pick live or test. A test key runs agents without tools, so it never sends or writes anything: use it while you build.
- Give it scopes (
agents:runandruns:readto run agents) and the agents it may reach. Choose personal if it should act as you, which deciding approvals needs. - Optionally set an expiry, an IP allowlist (Business) or a lower rate limit.
- Copy the key. It is shown once, with every snippet on this page filled in. Store it as
NAGENT_API_KEYin your secret manager or shell.
Rotate makes a successor with the same access and keeps the old key working for 24 hours. Revoke stops it at once. Keys start on the Team plan.
From any language
The API is plain HTTPS and JSON with a bearer key, so any stack can call it. Official SDKs exist for Python and TypeScript. For anything else, generate a client from the OpenAPI document at /api/v1/openapi.json, or import that URL into Postman, Insomnia or Bruno to try every call with your key.
Python
# pip install nagent
import os
from nagent import Nagent
nagent = Nagent(api_key=os.environ["NAGENT_API_KEY"], base_url="https://nagent.ai")
run = nagent.runs.create("nora", input="Research retail-1.example and draft a first email")
print(run["status"], run["output"])TypeScript
// npm install @nagent/sdk
import { Nagent } from '@nagent/sdk';
const nagent = new Nagent({ apiKey: process.env.NAGENT_API_KEY!, baseUrl: 'https://nagent.ai' });
const run = await nagent.runs.create('nora', { input: 'Research retail-1.example and draft a first email' });
console.log(run.status, run.output);Go, Java, C#, Ruby, PHP and the rest
# Any language: generate a client from the OpenAPI document
npx @openapitools/openapi-generator-cli generate \
-i https://nagent.ai/api/v1/openapi.json -g go -o ./nagent-go
# -g java | csharp | ruby | php | kotlin | swift5 | rust | …Keys and scopes
A key belongs to your workspace and is made by a member. It can hold no scope its maker lacks, reaches only the agents it is given, and is checked on every call against the plan the workspace is on that day. Keys start nag_live_ or nag_test_; a test key runs agents without tools, so it never sends or writes anything. Rotate makes a successor and keeps the old key working for 24 hours; revoke stops it at once.
| Scope | What it allows | From |
|---|---|---|
agents:read | List the agents the key reaches and read their agent.md. | Team |
agents:run | Start a governed run. Actions below the autonomy floor wait for a person. | Team |
runs:read | Read a run’s status, output and the actions it took or is waiting on. | Team |
approvals:read | List the actions waiting for a person. | Business |
approvals:decide | Approve or reject a waiting action, as the member the key belongs to. Personal keys only. | Business |
webhooks:manage | Add and remove the endpoints Nagent posts events to. | Business |
knowledge:read | List, search and read the Knowledge Hub documents the key’s member may read. | Team |
knowledge:write | Add a document to the Knowledge Hub from text or a web address. | Team |
leads:read | List and read leads, with contact fields as the member’s role shows them. | Team |
leads:write | Create leads and change their stage, owner and details. | Team |
ops:triggers | List the triggers of the agents the key reaches, fire one now, or subscribe an agent to an event. | Team |
Plans and limits
| Plan | API and MCP | Requests a minute | Webhook endpoints |
|---|---|---|---|
| Solo | The settings page and snippets; keys start on Team | None | None |
| Team | Live keys, agents and runs scopes | 60 | 1 |
| Business | Every scope, personal keys that decide approvals, IP allowlists | 600 | 5 |
| Enterprise | Every scope, personal keys, IP allowlists, unlimited call log | 6,000 | 25 |
See pricing for the plans themselves.
REST reference
Base URL https://nagent.ai/api/v1. JSON in and out; errors are RFC 9457 problem documents; every response carries X-Request-Id, Nagent-Api-Version and RateLimit-*. The machine-readable reference is /api/v1/openapi.json.
| Call | What it does | Scope |
|---|---|---|
GET /agents | List the agents this key reaches | agents:read |
GET /agents/{key} | Read one agent | agents:read |
GET /agents/{key}/agent.md | The agent as a markdown specification | agents:read |
POST /agents/{key}/runs | Run an agent | agents:run |
GET /runs/{id} | Read a run, with its output once it has one | runs:read |
GET /runs/{id}/events | A run as server-sent events | runs:read |
GET /approvals | Actions waiting for a person | approvals:read |
POST /approvals/{id}/decide | Approve or reject a waiting action | approvals:decide |
GET /webhooks | List webhook endpoints | webhooks:manage |
POST /webhooks | Add a webhook endpoint | webhooks:manage |
DELETE /webhooks/{id} | Remove a webhook endpoint | webhooks:manage |
GET /knowledge | Knowledge Hub documents the key’s member may read | knowledge:read |
POST /knowledge | Add a document | knowledge:write |
GET /knowledge/{id} | Read a document with its text | knowledge:read |
GET /leads | Leads, newest first | leads:read |
POST /leads | Create a lead | leads:write |
GET /leads/{id} | Read a lead | leads:read |
PATCH /leads/{id} | Change a lead | leads:write |
GET /triggers | Triggers of the agents this key reaches | ops:triggers |
POST /triggers/{id}/fire | Fire a trigger now | ops:triggers |
Runs, sessions and async
POST /agents/{key}/runs takes input (up to 9000 characters) and optionally:
- A run’s
outputis returned to the key that started it; another key reads the run without it. sessionId: continue a conversation. The same id sends the agent the last turns this key ran in it, so a chat in your product keeps its thread. Sessions belong to the key.metadata: up to 16 string labels of up to 500 characters, returned on the run and on its webhook events, for your own ticket or user ids.- Header
Idempotency-Key: a retry within 24 hours is refused with 409 rather than run twice. - Header
Prefer: respond-async: answer 202 at once with the runrunningand aLocation; with the same key, pollGET /runs/{id}, followGET /runs/{id}/events, or wait for therun.completedwebhook. Use it for long runs and from serverless functions with short timeouts.
curl -X POST https://nagent.ai/api/v1/agents/nora/runs \
-H "Authorization: Bearer $NAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Prefer: respond-async" \
-d '{"input": "Audit retail-1.example", "sessionId": "crm-4711", "metadata": {"ticket": "T-88"}}'
# 202 Accepted, Location: /api/v1/runs/<id>, status "running". Then:
curl https://nagent.ai/api/v1/runs/<id> -H "Authorization: Bearer $NAGENT_API_KEY"Lists page with a cursor: pass nextCursor back as cursor until hasMore is false.
curl "https://nagent.ai/api/v1/approvals?status=pending&limit=50" -H "Authorization: Bearer $NAGENT_API_KEY"
# { "object": "list", "data": [...], "hasMore": true, "nextCursor": "MTc2…" }
curl "https://nagent.ai/api/v1/approvals?status=pending&limit=50&cursor=MTc2…" -H "Authorization: Bearer $NAGENT_API_KEY"Knowledge, leads and triggers (Beta)
These scopes start on the Team plan, and the key acts as its member: it reads and writes what that person may in Nagent, no more.
knowledge:readlists and reads the Knowledge Hub documents the member may read, matched on title and topics, or by meaning withmode=semantic, which draws a little from the workspace’s credits. A document they may not read answers 404.knowledge:writeadds a document fromtitleandtext, or from aurlNagent reads. A full Knowledge Hub answers 402.leads:readlists and reads leads, with contact fields as the member’s role shows them in the CRM.leads:writecreates a lead from an email, phone or LinkedIn URL, owned by the member, and updates one through the CRM’s own update. A lead that already exists answers 409 with its address inLocation, so a sync can retry safely. A lead made here is not sent outreach on its own; run an agent for that, and its sends wait for approval as usual.ops:triggerslists the triggers of the agents the key reaches and fires one now. A trigger that is off or paused answers 409, one past its hourly limit 429, and the fire is governed as when it fires on schedule: the outcome isfired, orqueuedwhen its action waits for a person.
MCP for coding agents
The server is https://nagent.ai/api/mcp/nagent over Streamable HTTP, with the same key as a bearer header. It works with Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Devin and any client that speaks MCP; a client that only starts local servers reaches it through mcp-remote. Each agent the key reaches is a <agent>_run tool (with optional sessionId and metadata); nagent_list_agents, nagent_get_run, nagent_wait_run and, on a personal key, nagent_decide_approval follow the key’s scopes, as do nagent_search_knowledge, nagent_read_knowledge, nagent_add_knowledge, nagent_list_leads, nagent_get_lead, nagent_create_lead, nagent_update_lead, nagent_list_triggers and nagent_fire_trigger. Each agent’s agent.md and card are resources. Discovery: /.well-known/mcp.json. Set NAGENT_API_KEY in the environment the client starts from, or paste the key where the snippet names it.
Claude Code
claude mcp add --transport http nagent https://nagent.ai/api/mcp/nagent --header "Authorization: Bearer $NAGENT_API_KEY"Codex in ~/.codex/config.toml
# Set NAGENT_API_KEY in the environment Codex starts from.
[mcp_servers.nagent]
url = "https://nagent.ai/api/mcp/nagent"
bearer_token_env_var = "NAGENT_API_KEY"Cursor in .cursor/mcp.json
{
"mcpServers": {
"nagent": {
"url": "https://nagent.ai/api/mcp/nagent",
"headers": {
"Authorization": "Bearer ${env:NAGENT_API_KEY}"
}
}
}
}VS Code in .vscode/mcp.json
{
"inputs": [
{
"type": "promptString",
"id": "nagent-api-key",
"description": "Nagent API key",
"password": true
}
],
"servers": {
"nagent": {
"type": "http",
"url": "https://nagent.ai/api/mcp/nagent",
"headers": {
"Authorization": "Bearer ${input:nagent-api-key}"
}
}
}
}Windsurf in ~/.codeium/windsurf/mcp_config.json
{
"mcpServers": {
"nagent": {
"serverUrl": "https://nagent.ai/api/mcp/nagent",
"headers": {
"Authorization": "Bearer ${env:NAGENT_API_KEY}"
}
}
}
}Gemini CLI in ~/.gemini/settings.json
{
"mcpServers": {
"nagent": {
"httpUrl": "https://nagent.ai/api/mcp/nagent",
"headers": {
"Authorization": "Bearer $NAGENT_API_KEY"
}
}
}
}Claude Desktop in claude_desktop_config.json
{
"mcpServers": {
"nagent": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://nagent.ai/api/mcp/nagent",
"--header",
"Authorization:${NAGENT_AUTH}"
],
"env": {
"NAGENT_AUTH": "Bearer <your key>"
}
}
}
}Devin and others in the client’s MCP settings
Transport: Streamable HTTP
URL: https://nagent.ai/api/mcp/nagent
Header: Authorization: Bearer <your key>
A client that only runs local (stdio) servers can use:
npx -y mcp-remote https://nagent.ai/api/mcp/nagent --header "Authorization:Bearer <your key>"Webhooks
Add an https endpoint in Settings, API & MCP. Nagent signs every delivery in the Standard Webhooks scheme. A delivery that fails is retried after 1 minute, 5, 30, then 2, 5 and 10 hours; after 24 hours the endpoint pauses and its maker is emailed. De-duplicate on webhook-id, which is the same on every retry.
| Event | When |
|---|---|
run.started | A run started through the API or MCP began. Carries the run id, agent, session and metadata. |
run.completed | A run started through the API or MCP finished. Carries the run without its output. |
run.failed | A run started through the API or MCP did not finish, and why. |
approval.requested | A run is waiting for a person to approve one or more actions, by approval id. |
approval.decided | A waiting action was approved or rejected over the API. |
Verifying a delivery: TypeScript
import crypto from 'node:crypto';
// secret: the whsec_… shown when you added the endpoint
export function verify(secret: string, headers: Headers, body: string): boolean {
const id = headers.get('webhook-id') ?? '';
const ts = Number(headers.get('webhook-timestamp'));
if (!id || Math.abs(Date.now() / 1000 - ts) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key).update(`${id}.${ts}.${body}`).digest();
return (headers.get('webhook-signature') ?? '').split(' ').some(entry => {
const [v, sig] = entry.split(',');
const got = Buffer.from(sig ?? '', 'base64');
return v === 'v1' && got.length === expected.length && crypto.timingSafeEqual(got, expected);
});
}Verifying a delivery: Python
import base64, hashlib, hmac, time
# secret: the whsec_… shown when you added the endpoint
def verify(secret: str, headers: dict, body: bytes) -> bool:
msg_id = headers.get("webhook-id", "")
ts = int(headers.get("webhook-timestamp", "0"))
if not msg_id or abs(time.time() - ts) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
expected = hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()
for entry in headers.get("webhook-signature", "").split(" "):
version, _, sig = entry.partition(",")
if version == "v1" and hmac.compare_digest(base64.b64decode(sig), expected):
return True
return FalseAny Standard Webhooks library verifies these too; the SDKs include the check.
Approvals
A run is one governed pass of an agent over your input. Each agent works at an autonomy level, L1 Observe to L5 Lead; an action below its level does not run on its own but waits for a person, and the run comes back waiting_for_approval naming the approvals. A member decides them in Nagent, or a personal key with approvals:decide decides them as that member. Every run is checked against the workspace budget first.
Versioning
This is version 1, named in the path and in the Nagent-Api-Version header. Within v1, fields and operations are only added, so ignore fields you do not know. A breaking change ships as /api/v2. An operation being retired answers with Deprecation, a Sunset date at least six months out, and a Link to what replaces it. The older /api/public/agent/run is the website widget’s endpoint, not this API; called with a key it answers Deprecation and points here.
Errors
| Status | Type | Meaning |
|---|---|---|
| 401 | #missing-key | No `Authorization: Bearer` header, or not a key. |
| 401 | #invalid-key | No workspace has this key. |
| 401 | #key-revoked | The key was revoked. |
| 401 | #key-expired | The key passed its expiry. |
| 401 | #key-rotated | The key was rotated and its 24 hours of grace are over. |
| 403 | #plan | The workspace plan does not include the API or MCP. |
| 403 | #ip-not-allowed | The call came from outside the key’s allowlist. |
| 403 | #scope-missing | The key was not given the scope this call needs. |
| 403 | #scope-withheld | The key holds the scope, but the plan no longer includes it. |
| 403 | #agent-not-allowed | The key was not given this agent. |
| 404 | #agent-not-found | No agent with that key in the workspace. |
| 409 | #agent-paused | The agent is paused in the workspace. |
| 409 | #duplicate-request | The Idempotency-Key was used in the last 24 hours. |
| 400 | #invalid-input | The run request is missing its input, or a session id or metadata is malformed. |
| 400 | #invalid-cursor | The cursor was not one a previous page returned. |
| 429 | #rate-limited | Over the key’s requests a minute. Retry after the Retry-After seconds. |
SDKs
Both are thin clients over the REST API with no dependencies. Tests hold each to the OpenAPI document, so a method is one operation. Errors raise the problem document with its request id. Both are published at 1.0.0: nagent on PyPI and @nagent/sdk on npm, an ES module you load with import. The REST API and its OpenAPI document above work from any other stack.
Python: pip install nagent
# pip install nagent
import os
from nagent import Nagent
nagent = Nagent(api_key=os.environ["NAGENT_API_KEY"])
accepted = nagent.runs.create("nora", input="Research retail-1.example", session_id="crm-4711", wait=False)
run = nagent.runs.wait(accepted["id"])
print(run["status"], run["output"])
for approval in nagent.approvals.iter():
print(approval["id"], approval["action"])TypeScript and JavaScript: npm install @nagent/sdk
// npm install @nagent/sdk
import { Nagent } from '@nagent/sdk';
const nagent = new Nagent({ apiKey: process.env.NAGENT_API_KEY! });
const accepted = await nagent.runs.create('nora', { input: 'Research retail-1.example', sessionId: 'crm-4711' }, { async: true });
const run = await nagent.runs.wait(accepted.id!);
if (run.status === 'waiting_for_approval') console.log('Waiting on', run.waitingOn);
else console.log(run.output);
for await (const approval of nagent.approvals.all()) console.log(approval.id, approval.action);