# Coding Language Examples

This guide demonstrates how to validate and deploy a nativeMsg experience using seven different languages and tools. All examples use the same customer support experience definition.

## The Example Experience

The following experience defines three workflows for a customer support bot: a greeting, an order tracking flow, and a human escalation path.

```json
{
  "name": "Acme Customer Support",
  "welcomeMessageExecute": "greeting",
  "workflows": [
    {
      "name": "greeting",
      "intents": ["greeting"],
      "expressions": [
        "hello",
        "hi",
        "hey",
        "good morning",
        "good afternoon",
        "start"
      ],
      "actions": [
        {
          "name": "send-welcome",
          "send": {
            "message": {
              "text": "Welcome to Acme Support! How can I help you today?",
              "quickReplies": [
                {
                  "type": "text",
                  "title": "Track an order",
                  "payload": "track_order",
                  "execute": "order-tracking"
                },
                {
                  "type": "text",
                  "title": "Start a return",
                  "payload": "start_return",
                  "execute": "returns"
                },
                {
                  "type": "text",
                  "title": "Talk to an agent",
                  "payload": "human_agent",
                  "execute": "escalate-to-human"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "order-tracking",
      "intents": ["track_order", "order_status"],
      "expressions": [
        "where is my order",
        "track my package",
        "order status",
        "when will my order arrive",
        "has my order shipped"
      ],
      "actions": [
        {
          "name": "ask-order-number",
          "send": {
            "message": {
              "text": "Please enter your order number (format: ACM-XXXXX)."
            }
          }
        },
        {
          "name": "capture-order-number",
          "waitFor": {
            "data": "text",
            "content": "orderNumber",
            "timeout": "3m",
            "executeOnTimeout": "order-number-timeout"
          }
        },
        {
          "name": "validate-order-number",
          "validation": {
            "content": "orderNumber",
            "pattern": "^ACM-\\d{5}$",
            "executeOnError": "invalid-order-number"
          }
        },
        {
          "name": "fetch-order-status",
          "send": {
            "request": {
              "url": "https://api.acme.com/v1/orders/{orderNumber}",
              "method": "GET",
              "headers": {
                "Authorization": "Bearer {apiToken}",
                "Accept": "application/json"
              },
              "response": {
                "status": "orderStatus",
                "carrier": "carrierName",
                "estimatedDelivery": "deliveryDate"
              },
              "retries": 2,
              "fallback": "order-lookup-failed"
            }
          }
        },
        {
          "name": "send-order-status",
          "send": {
            "message": {
              "text": "Order {orderNumber}: {orderStatus}\nCarrier: {carrierName}\nEstimated delivery: {deliveryDate}",
              "quickReplies": [
                {
                  "type": "text",
                  "title": "Track shipment",
                  "payload": "track_shipment"
                },
                {
                  "type": "text",
                  "title": "Something else",
                  "payload": "main_menu",
                  "execute": "greeting"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "escalate-to-human",
      "intents": ["human_agent", "speak_to_agent"],
      "expressions": [
        "talk to a person",
        "speak to an agent",
        "human please",
        "I need help from a person"
      ],
      "actions": [
        {
          "name": "send-escalation-message",
          "send": {
            "message": {
              "text": "Connecting you to a support agent now. Average wait time is 2 minutes."
            }
          }
        },
        {
          "name": "set-priority-high",
          "updateSettings": {
            "conversation": {
              "priority": "high"
            }
          }
        },
        {
          "name": "tag-for-agent",
          "assignTags": ["needs-human", "escalated"]
        },
        {
          "name": "notify-support-team",
          "send": {
            "email": {
              "to": "support@acme.com",
              "subject": "Escalation request from {contactPhone}",
              "text": "A customer has requested to speak with an agent.\nPhone: {contactPhone}\nTime: {currentTimestamp}"
            }
          }
        }
      ]
    }
  ]
}
```

## Deploying the Experience

The examples below validate the experience locally then POST it to the nativeMsg API. Replace `YOUR_API_KEY` and `YOUR_ACCOUNT_ID` with your actual credentials.

