· By Jeff Baart · Updated Oct 8, 2026

Bring your own model: Provider key - BYOK with Unity AI Gateway (part 3 of 3)

Part three of three. Part one covered Genie Code and Genie Agents; part two covered the three Databricks-hosted ways to serve a model. This one is the fourth option, and the newest.

Bring your own provider key

Scenario: you already hold a committed-spend agreement with Anthropic. You want that contract consumed rather than a second token bill — and you'd rather not have API keys pasted into notebooks to get there.


What Unity AI Gateway is actually buying you

The credential handling is the visible part. The governance is where the real value is.

Almost nobody ends up with one provider. You end up with Anthropic for one team, OpenAI for another, Bedrock somewhere in a regulated business unit, and a Gemini pilot nobody told you about. Each one shows up with its own key, its own console, its own bill, and its own idea of what a log looks like. Unity AI Gateway puts all of it behind one door. Three things you get:

1. You can see the spend — all of it, in one query. Every request gets metered the same way no matter who served it. Databricks works out the estimated dollars from token counts and the provider's published prices and lands it in system.ai_gateway.external_model_spend. So "what are we spending on outside models" is one query instead of three console logins and a spreadsheet. Tag requests and you can split it by project or team from the first call.

2. The logs look the same whoever served the request. system.ai_gateway.usage records latency, tokens, status codes, who asked, and which destination answered — same columns for a Databricks-hosted model and an outside one. No stitching three vendors' log formats together to answer "which model is slow" or "who called this."

3. Guardrails go on once. Rate limits, content policies, and access control sit on the gateway object instead of being rebuilt per provider. Add a fourth provider and it lands under the same rules.

The point: the credential is what you register, but the common governance interface is what you get. Providers become swappable destinations behind one set of logs, one bill, and one set of rules. Adding a fifth is a config change, not a project.

Two limits, both covered below: Databricks budgets don't cover this spend, and passthrough requests skip everything I just listed.

This is the newest option and the least known, so the walkthrough below skips nothing. It's all from a working setup on my own workspace.

**The thing you create is a model provider service, and it lives in Unity Catalog like a table does.** It is not the older "external model endpoint" — different path, don't mix them up. The docs put it well:

"External model providers let you bring your own key (BYOK): connect the LLM providers you already use, such as OpenAI, Anthropic, or Amazon Bedrock, and govern them through one control point. Register each provider once, and many users and model APIs route to it without ever handling the credentials."

Three-part name, e.g. main.default.anthropic_prod. Databricks "encrypts and stores" the credential and "does not return credentials on read."

The bit worth understanding — it runs as the owner, not the caller:

"Azure Databricks evaluates a query against the owner's privileges rather than the caller's... The caller does not need direct access to those destinations."

You hold the key. Everyone else holds EXECUTE. Your analysts query Claude and never see the key — that's the whole idea in one sentence.

Supported providers on this path (deliberately narrower than the legacy external-models path):

ProviderAuthentication
OpenAIAPI key
Azure OpenAIAPI key, or Entra ID service principal
AnthropicAPI key
Amazon BedrockAWS access key pair
Microsoft FoundryAPI key, or Entra ID service principal
Google Gemini EnterpriseAPI key + GCP project ID and region
CustomBearer token + endpoint base URL

No Vertex AI, Cohere, or AI21 here — those remain on the legacy path.


Setting one up, end to end

Most people don't know this exists yet, so here's the whole path with nothing skipped. Total time is a couple of minutes.

Step 0 — Confirm the feature is available in your metastore

Unity AI Gateway is GA, but availability rolls out by region and metastore, and a workspace admin may need to enable it from the account console. Check before you plan around it:

databricks api get /api/2.1/unity-catalog/model-provider-services --profile <your-profile>
  • {} or a list of services → available, you're good to go
  • No API found for 'GET /unity-catalog/model-provider-services' → not enabled in this metastore yet

That's a five-second check that saves a confusing round trip through the create form.


Step 1 — Get a provider API key

For Anthropic, that's a key from the Anthropic Console (console.anthropic.com) — the one for the Claude API and client SDKs, in the sk-ant-... shape, with credit on the account. A Claude.ai Pro/Max subscription is a different product and has no API surface.

Step 2 — Choose where the object lives

This object gets a three-part name like a table, so where you put it is a permissions decision, not a filing decision. Two things I'd think about:

  • Not a schema everyone can read. My gold schema grants SELECT and USE SCHEMA to account users. Right for gold tables, too open for the thing holding an API key.
  • Not bronze/silver/gold at all. Those describe how refined your data is. A provider key isn't data. Use default, or make an ai_gateway schema for it.

