Skip to content

Best Practices

The SDK is small and well-typed; using it safely is more about how you wire it into your application than about the SDK itself. These are the patterns we've seen work in production.

Credentials

Never commit keys to source

Use .env (gitignored), environment variables, or a secrets manager. The SDK reads plain strings — it doesn't care where they came from.

One key per service

If service A leaks its key, you revoke and reissue without touching service B. Keys are cheap; rotation should be local.

Name keys for traceability

checkout-api-prod beats key3. The name appears in audit logs and the API Keys page.

Rotate periodically

Even without leaks. Quarterly is sensible. Tie it to your existing secrets-rotation cadence.

Construct once, use many

The constructor is cheap but adds zero value when called multiple times. Single instance per process, injected as a dependency:

python
# app/deps.py
import os
from dotenv import load_dotenv
from elsai_prompts.prompt_manager import PromptManager

load_dotenv()

pm = PromptManager(
    api_key=os.environ["ELSAI_API_KEY"],
    project_id=os.environ["ELSAI_PROJECT_ID"],
    environment=os.environ["ELSAI_ENVIRONMENT"],
)
python
# Anywhere else:
from app.deps import pm
prompt = pm.get_active_prompt_version("...")

Cache fetches

The SDK does no caching. Every get_active_prompt_version call is a network round trip. That's deliberate — caching policy is application-specific.

Simple in-memory cache (good default):

python
from datetime import datetime, timedelta, timezone

_cache = {}
_TTL = timedelta(seconds=60)

def get_prompt(name):
    now = datetime.now(timezone.utc)
    hit = _cache.get(name)
    if hit and now - hit[0] < _TTL:
        return hit[1]
    prompt = pm.get_active_prompt_version(name)
    _cache[name] = (now, prompt)
    return prompt

For multi-process apps, swap the dict for Redis with the same shape.

TIP

TTL tradeoff: shorter = fresher prompts, more API load; longer = staler prompts, more resilience to upstream outages. 60 seconds is a defensible default.

Add observability

Wrap fetches with metrics + structured logs:

python
import time
import logging

logger = logging.getLogger(__name__)

def fetch_observed(name):
    started = time.monotonic()
    try:
        prompt = pm.get_active_prompt_version(name)
        elapsed_ms = (time.monotonic() - started) * 1000
        logger.info(
            "prompt_fetch_ok",
            extra={"prompt_name": name, "sha": prompt.sha, "ms": elapsed_ms},
        )
        return prompt
    except Exception as e:
        elapsed_ms = (time.monotonic() - started) * 1000
        logger.exception(
            "prompt_fetch_failed",
            extra={"prompt_name": name, "ms": elapsed_ms, "error": type(e).__name__},
        )
        raise

With OpenTelemetry:

python
from opentelemetry import trace
tracer = trace.get_tracer(__name__)

def fetch_traced(name):
    with tracer.start_as_current_span("prompt.fetch", attributes={"prompt.name": name}) as span:
        prompt = pm.get_active_prompt_version(name)
        span.set_attribute("prompt.sha", prompt.sha or "")
        return prompt

Handle 409 explicitly

The 409 path means "the platform is refusing to serve unreleased content." How you respond depends on whether the prompt is critical:

python
from elsai_prompts.prompt_manager import PromptNotReleasedInEnvironmentError

# Critical path - fail loudly:
try:
    prompt = pm.get_active_prompt_version("payment_confirmation")
except PromptNotReleasedInEnvironmentError:
    logger.critical("payment_confirmation prompt missing in production")
    raise   # page the on-call

# Non-critical - fall back:
try:
    prompt = pm.get_active_prompt_version("footer_message")
    text = prompt.render({})
except PromptNotReleasedInEnvironmentError:
    text = "© 2026 Acme Inc."   # safe default

Never except Exception away a 409 — you'll silently mask configuration bugs.

Pre-warm on startup

For a hot path that can't afford a cold-cache miss, fetch the prompt at startup:

python
PROMPTS_TO_PREWARM = ["welcome_email", "support_dialog", "summarizer"]

def prewarm_prompts():
    for name in PROMPTS_TO_PREWARM:
        try:
            _ = pm.get_active_prompt_version(name)
            logger.info("prewarmed %s", name)
        except Exception as e:
            logger.exception("prewarm failed for %s", name)

If you fail-fast on prewarm errors, your deploy will refuse to roll out when a critical prompt is misconfigured — that's usually the right tradeoff for production.

Lock the SDK version

Pin elsai-prompts to a specific version in your requirements file:

elsai-prompts==2.0.0

Not >=2.0.0. Not ~=2.0. A specific version. SDK upgrades should be a deliberate change with a tested release, not something that comes along on pip install.

Test against the real platform

Mocking the SDK in unit tests is fine for application logic. But have at least one integration test that hits a real prompt against a test project:

python
def test_integration_fetches_real_prompt():
    """Hits the real platform - requires test creds in env."""
    pm = PromptManager(
        api_key=os.environ["ELSAI_TEST_KEY"],
        project_id=os.environ["ELSAI_TEST_PROJECT"],
        environment="development",
    )
    prompt = pm.get_active_prompt_version("smoke_test_prompt")
    assert prompt.kind in ("instruction", "f_string", "chat", "structured")

This catches schema drift between the SDK and the platform that mocked tests never will.

Don't reinvent variable validation

Use prompt.variables as the source of truth:

python
def render_safe(prompt, inputs):
    declared = {v["name"] for v in prompt.variables}
    extra = inputs.keys() - declared
    missing = declared - inputs.keys()
    if missing:
        raise ValueError(f"Missing: {missing}")
    if extra:
        logger.warning("Extra inputs ignored: %s", extra)
    return prompt.render(inputs)

Common anti-patterns

❌ Constructing a new PromptManager per request

No benefit, plus you re-parse env vars on every request. Construct once at startup.

❌ Catching all exceptions and returning a default

Masks 409s (release config bugs), 404s (key revoked, prompt renamed), and 5xx (platform down). Each deserves a different response. Match exception types explicitly.

❌ Hardcoding prompts as a fallback for the same content

Defeats the point of centralized prompts. If you must have a default, make it a safe degraded message, not the same content the prompt would have produced.

❌ Using environment='production' in development

Causes 409s if the production version differs from dev, and worse, lets dev code silently serve customer-facing prompts. Use the environment that matches your runtime.

❌ Skipping the env filter ('just give me whatever is active')

The env filter is the whole point — it prevents unreleased content from leaking. There's no SDK flag to disable it, and there shouldn't be.

Copyright © 2026 elsai foundry.