Skip to main content

Podcast Creator

An AI-powered podcast generation library that creates conversational audio content from text-based sources. This pip-installable package processes documents, generates structured outlines, creates natural dialogue transcripts, and converts them into high-quality audio podcasts using LangGraph workflow orchestration.

🎧 Live Demo

Listen to a real podcast generated with this tool - a 4-person debate on the Situational Awareness Paper. Includes my own cloned voice 😂

Generated using the diverse_panel episode profile with 4 AI experts discussing the nuances of artificial general intelligence and situational awareness.

And here is a one-speaker version of it, like your real dedicated teacher.

🚀 Quick Start

Installation

# Library only (programmatic use)
uv add podcast-creator
# or pip install podcast-creator

# Full installation with web UI
uv add podcast-creator --extra ui
# or pip install podcast-creator[ui]

# Or install from source
git clone <repository-url>
cd podcast-creator
uv sync

# Don't have uv? Install it with:
# curl -LsSf https://astral.sh/uv/install.sh | sh
# or
# pip install uv

Installation Options:

  • Library only: pip install podcast-creator - For programmatic use without web interface
  • With UI: pip install podcast-creator[ui] - Includes Streamlit web interface for visual management

Configure API Keys

# Copy the example environment file
cp .env.example .env

# Edit .env and add your API keys:
# - OpenAI API key for LLM models
# - ElevenLabs API key for high-quality TTS
# - Other provider keys as needed

Initialize Your Project

# Create templates and configuration files
podcast-creator init

# This creates:
# - prompts/podcast/outline.jinja
# - prompts/podcast/transcript.jinja  
# - speakers_config.json
# - episodes_config.json
# - example_usage.py

Generate Your First Podcast

🎨 New: Web Interface

Podcast Creator Studio Interface

# Launch the Streamlit web interface
podcast-creator ui

# Custom port/host
podcast-creator ui --port 8080 --host 0.0.0.0

# The UI provides:
# - Visual profile management
# - Multi-content podcast generation  
# - Episode library with playback
# - Import/export functionality

🚀 Episode Profiles (Streamlined)

import asyncio
from podcast_creator import create_podcast

async def main():
    # One-liner podcast creation with episode profiles!
    result = await create_podcast(
        content="Your content here...",
        episode_profile="tech_discussion",  # 🎯 Pre-configured settings
        episode_name="my_podcast",
        output_dir="output/my_podcast"
    )
    print(f"✅ Podcast created: {result['final_output_file_path']}")

asyncio.run(main())

📝 Classic: Full Configuration

import asyncio
from podcast_creator import create_podcast

async def main():
    result = await create_podcast(
        content="Your content here...",
        briefing="Create an engaging discussion about...",
        episode_name="my_podcast",
        output_dir="output/my_podcast",
        speaker_config="ai_researchers"
    )
    print(f"✅ Podcast created: {result['final_output_file_path']}")

asyncio.run(main())

🎯 Episode Profiles - Streamlined Podcast Creation

Episode Profiles are pre-configured sets of podcast generation parameters that enable one-liner podcast creation for common use cases while maintaining full customization flexibility.

🚀 Why Episode Profiles?

  • 67% fewer parameters to specify for common use cases
  • Consistent configurations across podcast series
  • Faster iteration and prototyping
  • Team collaboration with shared settings
  • Full backward compatibility with existing code

📋 Bundled Profiles

Profile Description Speakers Segments Use Case
tech_discussion Technology topics with expert analysis 2 AI researchers 4 Technical content, AI/ML topics
solo_expert Educational explanations 1 expert teacher 3 Learning content, tutorials
business_analysis Market and business insights 3 business analysts 4 Business strategy, market analysis
diverse_panel Multi-perspective discussions 4 diverse voices 5 Complex topics, debate-style content

🎪 Usage Patterns

# 1. Simple profile usage
result = await create_podcast(
    content="Your content...",
    episode_profile="tech_discussion",
    episode_name="my_podcast",
    output_dir="output/my_podcast"
)