{% tabs %}
{% tab title="JSON" %}
The raw experience definition above is the complete deployable artifact. Save it as `experience.json` and use any of the code examples below to deploy it.
{% endtab %}

{% tab title="JavaScript / Node.js" %}

```javascript
const fs = require('fs');
const https = require('https');
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const axios = require('axios');

const API_KEY = process.env.NATIVEMSG_API_KEY;
const ACCOUNT_ID = process.env.NATIVEMSG_ACCOUNT_ID;
const SCHEMA_URL = 'https://docs.nativemsg.com/schemas/rcs-experience-schema-1-0-0.json';
const API_URL = `https://api.nativemsg.com/v1/bots/`;

async function fetchSchema(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      let data = '';
      res.on('data', (chunk) => { data += chunk; });
      res.on('end', () => resolve(JSON.parse(data)));
      res.on('error', reject);
    });
  });
}

async function deployExperience(experienceFile) {
  const experience = JSON.parse(fs.readFileSync(experienceFile, 'utf8'));

  // Validate
  const ajv = new Ajv({ allErrors: true });
  addFormats(ajv);
  const schema = await fetchSchema(SCHEMA_URL);
  const validate = ajv.compile(schema);

  if (!validate(experience)) {
    console.error('Validation failed:');
    validate.errors.forEach((err) => {
      console.error(`  ${err.instancePath} ${err.message}`);
    });
    process.exit(1);
  }
  console.log('✓ Validation passed');

  // Deploy
  const response = await axios.post(API_URL, experience, {
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  });

  console.log(`✓ Experience deployed. ID: ${response.data.id}`);
  console.log(`  Status: ${response.status}`);
  return response.data;
}

deployExperience('experience.json').catch((err) => {
  console.error('Deployment failed:', err.response?.data || err.message);
  process.exit(1);
});
```

```bash
npm install ajv ajv-formats axios
NATIVEMSG_API_KEY=your_key NATIVEMSG_ACCOUNT_ID=your_id node deploy.js
```

{% endtab %}

{% tab title="Python" %}

```python
import json
import os
import sys
import urllib.request
import requests
from jsonschema import Draft7Validator

API_KEY = os.environ['NATIVEMSG_API_KEY']
ACCOUNT_ID = os.environ['NATIVEMSG_ACCOUNT_ID']
SCHEMA_URL = 'https://docs.nativemsg.com/schemas/rcs-experience-schema-1-0-0.json'
API_URL = f'https://api.nativemsg.com/v1/bots'

def load_schema(url):
    with urllib.request.urlopen(url) as response:
        return json.loads(response.read())

def validate_experience(experience, schema):
    validator = Draft7Validator(schema)
    errors = list(validator.iter_errors(experience))
    if errors:
        print('Validation failed:')
        for error in errors:
            path = ' > '.join(str(p) for p in error.absolute_path) or '(root)'
            print(f'  [{path}] {error.message}')
        sys.exit(1)
    print('✓ Validation passed')

def deploy_experience(experience_file):
    with open(experience_file, 'r') as f:
        experience = json.load(f)

    schema = load_schema(SCHEMA_URL)
    validate_experience(experience, schema)

    response = requests.post(
        API_URL,
        json=experience,
        headers={
            'Authorization': f'Bearer {API_KEY}',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    )
    response.raise_for_status()

    data = response.json()
    print(f'✓ Experience deployed. ID: {data["id"]}')
    print(f'  Status: {response.status_code}')
    return data

if __name__ == '__main__':
    experience_file = sys.argv[1] if len(sys.argv) > 1 else 'experience.json'
    deploy_experience(experience_file)
```

```bash
pip install jsonschema requests
NATIVEMSG_API_KEY=your_key NATIVEMSG_ACCOUNT_ID=your_id python deploy.py experience.json
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import * as fs from 'fs';
import * as https from 'https';
import Ajv, { ValidateFunction } from 'ajv';
import addFormats from 'ajv-formats';
import axios, { AxiosResponse } from 'axios';

const API_KEY: string = process.env.NATIVEMSG_API_KEY!;
const ACCOUNT_ID: string = process.env.NATIVEMSG_ACCOUNT_ID!;
const SCHEMA_URL = 'https://docs.nativemsg.com/schemas/rcs-experience-schema-1-0-0.json';
const API_URL = `https://api.nativemsg.com/v1/bots`;

interface DeployedExperience {
  id: string;
  name: string;
  status: string;
}

function fetchSchema(url: string): Promise<object> {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      let data = '';
      res.on('data', (chunk: string) => { data += chunk; });
      res.on('end', () => resolve(JSON.parse(data)));
      res.on('error', reject);
    });
  });
}

