<!--
  GENERATED FILE — do not edit.

  Change tools/generate-guides.ts or its typed sources, run `bun run guides`,
  and commit the generator and rendered files together.
-->

# Service providers

Send through whichever provider is up. Priority and failover, reordered live.

## Install

Typed external providers with priority, health and classified fallback. Anyone calling somebody else's API who needs the failure classified rather than guessed. Provider capabilities stay independent from application-owned services, which attach named methods by priority with health tracking.

```text
bun add @forgezero/providers
```

<a id="provider-billing"></a>
## Bind a reviewed payment-provider API version

ForgeZero receives only hosted setup URLs and opaque provider references. The concrete HTTP driver and its Vault-backed credentials remain at the host boundary.

```text
import {
  paymentProviderAdapter,
  type PaymentProviderDriver,
} from '@forgezero/providers/billing';

declare const stripeDriver: PaymentProviderDriver;
const stripe = paymentProviderAdapter({
  key: 'stripe:v1',
  providerApiVersion: '2026-08',
  driver: stripeDriver
});

const setup = await stripe.createHostedSetup({
  tenantKey: 'tenant_123',
  returnUrl: 'https://app.example.com/billing/return',
  cancelUrl: 'https://app.example.com/billing',
  idempotencyKey: crypto.randomUUID()
});
location.assign(setup.hostedUrl);
```

<a id="provider-email"></a>
## Email with ordered provider fallback

The service owns send/sendBatch. Ordered entries select exact provider method versions; SMTP is never presented as batch-capable.

```text
import {
  createService,
  type EmailMessage,
} from '@forgezero/providers';
import {
  emailService,
  jetemail,
  smtp,
  type SmtpTransport,
} from '@forgezero/providers/email';
import {
  createVault,
} from '@forgezero/vault';
import {
  vaultCredentials,
} from '@forgezero/vault/providers';

// Adapt Nodemailer, Bun, or an HTTP SMTP relay once at the host boundary.
declare const transport: SmtpTransport;

const vault = createVault({ deploymentKey: 'my-deployment', environmentKey: 'production' });
const email = createService(emailService, {
  send: [
    { provider: jetemail, method: 'send', credentials: vaultCredentials(vault, 'jetemail'), config: { from: 'ops@example.com' }, priority: 1 },
    { provider: smtp, method: 'send', credentials: vaultCredentials(vault, 'smtp'), config: { host: 'smtp.example.com', port: 587, from: 'ops@example.com', transport }, priority: 2 }
  ],
  sendBatch: [
    { provider: jetemail, method: 'sendBatch', credentials: vaultCredentials(vault, 'jetemail'), config: { from: 'ops@example.com' }, priority: 1 }
  ]
});

const message: EmailMessage = { to: 'user@example.com', subject: 'Hello', text: 'Sent by ForgeZero' };
const outcome = await email.call('send', message);
if (!outcome.ok) throw outcome.error;
console.log(outcome.result, outcome.attempts);
```

<a id="provider-custom"></a>
## Extend with a custom provider and service

Public consumers define provider capabilities and application services independently, then attach them with the same ordered-array API.

```text
import {
  createService,
  defineService,
  defineSingleMethodProvider,
  serviceMethod,
} from '@forgezero/providers';
import {
  createVault,
} from '@forgezero/vault';
import {
  vaultCredentials,
} from '@forgezero/vault/providers';

type Notice = { text: string };
type Receipt = { id: string };
const vault = createVault({ deploymentKey: 'my-deployment', environmentKey: 'production' });

const postmark = defineSingleMethodProvider({
  id: 'postmark', label: 'Postmark', method: 'deliver',
  async invoke(context, notice: Notice): Promise<Receipt> {
    const response = await fetch('https://api.postmarkapp.com/email', {
      method: 'POST', signal: context.signal,
      headers: { 'x-postmark-server-token': await context.secret('apiKey') },
      body: JSON.stringify(notice)
    });
    if (!response.ok) throw new Error(`Postmark returned ${response.status}`);
    return { id: response.headers.get('x-message-id') ?? crypto.randomUUID() };
  },
  classify: () => 'retryable'
});
const notifications = defineService({
  key: 'notifications', methods: { deliver: serviceMethod<Notice, Receipt>() }
});
const service = createService(notifications, {
  deliver: [{ provider: postmark, method: 'deliver', credentials: vaultCredentials(vault, 'postmark'), priority: 1 }]
});
```

