Skip to main content

Your First API Call

The basic Shannon workflow is simple: submit a task, get a task ID, check the status, and retrieve the result.

Complete Example (cURL)

# 1. Submit a task
RESPONSE=$(curl -sS -X POST http://localhost:8080/api/v1/tasks \
  -H "X-API-Key: sk_test_123456" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What is the capital of France?"
  }')

# 2. Extract task ID
TASK_ID=$(echo $RESPONSE | jq -r '.task_id')
echo "Task ID: $TASK_ID"

# 3. Poll for result
while true; do
  STATUS=$(curl -sS http://localhost:8080/api/v1/tasks/$TASK_ID \
    -H "X-API-Key: sk_test_123456")

  STATE=$(echo $STATUS | jq -r '.status')

  if [ "$STATE" = "TASK_STATUS_COMPLETED" ]; then
    echo $STATUS | jq -r '.result'
    break
  elif [ "$STATE" = "TASK_STATUS_FAILED" ]; then
    echo "Error: $(echo $STATUS | jq -r '.error')"
    exit 1
  fi

  sleep 2
done
Expected Output:
Task ID: task_01HQZX3Y9K8M2P4N5S7T9W2V
The capital of France is Paris.

Common API Patterns

1. Submit → Poll → Retrieve

The standard workflow for any task:
import httpx
import time

# Submit task
response = httpx.post(
    "http://localhost:8080/api/v1/tasks",
    headers={"X-API-Key": "sk_test_123456"},
    json={"query": "What is the capital of France?"}
)
task_id = response.json()["task_id"]
print(f"Task submitted: {task_id}")

# Poll until complete
while True:
    status_resp = httpx.get(
        f"http://localhost:8080/api/v1/tasks/{task_id}",
        headers={"X-API-Key": "sk_test_123456"}
    )
    status = status_resp.json()

    if status["status"] == "TASK_STATUS_COMPLETED":
        print("Result:", status["result"])
        break
    elif status["status"] == "TASK_STATUS_FAILED":
        print("Error:", status["error"])
        break

    time.sleep(2)
const axios = require('axios');

async function runTask() {
  // Submit task
  const submitResp = await axios.post(
    'http://localhost:8080/api/v1/tasks',
    { query: 'What is the capital of France?' },
    { headers: { 'X-API-Key': 'sk_test_123456' } }
  );

  const taskId = submitResp.data.task_id;
  console.log('Task submitted:', taskId);

  // Poll until complete
  while (true) {
    const statusResp = await axios.get(
      `http://localhost:8080/api/v1/tasks/${taskId}`,
      { headers: { 'X-API-Key': 'sk_test_123456' } }
    );

    const { status, result, error } = statusResp.data;

    if (status === 'TASK_STATUS_COMPLETED') {
      console.log('Result:', result);
      break;
    } else if (status === 'TASK_STATUS_FAILED') {
      console.log('Error:', error);
      break;
    }

    await new Promise(resolve => setTimeout(resolve, 2000));
  }
}

runTask().catch(console.error);
#!/bin/bash

API_KEY="sk_test_123456"

