> ## Documentation Index
> Fetch the complete documentation index at: https://usefused.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Build an SDK

> Turn a set of approved operations into a typed package your product can import.

An SDK is a typed package containing only the services and operations one product needs. Authentication, retries, pagination, and provider quirks stay in the Engine. Your application code gets methods.

<Card title="Create or update from a goal" icon="wand-magic-sparkles" href="/docs/app/from-a-prompt">
  Use prompt to add capabilities to an existing local SDK or draft a sequential Unified Operation, then review the proposal before apply.
</Card>

## Create the SDK

```bash theme={null}
fused-cli init support-sdk --sdk \
  --service linear \
  --service slack
```

For each service, `fused-cli init` reuses an enabled workspace version or pins the latest public Registry version. It then opens this choice:

```text theme={null}
Select operations for linear v1

❯ All operations
  Choose operations…
```

Press Enter to take the complete surface. **Choose operations…** opens a searchable multi-select matching operation IDs, methods, paths, summaries, and tags. Press `/` to filter, type your search, press Space to toggle an operation, and press Enter to confirm. You do not need to know an endpoint name before you start.

If activation is needed, init shows one combined confirmation and applies the workspace change under its own receipt. It then builds and validates the SDK candidate in memory and plans the SDK. Missing credentials are readiness warnings, not publication failures: a terminal securely offers to fill them and retry once, while skipping the offer still keeps the valid plan. Only a successful plan creates `.fused/sdks/support-sdk.yaml`; init then applies the SDK, downloads the package, and finishes with first-call guidance. Workspace and SDK changes retain separate receipts.