async function deployExperience(experienceFile: string): Promise<DeployedExperience> {
  const experience = JSON.parse(fs.readFileSync(experienceFile, 'utf8'));

  const ajv = new Ajv({ allErrors: true });
  addFormats(ajv);

  const schema = await fetchSchema(SCHEMA_URL);
  const validate: ValidateFunction = ajv.compile(schema);

  if (!validate(experience)) {
    console.error('Validation failed:');
    (validate.errors || []).forEach((err) => {
      console.error(`  ${err.instancePath} ${err.message}`);
    });
    process.exit(1);
  }
  console.log('✓ Validation passed');

  const response: AxiosResponse<DeployedExperience> = await axios.post<DeployedExperience>(
    API_URL,
    experience,
    {
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
        Accept: 'application/json'
      }
    }
  );

  console.log(`✓ Experience deployed. ID: ${response.data.id}`);
  return response.data;
}

deployExperience('experience.json').catch((err: Error) => {
  console.error('Deployment failed:', err.message);
  process.exit(1);
});
```

```bash
npm install ajv ajv-formats axios
npx ts-node deploy.ts
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'json'
require 'net/http'
require 'uri'

API_KEY    = ENV.fetch('NATIVEMSG_API_KEY')
ACCOUNT_ID = ENV.fetch('NATIVEMSG_ACCOUNT_ID')
SCHEMA_URL = URI('https://docs.nativemsg.com/schemas/rcs-experience-schema-1-0-0.json')
API_URL    = URI("https://api.nativemsg.com/v1/bots")

def fetch_json(url)
  response = Net::HTTP.get_response(url)
  raise "Failed to fetch #{url}: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
  JSON.parse(response.body)
end

def deploy_experience(experience_file)
  experience = JSON.parse(File.read(experience_file))

  raise 'Missing required field: name' unless experience.key?('name')
  raise 'Missing required field: workflows' unless experience.key?('workflows')
  raise 'workflows must be an array' unless experience['workflows'].is_a?(Array)
  puts '✓ Basic validation passed'

  request = Net::HTTP::Post.new(API_URL)
  request['Authorization'] = "Bearer #{API_KEY}"
  request['Content-Type']  = 'application/json'
  request['Accept']        = 'application/json'
  request.body = JSON.generate(experience)

  response = Net::HTTP.start(API_URL.host, API_URL.port, use_ssl: true) do |http|
    http.request(request)
  end

  unless response.is_a?(Net::HTTPSuccess)
    raise "Deployment failed: #{response.code} #{response.body}"
  end

  data = JSON.parse(response.body)
  puts "✓ Experience deployed. ID: #{data['id']}"
  data
rescue StandardError => e
  warn "Error: #{e.message}"
  exit 1
end