# 2. Profile with briefing suffix
result = await create_podcast(
    content="Your content...",
    episode_profile="business_analysis",
    briefing_suffix="Focus on ROI and cost optimization",
    episode_name="my_podcast",
    output_dir="output/my_podcast"
)

# 3. Profile with parameter overrides
result = await create_podcast(
    content="Your content...",
    episode_profile="solo_expert",
    outline_model="gpt-4o",  # Override default
    num_segments=5,          # Override default
    episode_name="my_podcast",
    output_dir="output/my_podcast"
)

🔧 Custom Episode Profiles

from podcast_creator import configure

# Define your own episode profiles
configure("episode_config", {
    "profiles": {
        "my_startup_pitch": {
            "speaker_config": "business_analysts",
            "outline_model": "gpt-4o",
            "default_briefing": "Create an engaging startup pitch...",
            "num_segments": 6,
            "audio_gap_ms": 500  # optional pause between turns (default 400, 0 disables)
        }
    }
})

# Use your custom profile
result = await create_podcast(
    content="Your content...",
    episode_profile="my_startup_pitch",
    episode_name="pitch_deck",
    output_dir="output/pitch_deck"
)

✨ Features

🔧 Flexible Configuration

from podcast_creator import configure

# Configure with custom templates
configure("templates", {
    "outline": "Your custom outline template...",
    "transcript": "Your custom transcript template..."
})

# Configure with custom paths
configure({
    "prompts_dir": "./my_templates",
    "speakers_config": "./my_speakers.json",
    "output_dir": "./podcasts"
})

# Configure speakers inline
configure("speakers_config", {
    "profiles": {
        "my_hosts": {
            "tts_provider": "elevenlabs",
            "tts_model": "eleven_flash_v2_5",
            "speakers": [...]
        }
    }
})

🎙️ Core Features

  • 🎨 Web Interface: Complete Streamlit UI for visual podcast creation
  • 🎯 Episode Profiles: Pre-configured settings for one-liner podcast creation
  • 🔄 LangGraph Workflow: Advanced state management and parallel processing
  • 🔁 Automatic Retry: Exponential backoff for transient API failures (LLM & TTS)
  • 👥 Multi-Speaker Support: Dynamic 1-4 speaker configurations with rich personalities
  • ⚡ Parallel Audio Generation: API-safe batching with concurrent processing
  • 🔧 Fully Configurable: Multiple AI providers (OpenAI, Anthropic, Google, etc.)
  • 📊 Multi-Content Support: Combine text, files, and URLs in structured arrays
  • 🤖 AI-Powered Generation: Creates structured outlines and natural dialogues
  • 🎵 Multi-Provider TTS: ElevenLabs, OpenAI, Google TTS support
  • 📝 Flexible Templates: Jinja2-based prompt customization
  • 🌍 Multilingual Support: Generate content in multiple languages
  • 📚 Episode Library: Built-in audio playback and transcript viewing

🏗️ Architecture

Configuration Priority

