Sazabi
API

Workflows

Common patterns and workflows for integrating with the Sazabi API.

This guide covers common integration patterns and workflows when using the Sazabi REST API directly.

Creating and managing threads

Create a thread and send a message

Start a new conversation with the AI assistant:

# Create a new thread
THREAD=$(curl -s -X POST https://api.sazabi.com/threads \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Production error investigation"}')

THREAD_ID=$(echo $THREAD | jq -r '.data.id')

# Send a message
curl -X POST "https://api.sazabi.com/threads/$THREAD_ID/messages" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "What errors have occurred in the last hour?"}'

Continue an existing thread

Add follow-up messages to an existing conversation:

curl -X POST "https://api.sazabi.com/threads/thr_abc123/messages" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Show me the stack traces for those errors"}'

List threads with filtering

Retrieve threads with pagination and filtering:

# List recent threads
curl "https://api.sazabi.com/threads?limit=20" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY"

# Get the next page
curl "https://api.sazabi.com/threads?limit=20&cursor=eyJpZCI6InRocl9hYmMxMjMifQ" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY"

Sending logs

Basic log ingestion

Send logs to Sazabi using the intake API:

curl -X POST https://intake.sazabi.com/v1/logs \
  -H "x-sazabi-key: $SAZABI_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "User signed in successfully",
    "level": "info",
    "service": "auth-service",
    "timestamp": "2024-01-15T10:30:00Z",
    "attributes": {
      "user_id": "usr_123",
      "ip_address": "192.168.1.1"
    }
  }'

Batch log ingestion

Send multiple logs in a single request for better efficiency:

curl -X POST https://intake.sazabi.com/v1/logs \
  -H "x-sazabi-key: $SAZABI_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "logs": [
      {
        "message": "Request started",
        "level": "info",
        "timestamp": "2024-01-15T10:30:00.000Z"
      },
      {
        "message": "Database query completed",
        "level": "debug",
        "timestamp": "2024-01-15T10:30:00.150Z"
      },
      {
        "message": "Request completed",
        "level": "info",
        "timestamp": "2024-01-15T10:30:00.200Z"
      }
    ]
  }'

Structured logging

Include structured data for better searchability:

curl -X POST https://intake.sazabi.com/v1/logs \
  -H "x-sazabi-key: $SAZABI_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Order processed",
    "level": "info",
    "service": "order-service",
    "environment": "production",
    "attributes": {
      "order_id": "ord_abc123",
      "customer_id": "cus_xyz789",
      "total_amount": 99.99,
      "items_count": 3,
      "processing_time_ms": 245
    }
  }'

Polling for async operations

Some operations return immediately while processing continues in the background. Use polling to check for completion.

Poll for message response

After sending a message, poll for the assistant's response:

