3. Full example
End-to-end script — agent setup, deploy, conversation — in every supported language.
Everything from Configure and deploy and Drive a conversation, stitched into a single copy-paste script.
Each script:
- Creates an agent named "Support agent".
- Creates a "Brand voice" rule.
- Creates a vault with a
STRIPE_API_KEYsecret scoped to*.stripe.com. - Attaches the rule and the vault to the agent.
- Deploys the agent.
- Starts a conversation with the agent.
- Polls the user message until the agent finishes processing.
- Lists the conversation messages and prints the non-
progressones. - Sends a follow-up on the same conversation.
All scripts read NAIRI_API_KEY from the environment. Export it before running:
export NAIRI_API_KEY=your-key-hereRequires curl and jq. Save as quickstart.sh and run with bash quickstart.sh.
#!/usr/bin/env bash
set -euo pipefail
BASE=https://api.nairi.ai/api/public/v1
AUTH="Authorization: Bearer ${NAIRI_API_KEY:?NAIRI_API_KEY must be set}"
CT="Content-Type: application/json"
# 1. Create the agent.
AGENT_ID=$(curl -s -X POST "$BASE/agents" \
-H "$AUTH" -H "$CT" \
-d '{"name": "Support agent", "instances_count": 1}' \
| jq -r .agent_id)
# 2. Create a rule.
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)
# 3. Create a vault and drop in a secret.
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"]
}' > /dev/null
# 4. Attach the rule and the vault.
curl -s -X POST "$BASE/agents/$AGENT_ID/resources" \
-H "$AUTH" -H "$CT" \
-d "{\"entity_type\":\"agent_artifact_rule\",\"entity_id\":\"$RULE_ID\"}" > /dev/null
curl -s -X POST "$BASE/agents/$AGENT_ID/resources" \
-H "$AUTH" -H "$CT" \
-d "{\"entity_type\":\"vault\",\"entity_id\":\"$VAULT_ID\"}" > /dev/null
# 5. Deploy.
curl -s -X POST "$BASE/agents/$AGENT_ID/deploy" \
-H "$AUTH" -H "$CT" \
-d '{"update_config_only": false}' > /dev/null
# 6. Start the conversation.
RESP=$(curl -s -X POST "$BASE/conversations/start" \
-H "$AUTH" -H "$CT" \
-d "{
\"agent_id\": \"$AGENT_ID\",
\"prompt\": \"Refund the failed charge for customer cus_123.\",
\"ask_mode\": false
}")
JOB_ID=$(echo "$RESP" | jq -r .job_id)
MSG_ID=$(echo "$RESP" | jq -r .message_id)
# 7. Poll the user message.
while true; do
STATUS=$(curl -s -H "$AUTH" "$BASE/messages/$MSG_ID" | jq -r .status)
case "$STATUS" in
completed|failed) break ;;
esac
sleep 2
done
# 8. Print the assistant's reply.
curl -s -H "$AUTH" "$BASE/conversations/$JOB_ID/messages" \
| jq '.messages[] | select(.role != "progress")'
# 9. Follow-up.
curl -s -X POST "$BASE/conversations/$JOB_ID/continue" \
-H "$AUTH" -H "$CT" \
-d '{"prompt": "Also send the customer an apology email."}' > /dev/nullRequires Node.js 18+ (for the built-in fetch) and @types/node for the type-checker. Save as quickstart.ts and run with npx tsx quickstart.ts.
const BASE = "https://api.nairi.ai/api/public/v1";
function headers(): Record<string, string> {
const apiKey = process.env.NAIRI_API_KEY;
if (!apiKey) throw new Error("NAIRI_API_KEY must be set");
return {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
}
async function api<T>(method: string, path: string, body?: unknown): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
method,
headers: headers(),
body: body === undefined ? undefined : JSON.stringify(body),
});
const text = await res.text();
return text.length === 0 ? ({} as T) : (JSON.parse(text) as T);
}
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
type Message = { role: string; content: string };
async function main() {
// 1. Create the agent.
const agent = await api<{ agent_id: string }>("POST", "/agents", {
name: "Support agent",
instances_count: 1,
});
const agentId = agent.agent_id;
// 2. Create a rule.
const rule = await api<{ id: string }>("POST", "/artifacts/rules", {
title: "Brand voice",
description: "How the agent talks to customers",
content: "Always respond in plain English. No bullet lists unless asked.",
});
const ruleId = rule.id;
// 3. Create a vault and drop in a secret.
const vault = await api<{ id: string }>("POST", "/vaults", {
name: "Production secrets",
});
const vaultId = vault.id;
await api("POST", `/vaults/${vaultId}/secrets`, {
env_key: "STRIPE_API_KEY",
value: "sk_live_...",
allowed_domains: ["*.stripe.com"],
});
// 4. Attach the rule and the vault.
for (const [entityType, entityId] of [
["agent_artifact_rule", ruleId],
["vault", vaultId],
] as const) {
await api("POST", `/agents/${agentId}/resources`, {
entity_type: entityType,
entity_id: entityId,
});
}
// 5. Deploy.
await api("POST", `/agents/${agentId}/deploy`, { update_config_only: false });
// 6. Start the conversation.
const started = await api<{ job_id: string; message_id: string }>(
"POST",
"/conversations/start",
{
agent_id: agentId,
prompt: "Refund the failed charge for customer cus_123.",
ask_mode: false,
},
);
const { job_id: jobId, message_id: messageId } = started;
// 7. Poll the user message.
while (true) {
const msg = await api<{ status: string }>("GET", `/messages/${messageId}`);
if (msg.status === "completed" || msg.status === "failed") break;
await sleep(2000);
}
// 8. Print the assistant's reply.
const convo = await api<{ messages: Message[] }>(
"GET",
`/conversations/${jobId}/messages`,
);
for (const m of convo.messages) {
if (m.role !== "progress") console.log(m);
}
// 9. Follow-up.
await api("POST", `/conversations/${jobId}/continue`, {
prompt: "Also send the customer an apology email.",
});
}
main().catch((err) => {
console.error(err);
process.exit(1);
});Stdlib only — works in plain Ruby or a Rails console. Save as quickstart.rb and run with ruby quickstart.rb.
require "net/http"
require "json"
require "uri"
BASE = "https://api.nairi.ai/api/public/v1"
API_KEY = ENV.fetch("NAIRI_API_KEY")
HEADERS = {
"Authorization" => "Bearer #{API_KEY}",
"Content-Type" => "application/json",
}
def api(method, path, body = nil)
uri = URI("#{BASE}#{path}")
req = case method
when :get then Net::HTTP::Get.new(uri, HEADERS)
when :post then Net::HTTP::Post.new(uri, HEADERS)
end
req.body = body.to_json if body
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
res.body.empty? ? {} : JSON.parse(res.body)
end
# 1. Create the agent.
agent = api(:post, "/agents", { name: "Support agent", instances_count: 1 })
agent_id = agent["agent_id"]
# 2. Create a rule.
rule = api(: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"]
# 3. Create a vault and drop in a secret.
vault = api(:post, "/vaults", { name: "Production secrets" })
vault_id = vault["id"]
api(:post, "/vaults/#{vault_id}/secrets", {
env_key: "STRIPE_API_KEY",
value: "sk_live_...",
allowed_domains: ["*.stripe.com"],
})
# 4. Attach the rule and the vault.
[
["agent_artifact_rule", rule_id],
["vault", vault_id],
].each do |entity_type, entity_id|
api(:post, "/agents/#{agent_id}/resources", {
entity_type: entity_type,
entity_id: entity_id,
})
end
# 5. Deploy.
api(:post, "/agents/#{agent_id}/deploy", { update_config_only: false })
# 6. Start the conversation.
started = api(:post, "/conversations/start", {
agent_id: agent_id,
prompt: "Refund the failed charge for customer cus_123.",
ask_mode: false,
})
job_id = started["job_id"]
message_id = started["message_id"]
# 7. Poll the user message.
loop do
status = api(:get, "/messages/#{message_id}")["status"]
break if %w[completed failed].include?(status)
sleep 2
end
# 8. Print the assistant's reply.
messages = api(:get, "/conversations/#{job_id}/messages")["messages"]
messages.reject { |m| m["role"] == "progress" }.each { |m| puts m }
# 9. Follow-up.
api(:post, "/conversations/#{job_id}/continue", {
prompt: "Also send the customer an apology email.",
})Requires requests (pip install requests). Save as quickstart.py and run with python quickstart.py.
import os
import time
import requests
BASE = "https://api.nairi.ai/api/public/v1"
API_KEY = os.environ["NAIRI_API_KEY"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def api(method, path, body=None):
res = requests.request(
method,
f"{BASE}{path}",
headers=HEADERS,
json=body,
)
return res.json() if res.content else {}
# 1. Create the agent.
agent = api("POST", "/agents", {"name": "Support agent", "instances_count": 1})
agent_id = agent["agent_id"]
# 2. Create a rule.
rule = api(
"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"]
# 3. Create a vault and drop in a secret.
vault = api("POST", "/vaults", {"name": "Production secrets"})
vault_id = vault["id"]
api(
"POST",
f"/vaults/{vault_id}/secrets",
{
"env_key": "STRIPE_API_KEY",
"value": "sk_live_...",
"allowed_domains": ["*.stripe.com"],
},
)
# 4. Attach the rule and the vault.
for entity_type, entity_id in [
("agent_artifact_rule", rule_id),
("vault", vault_id),
]:
api(
"POST",
f"/agents/{agent_id}/resources",
{"entity_type": entity_type, "entity_id": entity_id},
)
# 5. Deploy.
api("POST", f"/agents/{agent_id}/deploy", {"update_config_only": False})
# 6. Start the conversation.
started = api(
"POST",
"/conversations/start",
{
"agent_id": agent_id,
"prompt": "Refund the failed charge for customer cus_123.",
"ask_mode": False,
},
)
job_id = started["job_id"]
message_id = started["message_id"]
# 7. Poll the user message.
while True:
status = api("GET", f"/messages/{message_id}")["status"]
if status in ("completed", "failed"):
break
time.sleep(2)
# 8. Print the assistant's reply.
messages = api("GET", f"/conversations/{job_id}/messages")["messages"]
for m in messages:
if m["role"] != "progress":
print(m)
# 9. Follow-up.
api(
"POST",
f"/conversations/{job_id}/continue",
{"prompt": "Also send the customer an apology email."},
)Stdlib only — no module dependencies. Save as quickstart.go and run with go run quickstart.go.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const base = "https://api.nairi.ai/api/public/v1"
func api(method, path string, body any) (map[string]any, error) {
var reader io.Reader
if body != nil {
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
reader = bytes.NewReader(buf)
}
req, err := http.NewRequest(method, base+path, reader)
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
}
if len(raw) == 0 {
return map[string]any{}, nil
}
var out map[string]any
return out, json.Unmarshal(raw, &out)
}
func must(out map[string]any, err error) map[string]any {
if err != nil {
panic(err)
}
return out
}
func main() {
if os.Getenv("NAIRI_API_KEY") == "" {
panic("NAIRI_API_KEY must be set")
}
// 1. Create the agent.
agent := must(api("POST", "/agents", map[string]any{
"name": "Support agent",
"instances_count": 1,
}))
agentID := agent["agent_id"].(string)
// 2. Create a rule.
rule := must(api("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.",
}))
ruleID := rule["id"].(string)
// 3. Create a vault and drop in a secret.
vault := must(api("POST", "/vaults", map[string]any{"name": "Production secrets"}))
vaultID := vault["id"].(string)
must(api("POST", "/vaults/"+vaultID+"/secrets", map[string]any{
"env_key": "STRIPE_API_KEY",
"value": "sk_live_...",
"allowed_domains": []string{"*.stripe.com"},
}))
// 4. Attach the rule and the vault.
attachments := []struct {
entityType string
entityID string
}{
{"agent_artifact_rule", ruleID},
{"vault", vaultID},
}
for _, a := range attachments {
must(api("POST", "/agents/"+agentID+"/resources", map[string]any{
"entity_type": a.entityType,
"entity_id": a.entityID,
}))
}
// 5. Deploy.
must(api("POST", "/agents/"+agentID+"/deploy", map[string]any{
"update_config_only": false,
}))
// 6. Start the conversation.
started := must(api("POST", "/conversations/start", map[string]any{
"agent_id": agentID,
"prompt": "Refund the failed charge for customer cus_123.",
"ask_mode": false,
}))
jobID := started["job_id"].(string)
messageID := started["message_id"].(string)
// 7. Poll the user message.
for {
msg := must(api("GET", "/messages/"+messageID, nil))
status, _ := msg["status"].(string)
if status == "completed" || status == "failed" {
break
}
time.Sleep(2 * time.Second)
}
// 8. Print the assistant's reply.
convo := must(api("GET", "/conversations/"+jobID+"/messages", nil))
if messages, ok := convo["messages"].([]any); ok {
for _, m := range messages {
msg, _ := m.(map[string]any)
if msg["role"] != "progress" {
fmt.Println(msg)
}
}
}
// 9. Follow-up.
must(api("POST", "/conversations/"+jobID+"/continue", map[string]any{
"prompt": "Also send the customer an apology email.",
}))
}