I used golf_league.default.

Step 3 — Create it

AI Gateway path: workspace sidebar → AI Gateway → Providers tab → Create

Catalog Explorer path (prefills catalog and schema): navigate to your schema → Create → Service → Model provider service




Fill in the name, catalog, and schema; select Anthropic; paste the key. On save, "Azure Databricks encrypts and stores the credentials. The UI does not display them after this point."

This step is UI-only. No API, CLI, or SQL path is documented, so you click it rather than scripting it into a bundle.

One naming rule: model provider services and model services share a namespace inside a schema, so you can't reuse a name across the two.

Step 4 — Check Advanced settings before you save

I'd have missed this if I hadn't read the saved config back. Three forwarding options sit under Advanced settings, and they're on by default:

"forward_headers": true,
"forward_query_parameters": true,
"forward_unmanaged_paths": true

Each exists for a reason, and each widens what a caller can push through the object holding your key:

SettingWhat it doesConsider turning off when
forward_headersPasses client request headers upstream. "Enable this when a provider requires a header that Unity AI Gateway does not set for you, such as OpenAI-Organization."Anthropic's Messages API doesn't need one
forward_query_parametersPasses client query parameters upstreamSame — nothing in the managed path requires it
forward_unmanaged_pathsLets callers reach provider endpoints beyond the managed pathsSee the governance note below

Read the third one twice. The docs are blunt about the trade:

"Usage token and cost tracking, token-based rate limits, model access control, and service policies do not apply to passthrough requests."

So passthrough also skips the cost tracking and the rules — those requests bypass them completely. If you came here to track spend against a contract, leaving it on works against you. Turn it on for a specific endpoint the managed paths don't cover, like OpenAI's files or batch APIs. Not by default.



Read back what actually got saved. More reliable than remembering what you clicked:

databricks api get /api/2.1/unity-catalog/model-provider-services --profile <p>
{
  "name": "model-provider-services/golf_league.default.golf-league-anthropic-prod",
  "securable_type": "MODEL_PROVIDER_SERVICE",
  "effective_owner": "jeff.baart@outlook.com",
  "config": {
    "provider_type": "EXTERNAL_MODEL_PROVIDER_TYPE_ANTHROPIC",
    "anthropic": { "direct": { "api_key": {} } },
    "allow_all_targets": false,
    "targets": [
      { "model": "claude-haiku-4-5", "native_api_types": ["anthropic/v1/messages"] }
    ]
  }
}

Two things worth noticing. "api_key": {} — the credential is stored but never returned, matching "Azure Databricks does not return credentials on read." And "allow_all_targets": false with one target listed means callers only get claude-haiku-4-5 — nobody can quietly switch to a pricier model.

Step 5 — Grant EXECUTE

Creating it doesn't make it usable by anyone else. In Catalog Explorer: open the service → Permissions → Grant → pick who → EXECUTE.

EXECUTE on its own isn't enough — the docs also want "EXECUTE on the model provider service and USE CATALOG and USE SCHEMA on its catalog and schema." Granting to account users is simplest if they already hold USE CATALOG, since USE SCHEMA inherits.


Verify — and note the object type name, which is MODEL_PROVIDER_SERVICE, not SERVICE:

databricks grants get MODEL_PROVIDER_SERVICE \
  golf_league.default.golf-league-anthropic-prod --profile <p>
{ "privilege_assignments": [ { "principal": "account users", "privileges": ["EXECUTE"] } ] }

The full set: CREATE SERVICE on the schema, EXECUTE to query it, READ_METADATA to see the config without querying, MANAGE to edit, delete, and grant.

Step 6 — Query it

Here's the part that isn't obvious: you pick the service with a header, not a URL path. Base URL is your workspace plus /ai-gateway, and you authenticate with your Databricks token — never the provider's.

curl https://<workspace-url>/ai-gateway/anthropic/v1/messages \
  -H "Authorization: Bearer $DATABRICKS_TOKEN" \
  -H "Databricks-Model-Provider-Service: golf_league.default.golf-league-anthropic-prod" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5",
    "max_tokens": 32,
    "messages": [{"role": "user", "content": "Reply with exactly: BYOK works"}]
  }'

The response is Anthropic's native shape, straight through:

{
  "model": "claude-haiku-4-5-20251001",
  "content": [{"type": "text", "text": "BYOK works"}],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 15, "output_tokens": 7, "service_tier": "standard"}
}