# Submit
TASK_ID=$(curl -sS -X POST http://localhost:8080/api/v1/tasks \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What is the capital of France?"}' | jq -r '.task_id')

echo "Task ID: $TASK_ID"

# Poll
while true; do
  STATUS=$(curl -sS http://localhost:8080/api/v1/tasks/$TASK_ID \
    -H "X-API-Key: $API_KEY" | jq -r '.status')

  if [ "$STATUS" = "TASK_STATUS_COMPLETED" ]; then
    curl -sS http://localhost:8080/api/v1/tasks/$TASK_ID \
      -H "X-API-Key: $API_KEY" | jq -r '.result'
    break
  fi

  sleep 2
done

2. Multi-Turn Conversations (Sessions)

Use the same session_id to maintain context across multiple tasks:
import httpx

API_KEY = "sk_test_123456"
SESSION_ID = "user-123-chat"

# Turn 1
resp1 = httpx.post(
    "http://localhost:8080/api/v1/tasks",
    headers={"X-API-Key": API_KEY},
    json={
        "query": "What is Python?",
        "session_id": SESSION_ID
    }
)
print("Task 1:", resp1.json()["task_id"])

# Turn 2 (references previous context)
resp2 = httpx.post(
    "http://localhost:8080/api/v1/tasks",
    headers={"X-API-Key": API_KEY},
    json={
        "query": "What are its main advantages?",
        "session_id": SESSION_ID  # Same session
    }
)
print("Task 2:", resp2.json()["task_id"])
const axios = require('axios');

const API_KEY = 'sk_test_123456';
const SESSION_ID = 'user-123-chat';

async function multiTurnConversation() {
  // Turn 1
  const resp1 = await axios.post(
    'http://localhost:8080/api/v1/tasks',
    {
      query: 'What is Python?',
      session_id: SESSION_ID
    },
    { headers: { 'X-API-Key': API_KEY } }
  );
  console.log('Task 1:', resp1.data.task_id);

  // Turn 2 (references previous context)
  const resp2 = await axios.post(
    'http://localhost:8080/api/v1/tasks',
    {
      query: 'What are its main advantages?',
      session_id: SESSION_ID  // Same session
    },
    { headers: { 'X-API-Key': API_KEY } }
  );
  console.log('Task 2:', resp2.data.task_id);
}

multiTurnConversation();
API_KEY="sk_test_123456"
SESSION_ID="user-123-chat"

# Turn 1
curl -X POST http://localhost:8080/api/v1/tasks \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"query\": \"What is Python?\", \"session_id\": \"$SESSION_ID\"}"

# Turn 2 (references previous context)
curl -X POST http://localhost:8080/api/v1/tasks \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"query\": \"What are its main advantages?\", \"session_id\": \"$SESSION_ID\"}"

3. Real-Time Streaming (SSE)

Get live updates instead of polling:
import httpx

# Submit task
response = httpx.post(
    "http://localhost:8080/api/v1/tasks",
    headers={"X-API-Key": "sk_test_123456"},
    json={"query": "Write a haiku about coding"}
)
task_id = response.json()["task_id"]

# Stream events
with httpx.stream(
    "GET",
    f"http://localhost:8080/api/v1/tasks/{task_id}/stream",
    headers={"X-API-Key": "sk_test_123456"}
) as stream:
    for line in stream.iter_lines():
        if line.startswith("data: "):
            event_data = line[6:]  # Remove "data: " prefix
            print(event_data)
const EventSource = require('eventsource');

// Submit task first
const axios = require('axios');
const response = await axios.post(
  'http://localhost:8080/api/v1/tasks',
  { query: 'Write a haiku about coding' },
  { headers: { 'X-API-Key': 'sk_test_123456' } }
);

const taskId = response.data.task_id;

// Stream events
const eventSource = new EventSource(
  `http://localhost:8080/api/v1/tasks/${taskId}/stream`,
  { headers: { 'X-API-Key': 'sk_test_123456' } }
);

eventSource.onmessage = (event) => {
  console.log(event.data);
};

eventSource.onerror = (error) => {
  console.error('Stream error:', error);
  eventSource.close();
};
# Submit task
TASK_ID=$(curl -sS -X POST http://localhost:8080/api/v1/tasks \
  -H "X-API-Key: sk_test_123456" \
  -H "Content-Type: application/json" \
  -d '{"query": "Write a haiku about coding"}' | jq -r '.task_id')

# Stream events
curl -N http://localhost:8080/api/v1/tasks/$TASK_ID/stream \
  -H "X-API-Key: sk_test_123456"

4. Error Handling

Always handle errors gracefully:
import httpx

try:
    response = httpx.post(
        "http://localhost:8080/api/v1/tasks",
        headers={"X-API-Key": "sk_test_123456"},
        json={"query": "Analyze this data"},
        timeout=30.0
    )
    response.raise_for_status()

    task = response.json()
    print(f"Success: {task['task_id']}")

except httpx.HTTPStatusError as e:
    if e.response.status_code == 401:
        print("Error: Invalid API key")
    elif e.response.status_code == 429:
        retry_after = e.response.headers.get("Retry-After", "60")
        print(f"Rate limited. Retry after {retry_after}s")
    else:
        print(f"HTTP error: {e.response.status_code}")
        print(e.response.json())

except httpx.TimeoutException:
    print("Request timed out")

except Exception as e:
    print(f"Unexpected error: {e}")
const axios = require('axios');

async function submitWithErrorHandling(query) {
  try {
    const response = await axios.post(
      'http://localhost:8080/api/v1/tasks',
      { query },
      {
        headers: { 'X-API-Key': 'sk_test_123456' },
        timeout: 30000
      }
    );

    console.log('Success:', response.data.task_id);
    return response.data;

  } catch (error) {
    if (error.response) {
      // Server responded with error status
      switch (error.response.status) {
        case 401:
          console.error('Invalid API key');
          break;
        case 429:
          const retryAfter = error.response.headers['retry-after'] || 60;
          console.error(`Rate limited. Retry after ${retryAfter}s`);
          break;
        default:
          console.error('HTTP error:', error.response.status);
          console.error(error.response.data);
      }
    } else if (error.request) {
      // No response received
      console.error('No response from server');
    } else {
      // Request setup error
      console.error('Error:', error.message);
    }

    throw error;
  }
}

submitWithErrorHandling('Analyze this data');
#!/bin/bash

API_KEY="sk_test_123456"

RESPONSE=$(curl -sS -w "\n%{http_code}" -X POST http://localhost:8080/api/v1/tasks \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "Analyze this data"}')

HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)

case $HTTP_CODE in
  200)
    echo "Success: $(echo $BODY | jq -r '.task_id')"
    ;;
  401)
    echo "Error: Invalid API key"
    exit 1
    ;;
  429)
    echo "Error: Rate limited"
    exit 1
    ;;
  *)
    echo "Error: HTTP $HTTP_CODE"
    echo $BODY | jq
    exit 1
    ;;
