Skip to main content

LSBible API client - structured, type-safe Bible SDK

Project description

LSBible Python SDK

PyPI version Python Version License: MIT Code style: ruff

A structured, type-safe Python client for the LSBible API at read.lsbible.org.

Disclaimer: This is an unofficial, third-party SDK and is not affiliated with, endorsed by, or connected to LSBible or its creators. This project is an independent client library for educational and development purposes.

Why the LSB?

The Legacy Standard Bible (LSB) is a modern literal translation that prioritizes accuracy and consistency:

  • ๐Ÿ“– Formal Equivalence - Word-for-word translation philosophy preserving original structure
  • โœ๏ธ Based on NASB95 - Built on the respected New American Standard Bible, updated for modern scholarship
  • ๐Ÿ”ค Consistent Translation - Same Hebrew/Greek words translated consistently throughout
  • ๐ŸŒŸ Divine Name - Uses "Yahweh" for the Tetragrammaton (YHWH) instead of "LORD"
  • ๐Ÿ†“ Freely Accessible - Available for use in applications and tools
  • ๐Ÿ“… Modern Scholarship - Published in 2021 with latest textual research

The LSB's literal approach makes it ideal for serious Bible study, and its structured HTML output is perfect for SDK development, preserving formatting like red-letter text for Jesus' words and italics for translator clarifications.

Table of Contents

Features at a Glance

โœจ What makes this SDK special:

  • โœ… 100% Type-Safe - Full Pydantic validation with type hints throughout
  • โœ… Structured Parameters - No string parsing, explicit book/chapter/verse parameters
  • โœ… IDE Autocomplete - Enum-based book names with all 66 books
  • โœ… Strict Validation - Early error detection before API calls
  • โœ… Response Caching - Built-in TTL-based caching for better performance
  • โœ… Rich Formatting - Extract red-letter text, italics, small-caps, and more
  • โœ… Search Analytics - Distribution metadata across Bible sections and books
  • โœ… Immutable Models - All data structures are frozen for safety
  • โœ… Comprehensive Tests - >80% code coverage with thorough test suite

Installation

Install the LSBible Python SDK using your preferred package manager:

# Using uv (recommended)
uv pip install lsbible

# Using pip
pip install lsbible

Quick Start

import asyncio
from lsbible import LSBibleClient, BookName

async def main():
    # Initialize client (use async context manager for automatic cleanup)
    async with LSBibleClient() as client:
        # Get a single verse
        passage = await client.get_verse(BookName.JOHN, 3, 16)
        print(f"Reference: {passage.title}")
        print(f"Text: {passage.verses[0].plain_text}")

        # Get a passage range
        passage = await client.get_passage(
            BookName.JOHN, 3, 16,
            BookName.JOHN, 3, 18
        )
        print(f"Got {passage.verse_count} verses")

        # Get an entire chapter
        chapter = await client.get_chapter(BookName.JOHN, 3)
        print(f"John 3 has {chapter.verse_count} verses")

        # Search for text
        results = await client.search("love")
        print(f"Found {results.match_count} passages")

asyncio.run(main())

SDK Usage Guide

Core Concepts

Design Philosophy: Structured Parameters Over String Parsing

This SDK uses explicit, validated parameters instead of parsing strings:

from lsbible import LSBibleClient, BookName

client = LSBibleClient()

# โœ… GOOD - Type-safe with validation
passage = client.get_verse(BookName.JOHN, 3, 16)

# โœ… ALSO GOOD - String validated at runtime
passage = client.get_verse("John", 3, 16)

# โŒ NOT SUPPORTED - String parsing
passage = client.get_verse("John 3:16")  # This won't work

Why this approach?

  1. Full IDE autocomplete - Type BookName. and see all 66 books
  2. Catch errors early - Invalid references caught before API calls
  3. No parsing ambiguity - Clear distinction between book, chapter, verse
  4. Better testing - Easy to generate test cases programmatically
  5. Type safety - Python type checkers can verify your code

Using Book Names

You have two options for specifying books:

Option 1: BookName Enum (Recommended)

from lsbible import BookName

