cancel
Showing results for 
Search instead for 
Did you mean: 
Data Engineering
Join discussions on data engineering best practices, architectures, and optimization strategies within the Databricks Community. Exchange insights and solutions with fellow data engineers.
cancel
Showing results for 
Search instead for 
Did you mean: 

Unity Catalog service credential get_token rejects api:// scope format — "not a valid URI"

Oumeima
New Contributor III

Calling get_token() on a Unity Catalog service credential fails for any scope using the api:// App ID URI format. Only https://-scheme resource scopes succeed. The same api:// scopes work correctly with a service principal.

Reproduction

credential = dbutils.credentials.getServiceCredentialsProvider(
    "managed-identity"
)

scopes = [
    "https://vault.azure.net/.default",
    "api://<domain>/.default",
    "api://<domain>",
    "api://<domain>/",
]

for scope in scopes:
    try:
        result = credential.get_token(scope)
        print(scope, "OK; expires:", result.expires_on)
    except Exception as error:
        print(scope, type(error).__name__, str(error))

Result

The first scope succeeds. Every api:// variant fails with:

Error while retrieving temporary credentials: '<scope>' is not a valid URI

Substituting the app registration's Application (client) ID GUID — both as api://<client-id>/.default and as a bare <client-id>/.default — fails identically.

This isolates the failure to scope-string validation in the temporary-credentials path, not to the credential, the managed identity, or its permissions — the same credential object returns a valid token for the https:// scope in the same loop.

Control test: the same scope succeeds via ClientSecretCredential

On the same compute, in the same notebook, the identical api:// scope returns a valid token when requested through a service principal with azure.identity instead of the service credential provider:

from azure.identity import ClientSecretCredential

client_id = dbutils.secrets.get(scope="keyvault", key="client_id")
client_secret = dbutils.secrets.get(scope="keyvault", key="client_secret")
tenant_id = dbutils.secrets.get(scope="keyvault", key="tenant_id")

credential = ClientSecretCredential(
    tenant_id=tenant_id,
    client_id=client_id,
    client_secret=client_secret
)

scopes = ["api://<domain>/.default"]

for scope in scopes:
    try:
        result = credential.get_token(scope)
        print(scope, "OK; expires:", result.expires_on)
    except Exception as error:
        print(scope, type(error).__name__, str(error))

Result

api://<domain>/.default returns a token successfully.

This confirms the scope string is valid and that the app registration is correctly configured. The rejection originates solely in the Databricks temporary-credentials layer.

Environments tested

  • Serverless compute
  • Classic compute, DBR 17.3 LTS

Identical behavior on both.

Business impact

Our security policy mandates managed identities. Unity Catalog service credentials are therefore our only supported path for machine-to-machine authentication from Databricks to internal APIs. This validation blocks that path entirely for any internal API using the default api:// identifier URI.

The ClientSecretCredential approach shown above works, but is not a viable workaround for us: it requires storing and rotating a client secret, which is precisely what the managed-identity mandate exists to avoid.

Questions

  1. Is this a known limitation, or a bug in scope validation for temporary service credentials?
  2. If intentional, where is the accepted resource-URI format documented? We found no mention of a scheme restriction in the service credentials documentation.
1 REPLY 1

ShamenParis
Contributor

Hi @Oumeima ,
Thank you for the detailed reproduction. As per my understanding of the issue you are facing, your isolation of the problem is entirely correct.

This behavior is caused by an undocumented strict URI validation bug within the Databricks dbutils.credentials layer. When you request a token, the scope parameter (typically ending in /.default) tells Entra ID exactly which target API you want to access and automatically requests all permissions previously granted to your managed identity for that specific resource.

However, before passing this scope string to Azure to generate the token, the Databricks temporary-credentials path validates it. It currently expects standard Azure resource endpoints starting with https:// and inadvertently rejects api:// schemes as invalid URIs. Because this is an internal validation oversight rather than an intentional platform constraint, it is not mentioned in the official Databricks documentation.

To maintain strict compliance with your managed identity mandate without using client secrets, the most reliable workaround is to modify the Application ID URI of your internal API in the Azure Portal. Entra ID fully supports HTTPS-based Application ID URIs. By changing your exposed API's identifier from the default api://<client-id