esac

Authentication Setup

Getting Your API Key

1

Development Mode (Skip Auth)

For local testing, you can disable authentication:
# In .env file
GATEWAY_SKIP_AUTH=1
Then use any placeholder key:
curl -H "X-API-Key: dev" http://localhost:8080/api/v1/tasks
2

Production Mode (Real API Keys)

Generate an API key from your Shannon dashboard:
  1. Log in to Shannon Cloud
  2. Navigate to Settings → API Keys
  3. Click “Create API Key”
  4. Copy the key (starts with sk_)
Store it securely (e.g., environment variable):
export SHANNON_API_KEY="sk_live_abc123..."

Including the API Key

Always include your API key in the X-API-Key header:
# ✅ Correct
curl -H "X-API-Key: sk_test_123456" http://localhost:8080/api/v1/tasks

# ❌ Wrong - Authorization header doesn't work for API keys
curl -H "Authorization: Bearer sk_test_123456" http://localhost:8080/api/v1/tasks
Never commit API keys to version control. Use environment variables or secret management tools.

Complete Workflow Example

Here’s a real-world example that combines all patterns:
import httpx
import time
import os

class ShannonClient:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url
        self.api_key = api_key

    def submit_task(self, query: str, session_id: str = None):
        """Submit a task and return task ID."""
        response = httpx.post(
            f"{self.base_url}/api/v1/tasks",
            headers={"X-API-Key": self.api_key},
            json={
                "query": query,
                "session_id": session_id
            }
        )
        response.raise_for_status()
        return response.json()["task_id"]

    def wait_for_completion(self, task_id: str, timeout: int = 300):
        """Poll until task completes."""
        start_time = time.time()

        while True:
            if time.time() - start_time > timeout:
                raise TimeoutError(f"Task timeout after {timeout}s")

            response = httpx.get(
                f"{self.base_url}/api/v1/tasks/{task_id}",
                headers={"X-API-Key": self.api_key}
            )
            status = response.json()

            if status["status"] == "TASK_STATUS_COMPLETED":
                return status["result"]
            elif status["status"] == "TASK_STATUS_FAILED":
                raise Exception(f"Task failed: {status['error']}")

            time.sleep(2)

    def run_task(self, query: str, session_id: str = None):
        """Submit and wait for result in one call."""
        task_id = self.submit_task(query, session_id)
        return self.wait_for_completion(task_id)