# Full IDE autocomplete support
BookName.JOHN
BookName.GENESIS
BookName.REVELATION
BookName.SAMUEL_1  # For numbered books
BookName.CORINTHIANS_2

Option 2: Strings (Also Valid)

# Simple string (case-insensitive, normalized)
client.get_verse("John", 3, 16)
client.get_verse("john", 3, 16)  # Also works
client.get_verse("1 John", 1, 1)  # Numbered books

Basic Examples

Fetching a Single Verse

from lsbible import LSBibleClient, BookName

with LSBibleClient() as client:
    passage = client.get_verse(BookName.JOHN, 3, 16)

    # Access basic info
    verse = passage.verses[0]
    print(f"Reference: {verse.reference}")  # "John 3:16"
    print(f"Plain text: {verse.plain_text}")

    # Check if it's a single verse
    print(f"Single verse: {passage.is_single_verse}")  # True

Fetching a Passage Range

# Same book, same chapter
passage = client.get_passage(
    BookName.JOHN, 3, 16,   # From: John 3:16
    BookName.JOHN, 3, 18    # To: John 3:18
)

# Same book, different chapters
passage = client.get_passage(
    BookName.JOHN, 1, 1,    # From: John 1:1
    BookName.JOHN, 2, 11    # To: John 2:11
)

# Different books
passage = client.get_passage(
    BookName.JUDE, 1, 24,   # From: Jude 1:24
    BookName.JUDE, 1, 25    # To: Jude 1:25
)

print(f"Got {passage.verse_count} verses")

Fetching an Entire Chapter

# Get all verses in John chapter 3
chapter = client.get_chapter(BookName.JOHN, 3)

print(f"Chapter title: {chapter.title}")
print(f"Total verses: {chapter.verse_count}")

for verse in chapter.verses:
    print(f"Verse {verse.verse_number}: {verse.plain_text}")

Searching for Text

# Search for verses containing "love"
results = client.search("love")

print(f"Query: {results.query}")
print(f"Total matches: {results.match_count}")
print(f"Passages returned: {results.passage_count}")

# Display results
for passage in results.passages:
    print(f"\n{passage.title}")
    for verse in passage.verses:
        print(f"  {verse.plain_text}")

Advanced Features

Accessing Rich Formatting

The SDK preserves all formatting from the LSB translation:

passage = client.get_verse(BookName.JOHN, 3, 16)
verse = passage.verses[0]

# Iterate through text segments with formatting metadata
for segment in verse.segments:
    # Check formatting flags
    if segment.is_red_letter:
        print(f'๐Ÿ”ด Jesus said: "{segment.text}"')
    elif segment.is_italic:
        print(f'๐Ÿ“ Clarification: [{segment.text}]')
    elif segment.is_small_caps:
        print(f'โœจ LORD (YHWH): {segment.text}')
    elif segment.is_bold:
        print(f'๐Ÿ“Œ Emphasis: {segment.text}')
    else:
        print(f'   Regular: {segment.text}')

# Or use the formatted_text property for simple formatting
print(verse.formatted_text)
# Output: "For God so loved the world, that He gave..."

Formatting Metadata:

  • is_red_letter: Words of Jesus Christ (traditionally printed in red)
  • is_italic: Clarifying words added by translators
  • is_small_caps: "LORD" representing YHWH (Yahweh)
  • is_bold: Emphasis in the original text
  • Poetry/prose detection: verse.is_poetry or verse.is_prose
  • Subheadings: verse.has_subheading and verse.subheading_text

Working with Verse Metadata

passage = client.get_verse(BookName.PSALMS, 23, 1)
verse = passage.verses[0]

# Check structural features
if verse.has_subheading:
    print(f"Subheading: {verse.subheading_text}")

if verse.is_poetry:
    print("This is poetic text")

if verse.chapter_start:
    print("This is the first verse of a chapter")

# Access reference details
ref = verse.reference
print(f"Book: {ref.book_name.value}")  # "Psalms"
print(f"Chapter: {ref.chapter}")        # 23
print(f"Verse: {ref.verse}")            # 1
print(f"Book number: {ref.book_number}") # 19 (Psalms is the 19th book)

Configuring the Client

from lsbible import LSBibleClient, MemoryCacheProvider, CacheTTL