# Send message and get run ID
RESPONSE=$(curl -s -X POST "https://api.sazabi.com/threads/$THREAD_ID/messages" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Analyze the error patterns"}')

RUN_ID=$(echo $RESPONSE | jq -r '.data.runId')

# Poll until complete
while true; do
  STATUS=$(curl -s "https://api.sazabi.com/runs/$RUN_ID" \
    -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" | jq -r '.data.status')

  if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then
    break
  fi

  echo "Status: $STATUS, waiting..."
  sleep 2
done

# Get the response
curl -s "https://api.sazabi.com/runs/$RUN_ID" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" | jq '.data.response'

Pagination patterns

Iterate through all results

Fetch all items from a paginated endpoint:

#!/bin/bash

CURSOR=""
ALL_THREADS="[]"

while true; do
  URL="https://api.sazabi.com/threads?limit=100"
  if [ -n "$CURSOR" ]; then
    URL="$URL&cursor=$CURSOR"
  fi

  RESPONSE=$(curl -s "$URL" \
    -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY")

  # Extract threads and append
  THREADS=$(echo $RESPONSE | jq '.data.items')
  ALL_THREADS=$(echo $ALL_THREADS $THREADS | jq -s 'add')

  # Get next cursor
  CURSOR=$(echo $RESPONSE | jq -r '.data.nextCursor // empty')

  if [ -z "$CURSOR" ]; then
    break
  fi
done

echo $ALL_THREADS | jq

Error handling

Implement retry with exponential backoff

#!/bin/bash

function api_call_with_retry() {
  local url=$1
  local max_retries=3
  local retry=0
  local backoff=1

  while [ $retry -lt $max_retries ]; do
    RESPONSE=$(curl -s -w "\n%{http_code}" "$url" \
      -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY")

    HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
    BODY=$(echo "$RESPONSE" | sed '$d')

    case $HTTP_CODE in
      200|201)
        echo "$BODY"
        return 0
        ;;
      429)
        # Rate limited, wait and retry
        RETRY_AFTER=$(echo "$BODY" | jq -r '.error.retryAfter // 60')
        echo "Rate limited. Waiting $RETRY_AFTER seconds..." >&2
        sleep $RETRY_AFTER
        ;;
      500|502|503|504)
        # Server error, exponential backoff
        echo "Server error $HTTP_CODE. Retrying in $backoff seconds..." >&2
        sleep $backoff
        backoff=$((backoff * 2))
        ;;
      *)
        # Non-retryable error
        echo "Error $HTTP_CODE: $BODY" >&2
        return 1
        ;;
    esac

    retry=$((retry + 1))
  done

  echo "Max retries exceeded" >&2
  return 1
}

# Usage
api_call_with_retry "https://api.sazabi.com/threads"

Webhook integration

Receive webhook events

Set up an endpoint to receive Sazabi webhooks:

// Express.js example
app.post("/webhooks/sazabi", (req, res) => {
  const event = req.body;

  switch (event.type) {
    case "alert.created":
      console.log("New alert:", event.data.alert);
      // Handle new alert
      break;

    case "alert.resolved":
      console.log("Alert resolved:", event.data.alert);
      // Handle resolution
      break;

    case "thread.message.created":
      console.log("New message in thread:", event.data.threadId);
      // Handle new message
      break;
  }

  res.status(200).json({ received: true });
});

Verify webhook signatures

Always verify webhook signatures to ensure authenticity:

const crypto = require("crypto");

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expectedSignature}`)
  );
}

app.post("/webhooks/sazabi", (req, res) => {
  const signature = req.headers["x-sazabi-signature"];
  const payload = JSON.stringify(req.body);

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process the webhook...
});

Best practices

Use idempotency keys

For operations that should only happen once, include an idempotency key:

curl -X POST "https://api.sazabi.com/threads/$THREAD_ID/messages" \
  -H "x-sazabi-secret-key: $SAZABI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: msg_$(uuidgen)" \
  -d '{"content": "Process this order"}'

Cache responses

Cache responses for read operations to reduce API calls:

const cache = new Map();
const CACHE_TTL = 60000; // 1 minute

async function getThread(threadId) {
  const cacheKey = `thread:${threadId}`;
  const cached = cache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const response = await fetch(
    `https://api.sazabi.com/threads/${threadId}`,
    { headers: { "x-sazabi-secret-key": API_KEY } }
  );
  const data = await response.json();

  cache.set(cacheKey, { data: data.data, timestamp: Date.now() });
  return data.data;
}

Handle rate limits gracefully

Monitor rate limit headers and throttle requests proactively:

class RateLimitedClient {
  constructor() {
    this.remaining = Infinity;
    this.resetAt = 0;
  }

  async request(url, options) {
    // Wait if we're rate limited
    if (this.remaining <= 0 && Date.now() < this.resetAt) {
      const waitTime = this.resetAt - Date.now();
      await new Promise((resolve) => setTimeout(resolve, waitTime));
    }

    const response = await fetch(url, options);

    // Update rate limit info
    this.remaining = parseInt(response.headers.get("X-RateLimit-Remaining") || "Infinity");
    this.resetAt = parseInt(response.headers.get("X-RateLimit-Reset") || "0") * 1000;

    return response;
  }
}

Next steps