# Stability AI Client

Generate, edit, and upscale images using Stability AI's image generation models.

## Methods

| Method                                   | Description                                              |
| ---------------------------------------- | -------------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Stability AI API request with schema validation |

## Usage

### Generate an Image (Text-to-Image)

```typescript
import { api, z, stabilityAI } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_STABILITY = "a1b2c3d4-5678-90ab-cdef-stabilit0001";

const ImageArtifactSchema = z.object({
  base64: z.string(),
  finishReason: z.string(), // SUCCESS, CONTENT_FILTERED, ERROR
  seed: z.number(),
});

const GenerationResponseSchema = z.object({
  artifacts: z.array(ImageArtifactSchema),
});

export default api({
  integrations: {
    stability: stabilityAI(PROD_STABILITY),
  },
  name: "StabilityAIExample",
  input: z.object({
    prompt: z.string(),
    negativePrompt: z.string().optional(),
    width: z.number().default(1024),
    height: z.number().default(1024),
  }),
  output: z.object({
    images: z.array(
      z.object({
        base64: z.string(),
        seed: z.number(),
      }),
    ),
  }),
  async run(ctx, { prompt, negativePrompt, width, height }) {
    const result = await ctx.integrations.stability.apiRequest(
      {
        method: "POST",
        path: "/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image",
        body: {
          text_prompts: [
            { text: prompt, weight: 1 },
            ...(negativePrompt ? [{ text: negativePrompt, weight: -1 }] : []),
          ],
          cfg_scale: 7,
          width: width,
          height: height,
          samples: 1,
          steps: 30,
        },
      },
      { response: GenerationResponseSchema },
    );

    return {
      images: result.artifacts
        .filter((a) => a.finishReason === "SUCCESS")
        .map((a) => ({
          base64: a.base64,
          seed: a.seed,
        })),
    };
  },
});
```

### Image-to-Image (Transform Existing Image)

```typescript
const result = await ctx.integrations.stability.apiRequest(
  {
    method: "POST",
    path: "/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image",
    headers: {
      "Content-Type": "multipart/form-data",
    },
    body: {
      init_image: sourceImageBase64,
      text_prompts: [{ text: "A vibrant oil painting style", weight: 1 }],
      image_strength: 0.35, // How much to modify (0-1)
      cfg_scale: 7,
      samples: 1,
      steps: 30,
    },
  },
  { response: GenerationResponseSchema },
);
```

### Upscale an Image

```typescript
const UpscaleResponseSchema = z.object({
  artifacts: z.array(
    z.object({
      base64: z.string(),
      finishReason: z.string(),
      seed: z.number(),
    }),
  ),
});

const result = await ctx.integrations.stability.apiRequest(
  {
    method: "POST",
    path: "/v1/generation/esrgan-v1-x2plus/image-to-image/upscale",
    headers: {
      "Content-Type": "multipart/form-data",
    },
    body: {
      image: sourceImageBase64,
      width: 2048, // Target width (optional)
    },
  },
  { response: UpscaleResponseSchema },
);

const upscaledImage = result.artifacts[0].base64;
```

### Inpainting (Edit Parts of an Image)

```typescript
const result = await ctx.integrations.stability.apiRequest(
  {
    method: "POST",
    path: "/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image/masking",
    headers: {
      "Content-Type": "multipart/form-data",
    },
    body: {
      init_image: sourceImageBase64,
      mask_image: maskImageBase64, // White areas will be regenerated
      mask_source: "MASK_IMAGE_WHITE",
      text_prompts: [{ text: "A golden retriever sitting", weight: 1 }],
      cfg_scale: 7,
      samples: 1,
      steps: 30,
    },
  },
  { response: GenerationResponseSchema },
);
```

### Generate with Specific Style

```typescript
const result = await ctx.integrations.stability.apiRequest(
  {
    method: "POST",
    path: "/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image",
    body: {
      text_prompts: [
        { text: "A serene mountain landscape at sunset", weight: 1 },
        { text: "photorealistic, detailed, high quality", weight: 0.5 },
        { text: "blurry, low quality, distorted", weight: -1 },
      ],
      cfg_scale: 7,
      style_preset: "photographic", // See style presets below
      width: 1024,
      height: 1024,
      samples: 1,
      steps: 30,
      seed: 12345, // For reproducibility
    },
  },
  { response: GenerationResponseSchema },
);
```

### Get Account Balance

```typescript
const BalanceResponseSchema = z.object({
  credits: z.number(),
});

const result = await ctx.integrations.stability.apiRequest(
  {
    method: "GET",
    path: "/v1/user/balance",
  },
  { response: BalanceResponseSchema },
);

console.log(`Available credits: ${result.credits}`);
```

### List Available Engines

```typescript
const EngineSchema = z.object({
  id: z.string(),
  name: z.string(),
  description: z.string(),
  type: z.string(),
});

const ListEnginesResponseSchema = z.object({
  engines: z.array(EngineSchema),
});

const result = await ctx.integrations.stability.apiRequest(
  {
    method: "GET",
    path: "/v1/engines/list",
  },
  { response: ListEnginesResponseSchema },
);

result.engines.forEach((engine) => {
  console.log(`${engine.id}: ${engine.name}`);
});
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### Streaming Is Not Supported

`apiRequest()` does not support streaming or Server-Sent Events. Do not set
`stream: true` — streaming responses fail schema validation. Every call
returns the complete response; if a UI needs real-time token streaming,
handle it at the frontend layer, not through the SDK.

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await stability.generateImage({ ... });
await stability.upscale({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.stability.apiRequest(
  { method: "POST", path: "/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image", body: { ... } },
  { response: GenerationResponseSchema }
);
```

