# Build Credential Providers

> Define reusable lifecycle code for imported, minted, rotating, or interactively acquired Credentials.

## Before you begin {#before-you-begin}

Separate provider installation settings, bootstrap Credential slots, and per-Credential configuration. Determine whether the external service supports deterministic minting, single-use refresh, verification, source-side destruction, or guided acquisition. Declare only the operations the integration can implement safely.

## Steps {#steps}

1. Export `credentialProvider()` with human metadata, a non-secret installation schema, per-Credential schema, rotation defaults, and bounded egress. This complete example exchanges a provider bootstrap secret for short-lived consumer material:

   ```ts
   import { credentialProvider } from "@constal/sdk";

   export default credentialProvider({
     id: "example-token", version: "1.0.0",
     displayName: "Example service token",
     description: "Creates a short-lived token for one Example account.",
     credentialSlots: ["bootstrap-token"],
     configSchema: { type: "object", required: ["clientId"], additionalProperties: false,
       properties: { clientId: { type: "string", title: "Client ID" } } },
     credentialConfigSchema: { type: "object", required: ["accountId"], additionalProperties: false,
       properties: { accountId: { type: "string", title: "Account ID" } } },
     rotation: { mode: "auto", intervalMs: null, overlapMs: 60_000,
       maxAgeMs: null, refreshBeforeMs: 300_000 },
     egress: { rules: [{ operations: ["mint"], origin: "https://api.example.com",
       pathPrefix: "/oauth/tokens", methods: ["POST"] }] },
     mintRecovery: { kind: "outcome-unknown" },
     async mint(request, context) {
       const config = context.config as { clientId: string };
       const credential = request.configuration as { accountId: string };
       const bootstrap = await context.secret("bootstrap-token");
       const response = await context.egress({
         url: "https://api.example.com/oauth/tokens", method: "POST",
         headers: { authorization: `Bearer ${bootstrap.value}`, "content-type": "application/json" },
         bodyBase64: btoa(JSON.stringify({ client_id: config.clientId, account_id: credential.accountId })),
         maximumResponseBytes: 65_536, maximumRedirects: 0,
       });
       if (response.status !== 201) throw new Error("Example token exchange failed");
       const token = JSON.parse(atob(response.bodyBase64)) as { access_token?: string; expires_in?: number };
       if (!token.access_token || !token.expires_in) throw new Error("Example token response is invalid");
       return { material: token.access_token, expiresAt: Date.now() + token.expires_in * 1_000 };
     },
   });
   ```

2. Use `context.secret(slot)` only for declared bootstrap Credentials and `context.egress()` only for allowlisted requests. Consumer material and provider-private lifecycle state have separate encrypted lifecycles.
3. Add the provider manifest, exact SDK dependency, and root `schema.json`; deploy the package privately.
4. Install the catalog package into a namespace, configure bootstrap slots, then create Credentials from the installed provider. Add `interaction.start` and `interaction.advance` when the provider needs redirects, forms, device instructions, callbacks, approval, or polling.

## Verify {#verify}

Test schemas, every action/event transition, callback state, proof verification, replay rejection, expiry, poll limits, refresh ambiguity, verification, destruction, and egress denial. Confirm APIs return fingerprints and lifecycle metadata but never material or private state.

## Next steps {#next-steps}

Use the complete [Build a CredentialProvider](/docs/credentials/providers/build) tutorial, [Interaction lifecycle](/docs/credentials/providers/interaction), and [CredentialProvider SDK reference](/docs/credentials/reference/sdk).
