Technical enablement · 07

Integrations & provider connections

How {{product.name}} connects to the systems around it: the AI providers whose spend it reads, the LiteLLM gateway that reports usage, and Microsoft Entra ID for sign-in. It also gives the recipe for adding a provider.

⚠

Status is stated per integration. Shipped means an adapter is registered in lib/tdal/adapters/registry.ts. Anything else is labelled configurable-only or roadmap. Third-party licensing and data-sharing terms are for Legal and Procurement, not engineering.

The model: ports and adapters

Product code talks to one product-owned interface, the ProviderAdapter contract in lib/tdal/adapters/base.ts. Each provider implements it in its own file and writes into one unified schema that uses FOCUS 1.5 field names (ADR 006). Adding a provider is therefore adding one adapter file and registering it; the schema does not change. The registry is also the only list the connections page consults, so a provider cannot be syncable in one place and unknown in another.

Sync and reconcile job routes, TDAL ProviderAdapter verify() · fetchPeriod() registry.ts lists the shipped adapters OpenAI Anthropic Azure OpenAI AWS Bedrock · Vertex AI SaaS vendors configurable, no sync billing & usage APIs
Figure 1 — Provider adapter pattern. Solid boxes have a registered adapter; the dashed box does not.

Integration inventory

IntegrationPurposeStatusSwap effort
Microsoft Entra IDStaff sign-in (Auth.js v5)ShippedConfiguration for another OIDC tenant; a different identity provider needs changes in lib/auth/config.ts
LiteLLM gatewayUsage telemetry, session keys, enforcementShippedHigh: ADR 001 makes it the substrate
Azure OpenAI and Foundry (Azure Cost Management, Monitor)Billing and token metricsShippedn/a
AWS Bedrock (Cost Explorer, CloudWatch)Billing and token metricsShippedn/a
Google Vertex AI (BigQuery, Monitoring, price catalogue)Usage; cost actual with a billing export, otherwise estimatedShipped (estimated cost without an export)n/a
OpenAI, Anthropic (admin APIs)Usage and cost reportsShippedn/a
SaaS-embedded AI (for example GitHub Copilot)Seat and rate recordsConfigurable only: connectors can be registered at /api/saas/connectors but there is no sync adapterMedium: write an adapter
Client outcome systemsVerified outcome eventsShipped as a webhook at /api/ingest/outcomes; scheduled file drop is roadmapClient builds the push
Per-tenant client identity providers, email one-time codesClient user sign-inRoadmap: described in the build brief, not in the codeNo plan or date. Proposed: Entra ID guest invitation first; other identity providers only on client demand.

Provider connections

A connection is a named record per provider account, with a scope and a credential. It is created disconnected; verification promotes it to active, and only active (and errored) connections are synced. verify() runs on save and edit, reports the granted scope, and the Test connection button re-runs it.

ProviderCredential and scopeClient-side prerequisite
Azure OpenAI / FoundryRead access to Cost Management and Monitoring on the subscription or resource group. One connection can cover a resource group or a whole subscription.None. Provisioned-throughput deployments report capacity rather than tokens.
AWS BedrockA read-only IAM principal allowed ce:GetCostAndUsage and cloudwatch:GetMetricData. Both are account-wide and cannot read workload content.Model invocation logging must be enabled; without it there are no token counts.
Google Vertex AIA service account (JSON key or token). BigQuery read on the billing export dataset and Monitoring viewer.A detailed usage cost export to BigQuery, if actual cost is wanted. There is no retroactive backfill.
OpenAIAn organisation admin key with usage and cost read. A project key will not work.None.
AnthropicAn admin API key with usage and cost report read.None.

The platform requests read-only access and never model-invocation scope. The in-app "What do I need?" guide is generated from lib/connections/credential-fields.ts; change requirements there, not in the UI.

How a credential is supplied

  • Entered in the app. The value is encrypted at rest with AES-256-GCM using CREDENTIAL_ENCRYPTION_KEY; the connection stores only that a value exists.
  • By reference. Accepted forms are env:<VAR>, keyvault:<secret> and, on Azure, managed-identity:system, managed-identity:<id> or azure-default. A field decides which it is by its value; a malformed reference is rejected rather than stored as a secret.
  • Never in the repository, and never in these pages.

Azure credentials

Use the azure-default reference. It resolves by chain: a managed identity where the platform provides one, else a service principal from AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET, else AZURE_ARM_TOKEN. The same connection record works in Azure, in another cloud and on a laptop; moving host means changing environment variables, not connections. Pinning to a specific managed identity works only inside Azure. AZURE_MI_CLIENT_ID selects a user-assigned identity and is deliberately separate from AZURE_CLIENT_ID, because they are different directory objects.