### Engine ID in Path

The engine/model ID is part of the URL path:

```typescript
// Text-to-image with SDXL 1.0
const path = "/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image";

// Image-to-image
const path = "/v1/generation/stable-diffusion-xl-1024-v1-0/image-to-image";

// Upscaling
const path = "/v1/generation/esrgan-v1-x2plus/image-to-image/upscale";

// Available engines:
// - stable-diffusion-xl-1024-v1-0 (SDXL 1.0)
// - stable-diffusion-v1-6
// - esrgan-v1-x2plus (upscaling)
```

### Text Prompts Format

Prompts are arrays with weights:

```typescript
// WRONG - Simple string
const body = {
  prompt: "A cat sitting on a couch",
};

// CORRECT - Array of weighted prompts
const body = {
  text_prompts: [{ text: "A cat sitting on a couch", weight: 1 }],
};

// Negative prompts use negative weight
const body = {
  text_prompts: [
    { text: "A beautiful sunset over mountains", weight: 1 },
    { text: "blurry, low quality, distorted", weight: -1 }, // Negative prompt
  ],
};
```

### Image Dimensions

SDXL 1.0 requires specific dimensions:

```typescript
// Valid dimensions for SDXL 1.0 (must be multiples of 64)
const validDimensions = [
  { width: 1024, height: 1024 },
  { width: 1152, height: 896 },
  { width: 896, height: 1152 },
  { width: 1216, height: 832 },
  { width: 832, height: 1216 },
  { width: 1344, height: 768 },
  { width: 768, height: 1344 },
  { width: 1536, height: 640 },
  { width: 640, height: 1536 },
];

// WRONG - Arbitrary dimensions
const body = { width: 1000, height: 800 };

// CORRECT - Valid dimensions
const body = { width: 1024, height: 1024 };
```

### CFG Scale

Controls how closely the image follows the prompt:

```typescript
// cfg_scale: 0-35 (default 7)
// Lower = more creative, less prompt adherence
// Higher = more literal, follows prompt closely

const body = {
  text_prompts: [{ text: "A cat", weight: 1 }],
  cfg_scale: 7, // Good balance
  // cfg_scale: 3  // More creative
  // cfg_scale: 15 // Very literal
};
```

### Content Filtering

Check finishReason for filtered content:

```typescript
const result = await ctx.integrations.stability.apiRequest(
  { method: "POST", path: "/v1/generation/...", body: { ... } },
  { response: GenerationResponseSchema }
);

result.artifacts.forEach((artifact) => {
  if (artifact.finishReason === "CONTENT_FILTERED") {
    console.warn("Image was filtered due to content policy");
  } else if (artifact.finishReason === "SUCCESS") {
    // Safe to use
    const image = artifact.base64;
  }
});
```

### Style Presets

Available style presets for consistent results:

```typescript
const stylePresets = [
  "3d-model",
  "analog-film",
  "anime",
  "cinematic",
  "comic-book",
  "digital-art",
  "enhance",
  "fantasy-art",
  "isometric",
  "line-art",
  "low-poly",
  "modeling-compound",
  "neon-punk",
  "origami",
  "photographic",
  "pixel-art",
  "tile-texture",
];

const body = {
  text_prompts: [{ text: "A warrior", weight: 1 }],
  style_preset: "fantasy-art",
};
```

### Base64 Image Handling

Response images are base64 encoded:

```typescript
// Response contains base64 encoded PNG
const imageBase64 = result.artifacts[0].base64;

// To save or display, you may need to add data URL prefix
const dataUrl = `data:image/png;base64,${imageBase64}`;

// For input images (image-to-image), provide raw base64 without prefix
const inputImage = rawBase64WithoutPrefix;
```

### Seed for Reproducibility

Use the same seed to reproduce results:

```typescript
// First generation
const body = {
  text_prompts: [{ text: "A dragon", weight: 1 }],
  seed: 0, // Random seed
};

// Response includes the seed used
const usedSeed = result.artifacts[0].seed; // e.g., 1234567890

// Reproduce same image
const body = {
  text_prompts: [{ text: "A dragon", weight: 1 }],
  seed: 1234567890, // Same seed = same image
};
```

### Steps vs Quality

More steps = better quality but slower and more credits:

```typescript
// steps: 10-150 (default 30)
const body = {
  text_prompts: [{ text: "...", weight: 1 }],
  steps: 30, // Good balance
  // steps: 50 // Higher quality
  // steps: 15 // Faster, lower quality
};
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.stability.apiRequest(
    { method: "POST", path: "/v1/generation/...", body: { ... } },
    { response: GenerationResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Stability AI API Documentation](https://platform.stability.ai/docs/api-reference)
- [Text-to-Image](https://platform.stability.ai/docs/api-reference#tag/Text-to-Image)
- [Image-to-Image](https://platform.stability.ai/docs/api-reference#tag/Image-to-Image)
- [Upscaling](https://platform.stability.ai/docs/api-reference#tag/Image-to-Image/operation/upscaleImage)