<Note>
  If an older selected snapshot lacks its retained generation contract, generated SDK init prints each exact `service@version` as it refreshes that snapshot and retries the unchanged plan once. It does this deterministically with `--no-input` too. It never invents a pin, selects another version, or bypasses Registry retention. A failed refresh or retry creates no app config or app receipt, while any completed exact refresh remains explicit. See [Recover without guessing](/docs/advanced/control-the-workflow#recover-without-guessing) for the failure boundaries.
</Note>

If you press Enter for both services, the resulting config records that complete-surface intent without requiring you to type either service version or operation ID:

```yaml theme={null}
apiVersion: fused/v1
kind: sdk
name: support-sdk
version: "1.0.0"
language: typescript
generate: true
bucket: default
services:
  linear:
    version: "v1"
    select_all: true
  slack:
    version: "v1"
    select_all: true
```

Each service key is the canonical Registry slug selected by the CLI. `--service` accepts comma-separated selectors or repeated flags; pass an explicit `<service>@<version>` only when you need a version other than the default.

`operations` and `select_all: true` are alternatives. Exactly one is required per service, never both and never neither.

## init fills in the routing for you

Some provider hosts are templated — `https://api-{app_id}.sendbird.com` needs an `app_id` before it can be called. `init` spots those and writes the injection itself:

```yaml theme={null}
injections:
  - location: server_variable
    name: app_id
    value: ${bucket.env.SENDBIRD_APP_ID}
    mode: force
```

It reports how many it added. Re-running it never overwrites an injection you wrote yourself.

<Warning>
  That value is a reference. If init stops because the matching bucket value is missing, create it with `fused-cli value set`, then rerun init. The app config is not written until its plan succeeds.
</Warning>

## Find the downloaded package

SDK mode plans, applies, and downloads the generated package to `fused-sdks/support-sdk`. If a later transfer needs to be retried without replaying apply, run:

```bash theme={null}
fused-cli sdk download support-sdk@1.0.0
```

<Warning>
  The first successful initialization or apply prints the SDK execution token **once**. Capture it immediately — an idempotent apply will not show it again, and it is never written to the config, the receipt, or CLI state.
</Warning>

## Need more control?

The initializer composes the ordinary plan and apply functions; it does not bypass them. Use the [advanced controlled workflow](/docs/advanced/control-the-workflow) when you want independent review steps, structured CI output, offline validation, custom receipt paths, or separate retry boundaries.

## Skip the package

Some apps call the centralized API over REST and never import a generated package. Use API mode:

```bash theme={null}
fused-cli init support-api --api --service linear
```

API mode is not another resource type. It writes the same `kind: sdk` config with package generation disabled:

```yaml theme={null}
generate: false
```

Nothing else changes. Init applies the version and prints a concrete REST request template for `POST /v1/apps/{app_id}/executions`. Replace the operation placeholder when all operations were selected and provide that operation's required input. The app remains describable with `fused-cli sdk openapi`. Only the package and download are absent: `sdk apply --json` reports `generation.status: "skipped"`, and `--download` is refused before anything is applied.

Flipping the field edits the file, which moves the source hash, so switching between the two needs a version bump like any other scope change.

## Point the client at the Engine

The generated client takes `grpcUrl` — `grpc_url` in Python. The name is the instruction: it wants the Engine's gRPC address, not the HTTP one you give the CLI.

```typescript theme={null}
const sdk = new FusedSDK({
  grpcUrl: process.env.FUSED_ENGINE_GRPC_URL,
  token: process.env.FUSED_SDK_TOKEN,
});
```

It resolves the target in this order, first non-empty wins:

1. `grpcUrl` / `grpc_url` passed to the constructor
2. `FUSED_ENGINE_GRPC_URL`
3. `FUSED_ENGINE_URL`
4. `http://127.0.0.1:50051`

<Warning>
  Step 3 is the one that catches people. `FUSED_ENGINE_URL` is the **HTTP** management URL the CLI uses. If it is set in your environment and you do not pass `grpcUrl`, the client speaks gRPC at the REST port and the server answers **HTTP 405**. Set `FUSED_ENGINE_GRPC_URL` explicitly and the ambiguity goes away.
</Warning>

Each `FusedSDK` instance opens one gRPC channel that every service on it shares, so there is no per-service connection cost.

## What is in the package

Alongside the typed client, generation writes two files from your actual selections:

| File        | For                                                                          |
| ----------- | ---------------------------------------------------------------------------- |
| `README.md` | Installing, authenticating, and calling the operations this SDK actually has |
| `SKILL.md`  | A coding agent working in your repo                                          |

Both are derived from the generated source and validated against it, so the methods they demonstrate exist. Neither is a substitute for this site — they describe your package, not the platform.

## Flags

### `fused-cli init`

| Flag           | What it does                                                                                                                                       | Example                                                                          |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `--sdk`        | Creates, applies, and downloads a typed SDK                                                                                                        | `fused-cli init support --sdk --service linear`                                  |
| `--api`        | Creates and applies a `kind: sdk` app with `generate: false`                                                                                       | `fused-cli init support-api --api --service linear`                              |
| `--service`    | Adds services as `<service>[@<version>]`; comma-separated or repeatable, with an omitted version defaulting to an enabled or latest public version | `fused-cli init support --sdk --service linear@v1,slack`                         |
| `--operation`  | Skips the selector for one exact operation; repeatable                                                                                             | `fused-cli init support --sdk --service linear --operation 'linear=issueUpdate'` |
| `--select-all` | Skips the selector and takes every operation of a service                                                                                          | `fused-cli init support --sdk --service linear --select-all linear`              |
| `--bucket`     | References an existing bucket; never creates one                                                                                                   | `fused-cli init support --sdk --bucket prod-credentials`                         |
| `--version`    | Sets the initial app version                                                                                                                       | `fused-cli init support --sdk --version 2.0.0 --service linear`                  |
| `--language`   | `typescript` (default) or `python`; SDK mode only                                                                                                  | `fused-cli init support --sdk --language python`                                 |
| `--no-apply`   | Writes validated desired state, saves available plan receipts, and prints the commands to apply later                                              | `fused-cli init support --sdk --service linear --no-apply`                       |

Omit `--sdk`, `--api`, and `--mcp` in a terminal to choose the app mode interactively. Creating never replaces an existing file. Init writes only the chosen app config and any workspace config needed for activation; it does not create sample files for the other modes.

In a terminal, omit `--operation` and `--select-all` to use the guided selector. With `--no-input`, declare the mode and one operation boundary for every service. Use the explicit plan/apply path when automation needs structured results.

### `fused-cli extend`

Use `extend` after creation. It finds the existing YAML and infers generated SDK or direct API mode from that document, so you do not repeat `--sdk` or `--api`:

```bash theme={null}
fused-cli extend support --service slack
```

The same file is updated additively. An idempotent repeat keeps its version. A real change to a stable SemVer version advances to the next minor version and is shown in terminal confirmation; `--no-input` uses the same deterministic inference. Pass `--version <successor>` to override it. Prerelease and non-SemVer versions always require that explicit flag.

## Adjust the selection

```text theme={null}
fused-cli sdk service   <add|remove> <service-slug>
fused-cli sdk operation <add|remove> <service-slug> <operation-id...>
```

The service slug comes first, then as many operation IDs as you want:

```bash theme={null}
fused-cli sdk service add zendesk --version v2
fused-cli sdk operation add linear issueCreate issueArchive
fused-cli sdk operation remove slack chatDelete
```

`sdk operation add` accepts `--interactive` to pick from a list, and `--apply` (or `--download`, which implies it) to push the change straight through.

## Choosing auth

`auth.type` picks a Registry-declared scheme — `basic`, `bearer`, `api_key`, `oauth`, `oidc`, or `mtls` — and `auth.name` disambiguates two schemes of the same type.

```yaml theme={null}
services:
  linear:
    version: "v1"
    operations: ["issueUpdate"]
    auth: { type: "oauth" }
    connect: { scopes: ["read:issues", "write:issues"] }
```

Leave it out and the Engine picks each operation's first provider-declared alternative. `connect.scopes` is a ceiling: an application can request fewer scopes per user, never more.

The operation's imported security requirements are authoritative, and they are an ordered **OR of ANDs**: alternatives are OR, schemes within one alternative are AND, and an empty alternative permits anonymous execution.

<Warning>
  Do not collapse an AND alternative down to the one convenient scheme. A single selector never replaces a secondary credential such as an mTLS certificate or an additional API token — planning reports readiness for every scheme in the chosen AND set.
</Warning>

Where a service declares several named schemes of one type, carry both the type and the exact name. Never rely on declaration order.

Credentials themselves never appear in this file. They resolve from the bucket when a connection starts or an operation dispatches. Each service resolves through its own `bucket:` override when one is set, otherwise it falls back to the top-level `bucket:`:

```yaml theme={null}
services:
  slack:
    version: "v1"
    bucket: slack-credentials
    operations: ["chatPostMessage"]
```

Webhook secret references still resolve through the top-level bucket, and an `auth.ref` may only join two services that resolve to the same bucket.

<Card title="Use a Managed service" icon="link" href="/docs/workspace/use-managed-service">
  For supported OAuth services, select a Fused Managed App instead of storing your own client pair. Your Engine still owns each user's connection. Add the managed reference before publishing, or publish a new version when changing an existing app.
</Card>

<Card title="Store your own credentials" icon="key" href="/docs/bucket/store-credentials">
  Terminal plans can securely fill readiness warnings and retry once, but setup is optional. Automation receives structured `credential_readiness` metadata and may pre-provision the exact bucket. If setup is deferred, the first affected call returns `bucket_credentials_missing` with the exact safe command.
</Card>