The library uses a smart priority system for loading resources:

  1. User Configuration (highest priority)

    configure("templates", {"outline": "...", "transcript": "..."})
    
  2. Custom Paths

    configure("prompts_dir", "/path/to/templates")
    
  3. Working Directory

    • ./prompts/podcast/*.jinja
    • ./speakers_config.json
    • ./episodes_config.json
  4. Bundled Defaults (lowest priority)

    • Package includes production-ready templates
    • Multiple speaker profiles included

📚 Usage Examples

import asyncio
from podcast_creator import create_podcast

# Simple episode profile usage
async def main():
    result = await create_podcast(
        content="AI has transformed many industries...",
        episode_profile="tech_discussion",  # 🚀 One-liner magic!
        episode_name="ai_impact",
        output_dir="output/ai_impact"
    )

asyncio.run(main())

📝 Classic Configuration

import asyncio
from podcast_creator import create_podcast

async def main():
    result = await create_podcast(
        content="AI has transformed many industries...",
        briefing="Create an informative discussion about AI impact",
        episode_name="ai_impact",
        output_dir="output/ai_impact",
        speaker_config="ai_researchers"
    )

asyncio.run(main())

Advanced Configuration

from podcast_creator import configure, create_podcast

# Custom speaker configuration (with optional per-speaker TTS overrides)
configure("speakers_config", {
    "profiles": {
        "tech_experts": {
            "tts_provider": "elevenlabs",
            "tts_model": "eleven_flash_v2_5",
            "speakers": [
                {
                    "name": "Dr. Alex Chen",
                    "voice_id": "your_voice_id",
                    "backstory": "Senior AI researcher with focus on ethics",
                    "personality": "Thoughtful, asks probing questions"
                },
                {
                    "name": "Jamie Rodriguez",
                    "voice_id": "alloy",
                    "backstory": "Tech journalist and startup advisor",
                    "personality": "Enthusiastic, great at explanations",
                    "tts_provider": "openai",
                    "tts_model": "tts-1"
                }
            ]
        }
    }
})

# Custom templates
configure("templates", {
    "outline": """
    Create a {{ num_segments }}-part podcast outline about: {{ briefing }}
    
    Content: {{ context }}
    
    Speakers: {% for speaker in speakers %}{{ speaker.name }}: {{ speaker.personality }}{% endfor %}
    """,
    "transcript": """
    Generate natural dialogue for: {{ segment.name }}
    
    Keep it conversational and engaging.
    """
})

# Generate podcast with custom configuration
result = await create_podcast(
    content="Your content...",
    briefing="Your briefing...",
    episode_name="custom_podcast",
    speaker_config="tech_experts"
)

🎪 Episode Profile Variations

# Solo expert explanation
result = await create_podcast(
    content="Technical content...",
    episode_profile="solo_expert",
    episode_name="deep_dive",
    output_dir="output/deep_dive"
)

# Business analysis
result = await create_podcast(
    content="Market trends...",
    episode_profile="business_analysis",
    episode_name="market_analysis",
    output_dir="output/market_analysis"
)

# Panel discussion with diverse perspectives
result = await create_podcast(
    content="Complex topic...",
    episode_profile="diverse_panel",
    episode_name="panel_discussion",
    output_dir="output/panel_discussion"
)

🔧 Episode Profile Customization

# Use profile with briefing suffix
result = await create_podcast(
    content="Cloud computing trends...",
    episode_profile="business_analysis",
    briefing_suffix="Focus on cost optimization and ROI metrics",
    episode_name="cloud_economics",
    output_dir="output/cloud_economics"
)

# Override specific parameters
result = await create_podcast(
    content="Quantum computing...",
    episode_profile="tech_discussion",
    outline_model="gpt-4o",  # Override default
    num_segments=6,          # Override default
    episode_name="quantum_deep",
    output_dir="output/quantum_deep"
)

🔧 Configuration API

Main Functions

from podcast_creator import configure, get_config, create_podcast

# Set configuration
configure(key, value)
configure({"key1": "value1", "key2": "value2"})

# Get configuration
value = get_config("key", default_value)

# Generate podcast
result = await create_podcast(...)

Configuration Options

Key Type Description
prompts_dir str Directory containing template files
templates dict Inline template content
speakers_config str/dict Path to speaker JSON or inline config
episode_config str/dict Path to episode JSON or inline config
output_dir str Default output directory

🎭 Speaker Configuration

Speaker Profile Structure

{
  "profiles": {
    "profile_name": {
      "tts_provider": "elevenlabs",
      "tts_model": "eleven_flash_v2_5",
      "speakers": [
        {
          "name": "Speaker Name",
          "voice_id": "voice_id_from_provider",
          "backstory": "Rich background that informs expertise",
          "personality": "Speaking style and traits"
        }
      ]
    }
  }
}

Per-Speaker TTS Overrides

Individual speakers can override the profile-level TTS provider, model, and config. This lets you mix different TTS services within the same podcast — for example, one speaker on ElevenLabs and another on OpenAI TTS.

{
  "profiles": {
    "mixed_providers": {
      "tts_provider": "openai",
      "tts_model": "tts-1",
      "speakers": [
        {
          "name": "Dr. Sarah Chen",
          "voice_id": "custom_eleven_voice_id",
          "backstory": "AI researcher...",
          "personality": "Analytical and methodical",
          "tts_provider": "elevenlabs",
          "tts_model": "eleven_flash_v2_5",
          "tts_config": { "voice_settings": { "stability": 0.8 } }
        },
        {
          "name": "Marcus Rivera",
          "voice_id": "alloy",
          "backstory": "Tech journalist...",
          "personality": "Engaging and curious"
        }
      ]
    }
  }
}

In this example, Dr. Sarah Chen uses ElevenLabs while Marcus Rivera uses the profile-level OpenAI TTS. All three fields (tts_provider, tts_model, tts_config) are optional per speaker — any field not set falls back to the profile-level value. If a speaker defines tts_config, it replaces the profile-level config entirely (no merging).

Creating Custom Speakers

  1. Get Voice IDs from your TTS provider
  2. Design Personalities that complement each other
  3. Write Rich Backstories to guide content expertise
  4. Test Combinations with different content types

🌐 Supported Providers

Language Models (via Esperanto)

  • OpenAI: GPT-4o and newer GPT / o-series models
  • Anthropic: Claude Sonnet 5.5, Claude Opus 5.5
  • Google / Vertex AI: Gemini 2.5 Pro, Gemini 2.5 Flash
  • Groq: GPT-OSS, Llama models
  • Ollama: Local model support
  • Perplexity: Research-enhanced models
  • Azure OpenAI: Enterprise OpenAI
  • Mistral: Mistral models
  • DeepSeek: DeepSeek models
  • xAI: Grok models
  • OpenRouter: Multi-provider access
  • Cohere, DashScope (Qwen), MiniMax, Z.ai, SiliconFlow, Novita, PayPerQ and any OpenAI-compatible endpoint (vLLM, LM Studio, ...)

Text-to-Speech Services

  • ElevenLabs: Professional voice synthesis
  • OpenAI TTS: High-quality voices
  • Google (Gemini TTS) and Vertex AI
  • Azure OpenAI
  • Deepgram (Aura), Mistral (Voxtral), MiniMax, xAI
  • OpenRouter, PayPerQ and OpenAI-compatible TTS servers (e.g. Speaches/Kokoro)

See the Esperanto provider docs for the full, current list.

📁 Output Structure

output/episode_name/
├── outline.json          # Structured outline
├── transcript.json       # Complete dialogue
├── clips/               # Individual audio clips
│   ├── 0000.mp3         # First segment
│   ├── 0001.mp3         # Second segment
│   └── ...              # Additional segments
└── audio/               # Final output
    └── episode_name.mp3  # Complete podcast

🛠️ CLI Commands

# Launch web interface (requires UI installation)
podcast-creator ui

# Launch on custom port/host
podcast-creator ui --port 8080 --host 0.0.0.0

# Skip dependency check
podcast-creator ui --skip-init-check

# Initialize project with templates
podcast-creator init

# Initialize in specific directory
podcast-creator init --output-dir /path/to/project

# Overwrite existing files
podcast-creator init --force

# Show version
podcast-creator version

Note: The ui command requires the UI installation: pip install podcast-creator[ui]

🎨 Web Interface Features

The podcast-creator ui command launches a comprehensive Streamlit interface that provides:

  • 🏠 Dashboard: Statistics and quick actions
  • 🎙️ Speaker Management: Visual profile creation with voice selection dropdowns
  • 📺 Episode Management: Configure generation parameters and AI models
  • 🎬 Podcast Generation: Multi-content support (text, files, URLs) with real-time progress
  • 📚 Episode Library: Audio playback, transcript viewing, and downloads
  • 📤 Import/Export: Share profiles via JSON files

The interface automatically detects missing dependencies and offers to run initialization if needed.

🚀 Performance

  • ⚡ Parallel Processing: 5 concurrent audio clips per batch (configurable)
  • 🔄 API-Safe Batching: Respects provider rate limits
  • 📊 Scalable: Handles 30+ dialogue segments efficiently
  • ⏱️ Fast Generation: ~2-3 minutes for typical podcasts
  • 🎯 Optimized Workflow: Smart resource management

⚠️ Rate Limiting Configuration

If you encounter errors like ElevenLabs API error: Too many concurrent requests, you can adjust the parallel processing batch size:

# In your .env file
TTS_BATCH_SIZE=2  # Reduce from default 5 to 2 for ElevenLabs free plan

This is particularly useful for:

  • ElevenLabs Free Plan: Limited to 2 concurrent requests
  • Other TTS providers with stricter rate limits
  • Debugging: Set to 1 for sequential processing

⏸️ Pauses Between Turns

When combining clips, a short pause is inserted between dialogue turns for natural conversational pacing (TTS clips usually end without trailing silence). The default is 400 ms; set audio_gap_ms on the episode profile or pass it to create_podcast() (the argument wins), and use 0 to disable it:

result = await create_podcast(
    content="Your content...",
    episode_profile="tech_discussion",
    episode_name="my_podcast",
    output_dir="output/my_podcast",
    audio_gap_ms=600,
)

🔁 Retry Configuration

LLM and TTS API calls automatically retry on transient failures (network errors, timeouts, rate limits) with exponential backoff. Non-retryable errors are raised immediately without retry — this includes programming errors (e.g. ValueError) and HTTP 4xx client errors (e.g. 404 model not found, 401 auth failure), except 429 rate-limit which is retried.

# In your .env file
PODCAST_RETRY_MAX_ATTEMPTS=3       # Max retry attempts (default: 3)
PODCAST_RETRY_WAIT_MULTIPLIER=5    # Backoff multiplier in seconds (default: 5)
PODCAST_RETRY_WAIT_MAX=30          # Max wait between retries in seconds (default: 30)

You can also configure retries programmatically for LLM calls (outline and transcript generation):

result = await create_podcast(
    content="Your content...",
    episode_profile="tech_discussion",
    episode_name="my_podcast",
    output_dir="output/my_podcast",
    retry_max_attempts=5,        # Override default
    retry_wait_multiplier=3,     # Override default
)

To disable retries entirely, set PODCAST_RETRY_MAX_ATTEMPTS=1.

📏 LLM Output Limits

Outline and transcript generation request up to 8192 output tokens by default. This is podcast-creator's request limit, not a model cap: current models, including the default Claude Sonnet 5.5, support much larger outputs. If your model has a lower output limit (some legacy models cap at 4096), or you need more room, override max_tokens via outline_config / transcript_config, either in create_podcast() or in the episode profile:

result = await create_podcast(
    content="Your content...",
    episode_profile="tech_discussion",
    episode_name="my_podcast",
    output_dir="output/my_podcast",
    outline_config={"max_tokens": 4096},
    transcript_config={"max_tokens": 4096},
)

🧩 Structured Output

Outline and transcript generation ask the model for JSON that matches the package's own schemas (json_schema structured output via Esperanto), so providers that support it return exactly the expected shape. A few providers or older models cannot honor a JSON schema (for example, Anthropic requires Claude 4.5 or newer); Esperanto then fails fast with a clear error. For those, fall back to generic JSON mode:

result = await create_podcast(
    content="Your content...",
    episode_profile="tech_discussion",
    episode_name="my_podcast",
    output_dir="output/my_podcast",
    outline_config={"structured": {"type": "json"}},
    transcript_config={"structured": {"type": "json"}},
)

The same keys work inside an episode profile's outline_config / transcript_config.

🌐 Proxy Configuration

If you're behind a corporate firewall or need to route requests through a proxy, use standard environment variables:

# In your .env file or shell environment
HTTP_PROXY=http://proxy.example.com:8080
HTTPS_PROXY=http://proxy.example.com:8080
NO_PROXY=localhost,127.0.0.1

Authenticated Proxies:

# Proxies with authentication are supported
HTTP_PROXY=http://user:password@proxy.example.com:8080
HTTPS_PROXY=http://user:password@proxy.example.com:8080

The underlying libraries (esperanto, content-core) automatically detect and use these standard proxy environment variables for all network requests.

🧪 Development

Installing for Development

git clone <repository-url>
cd podcast-creator

# Install with uv (recommended)
uv sync

# This installs the package in editable mode
# along with all dependencies

Project Structure

podcast-creator/
├── src/
│   └── podcast_creator/
│       ├── __init__.py           # Public API
│       ├── config.py             # Configuration system
│       ├── cli.py                # CLI commands (with UI command)
│       ├── core.py               # Core utilities
│       ├── graph.py              # LangGraph workflow
│       ├── nodes.py              # Workflow nodes
│       ├── retry.py              # Retry utilities with exponential backoff
│       ├── speakers.py           # Speaker management
│       ├── episodes.py           # Episode profile management
│       ├── state.py              # State management
│       ├── validators.py         # Validation utilities
│       └── resources/            # Bundled templates
│           ├── prompts/
│           ├── speakers_config.json
│           ├── episodes_config.json
│           ├── streamlit_app/    # Web interface
│           └── examples/
├── pyproject.toml               # Package configuration
└── README.md

Testing

# Test the package
python -c "from podcast_creator import create_podcast; print('Import successful')"

# Test CLI
podcast-creator --help

# Test web interface
podcast-creator ui

# Test initialization
mkdir test_project
cd test_project
podcast-creator init
python example_usage.py

📝 Examples

Check the examples/ directory for:

  • Episode Profiles: Comprehensive guide to streamlined podcast creation
  • Basic usage examples
  • Advanced configuration
  • Custom speaker setups
  • Multi-language podcasts
  • Different content types

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details on:

  • 🚀 Getting started with development
  • 📋 Our pull request process
  • 🧪 Testing guidelines
  • 🎨 Code style and standards
  • 🐛 How to report bugs
  • 💡 How to suggest new features

Quick links:

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


Made with ❤️ for the AI community

Metadata

Release files for podcast-creator 0.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for podcast-creator 0.13.0
File Size Uploaded
podcast_creator-0.13.0.tar.gz 497.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for podcast-creator 0.13.0
File Interpreter ABI Platform
podcast_creator-0.13.0-py3-none-any.whl Python 3 none any Details

Total release size: 579.9 kB

Release files / podcast_creator-0.13.0.tar.gz

Download URL podcast_creator-0.13.0.tar.gz
Size 497.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d9bab87506d9b42605c6b18f0ab3035ccd1fde744d1736e3cdb3cf5d283296bb
BLAKE2b-256 checksum
How to use checksums
3c23e3e5ba947a672cd39dab0b0a726dfd8ce76638bf71d1ffbfc356cb3b9733
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / podcast_creator-0.13.0-py3-none-any.whl

Download URL podcast_creator-0.13.0-py3-none-any.whl
Size 82.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e67d30fa8f4049fa3b4653b009cab6f69a3d5ed5ec551decae0901ab1be5b665
BLAKE2b-256 checksum
How to use checksums
21927cfa0b0c7992ea91ee1398d2ebe84a566159219f773402bef600055915c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.13.0 This release

2 release files

0.11.2

2 release files

0.11.1

2 release files

0.11.0

2 release files

0.10.0

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.3

2 release files

0.7.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.6

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page