Documentation Index Fetch the complete documentation index at: https://docs.shannon.run/llms.txt
Use this file to discover all available pages before exploring further.
Authentication
Shannon Gateway supports two authentication methods for all protected endpoints.
API Key Authentication
Include your API key in the X-API-Key header:
curl -H "X-API-Key: sk_test_123456" \
http://localhost:8080/api/v1/tasks
Bearer Token Authentication
Alternatively, use the Authorization: Bearer header with an API key or JWT token:
curl -H "Authorization: Bearer sk_test_123456" \
http://localhost:8080/api/v1/tasks
Both methods are equivalent — use whichever fits your tooling.
Authentication Errors
401 Unauthorized - Missing or invalid credentials:
// Missing auth header
{ "error" : "Authentication required" }
// Invalid API key
{ "error" : "Invalid API key" }
// Expired JWT token
{ "error" : "Invalid or expired token" }
Common causes :
Missing X-API-Key or Authorization header
Invalid API key format
Disabled or expired API key
Expired JWT token
X-API-Key
Purpose : Authentication
Required : Yes (unless GATEWAY_SKIP_AUTH=1)
Format : String
X-API-Key: sk_test_123456
Content-Type (POST requests)
Purpose : Specify request body format
Required : Yes for POST requests
Format : application/json
Content-Type: application/json
Idempotency-Key
Purpose : Prevent duplicate task submissions
Required : No (recommended for critical operations)
Format : UUID or unique string
Cache Duration : 24 hours
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Example :
# First request - creates task
curl -X POST http://localhost:8080/api/v1/tasks \
-H "X-API-Key: sk_test_123456" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"query": "Process payment #12345"}'
# Duplicate request - returns cached response
curl -X POST http://localhost:8080/api/v1/tasks \
-H "X-API-Key: sk_test_123456" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"query": "Process payment #12345"}'
traceparent
Purpose : W3C distributed tracing
Required : No (recommended for observability)
Format : {version}-{trace-id}-{parent-id}-{flags}
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Format Specification :
version : 00 (current version)
trace-id : 32 hex characters (128 bits)
parent-id : 16 hex characters (64 bits)
flags : 2 hex characters (sampled: 01, not sampled: 00)
Example :
import uuid
def generate_traceparent ():
trace_id = uuid.uuid4().hex + uuid.uuid4().hex # 32 chars
parent_id = uuid.uuid4().hex[: 16 ] # 16 chars
return f "00- { trace_id } - { parent_id } -01"
traceparent = generate_traceparent()
# "00-4bf92f3577b34da6a3ce929d0e0e47360011223344556677-00f067aa0ba902b7-01"
tracestate
Purpose : Vendor-specific trace context
Required : No
Format : Comma-separated key-value pairs
tracestate: shannon=task_abc123,vendor=value
Cache-Control
Purpose : Control caching behavior
Required : No
Format : Standard HTTP cache directives
Cache-Control: no-cache
Cache-Control: max-age= 300
Last-Event-ID (SSE only)
Purpose : Resume SSE stream from a specific event
Required : No
Format : Event ID string — either a Redis stream ID (e.g., 1700000000000-0) or a numeric sequence (e.g., 42)
Last-Event-ID: 1700000000000-0
Used for SSE reconnection:
const eventSource = new EventSource ( url , {
headers: {
'X-API-Key' : 'sk_test_123456' ,
'Last-Event-ID' : '1700000000000-0' // Resume from this stream ID (or use a numeric seq)
}
});
Standard Response Headers
X-Workflow-ID
Purpose : Temporal workflow identifier
Present In : POST /api/v1/tasks, GET /api/v1/tasks/
Format : String (same as task_id)
X-Workflow-ID: task_01HQZX3Y9K8M2P4N5S7T9W2V
Use Case : Track workflow execution in Temporal UI
WORKFLOW_ID = $( curl -s -X POST ... | grep -i "X-Workflow-ID" | cut -d: -f2 )
echo "Monitor at: http://localhost:8088/workflows/ $WORKFLOW_ID "
X-Session-ID
Purpose : Session identifier for multi-turn conversations
Present In : POST /api/v1/tasks
Format : UUID string
X-Session-ID: user-123-chat-session
Content-Type
Purpose : Response body format
Present In : All JSON responses
Format : application/json
Content-Type: application/json
For SSE:
Content-Type: text/event-stream
X-RateLimit-Limit
Purpose : Maximum requests allowed per window
Present In : All authenticated requests
Format : Integer
X-RateLimit-Remaining
Purpose : Remaining requests in current window
Present In : All authenticated requests
Format : Integer
X-RateLimit-Remaining: 95
X-RateLimit-Reset
Purpose : Unix timestamp when rate limit resets
Present In : All authenticated requests
Format : Unix timestamp (seconds)
X-RateLimit-Reset: 1609459200
Example - Check Rate Limit :
curl -v http://localhost:8080/api/v1/tasks \
-H "X-API-Key: sk_test_123456" 2>&1 | grep -i "X-RateLimit"
# Output:
# < X-RateLimit-Limit: 100
# < X-RateLimit-Remaining: 95
# < X-RateLimit-Reset: 1609459200
Retry-After
Purpose : Seconds to wait before retrying (429 responses)
Present In : 429 Too Many Requests responses
Format : Integer (seconds)
Example - Handle Rate Limit :
import time
import httpx
response = httpx.post( ... )
if response.status_code == 429 :
retry_after = int (response.headers.get( "Retry-After" , 60 ))
print ( f "Rate limited, waiting { retry_after } s..." )
time.sleep(retry_after)
# Retry request...
Access-Control-Allow-Origin
Purpose : Allowed origins for CORS
Present In : All responses (development mode)
Format : Domain or *
Access-Control-Allow-Origin: *
Production : Configure specific domains:
// gateway/main.go
w . Header (). Set ( "Access-Control-Allow-Origin" , "https://app.example.com" )
Access-Control-Allow-Methods
Purpose : Allowed HTTP methods
Present In : CORS preflight responses
Format : Comma-separated methods
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Purpose : Allowed request headers
Present In : CORS preflight responses
Format : Comma-separated headers
Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key, Idempotency-Key, traceparent
Minimal Request (GET)
curl http://localhost:8080/api/v1/tasks \
-H "X-API-Key: sk_test_123456"
Full Request (POST)
curl -X POST http://localhost:8080/api/v1/tasks \
-H "X-API-Key: sk_test_123456" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" \
-d '{"query": "Analyze data"}'
import httpx
import uuid
def generate_traceparent ():
trace_id = uuid.uuid4().hex + uuid.uuid4().hex
parent_id = uuid.uuid4().hex[: 16 ]
return f "00- { trace_id } - { parent_id } -01"
response = httpx.post(
"http://localhost:8080/api/v1/tasks" ,
headers = {
"X-API-Key" : "sk_test_123456" ,
"Content-Type" : "application/json" ,
"Idempotency-Key" : str (uuid.uuid4()),
"traceparent" : generate_traceparent()
},
json = { "query" : "Analyze data" }
)
# Extract response headers
workflow_id = response.headers.get( "X-Workflow-ID" )
session_id = response.headers.get( "X-Session-ID" )
rate_limit = response.headers.get( "X-RateLimit-Remaining" )
print ( f "Workflow: { workflow_id } " )
print ( f "Session: { session_id } " )
print ( f "Rate limit remaining: { rate_limit } " )
JavaScript - Fetch API
const response = await fetch ( 'http://localhost:8080/api/v1/tasks' , {
method: 'POST' ,
headers: {
'X-API-Key' : 'sk_test_123456' ,
'Content-Type' : 'application/json' ,
'Idempotency-Key' : crypto . randomUUID ()
},
body: JSON . stringify ({
query: 'Analyze data'
})
});
// Read response headers
const workflowId = response . headers . get ( 'X-Workflow-ID' );
const rateLimit = response . headers . get ( 'X-RateLimit-Remaining' );
console . log ( 'Workflow:' , workflowId );
console . log ( 'Rate limit remaining:' , rateLimit );
Security Best Practices
1. Protect API Keys
Never commit API keys to version control :
# ✅ Good - Use environment variables
export SHANNON_API_KEY = "sk_test_123456"
curl -H "X-API-Key: $SHANNON_API_KEY " ...
# ❌ Bad - Hardcoded in scripts
curl -H "X-API-Key: sk_test_123456" ...
Store in secure configuration :
import os
API_KEY = os.environ.get( "SHANNON_API_KEY" )
if not API_KEY :
raise ValueError ( "SHANNON_API_KEY not set" )
2. Use HTTPS in Production
# ✅ Production
BASE_URL = "https://shannon.example.com"
# ⚠️ Development only
BASE_URL = "http://localhost:8080"
3. Rotate API Keys Regularly
-- Disable old key
UPDATE auth . api_keys SET enabled = false WHERE key = 'sk_test_old' ;
-- Create new key
INSERT INTO auth . api_keys ( key , user_id, tenant_id, name , enabled )
VALUES ( 'sk_test_new' , 'user-uuid' , 'tenant-uuid' , 'Rotated Key' , true);
4. Implement Key Expiration
-- Add expiration to keys
UPDATE auth . api_keys SET expires_at = NOW () + INTERVAL '90 days' ;
-- Check for expired keys
SELECT * FROM auth . api_keys WHERE expires_at < NOW ();
5. Monitor API Key Usage
def track_api_usage ( api_key : str ):
"""Log API key usage for monitoring."""
response = httpx.post( ... )
# Log usage
logger.info( "API request" , extra = {
"api_key" : api_key[: 10 ] + "..." , # Partial key only
"endpoint" : "/api/v1/tasks" ,
"status" : response.status_code,
"rate_limit_remaining" : response.headers.get( "X-RateLimit-Remaining" )
})
Troubleshooting
Authentication Failures
Problem : Getting 401 Unauthorized
Solutions :
Check API key is included:
curl -v http://localhost:8080/api/v1/tasks 2>&1 | grep "X-API-Key"
Verify API key format:
# Should start with sk_
echo $API_KEY | grep -E "^sk_"
Check if auth is disabled:
docker compose exec gateway env | grep GATEWAY_SKIP_AUTH
Verify key in database:
SELECT key , enabled , expires_at FROM auth . api_keys WHERE key = 'sk_test_123456' ;
Rate Limit Issues
Problem : Getting 429 Too Many Requests
Solutions :
Check rate limit headers:
curl -v ... 2>&1 | grep "X-RateLimit"
Implement exponential backoff:
if response.status_code == 429 :
retry_after = int (response.headers.get( "Retry-After" , 60 ))
time.sleep(retry_after)
Increase rate limits (if needed):
# In .env
RATE_LIMIT_RPM = 200
RATE_LIMIT_BURST = 50
Idempotency Issues
Problem : Duplicate tasks created
Solutions :
Always include Idempotency-Key:
idempotency_key = str (uuid.uuid4())
headers[ "Idempotency-Key" ] = idempotency_key
Store idempotency keys:
# Store key with request
db.requests.insert({
"idempotency_key" : idempotency_key,
"task_id" : task_id,
"created_at" : datetime.now()
})
Check Redis cache:
docker compose exec redis redis-cli KEYS "idempotency:*"
REST API Overview Complete API reference
Submit Task POST /api/v1/tasks
Rate Limiting Rate limit details
Distributed Tracing Tracing setup