# Configure cache with per-operation TTLs
async with LSBibleClient(
    cache={
        "provider": MemoryCacheProvider(),
        "ttl": {
            "verse": CacheTTL.BIBLE_CONTENT,   # 30 days
            "passage": CacheTTL.BIBLE_CONTENT, # 30 days
            "chapter": CacheTTL.BIBLE_CONTENT, # 30 days
            "search": CacheTTL.SEARCH_RESULTS, # 7 days
        }
    },
    timeout=60,        # Request timeout in seconds (default: 30)
    build_id="custom"  # Optional: provide build ID (default: auto-detect)
) as client:
    # Use the client
    passage = await client.get_verse("John", 3, 16)

# Clear cache manually (MemoryCacheProvider only)
client.clear_cache()

# Use custom headers (e.g., for tracking)
custom_headers = {
    "X-App-Name": "My Bible App",
    "X-App-Version": "1.0.0"
}
async with LSBibleClient(headers=custom_headers) as client:
    results = await client.search("love")

Search Distribution Metadata

When you search for text (not a Bible reference), the API returns rich metadata showing how matches are distributed across Scripture.

Understanding Search Responses

results = client.search("love")

# Basic info available for ALL searches
print(f"Query: {results.query}")
print(f"Total matches: {results.match_count}")
print(f"Passages returned: {results.passage_count}")

# Distribution metadata (only for TEXT searches)
if results.has_search_metadata:
    print("\n๐Ÿ“Š Distribution Metadata Available!")
    print(f"Total count: {results.total_count}")
    print(f"Filtered count: {results.filtered_count}")
else:
    print("No distribution metadata (Bible reference lookup)")

Distribution by Bible Section

The Bible is divided into 8 major sections. Search metadata shows match counts for each:

results = client.search("love")

if results.has_search_metadata:
    from lsbible.books import SECTION_NAMES

    print("\n๐Ÿ“š Distribution by Section:")
    for section_id, count in results.counts_by_section.items():
        section_name = SECTION_NAMES[section_id]
        print(f"  {section_name}: {count} matches")

The 8 Bible Sections:

  1. Pentateuch (Genesis - Deuteronomy)
  2. History (Joshua - Esther)
  3. Wisdom and Poetry (Job - Song of Songs)
  4. Major Prophets (Isaiah - Daniel)
  5. Minor Prophets (Hosea - Malachi)
  6. Gospels and Acts (Matthew - Acts)
  7. Pauline Epistles (Romans - Philemon)
  8. General Epistles (Hebrews - Revelation)

Distribution by Individual Books

See which specific books contain the most matches:

results = client.search("faith")

if results.has_search_metadata:
    from lsbible.books import BIBLE_STRUCTURE

    print("\n๐Ÿ“– Top 10 Books by Match Count:")

    # Sort books by match count (descending)
    sorted_books = sorted(
        results.counts_by_book.items(),
        key=lambda x: x[1],
        reverse=True
    )

    for book_num, count in sorted_books[:10]:
        book_name = BIBLE_STRUCTURE[book_num]["name"]
        print(f"  {book_name}: {count} matches")

Example: Complete Search Analysis

from lsbible import LSBibleClient
from lsbible.books import SECTION_NAMES, BIBLE_STRUCTURE

with LSBibleClient() as client:
    results = client.search("salvation")

    print(f"๐Ÿ” Search Query: '{results.query}'")
    print(f"โœ… Total Matches: {results.match_count}")
    print(f"๐Ÿ“„ Passages Returned: {results.passage_count}")

    if results.has_search_metadata:
        print(f"\n๐Ÿ“Š DISTRIBUTION ANALYTICS")
        print(f"   Total: {results.total_count}")
        print(f"   Filtered: {results.filtered_count}")

        # Section distribution
        print(f"\n๐Ÿ“š By Section:")
        for section_id, count in sorted(results.counts_by_section.items()):
            section_name = SECTION_NAMES[section_id]
            percentage = (count / results.total_count) * 100
            print(f"   {section_name:25} {count:4} ({percentage:5.1f}%)")

        # Top books
        print(f"\n๐Ÿ“– Top 5 Books:")
        sorted_books = sorted(
            results.counts_by_book.items(),
            key=lambda x: x[1],
            reverse=True
        )
        for book_num, count in sorted_books[:5]:
            book_name = BIBLE_STRUCTURE[book_num]["name"]
            print(f"   {book_name:25} {count:4} matches")

    # Display sample verses
    print(f"\n๐Ÿ“ Sample Verses:")
    for passage in results.passages[:3]:  # First 3 passages
        for verse in passage.verses:
            print(f"   {verse.reference}: {verse.plain_text[:100]}...")

