# 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 %}
