Treat External SaaS Configuration as a Checked Registry

James Phoenix
James Phoenix

If application behaviour depends on values in a third-party dashboard, those values belong in a machine-checked inventory.

The Missing Half of Configuration as Code

I can keep application configuration in git and still suffer configuration drift. The reason is simple: part of the system lives outside the repository.

Stripe owns products and price IDs. An OAuth provider owns redirect URIs, scopes, and application status. A model gateway owns model identifiers and availability. A social platform owns approved capabilities. A deployment provider owns domains, environment variables, and webhook endpoints.

My code references those objects, but git cannot prove they still exist or mean what the code assumes.

Configuration is not truly versioned when the repository stores references but the provider owns their meaning. I close that gap by reading the external configuration into a normalised registry and validating my code against it.

The Registry Is an Inventory, Not a Secret Store

A checked registry contains identities and safe metadata:

{
  "provider": "stripe",
  "environment": "production",
  "generatedAt": "2026-08-13T09:00:00Z",
  "plans": {
    "parent-core-monthly": {
      "productId": "prod_example",
      "priceId": "price_example",
      "currency": "gbp",
      "unitAmount": 1900,
      "interval": "month",
      "active": true
    }
  }
}

It does not contain API keys, client secrets, signing secrets, access tokens, or credentials. Those remain in 1Password or the deployment platform. The registry says which public or non-secret provider objects the application expects and what properties matter.

This separation is important. “Put configuration in git” is dangerous advice if configuration includes credentials. I version the contract, not the authority needed to mutate the provider.

The Four-Step Loop

The pattern has four stages:

Leanpub Book

Read The Meta-Engineer

A practical book on building autonomous AI systems with Claude Code, context engineering, verification loops, and production harnesses.

Continuously updated
Claude Code + agentic systems
View Book
  1. Fetch provider state through its official CLI or API.
  2. Normalise unstable output into a deterministic schema.
  3. Validate application references and cross-environment expectations.
  4. Diff the registry and gate unexpected changes.

The fetch step is read-only. For Stripe, it lists products and prices. For an OAuth application, it may read registered redirect URIs and enabled scopes. For a model gateway, it reads the current model catalog and capability metadata.

The normaliser removes fields that create meaningless diffs, such as retrieval timestamps, provider ordering, request IDs, and dashboard presentation fields. Records are sorted by stable keys and projected into the smallest schema needed by the application.

The validator then asks concrete questions:

  • Does every identifier referenced by code exist?
  • Is the referenced object active?
  • Do amount, currency, cadence, region, or capability match expectations?
  • Are staging and production intentionally different?
  • Are deprecated objects still referenced?
  • Has the provider gained an object that should be classified?

Generate, Then Validate

I prefer the provider to generate the registry and the repository to validate it. Hand-editing the file would only create a second source of truth.

A typical workflow looks like this:

provider CLI/API
      |
      v
normalise-provider-config
      |
      v
generated registry JSON
      |
      +--> schema validation
      +--> application reference validation
      +--> staging/production parity policy
      +--> git diff or CI/CD artifact

For local development, I can regenerate the file on demand. In CI/CD, a scheduled or pre-release job reads the provider and compares the result with the committed expectation. If live access is inappropriate for pull requests, I validate code against the last approved snapshot on every PR and run live drift detection on a trusted schedule.

Propagate One Truth Through Every Layer

The registry should not become one more file that each runtime interprets differently. I use it as the input to generated, language-specific contracts:

live provider -> checked registry
                       |
                       +-> generated TypeScript constants and types
                       +-> generated Python models and constants
                       +-> generated SQL fixture for pgTAP assertions

TypeScript and Python import generated artifacts rather than maintaining separate identifier lists. The pgTAP suite uses the generated SQL fixture to assert that database rows, functions, constraints, or billing mappings expose the same logical plans and provider identities.

CI then proves two directions of truth. An upstream freshness test compares the registry with the provider’s current read-only state. Downstream synchronisation tests regenerate the TypeScript, Python, and SQL artifacts and fail if git contains a difference. Runtime tests in each layer confirm that those generated values are actually used.

The registry propagates truth, while the tests prove that the truth is current and has reached every layer. A green TypeScript test alone is insufficient if Python or Postgres still carries yesterday’s identifier.

Drift Needs Classification

Not every difference should block a release. I classify registry changes so the gate can respond proportionately.

Change Default response
Referenced object missing or inactive Block
Price, currency, scope, or endpoint changed Block and review
New unreferenced object Warn and classify
Display name or description changed Record
Expected staging-only difference Allow by policy
Secret value changed Never enter registry

This avoids turning the registry into a noisy mirror of the provider dashboard. I only track fields that affect runtime behaviour, billing, access, compliance, or release safety.

A Registry Must Prove Its Freshness

A committed snapshot creates false confidence if its age is unknown. I record its provider environment, generation time, schema version, and retrieval status. Sensitive release paths reject expired snapshots.

I distinguish “provider returned no objects” from “provider could not be queried.” A failed fetch must not replace the last good registry. Generation validates a candidate before replacing the approved snapshot.

A stale registry is documentation. A fresh registry is evidence.

Environment Parity Is a Policy, Not Equality

Staging and production should not always be identical. Staging may use test-mode prices, sandbox OAuth applications, limited scopes, or private webhook endpoints.

The useful invariant is semantic parity. Both environments should expose the same application concepts, even when their provider IDs differ.

{
  "logicalPlan": "parent-core-monthly",
  "staging": "price_test_example",
  "production": "price_live_example"
}

The registry maps stable logical names to environment-specific identities. Application code consumes the logical name. Environment configuration selects the concrete provider object. The validation gate proves that both objects satisfy the same contract.

Do Not Let the Sync Job Mutate Production

There is a crucial boundary between detection and reconciliation. A read-only registry job can run automatically. A job that creates, archives, reprices, or changes provider configuration can move money or break authentication.

I let automation report drift and generate a proposed change. I require explicit approval before destructive or financially meaningful provider mutations. The registry makes the difference legible before anyone acts.

Why This Helps Agents

Without a registry, an agent finds provider IDs scattered through code, templates, documentation, and screenshots. With one generated inventory, it can trace each logical identity across runtimes and run the synchronisation checks instead of guessing which copy is current.

The Rule

Whenever application behaviour depends on a third-party object, I ask whether the repository can enumerate and validate that object without exposing a secret.

If the answer is yes, I generate a checked registry. External configuration stops being dashboard folklore and becomes part of the release contract.

Related

Topics
Api DesignAutomationGitSoftware ArchitectureVerification

Newsletter

Become a better AI engineer

Weekly deep dives on production AI systems, context engineering, and the patterns that compound. No fluff, no tutorials. Just what works.

Join 306K+ developers. No spam. Unsubscribe anytime.


More Insights

Cover Image for Turn UI Consistency Into a Lint Rule

Turn UI Consistency Into a Lint Rule

If every page should use the same shell, loading pattern, and width policy, consistency should be checked instead of remembered.

James Phoenix
James Phoenix
Cover Image for Put Browser Verification at the End of the Agent Loop

Put Browser Verification at the End of the Agent Loop

I use the browser to verify a stable implementation, not to discover whether every intermediate edit compiles.

James Phoenix
James Phoenix