experience_file = ARGV[0] || 'experience.json'
deploy_experience(experience_file)
```

```bash
# For full Draft-07 validation, add the json-schema gem:
# gem install json-schema
NATIVEMSG_API_KEY=your_key NATIVEMSG_ACCOUNT_ID=your_id ruby deploy.rb experience.json
```

{% endtab %}

{% tab title="Go" %}

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	apiKey := os.Getenv("NATIVEMSG_API_KEY")
	accountID := os.Getenv("NATIVEMSG_ACCOUNT_ID")

	if apiKey == "" || accountID == "" {
		fmt.Fprintln(os.Stderr, "NATIVEMSG_API_KEY and NATIVEMSG_ACCOUNT_ID must be set")
		os.Exit(1)
	}

	experienceFile := "experience.json"
	if len(os.Args) > 1 {
		experienceFile = os.Args[1]
	}

	data, err := os.ReadFile(experienceFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to read file: %v\n", err)
		os.Exit(1)
	}

	var experience map[string]interface{}
	if err := json.Unmarshal(data, &experience); err != nil {
		fmt.Fprintf(os.Stderr, "Invalid JSON: %v\n", err)
		os.Exit(1)
	}
	if _, ok := experience["name"]; !ok {
		fmt.Fprintln(os.Stderr, "Missing required field: name")
		os.Exit(1)
	}
	if _, ok := experience["workflows"]; !ok {
		fmt.Fprintln(os.Stderr, "Missing required field: workflows")
		os.Exit(1)
	}
	fmt.Println("✓ Basic validation passed")

	apiURL := fmt.Sprintf("https://api.nativemsg.com/v1/bots", accountID)
	req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewReader(data))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
		os.Exit(1)
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Request failed: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		fmt.Fprintf(os.Stderr, "API error %d: %s\n", resp.StatusCode, body)
		os.Exit(1)
	}

	var result map[string]interface{}
	json.Unmarshal(body, &result)
	fmt.Printf("✓ Experience deployed. ID: %v\n", result["id"])
}
```

```bash
go run deploy.go experience.json
```

{% hint style="info" %}
For full Draft-07 JSON Schema validation in Go, use a library such as `github.com/qri-io/jsonschema` or `github.com/xeipuuv/gojsonschema`.
{% endhint %}
{% endtab %}

{% tab title="cURL" %}

```bash
#!/usr/bin/env bash
set -euo pipefail

API_KEY="${NATIVEMSG_API_KEY}"
ACCOUNT_ID="${NATIVEMSG_ACCOUNT_ID}"
EXPERIENCE_FILE="${1:-experience.json}"
API_URL="https://api.nativemsg.com/v1/bots"

if ! jq empty "${EXPERIENCE_FILE}" 2>/dev/null; then
  echo "ERROR: ${EXPERIENCE_FILE} is not valid JSON" >&2
  exit 1
fi

NAME=$(jq -r '.name // empty' "${EXPERIENCE_FILE}")
WORKFLOWS=$(jq -r '.workflows // empty' "${EXPERIENCE_FILE}")

if [ -z "${NAME}" ]; then
  echo "ERROR: Missing required field: name" >&2
  exit 1
fi
if [ -z "${WORKFLOWS}" ]; then
  echo "ERROR: Missing required field: workflows" >&2
  exit 1
fi

echo "✓ Basic validation passed (name: ${NAME})"

RESPONSE=$(curl --silent --show-error --fail \
  --request POST \
  --url "${API_URL}" \
  --header "Authorization: Bearer ${API_KEY}" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data-binary "@${EXPERIENCE_FILE}" \
  --write-out "\nHTTP_STATUS:%{http_code}")

HTTP_STATUS=$(echo "${RESPONSE}" | grep "HTTP_STATUS:" | cut -d: -f2)
BODY=$(echo "${RESPONSE}" | grep -v "HTTP_STATUS:")

EXPERIENCE_ID=$(echo "${BODY}" | jq -r '.id')
echo "✓ Experience deployed. ID: ${EXPERIENCE_ID} (HTTP ${HTTP_STATUS})"
```

```bash
chmod +x deploy.sh
NATIVEMSG_API_KEY=your_key NATIVEMSG_ACCOUNT_ID=your_id ./deploy.sh experience.json
```

{% hint style="info" %}
The cURL example requires `jq` for JSON parsing. Install it with `brew install jq` (macOS) or `apt-get install jq` (Debian/Ubuntu).
{% endhint %}
{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://playbook.nativemsg.com/rcs-experience-schema/guides/coding-language-examples.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
