Skip to main content

Error Handling

The Meter SDK uses exceptions to signal errors. All API-related errors raise MeterError, which you should catch and handle appropriately.

MeterError

The base exception for all Meter SDK errors.
from meter_sdk import MeterError

class MeterError(Exception):
    """Base exception for Meter SDK errors"""
    pass

Catching errors

from meter_sdk import MeterClient, MeterError

client = MeterClient(api_key="sk_live_...")

try:
    strategy = client.generate_strategy(
        url="https://example.com",
        description="Extract data",
        name="Test Strategy"
    )
except MeterError as e:
    print(f"API error: {e}")
    # Handle error appropriately

Common error scenarios

401 Unauthorized

Cause: Invalid or missing API key
try:
    client = MeterClient(api_key="invalid_key")
    strategies = client.list_strategies()
except MeterError as e:
    # "Invalid or missing API key"
    print(f"Authentication failed: {e}")
Solutions:
  • Verify your API key is correct
  • Check that the key hasn’t been deleted
  • Ensure you’re using the full key (starts with sk_live_)

400 Bad Request

Cause: Invalid request parameters
try:
    # Missing required parameter
    strategy = client.generate_strategy(
        url="https://example.com",
        description="",  # Empty description
        name="Test"
    )
except MeterError as e:
    # "Invalid request parameters"
    print(f"Validation error: {e}")
Solutions:
  • Check all required parameters are provided
  • Verify parameter types are correct
  • Ensure values are valid (e.g., URL is well-formed)

404 Not Found

Cause: Resource doesn’t exist
try:
    strategy = client.get_strategy("invalid-uuid")
except MeterError as e:
    # "Strategy not found"
    print(f"Resource not found: {e}")
Solutions:
  • Verify the UUID is correct
  • Check the resource hasn’t been deleted
  • Ensure you have permission to access the resource

429 Too Many Requests

Cause: Rate limit exceeded on strategy generation
try:
    strategy = client.generate_strategy(url, description, name)
except MeterError as e:
    # "Please slow down - limits on strategy generation"
    print(f"Rate limited: {e}")
Solutions:
  • Wait before retrying (the API returns a Retry-After header with seconds to wait)
  • Reduce request frequency
  • Use exponential backoff

500 Internal Server Error

Cause: Server-side error
try:
    job = client.create_job(strategy_id, url)
except MeterError as e:
    # "Internal server error"
    print(f"Server error: {e}")
Solutions:
  • Retry the request after a short delay
  • If persistent, contact support
  • Check API status page for incidents

503 Service Unavailable

Cause: LLM service temporarily unavailable
try:
    strategy = client.generate_strategy(url, description, name)
except MeterError as e:
    # "Service temporarily unavailable"
    print(f"Service unavailable: {e}")
Solutions:
  • Retry the request after 30-60 seconds
  • This is usually temporary

Timeout errors

When waiting for jobs with a timeout:
from meter_sdk import MeterError

try:
    completed = client.wait_for_job(
        job_id,
        timeout=60.0  # 1 minute
    )
except MeterError as e:
    # "Job timed out after 60 seconds"
    print(f"Timeout: {e}")

    # Check job status manually
    job = client.get_job(job_id)
    if job['status'] == 'failed':
        print(f"Job failed: {job['error']}")
    else:
        print(f"Job still {job['status']}, wait longer")

Best practices

Always catch MeterError

from meter_sdk import MeterClient, MeterError

client = MeterClient(api_key="sk_live_...")

try:
    # API calls
    strategy = client.generate_strategy(url, description, name)
    job = client.create_job(strategy['strategy_id'], url)
    result = client.wait_for_job(job['job_id'])

except MeterError as e:
    # Log error
    logger.error(f"Meter API error: {e}")

    # Handle appropriately
    send_alert(f"Scraping failed: {e}")

Implement retry logic

For transient errors (429, 500, 503, network issues):
import time
from meter_sdk import MeterError

def generate_strategy_with_retry(client, url, description, name, max_retries=3):
    """Generate strategy with retry logic"""
    for attempt in range(max_retries):
        try:
            return client.generate_strategy(url, description, name)
        except MeterError as e:
            error_str = str(e).lower()

            # Rate limited - wait longer (respect Retry-After)
            if "429" in str(e) or "slow down" in error_str:
                wait_time = 60  # Default from Retry-After header
                print(f"Rate limited. Waiting {wait_time}s...")
                time.sleep(wait_time)
                continue

            # Server errors - exponential backoff
            if "500" in str(e) or "503" in str(e) or "unavailable" in error_str:
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt
                    print(f"Retry {attempt + 1}/{max_retries} in {wait_time}s")
                    time.sleep(wait_time)
                else:
                    raise  # Max retries exceeded
            else:
                raise  # Non-retryable error

Graceful degradation

Handle errors without crashing:
from meter_sdk import MeterError

def fetch_latest_data(client, schedule_id):
    """Fetch changes with fallback"""
    try:
        changes = client.get_schedule_changes(schedule_id)
        return changes['changes']

    except MeterError as e:
        logger.warning(f"Failed to fetch changes: {e}")

        # Fallback: use cached data
        return load_from_cache(schedule_id)

Log errors for debugging

import logging
from meter_sdk import MeterError

logger = logging.getLogger(__name__)

try:
    strategy = client.generate_strategy(url, description, name)
except MeterError as e:
    # Log with context
    logger.error(
        "Strategy generation failed",
        extra={
            "url": url,
            "description": description,
            "error": str(e)
        }
    )
    raise

Error response format

API errors return JSON with details:
{
  "detail": "Error message describing what went wrong"
}
The SDK extracts this message and includes it in the MeterError exception.

Handling specific error types

Strategy generation failures

try:
    strategy = client.generate_strategy(url, description, name)
except MeterError as e:
    if "not accessible" in str(e).lower():
        print("URL cannot be accessed. Check if it's public.")
    elif "invalid url" in str(e).lower():
        print("URL format is invalid")
    else:
        print(f"Generation failed: {e}")

Job failures

job = client.get_job(job_id)

if job['status'] == 'failed':
    error = job['error']

    if "selector not found" in error.lower():
        print("Website structure changed. Regenerate strategy.")
    elif "timeout" in error.lower():
        print("Website took too long to respond")
    else:
        print(f"Job failed: {error}")

Error monitoring

Set up error tracking for production:
import sentry_sdk
from meter_sdk import MeterError

sentry_sdk.init(dsn="your-sentry-dsn")

try:
    strategy = client.generate_strategy(url, description, name)
except MeterError as e:
    # Automatically captured by Sentry
    sentry_sdk.capture_exception(e)
    raise

Next steps

Need help?

Email me at mckinnon@meter.sh