# Usage
client = ShannonClient(
    base_url="http://localhost:8080",
    api_key=os.getenv("SHANNON_API_KEY", "sk_test_123456")
)

# Single task
result = client.run_task("What is the capital of France?")
print(result)

# Multi-turn conversation
session_id = "chat-123"
result1 = client.run_task("What is Python?", session_id)
result2 = client.run_task("What are its advantages?", session_id)
print(result1)
print(result2)
const axios = require('axios');

class ShannonClient {
  constructor(baseUrl, apiKey) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
  }

  async submitTask(query, sessionId = null) {
    const response = await axios.post(
      `${this.baseUrl}/api/v1/tasks`,
      {
        query,
        session_id: sessionId
      },
      {
        headers: { 'X-API-Key': this.apiKey }
      }
    );

    return response.data.task_id;
  }

  async waitForCompletion(taskId, timeout = 300000) {
    const startTime = Date.now();

    while (true) {
      if (Date.now() - startTime > timeout) {
        throw new Error(`Task timeout after ${timeout}ms`);
      }

      const response = await axios.get(
        `${this.baseUrl}/api/v1/tasks/${taskId}`,
        { headers: { 'X-API-Key': this.apiKey } }
      );

      const { status, result, error } = response.data;

      if (status === 'TASK_STATUS_COMPLETED') {
        return result;
      } else if (status === 'TASK_STATUS_FAILED') {
        throw new Error(`Task failed: ${error}`);
      }

      await new Promise(resolve => setTimeout(resolve, 2000));
    }
  }

  async runTask(query, sessionId = null) {
    const taskId = await this.submitTask(query, sessionId);
    return await this.waitForCompletion(taskId);
  }
}

// Usage
const client = new ShannonClient(
  'http://localhost:8080',
  process.env.SHANNON_API_KEY || 'sk_test_123456'
);

async function main() {
  // Single task
  const result = await client.runTask('What is the capital of France?');
  console.log(result);

  // Multi-turn conversation
  const sessionId = 'chat-123';
  const result1 = await client.runTask('What is Python?', sessionId);
  const result2 = await client.runTask('What are its advantages?', sessionId);
  console.log(result1);
  console.log(result2);
}

main().catch(console.error);

Next Steps

Now that you know the basics, explore more advanced features:

Full API Reference

Complete documentation for all endpoints

Agents Catalog

Pre-built agents for specific tasks

Python SDK

Official Python client library

Streaming API

Real-time event streaming with SSE

Common Questions

Poll the GET /api/v1/tasks/{id} endpoint until status is TASK_STATUS_COMPLETED or TASK_STATUS_FAILED. For long-running tasks, use streaming instead of polling.
  • result is the raw text output from the LLM
  • response is only present if result contains valid JSON (parsed automatically)
Most of the time, you’ll use result.
Sessions persist for 30 days by default. All tasks with the same session_id share conversation history.
Yes, use POST /api/v1/tasks/{id}/cancel. The task will stop at the next safe checkpoint.
Streaming provides real-time updates with lower latency and less server load. Use it for long-running tasks or when you need immediate feedback.

Troubleshooting

401 Unauthorized

  • Check your API key is correct
  • Verify you’re using the X-API-Key header (not Authorization)
  • In dev mode, ensure GATEWAY_SKIP_AUTH=1 is set

429 Rate Limited

  • Check the Retry-After header
  • Implement exponential backoff
  • Upgrade your API tier for higher limits

Task stuck in RUNNING

  • Wait longer (complex tasks take time)
  • Use streaming to see progress
  • Check Temporal UI for detailed workflow status

Empty result field

  • Task might still be running (check status)
  • Task failed (check error field)
  • Use streaming to see intermediate output