# 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 [#setup]

Pick a language and copy the imports/setup once at the top of your file. Every step below uses these helpers.

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    ```bash
    BASE=https://api.nairi.ai/api/public/v1
    AUTH="Authorization: Bearer $NAIRI_API_KEY"
    CT="Content-Type: application/json"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const BASE = "https://api.nairi.ai/api/public/v1";
    const HEADERS = {
      Authorization: `Bearer ${process.env.NAIRI_API_KEY}`,
      "Content-Type": "application/json",
    };
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    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)
    end
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import os
    import requests

    BASE = "https://api.nairi.ai/api/public/v1"
    HEADERS = {
        "Authorization": f"Bearer {os.environ['NAIRI_API_KEY']}",
        "Content-Type": "application/json",
    }
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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)
    }
    ```
  </Tab>
</Tabs>

## Step 1: Create the agent [#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`.

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    ```bash
    AGENT_ID=$(curl -s -X POST $BASE/agents \
      -H "$AUTH" -H "$CT" \
      -d '{"name": "Support agent", "instances_count": 1}' \
      | jq -r .agent_id)
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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;
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    agent = post("/agents", { name: "Support agent", instances_count: 1 })
    agent_id = agent["agent_id"]
    ```
  </Tab>

  <Tab value="Python">
    ```python
    agent = requests.post(
        f"{BASE}/agents",
        headers=HEADERS,
        json={"name": "Support agent", "instances_count": 1},
    ).json()
    agent_id = agent["agent_id"]
    ```
  </Tab>

  <Tab value="Go">
    ```go
    agent, err := post("/agents", map[string]any{
    	"name":            "Support agent",
    	"instances_count": 1,
    })
    if err != nil {
    	panic(err)
    }
    agentID := agent["agent_id"].(string)
    ```
  </Tab>
</Tabs>

## Step 2: Create a rule [#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.

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    ```bash
    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)
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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;
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    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"]
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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"]
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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)
    ```
  </Tab>
</Tabs>

## Step 3: Create a vault and add a secret [#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`.

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    ```bash
    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"]
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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"],
      }),
    });
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    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"],
    })
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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"],
        },
    )
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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)
    }
    ```
  </Tab>
</Tabs>

## Step 4: Attach the rule and the vault to the agent [#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.

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    ```bash
    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\"}"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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 }),
      });
    }
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    [
      ["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,
      })
    end
    ```
  </Tab>

  <Tab value="Python">
    ```python
    for 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},
        )
    ```
  </Tab>

  <Tab value="Go">
    ```go
    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)
    	}
    }
    ```
  </Tab>
</Tabs>

## Step 5: Deploy [#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.

<Tabs items="[&#x22;bash&#x22;, &#x22;TypeScript&#x22;, &#x22;Ruby&#x22;, &#x22;Python&#x22;, &#x22;Go&#x22;]">
  <Tab value="bash">
    ```bash
    curl -s -X POST $BASE/agents/$AGENT_ID/deploy \
      -H "$AUTH" -H "$CT" \
      -d '{"update_config_only": false}'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    await fetch(`${BASE}/agents/${agentId}/deploy`, {
      method: "POST",
      headers: HEADERS,
      body: JSON.stringify({ update_config_only: false }),
    });
    ```
  </Tab>

  <Tab value="Ruby">
    ```ruby
    post("/agents/#{agent_id}/deploy", { update_config_only: false })
    ```
  </Tab>

  <Tab value="Python">
    ```python
    requests.post(
        f"{BASE}/agents/{agent_id}/deploy",
        headers=HEADERS,
        json={"update_config_only": False},
    )
    ```
  </Tab>

  <Tab value="Go">
    ```go
    if _, err := post("/agents/"+agentID+"/deploy", map[string]any{
    	"update_config_only": false,
    }); err != nil {
    	panic(err)
    }
    ```
  </Tab>
</Tabs>

<Callout type="info">
  The agent is now deploying. Head to &#x2A;*[Drive a conversation](/api/quickstart/drive-a-conversation)** to send it its first prompt.
</Callout>
