Skip to main content
You already have an app calling a provider directly. The provider key is in its environment, spend shows up on an invoice, and nothing records which part of your system asked for what. Dome speaks the providers’ own wire protocols, so adoption is a configuration change rather than a rewrite. The work is proving parity and keeping a way back.

Hand this to an AI agent pointed at your repository. It finds the provider calls, moves one environment onto Dome, and verifies nothing changed.

Open in Cursor
In this tutorial, you will repoint an app that already calls a provider at Dome, verify it behaves the same, then add the governance that was the reason for moving. Your application logic does not change. To do this, you will:
1

Check what Dome proxies

Confirm your API surface is supported before you start.
2

Mirror your model in Dome

Create a connection that matches what you call today.
3

Switch one environment

Change three values, keeping a one-variable rollback.
4

Verify parity

Compare the same request through both paths.
5

Add the governance

Apply the rule and the cap you moved for.

Prerequisites

For this tutorial, you will need:
  • An app that calls OpenAI or Anthropic through their official SDK or plain HTTP.
  • The Dome CLI, installed and signed in.
  • The provider API key your app uses today.
  • A workspace you can experiment in. Provision a sandbox if you do not have one:
Adopting an existing app usually spans two roles. A developer changes the configuration, while the rule and the spend cap belong to security and finance. Both halves are shown here.

Check what Dome proxies

Establish this before you change anything, because it determines whether your app can move today. If your app is built on the OpenAI Responses API, it cannot move yet. Find out now rather than midway through a migration:
Also find every place a client is constructed or a model is named. Those are the only lines this tutorial touches:
A base URL or key that is hardcoded rather than read from the environment is the one code change worth making. Make it configurable, and the rest of this is deployment work.

Mirror your model in Dome

Create a connection that resolves to exactly what your app calls today. Name it after the model id your code already passes, and your application’s model string keeps working unchanged:
Naming the connection after the upstream model is a deliberate migration convenience, not the end state. Dome resolves a connection name to a provider model, so a name of your own choosing lets you change providers later without touching the app. Adopt the model id first, rename once you are stable. The provider key now lives in Dome. Remove it from your app’s environment at the end of this tutorial, not yet. You still need it for the parity check. Register an agent for the app and mint its credential:

Switch one environment

Three values change, and nothing else: Naming the connection after the model handled the third. Wire the first two so that reverting is a single variable:
llm_client.py
Unsetting DOME_GATEWAY_URL returns the app to the provider. That is the rollback, and it needs no deploy of new code. Collect the endpoint values:
Smoke-test the endpoint before you point the app at it. This lists the models the gateway exposes to you and confirms the URL and credential are right:
Anthropic clients set base_url to the Gateway without the /v1 suffix, because their SDK appends /v1/messages itself. Anthropic routes also accept the credential in x-api-key when no Authorization header is present, so an app that only sets x-api-key works unchanged.

Verify parity

Run the same prompt through both paths and compare. Use a deterministic request so the comparison means something:
parity_check.py
Both lines should match. Check three things beyond the text itself: that response objects carry the same shape your code already destructures, that streaming still streams if you use it, and that your latency budget still holds with a proxy hop in the path. Then confirm the governed call was recorded, which the direct call was not:
You should see llm.model_call.attempted and llm.model_call.completed attributed to checkout-service, with token usage. That record is the thing you did not have before.
These are remediation steps if you get stuck:
  • 400 with select a gateway. The base URL is missing its /gateways/<id> segment. For OpenAI clients it must also end in /v1.
  • 404 with no connection for requested model. The model string in your code does not match a connection name in this workspace. Compare them:
  • 403 with model not available in this gateway. The connection is not attached to Default. Check membership with dome gateway get Default.
  • 403 with agent is not granted access to this gateway. The agent has no admission grant. Note that GET /v1/models skips this check, so a working smoke test does not prove the grant exists:
  • 401 from the gateway. Your app is still sending the provider key. This endpoint authenticates the agent.
  • 429 with llm: quota exceeded. A spend cap fired. The message names the subject that ran out.
  • 501 on a request that worked before. You are calling a surface Dome does not proxy yet, such as Responses or Moderations. Keep those calls on the direct path until they are supported.
  • Requests succeed but nothing appears in audit. The app is still on the direct path. Confirm DOME_GATEWAY_URL is set in the environment the process actually reads.

Add the governance

Parity holding is the midpoint, not the finish. Nothing is governed yet: the agent may call anything in the gateway and spend without limit. Deploy a rule naming what this app may invoke. Create checkout-service.cedar:
checkout-service.cedar
Then cap the spend. Set the limit from what this app actually costs, which you now have a record of:
Confirm both before you rely on them:
Now remove the provider key from your app’s environment. Until you do, the app can still bypass everything you just built by falling back to the direct path.
Keep the fallback branch in the code if you want, but point it at nothing. A rollback path that silently restores ungoverned access is worse than no rollback path, because it works.

Verify the results

Four things should now be true, and each has a command that proves it:
  1. The app reaches the provider only through Dome. Its environment no longer holds a provider key.
  2. Calls are attributed. dome audit query --limit 10 names checkout-service.
  3. Only the intended model is reachable. Simulating any other connection name returns DENY.
  4. Spend is capped. dome model quota list shows the limit and what has been used against it.
Roll out the way you would any configuration change: one environment, then a share of production traffic, then the rest. Because the switch is an environment variable, staged rollout and rollback use the mechanism you already have.

Next steps

In this tutorial, you:
  • Confirmed your API surface was proxied before starting, rather than discovering a gap midway.
  • Moved a provider credential into Dome and out of your app’s environment.
  • Changed three configuration values and no application logic, keeping a one-variable rollback.
  • Proved parity before adding constraints.
  • Applied a rule and a quota sized from real usage.
Continue with:

Debug a tool call denial

Diagnose the first rejection your rollout produces.

Govern per end user

The agent inherits each user’s permissions — same agent, different tool access and answers per person.

Pools

Failover and routing across providers once you are behind the gateway.

LLM gateway

Endpoints, pipeline stages, and error shapes in full.