When Distribution Metadata is Available

  • โœ… Text searches: client.search("love") โ†’ Has metadata
  • โŒ Bible references: client.search("John 3:16") โ†’ No metadata (use get_verse instead)

Error Handling

The SDK provides specific exceptions for different error cases:

from lsbible import (
    LSBibleClient,
    BookName,
    InvalidReferenceError,
    APIError,
    BuildIDError
)

with LSBibleClient() as client:
    try:
        # Invalid chapter (John only has 21 chapters)
        passage = client.get_verse(BookName.JOHN, 99, 1)
    except InvalidReferenceError as e:
        print(f"โŒ Invalid reference: {e}")
        # Output: "John only has 21 chapters, but chapter 99 was requested"

    try:
        # Invalid verse (John 3 only has 36 verses)
        passage = client.get_verse(BookName.JOHN, 3, 999)
    except InvalidReferenceError as e:
        print(f"โŒ Invalid reference: {e}")
        # Output: "John 3 only has 36 verses, but verse 999 was requested"

    try:
        # Invalid book name
        passage = client.get_verse("NotABook", 1, 1)
    except InvalidReferenceError as e:
        print(f"โŒ Invalid reference: {e}")
        # Output: "Unknown book: NotABook"

    try:
        # API error (network issues, etc.)
        passage = client.get_verse(BookName.JOHN, 3, 16)
    except APIError as e:
        print(f"โŒ API error: {e}")

    try:
        # Build ID detection failure
        passage = client.get_verse(BookName.JOHN, 3, 16)
    except BuildIDError as e:
        print(f"โŒ Build ID error: {e}")

Exception Hierarchy:

  • LSBibleError (base exception)
    • InvalidReferenceError - Invalid book/chapter/verse
    • APIError - API request failures
    • BuildIDError - Build ID detection failures

API Reference

LSBibleClient

Main async client class for interacting with the LSBible API.

__init__(cache=None, timeout=30, build_id=None, headers=None)

Initialize the client.

Parameters:

  • cache (CacheOptions | None): Cache configuration (optional)
  • timeout (int): Request timeout in seconds (default: 30)
  • build_id (str, optional): Next.js build ID (auto-detected if not provided)
  • headers (dict, optional): Custom HTTP headers

Example:

from lsbible import LSBibleClient, MemoryCacheProvider, CacheTTL

async with LSBibleClient(
    cache={
        "provider": MemoryCacheProvider(),
        "ttl": {
            "verse": CacheTTL.BIBLE_CONTENT,
            "search": CacheTTL.SEARCH_RESULTS,
        }
    },
    timeout=60
) as client:
    passage = await client.get_verse("John", 3, 16)

async search(query: str) -> SearchResponse

Search for passages containing text.

Parameters:

  • query (str): Search text or Bible reference

Returns: SearchResponse with structured passage data

Raises:

  • APIError: If API request fails

async get_verse(book, chapter, verse) -> Passage

Get a specific verse with validated parameters.

Parameters:

  • book (BookName | str): Book name (enum or string)
  • chapter (int): Chapter number
  • verse (int): Verse number

Returns: Passage containing the verse

Raises:

  • InvalidReferenceError: If reference is invalid
  • APIError: If API request fails

async get_passage(from_book, from_chapter, from_verse, to_book, to_chapter, to_verse) -> Passage

Get a passage spanning multiple verses.

Parameters:

  • from_book (BookName | str): Starting book
  • from_chapter (int): Starting chapter
  • from_verse (int): Starting verse
  • to_book (BookName | str): Ending book
  • to_chapter (int): Ending chapter
  • to_verse (int): Ending verse

Returns: Passage containing all verses in range

