> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefused.com/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.

## Author the config

```bash theme={null}
fused-cli sdk init support-sdk \
  --service 'linear=v1' \
  --operation 'linear=issueUpdate' \
  --service 'slack=v1' \
  --operation 'slack=chatPostMessage'
```

That writes `.fused/sdks/support-sdk.yaml`:

```yaml theme={null}
apiVersion: fused/v1
kind: sdk
name: support-sdk
version: "1.0.0"
language: typescript
bucket: default
services:
  linear:
    version: "v1"
    operations: ["issueUpdate"]
  slack:
    version: "v1"
    operations: ["chatPostMessage"]
```

Each service key is an activated service's Registry slug. Confirm one with `fused-cli workspace services list -q <slug>` — being visible in the Registry is not the same as being enabled here.

`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. Create the matching bucket entry with `fused-cli value set` before you plan, or there is nothing for dispatch to resolve.
</Warning>

## Plan, apply, download

<Steps>
  <Step title="Validate the config">
    ```bash theme={null}
    fused-cli sdk validate
    ```
  </Step>

  <Step title="Preview">
    ```bash theme={null}
    fused-cli sdk plan
    ```

    Plans are read-only. The receipt binds apply to this exact file content and this exact Engine.
  </Step>

  <Step title="Generate">
    ```bash theme={null}
    fused-cli sdk apply --download
    ```
  </Step>
</Steps>

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

## Skip the package

Some SDKs are never imported. The product calls them over REST instead, and a generated package is dead weight. Set `generate: false` and apply publishes the version without building one:

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

Nothing else changes. Same version, same operations, same execution contract — still callable at `POST /v1/apps/{app_id}/executions`, still describable with `fused-cli sdk openapi`. Only the download is gone: `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

### `sdk init`

| Flag           | What it does                                                | Example                                                       |
| -------------- | ----------------------------------------------------------- | ------------------------------------------------------------- |
| `--service`    | Adds a service at a version; repeatable                     | `fused-cli sdk init support --service 'linear=v1'`            |
| `--operation`  | Selects one operation; repeatable                           | `fused-cli sdk init support --operation 'linear=issueUpdate'` |
| `--select-all` | Takes every operation of a service                          | `fused-cli sdk init support --select-all 'linear'`            |
| `--extend`     | Adds to an existing config instead of refusing to overwrite | `fused-cli sdk init support --extend --service 'slack=v1'`    |
| `--bucket`     | References an existing bucket; never creates one            | `fused-cli sdk init support --bucket prod-credentials`        |
| `--version`    | Sets the config version; defaults to `1.0.0`                | `fused-cli sdk init support --version 2.0.0`                  |
| `--language`   | `typescript` (default) or `python`                          | `fused-cli sdk init support --language python`                |

Creating never replaces an existing file. `--extend` is additive and idempotent — re-running it reports `unchanged` rather than duplicating a service.

### `sdk plan`

| Flag            | What it does                                                             | Example                                               |
| --------------- | ------------------------------------------------------------------------ | ----------------------------------------------------- |
| `--json`        | Prints the plan result and required permissions                          | `fused-cli sdk plan --json`                           |
| `--interactive` | Securely fills credentials the Engine reports missing, then retries once | `fused-cli sdk plan --interactive`                    |
| `--owner-team`  | Sets the owning team; defaults to you                                    | `fused-cli sdk plan --owner-team payments`            |
| `--receipt-out` | Writes the receipt to a chosen path                                      | `fused-cli sdk plan --receipt-out ./ci/sdk.plan.json` |

`--interactive` is incompatible with `--json`, `--no-input`, and `CI=true`. It never creates or substitutes a bucket.

### `sdk apply`

| Flag         | What it does                                     | Example                                            |
| ------------ | ------------------------------------------------ | -------------------------------------------------- |
| `--download` | Downloads the package after generating it        | `fused-cli sdk apply --download`                   |
| `--json`     | Returns apply, generation, and download outcomes | `fused-cli sdk apply --json`                       |
| `--plan-id`  | Applies one exact remote plan                    | `fused-cli sdk apply --plan-id pln_a91…`           |
| `--receipt`  | Applies from a specific receipt                  | `fused-cli sdk apply --receipt ./ci/sdk.plan.json` |

For pipelines, prefer separate apply and download steps so a failed transfer can be retried without replaying the apply.

## 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 validates bucket metadata 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 at generation and dispatch time.

<Card title="Store the credentials" icon="key" href="/bucket/store-credentials">
  A plan that reports `bucket_credentials_missing` is telling you the bucket is not ready yet.
</Card>