AWS credentials

A deployed connection needs a long-lived read-only IAM user (key ids beginning AKIA), not temporary SSO credentials, which expire within hours and would break a scheduled sync. The policy needs only the two actions above. AWS charges per Cost Explorer request, so sync frequency is a cost decision (see operations: cost). make aws-creds refreshes temporary credentials in a local .env for testing only. A least-privilege policy document is kept in docs/aws/.

Google credentials

A service account key is recognised however it was pasted (raw JSON or base64), and a token is distinguished from a key by its value. Without a billing export, Vertex cost is estimated from the public price catalogue and marked as such.

LiteLLM gateway

LiteLLM is the substrate that routes model calls; the product adds Gate A authorisation and monitoring on top and does not fork it (ADR 001).

AspectDetail
Telemetry inLiteLLM fires an asynchronous callback after each call to POST /api/ingest/litellm, signed with the shared secret LITELLM_CALLBACK_SECRET. Requests are checked for a valid signature inside a short window and rejected on replay (Redis nonce).
AttributionEach key should carry a project_id and, ideally, a workload id in its metadata. Events from keys that name no workload are stored and can be adopted in Attribute → Workloads.
Enforcement outGate A returns a scoped gateway key with a hard budget (gateway-enforced mode). A pause sets the budget to current consumption; a stop revokes the key.
What is storedToken counts, model identifiers, estimated cost, session and workload identifiers. No prompt or completion content (ADR 005).
Known gapAgents that call provider SDKs directly bypass the gateway and sit outside Gate A coverage. This is surfaced as a named gap.
DeploymentA separate container, not part of this repository. It is being built as a separate project, so its location and owner are set there. Keys are scoped per application: the build brief has the app use the gateway master key only to mint and revoke scoped keys, and unattributed keys are named in Attribute → Workloads. Proposed: the gateway project team owns the gateway and issues keys, with ai-yield holding only the callback secret and a minting credential.
ⓘ

An architectural change to LiteLLM's callback API requires an adapter update. Record the traffic level at which LiteLLM's operating model becomes limiting when it is observed (ADR 001 revisit trigger). No threshold is recorded because none has been observed. Proposed: note the sustained request rate for one tenant at which callback latency is first noticed, and record it in the RUNBOOK.

Microsoft Entra ID

  • Auth.js v5 with the Microsoft Entra ID provider; configuration in lib/auth/config.ts.
  • The OAuth callback path is /api/auth/callback/azure-ad on the app's public base URL; register that full URL as a redirect URI under Authentication → Web in the app registration.
  • Variables: AUTH_SECRET, AUTH_URL, AUTH_TRUST_HOST, ENTRA_CLIENT_ID, ENTRA_CLIENT_SECRET, ENTRA_TENANT_ID.
  • A signed-in user must be provisioned with a role in the app; otherwise they reach a no-access page.
  • Client users: a per-tenant identity provider or email one-time code is described in the build brief but is not built. Proposed approach: guest invitation to the product's Entra ID directory, with a role assigned in the app; to be confirmed.

Adding a provider

  1. Confirm the port

    Read lib/tdal/adapters/base.ts and a similar existing adapter, for example openai.ts or bedrock.ts.

  2. Map the capability gap

    List what the provider cannot do or does differently: whether it publishes actual cost, how it meters tokens, its rate limits and its grouping concept (for example account, resource group or project).

  3. Implement and register

    Write the adapter, use the shared retry profiles in retry.ts so waiting fits inside the request ceiling, and register it in registry.ts. Add the provider to the provider enum with a migration, and describe its credential requirements in credential-fields.ts.

  4. Test against recorded fixtures

    Add a contract test in tests/domain/ in the style of adapters-openai.test.ts. An adapter should not ship without one.

  5. Verify on a real account, then roll out

    Create a connection, press Test connection, check the granted scope, and watch a scheduled run. Adding a column to the unified schema does not break the lock; renaming one does.

API access

ⓘ

The product exposes route handlers for Gate A, outcome ingest, checkpoints, escalations, optimisation and exports (see the API routes entry). There is no published OpenAPI description and no MCP server. Proposed: if clients need programmatic access, publish an OpenAPI description for the outcome ingest route first, since it is the one clients call; no date is set. Machine paths authenticate with their own credential (bearer token, signature or x-cron-secret), not a session cookie.