<a id="provider-vault-authority"></a>
## Purpose-bound Vault provider authority

Managed services resolve provider credentials from one scoped Vault reference. Bootstrap or host-recovery credentials are separate lifecycle inputs and are never an implicit application fallback.

```text
import {
  createService,
} from '@forgezero/providers';
import {
  createVault,
} from '@forgezero/vault';
import {
  vaultCredentials,
} from '@forgezero/vault/providers';

const vault = createVault({ deploymentKey: 'my-deployment', environmentKey: 'production' });
const credentials = vaultCredentials(vault, 'jetemail');
// Bind this source only to the reviewed provider method that declares it.
// A missing or locked managed Vault fails closed instead of reading systemd.
```

## Choose an entry point

Import the capability your application needs. Each package ships TypeScript declarations. Backend-only entries must stay out of browser bundles.

| Import or command | Use it for | Runtime |
|---|---|---|
| @forgezero/providers | The registry: priority, health, and a terminal-versus-retryable verdict per failure. | Portable |
| @forgezero/providers/email | JetEmail (`send`, `sendBatch`), SMTP (`send` only), and the typed email service contract; SMTP transport is injected. | Portable |
| @forgezero/providers/database | ArangoDB in the registry for credential rotation and health — with failover off by default. | Portable |
| @forgezero/providers/http | Outbound HTTP with a per-host weight budget reserved before the call and settled from the response. | Portable |
| @forgezero/providers/pool | Which outbound address a request leaves by, sticky per key, over the same budget as everything else. | Portable |
| @forgezero/providers/storage | S3-compatible object storage, SigV4 signed with Web Crypto and no vendor SDK. | Portable |
| @forgezero/providers/translation | Google AI Studio translation with strict batch alignment and classified quota failures. | Portable |
| @forgezero/providers/realtime | Cloudflare KV node-directory and Durable Object fan-out clients for project-defined realtime services. | Portable |
| @forgezero/providers/git | Versioned Git Connect contracts for accounts, repositories, branches, webhooks and short-lived clone credentials; GitHub, GitLab and private forges attach without changing the service. | Portable |
| @forgezero/providers/git-schema | Lightweight executable GitHub App configuration and credential schemas shared with metadata renderers without importing provider execution. | Portable |
| @forgezero/providers/billing | Versioned hosted-payment contracts for Stripe, PayPal and Razorpay; only opaque provider references cross the boundary. | Portable |

## Related packages

Add only the packages needed by your application.

| Package | Purpose | Guide |
|---|---|---|
| @forgezero/vault | Exact Deployment binding reads through a managed Agent or signed API credential. | [Read guide](/docs/vault-package) |
| @forgezero/access | Typed route, principal, factor, RBAC and request-pipeline contracts. | [Read guide](/docs/access) |
| @forgezero/runtime | Portable runtime primitives for queries, jobs, events, schemas and finance. | [Read guide](/docs/runtime) |
| @forgezero/agent | Operator CLI and managed-node agent for bootstrap, deploy and lifecycle. | [Read guide](/docs/agent) |

## Built-in provider capabilities

A provider owns versioned capabilities; it does not own a service. A service method attaches an exact provider, method and version in an ordered array. Current, legacy and deprecated branches can coexist; retired branches never run.

```text
github
  - beginUserAuthorization
  - completeUserAuthorization
  - readVerifiedIdentity
  - refreshUserGrant
  - revokeUserGrant
  - beginInstallation
  - completeInstallation
  - listInstallations
  - listRepositories
  - listBranches
  - mintCloneCredential
  - verifyWebhook
  - disconnectInstallation
jetemail
  - send
  - sendBatch
smtp
  - send
arangodb
  - query
http
  - request
s3
  - request
google-ai-studio
  - translate
```

## GitHub App (github)