That one call is the whole option. Databricks token in, real Claude response out, the Anthropic key never leaves Unity Catalog, and Anthropic bills my account instead of my Databricks bill.

The managed path depends on the provider's API — /ai-gateway/anthropic/v1/messages here, /ai-gateway/openai/v1/chat/completions for OpenAI, and so on. For OpenAI-shaped providers you can point the openai client straight at the gateway by setting base_url and passing the same header.

Step 7 — Tag requests so the spend is attributable

Do this on the first call — retrofitting attribution is miserable. Databricks-Ai-Gateway-Request-Tags takes a JSON object and lands in the request_tags column of the usage table and inference tables:

-H 'Databricks-Ai-Gateway-Request-Tags: {"project":"golf-league","team":"data-platform"}'

Cost by project or team, without building anything.

Who invoices you

"For requests routed to external models through model provider services in Unity Catalog, Azure Databricks computes the estimated USD spend for each request from its token usage and the external provider's published prices. Spend is aggregated hourly and recorded in the system.ai_gateway.external_model_spend system table."

>

"Spend for external models is calculated using the external provider's published prices and is provided for informational purposes only."

That's the answer if you've prepaid a provider: your contract gets drawn down, Databricks shows you an estimate so you can watch it, and the provider sends the invoice. This spend is not in system.billing.usage.

Two tables matter: system.ai_gateway.usage for per-request detail, and system.ai_gateway.external_model_spend for estimated dollars. Neither is real-time — mine took hours.

destination_type is the column that makes the split visible, and one query shows you both billing models side by side:

SELECT destination_type, destination_name, destination_model,
       COUNT(*) AS requests, SUM(input_tokens) AS in_tok, SUM(output_tokens) AS out_tok
FROM system.ai_gateway.usage
WHERE event_time >= current_date() - INTERVAL 2 DAYS
GROUP BY ALL ORDER BY requests DESC

Real output from my workspace:

destination_typedestination_namemodelreqsinout
PAY_PER_TOKEN_FOUNDATION_MODELsystem.ai.databricks-claude-opus-4-8Claude Opus 4.81598991
EXTERNAL_FOUNDATION_MODELgolf_league.default.golf-league-anthropic-prodclaude-haiku-4-568465
PAY_PER_TOKEN_FOUNDATION_MODELsystem.ai.llama_v3_3_70b_instructLlama 3.3 70B1372

Compare the middle row to the ones around it — that's the whole billing story in one table. The PAY_PER_TOKEN_FOUNDATION_MODEL rows bill to Databricks as DBUs. The EXTERNAL_FOUNDATION_MODEL row bills to my Anthropic account, and its name is my provider service rather than a system.ai.* model. Two invoices, one place to watch them, one column telling you which is which.

The request tags come through too, so attribution works from the first call:

"request_tags": {"project": "golf-league", "purpose": "blog-post-1b"}

These tables lag. Mine took hours to show up. When you're testing a setup, trust the API response — a 200 with a usage block — not the system table.

Set spend limits at the provider too. Verbatim: "Spend from model provider services is not tracked in budgets. Budget notifications, alerts, and hard spend caps do not apply to model provider service usage."

Databricks budgets cover Databricks spend. Provider spend gets capped at the provider. Set both — the exact thing you're here to protect is the thing Databricks budgets don't watch.

And passthrough skips all of it. With forward_unmanaged_paths on, those requests skip "usage token and cost tracking, token-based rate limits, model access control, and service policies." That's everything this section is about, switched off per request. Leave it off unless something specific needs it.

Mixed routing

One model service can point at both Databricks-hosted and outside destinations, which is how you get "spend my contract first, fall back to hosted when it runs out." Traffic splitting takes up to five destinations, and fallbacks retry in a priority order on 429s and 5xx errors. One constraint to know before you draw the diagram: provisioned throughput models can't be destinations here, so option 2 is out of any mixed-routing design.

When to reach for it: you hold a negotiated provider rate or prepurchase; you use more than one provider and want one metering, logging, and policy layer across all of them; you want failover across providers. If you have exactly one provider and no governance requirement, the hosted options are simpler — the value here scales with the number of providers you're trying to keep honest.



Where we land


If you're running one provider and nobody's asking governance questions, the hosted options in part two are simpler and you should use them.

But the moment you have two providers — or a contract you've already paid for, or someone asking which team spent what — this is the option that stops you from wiring three consoles together by hand. You register a key once. What you get back is one query for the spend, one set of logs, and one place to put the rules.

That's the series: Genie Code and Genie Agents, the three hosted options, and this one.