# Unity Catalog model names return 404 on /serving-endpoints

> A system.ai model name on a Databricks workspace's /serving-endpoints path returns 404 with Unity Gateway enforcement off. Why we route by name, not by URL.

Published: 2026-10-27
Author: Raja Vemuri
Tags: Databricks, Applied AI
Canonical: https://www.techfabric.com/blog/unity-catalog-model-names-return-404-on-serving-endpoints

---

If your product stores a Databricks workspace URL and calls the models it finds there, a `system.ai.claude-haiku-4-5` request to `<workspace-host>/serving-endpoints` returns HTTP 404 `ENDPOINT_NOT_FOUND`, and so does an embedding request to `system.ai.gte-large-en`, while `databricks-claude-haiku-4-5` on the same path returns 200. At TechFabric our engineers build Fabric, our AI work platform, where each organization connects its own model providers, and on October 24 our Databricks connection listed the catalog models in the picker and failed every chat and embedding call to them. The workspace did not enforce Unity Gateway. The 404 came anyway.

For a product that lets its users connect their own Databricks workspace, the picker can look complete while every call to a catalog model fails, with nothing changed on the product's side. The fix we shipped the same morning is a routing rule, not a URL swap. A three-part Unity Catalog name goes to `<workspace-host>/ai-gateway/mlflow/v1`, every other name stays on `/serving-endpoints`, and a connection that stored an explicit path keeps it. In our workspace the custom serving endpoint and the endpoint listing our model picker reads are on the old path, so a blanket switch to the gateway would have broken those.

If your teams call Databricks-hosted models through a stored URL, the first thing to check is which naming scheme your model list uses and which path each name is sent to.

## Three requests that show the split