Import: @forgezero/providers/git. Each method below is a provider capability that a service attaches by exact version. The OAuth Server projection verifies an email and revokes the temporary user grant before ForgeZero creates its own session. The Git Connect projection mints repository-scoped installation credentials on demand and never persists them. GitLab or a private forge can implement the same Git Connect service without changing pipelines or Agents.

```text
importPath: '@forgezero/providers/git'
providerId: 'github'
methods:
  - beginUserAuthorization
  - completeUserAuthorization
  - readVerifiedIdentity
  - refreshUserGrant
  - revokeUserGrant
  - beginInstallation
  - completeInstallation
  - listInstallations
  - listRepositories
  - listBranches
  - mintCloneCredential
  - verifyWebhook
  - disconnectInstallation
credentials:
  - clientSecret
  - privateKey
  - webhookSecret
config:
  - clientId
  - appId
  - slug
```

## JetEmail (jetemail)

Import: @forgezero/providers/email. Each method below is a provider capability that a service attaches by exact version. A 401 is RETRYABLE at the loop level — that key is bad, the next provider's may not be. Treating any 4xx as fatal is what loses the fallback.

```text
importPath: '@forgezero/providers/email'
providerId: 'jetemail'
methods:
  - send
  - sendBatch
credentials:
  - apiKey
config:
  - eu
  - from
```

## SMTP (smtp)

Import: @forgezero/providers/email. Each method below is a provider capability that a service attaches by exact version. Any number of named relays, each with its own priority slot. Also the bootstrap path: the vault opens after a ceremony, and the ceremony needs mail.

```text
importPath: '@forgezero/providers/email'
providerId: 'smtp'
methods:
  - send
credentials:
  - user
  - password
config:
  - host
  - port
  - secure
  - from
  - transport
```

## ArangoDB (arangodb)

Import: @forgezero/providers/database. Each method below is a provider capability that a service attaches by exact version. Failover is OFF by default. Writing to a different database because the first was slow is data loss with extra steps; this is here for credential rotation and health.

```text
importPath: '@forgezero/providers/database'
providerId: 'arangodb'
methods:
  - query
credentials:
  - password
config:
  - url
  - urls
  - readPreferredUrls
  - readPreferredFallback
  - clusterId
  - database
  - username
```

## HTTP (http)

Import: @forgezero/providers/http. Each method below is a provider capability that a service attaches by exact version. 418 and 429 back off rather than falling through. 418 is a venue saying "you ignored a 429 and are now banned", and hammering makes the ban longer.

```text
importPath: '@forgezero/providers/http'
providerId: 'http'
methods:
  - request
credentials:
  - apiKey
  - apiSecret
config:
  - baseUrl
  - limit
  - windowMs
  - headroom
```

## S3-compatible storage (s3)

Import: @forgezero/providers/storage. Each method below is a provider capability that a service attaches by exact version. A 403 is retryable because on S3 it usually means this key lacks a permission, not that the key is invalid. Works against AWS, R2, MinIO, Garage and Backblaze — path-style addressing by default, because that is what everything except AWS expects.

```text
importPath: '@forgezero/providers/storage'
providerId: 's3'
methods:
  - request
credentials:
  - accessKeyId
  - secretAccessKey
  - sessionToken
config:
  - endpoint
  - region
  - bucket
  - addressing
```

## Google AI Studio (google-ai-studio)

Import: @forgezero/providers/translation. Each method below is a provider capability that a service attaches by exact version. Machine translation for a tenant that wants it — ForgeZero translates nothing at runtime. Chosen as the first adapter because the free tier needs no billing account, so the feature can be tried without a procurement conversation. A 429 is a BACKOFF rather than a failure: on the free tier it is expected traffic, and treating it as terminal abandons a catalogue most of the way through. A short answer is terminal, because one translation missing from a batch shifts every later string onto the wrong source and nothing about the result looks broken afterwards.

```text
importPath: '@forgezero/providers/translation'
providerId: 'google-ai-studio'
methods:
  - translate
credentials:
  - apiKey
config:
  - model
```

Full rendered documentation: https://www.forgezero.net/docs/providers
