1. Configure and deploy an agent
Create an agent, attach a rule and a vault, then deploy it.
Five short calls take you from a blank organization to a running agent that knows your brand voice and has a secret it can use.
The agent we'll build is a "Support agent" with:
- A rule describing how the agent should talk to customers (its brand voice).
- A vault with one secret (
STRIPE_API_KEY) the agent can use when calling Stripe.
Setup
Pick a language and copy the imports/setup once at the top of your file. Every step below uses these helpers.
BASE=https://api.nairi.ai/api/public/v1
AUTH="Authorization: Bearer $NAIRI_API_KEY"
CT="Content-Type: application/json"const BASE = "https://api.nairi.ai/api/public/v1";
const HEADERS = {
Authorization: `Bearer ${process.env.NAIRI_API_KEY}`,
"Content-Type": "application/json",
};require "net/http"
require "json"
require "uri"
BASE = "https://api.nairi.ai/api/public/v1"
HEADERS = {
"Authorization" => "Bearer #{ENV['NAIRI_API_KEY']}",
"Content-Type" => "application/json",
}
def post(path, body)
uri = URI("#{BASE}#{path}")
req = Net::HTTP::Post.new(uri, HEADERS)
req.body = body.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
JSON.parse(res.body)
endimport os
import requests
BASE = "https://api.nairi.ai/api/public/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['NAIRI_API_KEY']}",
"Content-Type": "application/json",
}package main
import (
"bytes"
"encoding/json"
"io"
"net/http"
"os"
)
const base = "https://api.nairi.ai/api/public/v1"
func post(path string, body any) (map[string]any, error) {
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", base+path, bytes.NewReader(buf))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("NAIRI_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
raw, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
var out map[string]any
if len(raw) == 0 {
return nil, nil
}
return out, json.Unmarshal(raw, &out)
}Step 1: Create the agent
The agent's slug (agent_id) is the human-readable identifier you'll pass to every other endpoint. It's derived from name.
AGENT_ID=$(curl -s -X POST $BASE/agents \
-H "$AUTH" -H "$CT" \
-d '{"name": "Support agent", "instances_count": 1}' \
| jq -r .agent_id)const agentRes = await fetch(`${BASE}/agents`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ name: "Support agent", instances_count: 1 }),
});
const agent = (await agentRes.json()) as { agent_id: string };
const agentId = agent.agent_id;agent = post("/agents", { name: "Support agent", instances_count: 1 })
agent_id = agent["agent_id"]agent = requests.post(
f"{BASE}/agents",
headers=HEADERS,
json={"name": "Support agent", "instances_count": 1},
).json()
agent_id = agent["agent_id"]agent, err := post("/agents", map[string]any{
"name": "Support agent",
"instances_count": 1,
})
if err != nil {
panic(err)
}
agentID := agent["agent_id"].(string)Step 2: Create a rule
Rules are text fragments that get injected into the agent's system prompt. Brand voice, policies, runbooks — anything you'd otherwise paste into every prompt.
RULE_ID=$(curl -s -X POST $BASE/artifacts/rules \
-H "$AUTH" -H "$CT" \
-d '{
"title": "Brand voice",
"description": "How the agent talks to customers",
"content": "Always respond in plain English. No bullet lists unless asked."
}' | jq -r .id)const ruleRes = await fetch(`${BASE}/artifacts/rules`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
title: "Brand voice",
description: "How the agent talks to customers",
content: "Always respond in plain English. No bullet lists unless asked.",
}),
});
const rule = (await ruleRes.json()) as { id: string };
const ruleId = rule.id;rule = post("/artifacts/rules", {
title: "Brand voice",
description: "How the agent talks to customers",
content: "Always respond in plain English. No bullet lists unless asked.",
})
rule_id = rule["id"]rule = requests.post(
f"{BASE}/artifacts/rules",
headers=HEADERS,
json={
"title": "Brand voice",
"description": "How the agent talks to customers",
"content": "Always respond in plain English. No bullet lists unless asked.",
},
).json()
rule_id = rule["id"]rule, err := post("/artifacts/rules", map[string]any{
"title": "Brand voice",
"description": "How the agent talks to customers",
"content": "Always respond in plain English. No bullet lists unless asked.",
})
if err != nil {
panic(err)
}
ruleID := rule["id"].(string)Step 3: Create a vault and add a secret
Vaults are logical containers for secrets. Secrets are encrypted at rest and scoped to specific outbound domains — the agent can only use a secret when it's calling one of the allowed_domains.
VAULT_ID=$(curl -s -X POST $BASE/vaults \
-H "$AUTH" -H "$CT" \
-d '{"name": "Production secrets"}' | jq -r .id)
curl -s -X POST $BASE/vaults/$VAULT_ID/secrets \
-H "$AUTH" -H "$CT" \
-d '{
"env_key": "STRIPE_API_KEY",
"value": "sk_live_...",
"allowed_domains": ["*.stripe.com"]
}'const vaultRes = await fetch(`${BASE}/vaults`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ name: "Production secrets" }),
});
const vault = (await vaultRes.json()) as { id: string };
const vaultId = vault.id;
await fetch(`${BASE}/vaults/${vaultId}/secrets`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
env_key: "STRIPE_API_KEY",
value: "sk_live_...",
allowed_domains: ["*.stripe.com"],
}),
});vault = post("/vaults", { name: "Production secrets" })
vault_id = vault["id"]
post("/vaults/#{vault_id}/secrets", {
env_key: "STRIPE_API_KEY",
value: "sk_live_...",
allowed_domains: ["*.stripe.com"],
})vault = requests.post(
f"{BASE}/vaults",
headers=HEADERS,
json={"name": "Production secrets"},
).json()
vault_id = vault["id"]
requests.post(
f"{BASE}/vaults/{vault_id}/secrets",
headers=HEADERS,
json={
"env_key": "STRIPE_API_KEY",
"value": "sk_live_...",
"allowed_domains": ["*.stripe.com"],
},
)vault, err := post("/vaults", map[string]any{"name": "Production secrets"})
if err != nil {
panic(err)
}
vaultID := vault["id"].(string)
if _, err := post("/vaults/"+vaultID+"/secrets", map[string]any{
"env_key": "STRIPE_API_KEY",
"value": "sk_live_...",
"allowed_domains": []string{"*.stripe.com"},
}); err != nil {
panic(err)
}Step 4: Attach the rule and the vault to the agent
Resources live independently of agents. To make the agent actually see the rule and the secret, attach them.
curl -s -X POST $BASE/agents/$AGENT_ID/resources \
-H "$AUTH" -H "$CT" \
-d "{\"entity_type\":\"agent_artifact_rule\",\"entity_id\":\"$RULE_ID\"}"
curl -s -X POST $BASE/agents/$AGENT_ID/resources \
-H "$AUTH" -H "$CT" \
-d "{\"entity_type\":\"vault\",\"entity_id\":\"$VAULT_ID\"}"for (const [entityType, entityId] of [
["agent_artifact_rule", ruleId],
["vault", vaultId],
] as const) {
await fetch(`${BASE}/agents/${agentId}/resources`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ entity_type: entityType, entity_id: entityId }),
});
}[
["agent_artifact_rule", rule_id],
["vault", vault_id],
].each do |entity_type, entity_id|
post("/agents/#{agent_id}/resources", {
entity_type: entity_type,
entity_id: entity_id,
})
endfor entity_type, entity_id in [
("agent_artifact_rule", rule_id),
("vault", vault_id),
]:
requests.post(
f"{BASE}/agents/{agent_id}/resources",
headers=HEADERS,
json={"entity_type": entity_type, "entity_id": entity_id},
)attachments := []struct {
entityType string
entityID string
}{
{"agent_artifact_rule", ruleID},
{"vault", vaultID},
}
for _, a := range attachments {
if _, err := post("/agents/"+agentID+"/resources", map[string]any{
"entity_type": a.entityType,
"entity_id": a.entityID,
}); err != nil {
panic(err)
}
}Step 5: Deploy
POST /agents/{agent_id}/deploy is asynchronous — it returns 202 Accepted immediately while the container comes up in the background. Set update_config_only: true to roll config without restarting the container.
curl -s -X POST $BASE/agents/$AGENT_ID/deploy \
-H "$AUTH" -H "$CT" \
-d '{"update_config_only": false}'await fetch(`${BASE}/agents/${agentId}/deploy`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ update_config_only: false }),
});post("/agents/#{agent_id}/deploy", { update_config_only: false })requests.post(
f"{BASE}/agents/{agent_id}/deploy",
headers=HEADERS,
json={"update_config_only": False},
)if _, err := post("/agents/"+agentID+"/deploy", map[string]any{
"update_config_only": false,
}); err != nil {
panic(err)
}The agent is now deploying. Head to Drive a conversation to send it its first prompt.