# ModelProviderCredentialContract

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/api/tracon.testing.contracts.providers.modelprovidercredentialcontract/
> Summary: ModelProviderCredentialContract: Behavior tests for a provider that honors a per-tenant credential (BYOK).

<div class="api-provenance">Namespace Tracon.Testing.Contracts.Providers · Assembly <code>Tracon.Testing.Contracts.Xunit.dll</code></div>

Behavior tests for a provider that honors a per-tenant credential (BYOK).

```csharp
public abstract class ModelProviderCredentialContract : ModelProviderContract, IAsyncLifetime, IAsyncDisposable
```

<dl class="api-relations"><dt>Inheritance</dt><dd><a href="https://learn.microsoft.com/dotnet/api/system.object">object</a> ← <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/">ModelProviderContract</a> ← <a href="/api/tracon.testing.contracts.providers.modelprovidercredentialcontract/">ModelProviderCredentialContract</a></dd><dt>Implements</dt><dd>IAsyncLifetime, <a href="https://learn.microsoft.com/dotnet/api/system.iasyncdisposable">IAsyncDisposable</a></dd><dt>Inherited Members</dt><dd><a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_CreateProviderAsync">ModelProviderContract.CreateProviderAsync()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_InitializeAsync">ModelProviderContract.InitializeAsync()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_DisposeAsync">ModelProviderContract.DisposeAsync()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_OnDisposeAsync">ModelProviderContract.OnDisposeAsync()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Binding_System_String_System_String_">ModelProviderContract.Binding(string?, string?)</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Provider_name_is_not_empty">ModelProviderContract.Provider_name_is_not_empty()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Catalog_model_names_are_unique_and_not_empty">ModelProviderContract.Catalog_model_names_are_unique_and_not_empty()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Create_chat_client_returns_a_client_for_the_setup_time_credential">ModelProviderContract.Create_chat_client_returns_a_client_for_the_setup_time_credential()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Create_chat_client_returns_a_raw_client_that_builds_no_tool_call_loop">ModelProviderContract.Create_chat_client_returns_a_raw_client_that_builds_no_tool_call_loop()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Create_chat_client_accepts_a_binding_whose_provider_name_differs_in_case">ModelProviderContract.Create_chat_client_accepts_a_binding_whose_provider_name_differs_in_case()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_A_model_the_catalog_does_not_list_is_not_rejected">ModelProviderContract.A_model_the_catalog_does_not_list_is_not_rejected()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Concurrent_create_chat_client_calls_all_return_a_client">ModelProviderContract.Concurrent_create_chat_client_calls_all_return_a_client()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_A_client_that_is_never_disposed_does_not_stop_the_provider">ModelProviderContract.A_client_that_is_never_disposed_does_not_stop_the_provider()</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_Provider">ModelProviderContract.Provider</a>, <a href="/api/tracon.testing.contracts.providers.modelprovidercontract/#Tracon_Testing_Contracts_Providers_ModelProviderContract_UnknownModelName">ModelProviderContract.UnknownModelName</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.gettype">object.GetType()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone">object.MemberwiseClone()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.tostring">object.ToString()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a></dd></dl>

## Remarks

Honoring [ModelProviderCredential](/api/tracon.modelprovidercredential/) is **optional**.
A provider that offers it implements
[ITenantCredentialModelProvider](/api/tracon.itenantcredentialmodelprovider/). Deriving this class verifies
that explicit capability. A provider that does not offer BYOK does not
derive this class and records that as a `ContractCoverage` exemption.

Like [ModelProviderContract](/api/tracon.testing.contracts.providers.modelprovidercontract/), nothing here performs a model
call: the observable consequences of a credential are which client object
gets built, not what the network returns.

## Constructors

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract__ctor"></a> ModelProviderCredentialContract\(\)

```csharp
protected ModelProviderCredentialContract()
```

## Properties

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_Credential"></a> Credential

A credential this provider can build a client from. It is never used
against a real endpoint, so any syntactically valid key works.

```csharp
protected abstract ModelProviderCredential Credential { get; }
```

#### Property Value

 [ModelProviderCredential](/api/tracon.modelprovidercredential/)

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_OtherCredential"></a> OtherCredential