Raises:

  • InvalidReferenceError: If any reference is invalid
  • APIError: If API request fails

async get_chapter(book, chapter) -> Passage

Get an entire chapter.

Parameters:

  • book (BookName | str): Book name
  • chapter (int): Chapter number

Returns: Passage containing all verses in the chapter

Raises:

  • InvalidReferenceError: If reference is invalid
  • APIError: If API request fails

clear_cache() -> None

Clear the response cache (only works with MemoryCacheProvider).

async close() -> None

Close the HTTP client.

Models

All models are immutable Pydantic models with full validation.

BookName (Enum)

Enumeration of all 66 Bible books:

from lsbible import BookName

# Old Testament
BookName.GENESIS
BookName.EXODUS
# ... through ...
BookName.MALACHI

# New Testament
BookName.MATTHEW
BookName.MARK
# ... through ...
BookName.REVELATION

# Numbered books
BookName.SAMUEL_1      # "1 Samuel"
BookName.KINGS_2       # "2 Kings"
BookName.CORINTHIANS_1 # "1 Corinthians"

VerseReference

Immutable reference to a specific verse:

Fields:

  • book_number (int): Book number (1-66)
  • chapter (int): Chapter number
  • verse (int): Verse number

Properties:

  • book_name (BookName): The book name enum

Example:

ref = VerseReference(book_number=43, chapter=3, verse=16)
print(ref.book_name)  # BookName.JOHN
print(str(ref))       # "John 3:16"

TextSegment

Text with formatting metadata:

Fields:

  • text (str): The text content
  • is_red_letter (bool): Jesus' words
  • is_italic (bool): Clarifications
  • is_bold (bool): Emphasis
  • is_small_caps (bool): LORD (YHWH)

VerseContent

Complete structured content of a verse:

Fields:

  • reference (VerseReference): Verse reference
  • verse_number (int): Verse number
  • segments (list[TextSegment]): Text segments with formatting
  • has_subheading (bool): Whether verse has a subheading
  • subheading_text (str | None): Subheading text
  • is_poetry (bool): Poetic structure
  • is_prose (bool): Prose structure
  • chapter_start (bool): First verse of chapter

Properties:

  • plain_text (str): Text without formatting
  • formatted_text (str): Text with simple formatting markers

Passage

A passage containing one or more verses:

Fields:

  • from_ref (VerseReference): Starting reference
  • to_ref (VerseReference): Ending reference
  • title (str): Passage title
  • verses (list[VerseContent]): List of verses

Properties:

  • is_single_verse (bool): Whether passage is a single verse
  • verse_count (int): Number of verses

SearchResponse

Response from a search or verse lookup:

Fields:

  • query (str): Original query
  • match_count (int): Number of matches
  • passages (list[Passage]): List of passages
  • duration_ms (int): Query duration in milliseconds
  • timestamp (int): Unix timestamp in milliseconds
  • total_count (int | None): Total matches (text search only)
  • filtered_count (int | None): Filtered matches (text search only)
  • counts_by_book (dict | None): Match distribution by book
  • counts_by_section (dict | None): Match distribution by section

Properties:

  • passage_count (int): Number of passages returned
  • total_verses (int): Total verses across all passages
  • has_search_metadata (bool): Whether response includes distribution metadata

Caching

The SDK supports pluggable caching through the CacheProvider protocol, allowing you to choose the right caching strategy for your deployment environment.

Built-in Cache Providers

MemoryCacheProvider - In-memory caching with TTL support

  • โœ… Local development, testing, single-process apps
  • โŒ Multi-process apps, high-traffic production servers

NoopCacheProvider - Disables caching entirely

  • Useful for debugging or when caching isn't beneficial

Cache Configuration

from lsbible import LSBibleClient, MemoryCacheProvider, CacheTTL

async with LSBibleClient(
    cache={
        "provider": MemoryCacheProvider(),
        "ttl": {
            "verse": CacheTTL.BIBLE_CONTENT,   # 30 days
            "passage": CacheTTL.BIBLE_CONTENT, # 30 days
            "chapter": CacheTTL.BIBLE_CONTENT, # 30 days
            "search": CacheTTL.SEARCH_RESULTS, # 7 days
        }
    }
) as client:
    passage = await client.get_verse("John", 3, 16)

