# 3. Full example

End-to-end script — agent setup, deploy, conversation — in every supported language.





Everything from [Configure and deploy](/api/quickstart/configure-and-deploy) and [Drive a conversation](/api/quickstart/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.

<Callout type="warn">
  All scripts read `NAIRI_API_KEY` from the environment. Export it before running:

  ```bash
  export NAIRI_API_KEY=your-key-here
  ```
</Callout>

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    Requires [`curl`](https://curl.se) and [`jq`](https://jqlang.github.io/jq/). Save as `quickstart.sh` and run with `bash quickstart.sh`.

    ```bash
    #!/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
    ```
  </Tab>

  <Tab value="TypeScript">
    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`.

    ```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);
    });
    ```
  </Tab>

  <Tab value="Ruby">
    Stdlib only — works in plain Ruby or a Rails console. Save as `quickstart.rb` and run with `ruby quickstart.rb`.

    ```ruby
    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.",
    })
    ```
  </Tab>

  <Tab value="Python">
    Requires `requests` (`pip install requests`). Save as `quickstart.py` and run with `python quickstart.py`.

    ```python
    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."},
    )
    ```
  </Tab>

  <Tab value="Go">
    Stdlib only — no module dependencies. Save as `quickstart.go` and run with `go run quickstart.go`.

    ```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.",
    	}))
    }
    ```
  </Tab>
</Tabs>