A second credential that differs from [ModelProviderCredentialContract.Credential](/api/tracon.testing.contracts.providers.modelprovidercredentialcontract/#Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_Credential), used to
prove that two tenants do not collapse onto one client.

```csharp
protected virtual ModelProviderCredential OtherCredential { get; }
```

#### Property Value

 [ModelProviderCredential](/api/tracon.modelprovidercredential/)

## Methods

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_A_tenant_credential_is_applied_to_the_returned_client"></a> A\_tenant\_credential\_is\_applied\_to\_the\_returned\_client\(\)

```csharp
[Fact]
public void A_tenant_credential_is_applied_to_the_returned_client()
```

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_A_tenant_credential_produces_a_different_client_than_the_setup_time_one"></a> A\_tenant\_credential\_produces\_a\_different\_client\_than\_the\_setup\_time\_one\(\)

```csharp
[Fact]
public void A_tenant_credential_produces_a_different_client_than_the_setup_time_one()
```

#### Remarks

The key never falls back to the setup-time key: a tenant that supplied
a credential is billed on it, or the call fails. Handing back the
setup-time client would silently bill the wrong tenant, and distinct
instances are the observable consequence of not doing that.

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_An_endpoint_override_on_a_tenant_credential_is_accepted"></a> An\_endpoint\_override\_on\_a\_tenant\_credential\_is\_accepted\(\)

```csharp
[Fact]
public void An_endpoint_override_on_a_tenant_credential_is_accepted()
```

#### Remarks

An endpoint override is accepted; which address the resulting client
uses is not observable without a network call, so this asserts
acceptance only. Note that the endpoint — unlike the key — may fall
back to the setup-time address when a tenant overrides only the key.

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_AssertCredentialIsApplied_Microsoft_Extensions_AI_IChatClient_Tracon_ModelProviderCredential_"></a> AssertCredentialIsApplied\(IChatClient, ModelProviderCredential\)

Verifies that a returned client will use `credential`
for its provider request.

```csharp
protected abstract void AssertCredentialIsApplied(IChatClient client, ModelProviderCredential credential)
```

#### Parameters

`client` IChatClient

The client returned for the tenant credential.

`credential` [ModelProviderCredential](/api/tracon.modelprovidercredential/)

The tenant credential supplied to the provider.

#### Remarks

This is provider-specific by design. The shared `AI.IChatClient`
surface cannot expose an API key safely, so a provider must prove the
credential at its own observable boundary, such as a recording transport
or a vendor request factory. Checking only client identity is insufficient:
a new wrapper could be created while it still sends the setup-time key.

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_Concurrent_resolution_of_one_credential_stays_stable"></a> Concurrent\_resolution\_of\_one\_credential\_stays\_stable\(\)

```csharp
[Fact]
public Task Concurrent_resolution_of_one_credential_stays_stable()
```

#### Returns

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

#### Remarks

A per-credential client cache is normally a
`Concurrent.ConcurrentDictionary`,
whose `GetOrAdd` may run its factory more than once for the same
key when threads race, discarding the extra results. Building a client
twice must therefore be harmless, and resolution must stay stable under
concurrency.

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_Create_chat_client_with_a_tenant_credential_returns_a_client"></a> Create\_chat\_client\_with\_a\_tenant\_credential\_returns\_a\_client\(\)

```csharp
[Fact]
public void Create_chat_client_with_a_tenant_credential_returns_a_client()
```

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_Provider_declares_the_tenant_credential_capability"></a> Provider\_declares\_the\_tenant\_credential\_capability\(\)

```csharp
[Fact]
public void Provider_declares_the_tenant_credential_capability()
```

### <a id="Tracon_Testing_Contracts_Providers_ModelProviderCredentialContract_Two_different_credentials_do_not_share_one_client"></a> Two\_different\_credentials\_do\_not\_share\_one\_client\(\)

```csharp
[Fact]
public void Two_different_credentials_do_not_share_one_client()
```

#### Remarks

The credential, not the tenant, is the identity boundary: two distinct
credentials must not resolve to one client, or one tenant's key would
serve another's traffic.