Databricks documents the move to Unity Gateway as two edits in the client: [change the base URL from `/serving-endpoints` to `/ai-gateway/mlflow/v1` and change the model from the endpoint name to the fully qualified model service name](https://learn.microsoft.com/azure/databricks/ai-gateway/migrate-to-unity-gateway). The provided models are [registered in Unity Catalog under `system.ai`](https://learn.microsoft.com/azure/databricks/ai-gateway/query-endpoints-beta), and the same page shows the OpenAI client pointed at the gateway base URL with `model="system.ai.claude-sonnet-4-5"`.

What the pages leave out is what happens when a client sends the new name down the old path. We sent the same eight-token request three ways from one Azure workspace:

```bash
BODY='{"model":"MODEL","messages":[{"role":"user","content":"Reply with OK"}],"max_tokens":8}'

curl -s -w 'HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "${BODY/MODEL/system.ai.claude-haiku-4-5}" "$HOST/serving-endpoints/chat/completions"
curl -s -w 'HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "${BODY/MODEL/system.ai.claude-haiku-4-5}" "$HOST/ai-gateway/mlflow/v1/chat/completions"
curl -s -w 'HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "${BODY/MODEL/databricks-claude-haiku-4-5}" "$HOST/serving-endpoints/chat/completions"
```

| Path | Model name | Result |
| --- | --- | --- |
| `/serving-endpoints` | `system.ai.claude-haiku-4-5` | 404 `ENDPOINT_NOT_FOUND` |
| `/ai-gateway/mlflow/v1` | `system.ai.claude-haiku-4-5` | 200, content `OK` |
| `/serving-endpoints` | `databricks-claude-haiku-4-5` | 200, content `OK` |

This is the body of the 404:

```json
{"error_code":"ENDPOINT_NOT_FOUND","message":"The given endpoint does not exist, please retry after checking the specified model and version deployment exists."}
```

Embeddings behave the same way. `system.ai.gte-large-en` returned the same 404 on `/serving-endpoints` and a 200 with 1024 dimensions on `/ai-gateway/mlflow/v1/embeddings`. Streaming on the gateway path returned ordinary `chat.completion.chunk` events ending in `finish_reason: stop`.

The third row is also how we know the workspace had enforcement off. The migration page says that turning on the **Enforce Unity Gateway** setting disables the `databricks-` pay-per-token endpoints with `PERMISSION_DENIED`, and that legacy configurations stay unchanged until you turn it on. Our legacy name answered, so the setting was off, and the catalog names still had nowhere to go on the old path. The migration docs describe a switch you make. The workspace draws the line whether or not you have made it.

## Why one base URL per connection stopped working

Fabric stores one URL per Databricks connection, the same route our [structured-output post](https://www.techfabric.com/blog/structured-output-from-claude-on-databricks-model-serving) describes. An organization can paste the bare workspace host, and the code appended `/serving-endpoints` to it for every chat and embedding call. The issue that started this work proposed defaulting every bare host to the gateway path instead.

We rejected that for a reason the test made concrete. The workspace we tested on has a custom-named serving endpoint of its own, addressed under `/serving-endpoints`. The migration page's advice for provisioned throughput is to keep the existing serving endpoint and create a model service that references it, which means both names exist at once. The model list our picker shows also comes from `GET /api/2.0/serving-endpoints`, an API with no counterpart on the gateway path. A bare host switched wholesale to the gateway would have listed those endpoints and then failed to call them, the same shape of failure we were fixing.

## The rule that picks the path

So the model name decides. The naming schemes don't overlap. A Unity Catalog name has three dot-separated parts, an endpoint name has none, so a three-part name goes to `/ai-gateway/mlflow/v1` and any other name to `/serving-endpoints`.

```ts
const THREE_PART = /^[^./]+\.[^./]+\.[^./]+$/;

export function toDatabricksInferenceBaseUrl(stored: string, modelId: string): string {
  const url = new URL(stored);
  const path = url.pathname.replace(/\/+$/, '');
  if (path === '' || path === '/serving-endpoints') {
    return THREE_PART.test(modelId)
      ? `${url.origin}/ai-gateway/mlflow/v1`
      : `${url.origin}/serving-endpoints`;
  }
  return `${url.origin}${path}`;
}
```

Three things in that function were decisions. A stored path other than the root or `/serving-endpoints` is returned as it was typed, so an organization that points a connection at a proxy or a specific route is left alone. The test is any three-part name, not a `system.ai` prefix, because a model service you create in your own catalog follows the same `catalog.schema.name` form. And model discovery keeps calling the serving-endpoints API with no model name at all, because that is where the endpoint list lives.

```mermaid
%% caption: The model name picks the path. Three-part catalog names go to the gateway, endpoint names stay on /serving-endpoints, a stored explicit path wins, and discovery keeps its own API.
flowchart TD
  A["Chat or embedding request with a model name"] --> B{"Stored path other than the root?"}
  B -->|yes| C["Use the stored path as typed"]
  B -->|no| D{"Name has three dot-separated parts?"}
  D -->|yes| E["/ai-gateway/mlflow/v1"]
  D -->|no| F["/serving-endpoints"]
  G["Model discovery"] --> H["Endpoint list from the serving-endpoints API"]
  class E accent
```

The rule now sits in every place Fabric builds a Databricks inference URL. That is the model factory's chat and embedding paths, the agent runtime's copy of it, the chat route, the connection tester, and the embeddings tool our MCP server exposes. Fifteen files changed and two were added, and 298 focused tests across five packages passed before the change merged to staging the same morning.

## What this does to a migration plan

The practical consequence is that moving to Unity Gateway is a per-name decision, not a per-connection one. A workspace in the middle of migrating has `databricks-` endpoints, `system.ai` services, and probably a custom endpoint or two, and a client that keys the path on the model name serves all three without a flag day. Once the workspace enforces Unity Gateway, the legacy names start returning `PERMISSION_DENIED` and the fix is to remove them from the model list, a change to data with no code involved.

Two limits on what we tested. We ran the probe on one Azure workspace with enforcement off. The issue we started from asked for the enforcement-on case, a `system.ai` name on `/serving-endpoints` with the setting turned on, and we didn't run it; with enforcement on the gateway path is the only one the docs say will answer for provided models, so the routing rule holds either way, but the 404 itself is observed only without enforcement. And review raised one permission edge we left open: a service principal granted query rights on a serving endpoint, and nothing on the model service, would now reach the gateway path for a three-part name and may get `PERMISSION_DENIED` there. Those calls were returning 404 before, so nothing that worked stopped working, but the error changed shape.

One more thing to carry into your own reading of the docs. The [embeddings page](https://learn.microsoft.com/azure/databricks/machine-learning/model-serving/query-embedding-models) shows the Python SDK calling `w.serving_endpoints.query(name="system.ai.gte-large-en", ...)`, the legacy method with the new name, under a note that says to swap in an endpoint name if you use serving endpoints. We didn't run that SDK call; our REST request with the same name on `/serving-endpoints` was the one that returned 404. The two naming schemes appear side by side across the docs, and the client is where they have to be kept apart.

If you store Databricks URLs for your users, send one `system.ai` request to each path from your own client today. The result tells you whether your connection design can hold both names, before a new workspace or a newly listed catalog model tells your users.

## Frequently asked questions

### Does the 404 depend on Enforce Unity Gateway?

Not in our test. On a workspace where `databricks-claude-haiku-4-5` still answered on `/serving-endpoints`, which the migration docs say is the sign enforcement is off, `system.ai.claude-haiku-4-5` on the same path returned 404 `ENDPOINT_NOT_FOUND` for chat and for embeddings. With enforcement on we didn't test.

### Why not point every bare workspace host at /ai-gateway/mlflow/v1?

In our workspace the custom-named serving endpoint is addressed under `/serving-endpoints`, and the endpoint listing that model discovery reads is `GET /api/2.0/serving-endpoints`. The migration page keeps existing provisioned throughput endpoints in place and has a model service reference them. Switching the whole host to the gateway would have listed our endpoint and then failed to call it.

### What is the routing rule?

For a stored URL with no path or exactly `/serving-endpoints`, a model id with three dot-separated parts goes to `<origin>/ai-gateway/mlflow/v1` and any other id goes to `<origin>/serving-endpoints`. A stored URL with any other path is used as typed. Model discovery keeps calling the serving-endpoints API without a model name.

### Is Unity Gateway generally available?

Databricks [announced general availability on 4 August 2026](https://docs.databricks.com/aws/en/release-notes/unity-gateway), with service policies and agent services still in Beta, and the management API went GA on 16 September 2026. A cached copy of the overview page still carried a Beta label when we checked on 27 October, so the release notes are the page to trust. The routing rule keys on the name, so the status changes nothing in the client.
