# Build Credential Providers

> Define reusable lifecycle code for imported, minted, rotating, or browser-authorized 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 browser authorization. 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 or authorize Credentials from the installed provider. Add `authorization.begin` and `authorization.complete` only when the provider needs an interactive browser flow.

## Verify {#verify}

Test schemas, begin/complete state, PKCE, callback replay rejection, expiry, 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, [Authorization lifecycle](/docs/credentials/providers/authorization), and [CredentialProvider SDK reference](/docs/credentials/reference/sdk).