Recommended TTL constants:

  • CacheTTL.BIBLE_CONTENT (30 days) - Bible text is immutable
  • CacheTTL.SEARCH_RESULTS (7 days) - May change with API updates
  • CacheTTL.STATIC (1 year) - Never changes

Custom Cache Providers

Implement the CacheProvider protocol for custom backends like Redis, Memcached, or DynamoDB:

from typing import Any

class CacheProvider:
    async def get(self, key: str) -> Any | None:
        """Get value from cache, return None if not found"""
        ...

    async def set(self, key: str, value: Any, ttl: int) -> None:
        """Set value in cache with TTL in seconds"""
        ...

Complete examples:

Cache Keys

The SDK uses these cache key patterns:

  • verse:{book} {chapter}:{verse} - Single verses
  • passage:{query} - Passage ranges
  • chapter:{book} {chapter} - Full chapters
  • search:{query} - Text searches

Development

Setup

# Clone repository
git clone https://github.com/kdcokenny/lsbible.git
cd lsbible/packages/python-sdk

# Install dependencies
uv sync

Commands

# Run tests with coverage
uv run pytest

# Run specific test file
uv run pytest tests/test_client.py

# Run single test
uv run pytest tests/test_client.py::test_get_verse

# Type checking
uv run ty check lsbible

# Linting
uv run ruff check lsbible
uv run ruff check lsbible --fix  # Auto-fix

# Formatting
uv run ruff format lsbible

Project Structure

packages/python-sdk/
โ”œโ”€โ”€ lsbible/              # SDK source code
โ”‚   โ”œโ”€โ”€ __init__.py       # Public API exports
โ”‚   โ”œโ”€โ”€ client.py         # HTTP client
โ”‚   โ”œโ”€โ”€ models.py         # Pydantic models
โ”‚   โ”œโ”€โ”€ parser.py         # HTML parser
โ”‚   โ”œโ”€โ”€ validators.py     # Reference validation
โ”‚   โ”œโ”€โ”€ books.py          # Bible structure data
โ”‚   โ”œโ”€โ”€ cache.py          # Response caching
โ”‚   โ””โ”€โ”€ exceptions.py     # Custom exceptions
โ”œโ”€โ”€ tests/                # Test suite
โ”œโ”€โ”€ examples/             # Usage examples
โ”œโ”€โ”€ README.md             # This file
โ””โ”€โ”€ pyproject.toml        # Project configuration

Contributing

We welcome contributions! See the main Contributing Guidelines for details on:

  • Reporting bugs
  • Suggesting features
  • Submitting pull requests
  • Code style and standards

Quick Tips

  • Add tests for new features
  • Update docs for API changes
  • Follow code style (run ruff format)
  • Run tests before submitting (pytest)

License

MIT License - See LICENSE file for details.


Back to Monorepo Root โ†’

Made with โค๏ธ for Bible software developers

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lsbible-0.3.0.tar.gz (39.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lsbible-0.3.0-py3-none-any.whl (27.5 kB view details)

Uploaded Python 3

File details

Details for the file lsbible-0.3.0.tar.gz.

File metadata

  • Download URL: lsbible-0.3.0.tar.gz
  • Upload date:
  • Size: 39.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.7

File hashes

Hashes for lsbible-0.3.0.tar.gz
Algorithm Hash digest
SHA256 483c0ec8e61ecffa8db62ff13d576ea73f6e2ea09007a50489d0538ad1fc239e
MD5 e07c27be21e9ab01b6dd8af753b02453
BLAKE2b-256 f092d0df2a56c871bf18c4016a6876501ba6fab8d080e37666cf4e3356dd4060

See more details on using hashes here.

File details

Details for the file lsbible-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: lsbible-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 27.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.7

File hashes

Hashes for lsbible-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 670af4058ab3143e56792d7a92f9334c71bc3393df9b5f06efe40591c2e94814
MD5 057a11d08e3e0412128b7cb418c94f3d
BLAKE2b-256 3e576c4cfb7d0f288af987421494c13dd2151616929d4160f6453c30b4365951

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page