Quickstart

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:

  1. Creates an agent named "Support agent".
  2. Creates a "Brand voice" rule.
  3. Creates a vault with a STRIPE_API_KEY secret scoped to *.stripe.com.
  4. Attaches the rule and the vault to the agent.
  5. Deploys the agent.
  6. Starts a conversation with the agent.
  7. Polls the user message until the agent finishes processing.
  8. Lists the conversation messages and prints the non-progress ones.
  9. 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-here

Requires 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/null

Requires 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.",
	}))
}