diff --git a/.sdk.json b/.sdk.json
index 726a695..66a784a 100644
--- a/.sdk.json
+++ b/.sdk.json
@@ -1,5 +1,5 @@
{
- "id": "8edb506f-a1f6-47a0-bbd1-318c71e84e5a",
+ "id": "88e585d6-f705-42ee-a9cf-1e80c832f4c3",
"tracked_paths": [
{
"editable": true,
@@ -289,6 +289,22 @@
"editable": true,
"path": "src/resources/v1/auto-subtitle-generator/resource-client.ts"
},
+ {
+ "editable": true,
+ "path": "src/resources/v1/body-swap/README.md"
+ },
+ {
+ "editable": true,
+ "path": "src/resources/v1/body-swap/index.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/resources/v1/body-swap/request-types.ts"
+ },
+ {
+ "editable": true,
+ "path": "src/resources/v1/body-swap/resource-client.ts"
+ },
{
"editable": true,
"path": "src/resources/v1/face-detection/README.md"
@@ -833,6 +849,22 @@
"editable": false,
"path": "src/types/v1-auto-subtitle-generator-create-response.ts"
},
+ {
+ "editable": false,
+ "path": "src/types/v1-body-swap-create-body-assets.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-body-swap-create-body-resolution-enum.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-body-swap-create-body.ts"
+ },
+ {
+ "editable": false,
+ "path": "src/types/v1-body-swap-create-response.ts"
+ },
{
"editable": false,
"path": "src/types/v1-face-detection-create-body-assets.ts"
@@ -1185,6 +1217,10 @@
"editable": false,
"path": "test/v1-auto-subtitle-generator.test.ts"
},
+ {
+ "editable": false,
+ "path": "test/v1-body-swap.test.ts"
+ },
{
"editable": false,
"path": "test/v1-face-detection.test.ts"
diff --git a/README.md b/README.md
index b6fa617..e1bb4b1 100644
--- a/README.md
+++ b/README.md
@@ -277,6 +277,11 @@ Valid values are: `none`, `error`, `warn`, `info`, `debug` (case insensitive). I
- [create](src/resources/v1/auto-subtitle-generator/README.md#create) - Auto Subtitle Generator
- [generate](src/resources/v1/auto-subtitle-generator/README.md#generate) - Auto Subtitle Generator Generate Workflow
+### [v1.bodySwap](src/resources/v1/body-swap/README.md)
+
+- [create](src/resources/v1/body-swap/README.md#create) - Body Swap
+- [generate](src/resources/v1/body-swap/README.md#generate) - Body Swap Generate Workflow
+
### [v1.faceDetection](src/resources/v1/face-detection/README.md)
- [create](src/resources/v1/face-detection/README.md#create) - Face Detection
diff --git a/package-lock.json b/package-lock.json
index 59c26d8..f9d6578 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "magic-hour",
- "version": "0.60.0",
+ "version": "0.61.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "magic-hour",
- "version": "0.60.0",
+ "version": "0.61.0",
"license": "MIT",
"dependencies": {
"make-api-request-js": "^0.2.1",
diff --git a/package.json b/package.json
index 2fbcd75..a4e3ce5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "magic-hour",
- "version": "0.60.0",
+ "version": "0.61.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
diff --git a/src/README.md b/src/README.md
index 8ea3422..031c9f2 100644
--- a/src/README.md
+++ b/src/README.md
@@ -17,6 +17,7 @@
- [animation](resources/v1/animation/README.md) - animation
- [audio-projects](resources/v1/audio-projects/README.md) - audio-projects
- [auto-subtitle-generator](resources/v1/auto-subtitle-generator/README.md) - auto-subtitle-generator
+- [body-swap](resources/v1/body-swap/README.md) - body-swap
- [face-detection](resources/v1/face-detection/README.md) - face-detection
- [face-swap-photo](resources/v1/face-swap-photo/README.md) - face-swap-photo
- [face-swap](resources/v1/face-swap/README.md) - face-swap
diff --git a/src/environment.ts b/src/environment.ts
index e5f8405..9278f1e 100644
--- a/src/environment.ts
+++ b/src/environment.ts
@@ -1,4 +1,4 @@
export enum Environment {
Environment = "https://api.magichour.ai",
- MockServer = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.61.1",
+ MockServer = "https://api.sideko.dev/v1/mock/magichour/magic-hour/0.62.0",
}
diff --git a/src/resources/v1/README.md b/src/resources/v1/README.md
index 5e5986c..8bac326 100644
--- a/src/resources/v1/README.md
+++ b/src/resources/v1/README.md
@@ -17,6 +17,7 @@
- [animation](animation/README.md) - animation
- [audio-projects](audio-projects/README.md) - audio-projects
- [auto-subtitle-generator](auto-subtitle-generator/README.md) - auto-subtitle-generator
+- [body-swap](body-swap/README.md) - body-swap
- [face-detection](face-detection/README.md) - face-detection
- [face-swap](face-swap/README.md) - face-swap
- [face-swap-photo](face-swap-photo/README.md) - face-swap-photo
diff --git a/src/resources/v1/ai-image-generator/README.md b/src/resources/v1/ai-image-generator/README.md
index a4913a6..75e36a0 100644
--- a/src/resources/v1/ai-image-generator/README.md
+++ b/src/resources/v1/ai-image-generator/README.md
@@ -57,18 +57,18 @@ Create an AI image with advanced model selection and quality controls.
#### Parameters
-| Parameter | Required | Deprecated | Description | Example |
-| ---------------- | :------: | :--------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
-| `imageCount` | ✓ | ✗ | Number of images to generate. Maximum varies by model. | `1` |
-| `style` | ✓ | ✗ | The art style to use for image generation. | `{"prompt": "Cool image", "tool": "ai-anime-generator"}` |
-| `└─ prompt` | ✓ | — | The prompt used for the image(s). | `"Cool image"` |
-| `└─ qualityMode` | ✗ | ✓ | DEPRECATED: Use `model` field instead for explicit model selection. Legacy quality mode mapping: - `standard` → `z-image-turbo` model - `pro` → `seedream-v4` model If model is specified, it will take precedence over the legacy quality_mode field. | `"pro"` |
-| `└─ tool` | ✗ | — | The art style to use for image generation. Defaults to 'general' if not provided. | `"ai-anime-generator"` |
-| `aspectRatio` | ✗ | ✗ | The aspect ratio of the output image(s). If not specified, defaults to `1:1` (square). | `"1:1"` |
-| `model` | ✗ | ✗ | The AI model to use for image generation. Each model has different capabilities and costs. **Models:** - `default` - Use the model we recommend, which will change over time. This is recommended unless you need a specific model. This is the default behavior. - `flux-schnell` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `z-image-turbo` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `seedream-v4` - from 40 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana` - from 50 credits/image - Supported resolutions: 640px, 1k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana-2` - from 100 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana-pro` - from 150 credits/image - Supported resolutions: 1k, 2k, 4k - Available for tiers: creator, pro, business - Image count allowed: 1, 4, 9, 16 **Deprecated Enum Values:** - `seedream` - Use `seedream-v4` instead. | `"default"` |
-| `name` | ✗ | ✗ | Give your image a custom name for easy identification. | `"My Ai Image image"` |
-| `orientation` | ✗ | ✓ | DEPRECATED: Use `aspect_ratio` instead. The orientation of the output image(s). `aspect_ratio` takes precedence when `orientation` if both are provided. | `"landscape"` |
-| `resolution` | ✗ | ✗ | Maximum resolution (longest edge) for the output image. **Options:** - `640px` — up to 640px - `1k` — up to 1024px - `2k` — up to 2048px - `4k` — up to 4096px - `auto` — **Deprecated.** Mapped server-side from your subscription tier to the best matching resolution the model supports **Per-model support:** - `flux-schnell` - 640px, 1k, 2k - `z-image-turbo` - 640px, 1k, 2k - `seedream-v4` - 640px, 1k, 2k, 4k - `nano-banana` - 640px, 1k - `nano-banana-2` - 640px, 1k, 2k, 4k - `nano-banana-pro` - 1k, 2k, 4k Note: Resolution availability depends on the model and your subscription tier. | `"auto"` |
+| Parameter | Required | Deprecated | Description | Example |
+| ---------------- | :------: | :--------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
+| `imageCount` | ✓ | ✗ | Number of images to generate. Maximum varies by model. | `1` |
+| `style` | ✓ | ✗ | The art style to use for image generation. | `{"prompt": "Cool image", "tool": "ai-anime-generator"}` |
+| `└─ prompt` | ✓ | — | The prompt used for the image(s). | `"Cool image"` |
+| `└─ qualityMode` | ✗ | ✓ | DEPRECATED: Use `model` field instead for explicit model selection. Legacy quality mode mapping: - `standard` → `z-image-turbo` model - `pro` → `seedream-v4` model If model is specified, it will take precedence over the legacy quality_mode field. | `"pro"` |
+| `└─ tool` | ✗ | — | The art style to use for image generation. Defaults to 'general' if not provided. | `"ai-anime-generator"` |
+| `aspectRatio` | ✗ | ✗ | The aspect ratio of the output image(s). If not specified, defaults to `1:1` (square). | `"1:1"` |
+| `model` | ✗ | ✗ | The AI model to use for image generation. Each model has different capabilities and costs. **Models:** - `default` - Use the model we recommend, which will change over time. This is recommended unless you need a specific model. This is the default behavior. - `flux-schnell` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `z-image-turbo` - from 5 credits/image - Supported resolutions: 640px, 1k, 2k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `seedream-v4` - from 40 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana` - from 50 credits/image - Supported resolutions: 640px, 1k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 2, 3, 4 - `nano-banana-2` - from 100 credits/image - Supported resolutions: 640px, 1k, 2k, 4k - Available for tiers: free, creator, pro, business - Image count allowed: 1, 4, 9, 16 - `nano-banana-pro` - from 150 credits/image - Supported resolutions: 1k, 2k, 4k - Available for tiers: creator, pro, business - Image count allowed: 1, 4, 9, 16 **Deprecated Enum Values:** - `seedream` - Use `seedream-v4` instead. | `"default"` |
+| `name` | ✗ | ✗ | Give your image a custom name for easy identification. | `"My Ai Image image"` |
+| `orientation` | ✗ | ✓ | DEPRECATED: Use `aspect_ratio` instead. The orientation of the output image(s). `aspect_ratio` takes precedence when `orientation` if both are provided. | `"landscape"` |
+| `resolution` | ✗ | ✗ | Maximum resolution (longest edge) for the output image. **Options:** - `640px` — up to 640px - `1k` — up to 1024px - `2k` — up to 2048px - `4k` — up to 4096px - `auto` — **Deprecated.** Mapped server-side from your subscription tier to the best matching resolution the model supports **Per-model support:** - `flux-schnell` - 640px, 1k, 2k - `z-image-turbo` - 640px, 1k, 2k - `seedream-v4` - 640px, 1k, 2k, 4k - `nano-banana` - 640px, 1k - `nano-banana-2` - 640px, 1k, 2k, 4k - `nano-banana-pro` - 1k, 2k, 4k Note: Resolution availability depends on the model and your subscription tier. | `"auto"` |
#### Example Snippet
diff --git a/src/resources/v1/ai-image-generator/request-types.ts b/src/resources/v1/ai-image-generator/request-types.ts
index 595ef8c..41a80b8 100644
--- a/src/resources/v1/ai-image-generator/request-types.ts
+++ b/src/resources/v1/ai-image-generator/request-types.ts
@@ -43,7 +43,7 @@ export type CreateRequest = {
* - `nano-banana-2` - from 100 credits/image
* - Supported resolutions: 640px, 1k, 2k, 4k
* - Available for tiers: free, creator, pro, business
- * - Image count allowed: 1, 2, 3, 4
+ * - Image count allowed: 1, 4, 9, 16
* - `nano-banana-pro` - from 150 credits/image
* - Supported resolutions: 1k, 2k, 4k
* - Available for tiers: creator, pro, business
diff --git a/src/resources/v1/body-swap/README.md b/src/resources/v1/body-swap/README.md
new file mode 100644
index 0000000..929a4e5
--- /dev/null
+++ b/src/resources/v1/body-swap/README.md
@@ -0,0 +1,94 @@
+# v1.body-swap
+
+## Module Functions
+
+
+
+### Body Swap Generate Workflow
+
+The workflow performs the following action
+
+1. upload local assets to Magic Hour storage. So you can pass in a local path instead of having to upload files yourself
+2. trigger a generation
+3. poll for a completion status. This is configurable
+4. if success, download the output to local directory
+
+> [!TIP]
+> This is the recommended way to use the SDK unless you have specific needs where it is necessary to split up the actions.
+
+#### Parameters
+
+In addition to the parameters listed in the `create` section below, `generate` introduces 3 new parameters:
+
+- `waitForCompletion` (boolean, default true): Whether to wait for the project to complete.
+- `downloadOutputs` (boolean, default true): Whether to download the generated files
+- `downloadDirectory` (string, optional): Directory to save downloaded files (defaults to current directory)
+
+#### Example Snippet
+
+```typescript
+import { Client } from "magic-hour";
+
+const client = new Client({ token: process.env["API_TOKEN"]!! });
+const res = await client.v1.bodySwap.generate(
+ {
+ assets: {
+ personFilePath: "/path/to/person.png",
+ sceneFilePath: "/path/to/scene.png",
+ },
+ name: "My Body Swap image",
+ resolution: "1k",
+ },
+ {
+ waitForCompletion: true,
+ downloadOutputs: true,
+ downloadDirectory: ".",
+ },
+);
+```
+
+
+
+### Body Swap
+
+Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward).
+
+**API Endpoint**: `POST /v1/body-swap`
+
+#### Parameters
+
+| Parameter | Required | Description | Example |
+| ------------------- | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
+| `assets` | ✓ | Person image and scene image for body swap | `{"personFilePath": "api-assets/id/1234.png", "sceneFilePath": "api-assets/id/5678.png"}` |
+| `└─ personFilePath` | ✓ | Image of the person to place into the scene. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details. | `"api-assets/id/1234.png"` |
+| `└─ sceneFilePath` | ✓ | Target scene image (background). This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details. | `"api-assets/id/5678.png"` |
+| `resolution` | ✓ | Output resolution. Determines credits charged for the run. | `"1k"` |
+| `name` | ✗ | Give your image a custom name for easy identification. | `"My Body Swap image"` |
+
+#### Example Snippet
+
+```typescript
+import { Client } from "magic-hour";
+
+const client = new Client({ token: process.env["API_TOKEN"]!! });
+const res = await client.v1.bodySwap.create({
+ assets: {
+ personFilePath: "api-assets/id/1234.png",
+ sceneFilePath: "api-assets/id/5678.png",
+ },
+ name: "My Body Swap image",
+ resolution: "1k",
+});
+```
+
+#### Response
+
+##### Type
+
+[V1BodySwapCreateResponse](/src/types/v1-body-swap-create-response.ts)
+
+##### Example
+
+```typescript
+{"creditsCharged": 100, "frameCost": 100, "id": "cuid-example"}
+```
diff --git a/src/resources/v1/body-swap/index.ts b/src/resources/v1/body-swap/index.ts
new file mode 100644
index 0000000..673a665
--- /dev/null
+++ b/src/resources/v1/body-swap/index.ts
@@ -0,0 +1,2 @@
+export { CreateRequest } from "./request-types";
+export { BodySwapClient } from "./resource-client";
diff --git a/src/resources/v1/body-swap/request-types.ts b/src/resources/v1/body-swap/request-types.ts
new file mode 100644
index 0000000..fbdbca7
--- /dev/null
+++ b/src/resources/v1/body-swap/request-types.ts
@@ -0,0 +1,85 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1BodySwapCreateBodyAssets,
+ Schemas$V1BodySwapCreateBodyAssets,
+ V1BodySwapCreateBodyAssets,
+} from "magic-hour/types/v1-body-swap-create-body-assets";
+
+/**
+ * CreateRequest
+ */
+export type CreateRequest = {
+ /**
+ * Person image and scene image for body swap
+ */
+ assets: V1BodySwapCreateBodyAssets;
+ /**
+ * Give your image a custom name for easy identification.
+ */
+ name?: string | undefined;
+ /**
+ * Output resolution. Determines credits charged for the run.
+ */
+ resolution: "1k" | "2k" | "4k" | "640px";
+};
+
+/**
+ * @internal
+ * CreateRequest without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$CreateRequest = {
+ assets: External$V1BodySwapCreateBodyAssets;
+ name?: string | undefined;
+ resolution: "1k" | "2k" | "4k" | "640px";
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object CreateRequest
+ */
+const SchemaIn$CreateRequest: z.ZodType<
+ CreateRequest, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ assets: Schemas$V1BodySwapCreateBodyAssets.in,
+ name: z.string().optional(),
+ resolution: z.enum(["1k", "2k", "4k", "640px"]),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ name: "name",
+ resolution: "resolution",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$CreateRequest
+ */
+const SchemaOut$CreateRequest: z.ZodType<
+ External$CreateRequest, // output type of this zod object
+ z.ZodTypeDef,
+ CreateRequest // the object to be transformed
+> = z
+ .object({
+ assets: Schemas$V1BodySwapCreateBodyAssets.out,
+ name: z.string().optional(),
+ resolution: z.enum(["1k", "2k", "4k", "640px"]),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ name: "name",
+ resolution: "resolution",
+ });
+ });
+
+export const Schemas$CreateRequest = {
+ in: SchemaIn$CreateRequest,
+ out: SchemaOut$CreateRequest,
+};
diff --git a/src/resources/v1/body-swap/resource-client.ts b/src/resources/v1/body-swap/resource-client.ts
new file mode 100644
index 0000000..dcfed20
--- /dev/null
+++ b/src/resources/v1/body-swap/resource-client.ts
@@ -0,0 +1,161 @@
+import {
+ ApiPromise,
+ CoreClient,
+ CoreResourceClient,
+ RequestOptions,
+ ResourceClientOptions,
+} from "make-api-request-js";
+
+import {
+ GenerateOptions,
+ GenerateRequestType,
+} from "magic-hour/helpers/generate-type";
+import { getLogger } from "magic-hour/logger";
+import { FilesClient } from "magic-hour/resources/v1/files";
+import { ImageProjectsClient } from "magic-hour/resources/v1/image-projects";
+import * as requests from "magic-hour/resources/v1/body-swap/request-types";
+import * as types from "magic-hour/types";
+import { Schemas$V1BodySwapCreateBody } from "magic-hour/types/v1-body-swap-create-body";
+import { Schemas$V1BodySwapCreateResponse } from "magic-hour/types/v1-body-swap-create-response";
+
+type GenerateRequest = GenerateRequestType<
+ requests.CreateRequest,
+ {
+ /**
+ * Image of the person to place into the scene. This value is either
+ * - a direct URL to the image file
+ * - a path to a local file
+ *
+ * Note: if the path begins with `api-assets`, it will be assumed to already be uploaded to Magic Hour's storage, and will not be uploaded again.
+ */
+ personFilePath: string;
+ /**
+ * Target scene image (background). This value is either
+ * - a direct URL to the image file
+ * - a path to a local file
+ *
+ * Note: if the path begins with `api-assets`, it will be assumed to already be uploaded to Magic Hour's storage, and will not be uploaded again.
+ */
+ sceneFilePath: string;
+ }
+>;
+
+export class BodySwapClient extends CoreResourceClient {
+ constructor(coreClient: CoreClient, opts: ResourceClientOptions) {
+ super(coreClient, opts);
+ }
+
+ /**
+ * Body Swap
+ *
+ * Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward).
+ *
+ * This method provides a convenient way to create a request and automatically wait for completion and download outputs.
+ *
+ * @example
+ * ```typescript
+ * import { Client } from "magic-hour";
+ *
+ * const client = new Client({ token: process.env["API_TOKEN"]!! });
+ * const res = await client.v1.bodySwap.generate(
+ * {
+ * assets: {
+ * personFilePath: "/path/to/person.png",
+ * sceneFilePath: "/path/to/scene.png",
+ * },
+ * name: "My Body Swap image",
+ * resolution: "1k",
+ * },
+ * {
+ * waitForCompletion: true,
+ * downloadOutputs: true,
+ * downloadDirectory: ".",
+ * },
+ * );
+ * ```
+ */
+ async generate(request: GenerateRequest, opts: GenerateOptions = {}) {
+ const {
+ waitForCompletion = true,
+ downloadOutputs = true,
+ downloadDirectory = undefined,
+ ...createOpts
+ } = opts;
+
+ const fileClient = new FilesClient(this._client, this._opts);
+ const { personFilePath, sceneFilePath, ...restAssets } = request.assets;
+
+ getLogger().debug(
+ `Uploading file ${personFilePath} to Magic Hour's storage`,
+ );
+ getLogger().debug(
+ `Uploading file ${sceneFilePath} to Magic Hour's storage`,
+ );
+
+ const [uploadedPersonFilePath, uploadedSceneFilePath] = await Promise.all([
+ fileClient.uploadFile(personFilePath),
+ fileClient.uploadFile(sceneFilePath),
+ ]);
+
+ getLogger().info(
+ `Uploaded file ${personFilePath} to Magic Hour's storage as ${uploadedPersonFilePath}`,
+ );
+ getLogger().info(
+ `Uploaded file ${sceneFilePath} to Magic Hour's storage as ${uploadedSceneFilePath}`,
+ );
+
+ const createResponse = await this.create(
+ {
+ ...request,
+ assets: {
+ ...restAssets,
+ personFilePath: uploadedPersonFilePath,
+ sceneFilePath: uploadedSceneFilePath,
+ },
+ },
+ createOpts,
+ );
+
+ getLogger().info(`Created BodySwapClient project ${createResponse.id}`);
+
+ const projectsClient = new ImageProjectsClient(this._client, this._opts);
+
+ getLogger().debug(
+ `Checking result for BodySwapClient project ${createResponse.id}`,
+ );
+
+ const result = await projectsClient.checkResult(
+ { id: createResponse.id },
+ {
+ waitForCompletion,
+ downloadOutputs,
+ downloadDirectory,
+ ...createOpts,
+ },
+ );
+
+ return result;
+ }
+
+ /**
+ * Body Swap
+ *
+ * Swap a person into a scene image using Nano Banana 2. Credits depend on `resolution` (from 100 credits at 640px upward).
+ *
+ * POST /v1/body-swap
+ */
+ create(
+ request: requests.CreateRequest,
+ opts?: RequestOptions,
+ ): ApiPromise {
+ return this._client.makeRequest({
+ method: "post",
+ path: "/v1/body-swap",
+ auth: ["bearerAuth"],
+ contentType: "application/json",
+ body: Schemas$V1BodySwapCreateBody.out.parse(request),
+ responseSchema: Schemas$V1BodySwapCreateResponse.in,
+ opts,
+ });
+ }
+}
diff --git a/src/resources/v1/image-to-video/README.md b/src/resources/v1/image-to-video/README.md
index fa5c6d6..f4b4922 100644
--- a/src/resources/v1/image-to-video/README.md
+++ b/src/resources/v1/image-to-video/README.md
@@ -85,7 +85,7 @@ For detailed examples, see the [product page](https://magichour.ai/products/imag
| `└─ endImageFilePath` | ✗ | — | The image to use as the last frame of the video. * **`ltx-2`**: Not supported * **`wan-2.2`**: Not supported * **`seedance`**: Supports 480p, 720p, 1080p. * **`seedance-2.0`**: Supports 480p, 720p. * **`kling-2.5`**: Supports 1080p. * **`kling-3.0`**: Supports 1080p. * **`sora-2`**: Not supported * **`veo3.1`**: Not supported * **`veo3.1-lite`**: Not supported Legacy models: * **`kling-1.6`**: Not supported | `"api-assets/id/1234.png"` |
| `└─ imageFilePath` | ✓ | — | The path of the image file. This value is either - a direct URL to the video file - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls). See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details. | `"api-assets/id/1234.png"` |
| `endSeconds` | ✓ | ✗ | The total duration of the output video in seconds. Supported durations depend on the chosen model: * **`ltx-2`**: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30 * **`wan-2.2`**: 3, 4, 5, 6, 7, 8, 9, 10, 15 * **`seedance`**: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 * **`seedance-2.0`**: 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 * **`kling-2.5`**: 5, 10 * **`kling-3.0`**: 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 * **`sora-2`**: 4, 8, 12, 24, 36, 48, 60 * **`veo3.1`**: 4, 6, 8, 16, 24, 32, 40, 48, 56 * **`veo3.1-lite`**: 8, 16, 24, 32, 40, 48, 56 Legacy models: * **`kling-1.6`**: 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60 | `5.0` |
-| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Not supported * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` |
+| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Automatically included with no extra credits * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` |
| `height` | ✗ | ✓ | `height` is deprecated and no longer influences the output video's resolution. This field is retained only for backward compatibility and will be removed in a future release. | `123` |
| `model` | ✗ | ✗ | The AI model to use for video generation. * `default`: uses our currently recommended model for general use. For paid tiers, defaults to `kling-3.0`. For free tiers, it defaults to `ltx-2`. * `ltx-2`: Fast iteration with audio and lip-sync * `wan-2.2`: Fast, strong visuals with effects * `seedance`: Fast iteration and start/end frames * `seedance-2.0`: State-of-the-art quality and consistency * `kling-2.5`: Motion, action, and camera control * `kling-3.0`: Cinematic, multi-scene storytelling * `sora-2`: Story-first concepts and creativity * `veo3.1`: Realistic visuals and prompt adherence * `veo3.1-lite`: Good for fast, affordable, high-quality daily generation. Legacy models: * `kling-1.6`: Reliable baseline with smooth motion If you specify the deprecated model value that includes the `-audio` suffix, this will be the same as included `audio` as `true`. | `"kling-3.0"` |
| `name` | ✗ | ✗ | Give your video a custom name for easy identification. | `"My Image To Video video"` |
diff --git a/src/resources/v1/image-to-video/request-types.ts b/src/resources/v1/image-to-video/request-types.ts
index b4a2fad..492e2ae 100644
--- a/src/resources/v1/image-to-video/request-types.ts
+++ b/src/resources/v1/image-to-video/request-types.ts
@@ -27,7 +27,7 @@ export type CreateRequest = {
* * **`ltx-2`**: Automatically included with no extra credits
* * **`wan-2.2`**: Not supported
* * **`seedance`**: Not supported
- * * **`seedance-2.0`**: Not supported
+ * * **`seedance-2.0`**: Automatically included with no extra credits
* * **`kling-2.5`**: Automatically included with no extra credits
* * **`kling-3.0`**: Toggle-able (can enable/disable)
* * **`sora-2`**: Automatically included with no extra credits
diff --git a/src/resources/v1/index.ts b/src/resources/v1/index.ts
index a273ee2..39ad6dd 100644
--- a/src/resources/v1/index.ts
+++ b/src/resources/v1/index.ts
@@ -13,6 +13,7 @@ export * as aiVoiceGenerator from "./ai-voice-generator";
export * as animation from "./animation";
export * as audioProjects from "./audio-projects";
export * as autoSubtitleGenerator from "./auto-subtitle-generator";
+export * as bodySwap from "./body-swap";
export * as faceDetection from "./face-detection";
export * as faceSwap from "./face-swap";
export * as faceSwapPhoto from "./face-swap-photo";
diff --git a/src/resources/v1/resource-client.ts b/src/resources/v1/resource-client.ts
index eb047b6..7403bc2 100644
--- a/src/resources/v1/resource-client.ts
+++ b/src/resources/v1/resource-client.ts
@@ -19,6 +19,7 @@ import { AiVoiceGeneratorClient } from "magic-hour/resources/v1/ai-voice-generat
import { AnimationClient } from "magic-hour/resources/v1/animation";
import { AudioProjectsClient } from "magic-hour/resources/v1/audio-projects";
import { AutoSubtitleGeneratorClient } from "magic-hour/resources/v1/auto-subtitle-generator";
+import { BodySwapClient } from "magic-hour/resources/v1/body-swap";
import { FaceDetectionClient } from "magic-hour/resources/v1/face-detection";
import { FaceSwapClient } from "magic-hour/resources/v1/face-swap";
import { FaceSwapPhotoClient } from "magic-hour/resources/v1/face-swap-photo";
@@ -34,6 +35,7 @@ import { VideoProjectsClient } from "magic-hour/resources/v1/video-projects";
import { VideoToVideoClient } from "magic-hour/resources/v1/video-to-video";
export class V1Client extends CoreResourceClient {
+ private _bodySwapLazy?: BodySwapClient; // lazy-loading cache
private _headSwapLazy?: HeadSwapClient; // lazy-loading cache
private _aiVoiceClonerLazy?: AiVoiceClonerClient; // lazy-loading cache
private _audioProjectsLazy?: AudioProjectsClient; // lazy-loading cache
@@ -82,6 +84,7 @@ export class V1Client extends CoreResourceClient {
this.animation;
this.audioProjects;
this.autoSubtitleGenerator;
+ this.bodySwap;
this.faceDetection;
this.faceSwap;
this.faceSwapPhoto;
@@ -400,4 +403,14 @@ export class V1Client extends CoreResourceClient {
))
);
}
+
+ get bodySwap(): BodySwapClient {
+ return (
+ this._bodySwapLazy ??
+ (this._bodySwapLazy = new (require("./body-swap").BodySwapClient)(
+ this._client,
+ this._opts,
+ ))
+ );
+ }
}
diff --git a/src/resources/v1/text-to-video/README.md b/src/resources/v1/text-to-video/README.md
index 7e41865..202b2b0 100644
--- a/src/resources/v1/text-to-video/README.md
+++ b/src/resources/v1/text-to-video/README.md
@@ -87,7 +87,7 @@ For detailed examples, see the [product page](https://magichour.ai/products/text
| `└─ prompt` | ✓ | — | The prompt used for the video. | `"a dog running"` |
| `└─ qualityMode` | ✗ | ✓ | DEPRECATED: Please use `resolution` field instead. For backward compatibility: * `quick` maps to 720p resolution * `studio` maps to 1080p resolution This field will be removed in a future version. Use the `resolution` field to directly to specify the resolution. | `"quick"` |
| `aspectRatio` | ✗ | ✗ | Determines the aspect ratio of the output video. * **`ltx-2`**: Supports 9:16, 16:9, 1:1. * **`wan-2.2`**: Supports 9:16, 16:9, 1:1. * **`seedance`**: Supports 9:16, 16:9, 1:1. * **`seedance-2.0`**: Supports 9:16, 16:9, 1:1. * **`kling-2.5`**: Supports 9:16, 16:9, 1:1. * **`kling-3.0`**: Supports 9:16, 16:9, 1:1. * **`sora-2`**: Supports 9:16, 16:9. * **`veo3.1`**: Supports 9:16, 16:9. * **`veo3.1-lite`**: Supports 9:16, 16:9. Legacy models: * **`kling-1.6`**: Supports 9:16, 16:9, 1:1. | `"16:9"` |
-| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Not supported * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` |
+| `audio` | ✗ | ✗ | Whether to include audio in the video. Defaults to `false` if not specified. Audio support varies by model: * **`ltx-2`**: Automatically included with no extra credits * **`wan-2.2`**: Not supported * **`seedance`**: Not supported * **`seedance-2.0`**: Automatically included with no extra credits * **`kling-2.5`**: Automatically included with no extra credits * **`kling-3.0`**: Toggle-able (can enable/disable) * **`sora-2`**: Automatically included with no extra credits * **`veo3.1`**: Toggle-able (can enable/disable) * **`veo3.1-lite`**: Toggle-able (can enable/disable) Legacy models: * **`kling-1.6`**: Not supported | `true` |
| `model` | ✗ | ✗ | The AI model to use for video generation. * `default`: uses our currently recommended model for general use. For paid tiers, defaults to `kling-3.0`. For free tiers, it defaults to `ltx-2`. * `ltx-2`: Fast iteration with audio and lip-sync * `wan-2.2`: Fast, strong visuals with effects * `seedance`: Fast iteration and start/end frames * `seedance-2.0`: State-of-the-art quality and consistency * `kling-2.5`: Motion, action, and camera control * `kling-3.0`: Cinematic, multi-scene storytelling * `sora-2`: Story-first concepts and creativity * `veo3.1`: Realistic visuals and prompt adherence * `veo3.1-lite`: Good for fast, affordable, high-quality daily generation. Legacy models: * `kling-1.6`: Reliable baseline with smooth motion If you specify the deprecated model value that includes the `-audio` suffix, this will be the same as included `audio` as `true`. | `"kling-3.0"` |
| `name` | ✗ | ✗ | Give your video a custom name for easy identification. | `"My Text To Video video"` |
| `orientation` | ✗ | ✓ | Deprecated. Use `aspect_ratio` instead. | `"landscape"` |
diff --git a/src/resources/v1/text-to-video/request-types.ts b/src/resources/v1/text-to-video/request-types.ts
index 39c09a6..c79ca9f 100644
--- a/src/resources/v1/text-to-video/request-types.ts
+++ b/src/resources/v1/text-to-video/request-types.ts
@@ -35,7 +35,7 @@ export type CreateRequest = {
* * **`ltx-2`**: Automatically included with no extra credits
* * **`wan-2.2`**: Not supported
* * **`seedance`**: Not supported
- * * **`seedance-2.0`**: Not supported
+ * * **`seedance-2.0`**: Automatically included with no extra credits
* * **`kling-2.5`**: Automatically included with no extra credits
* * **`kling-3.0`**: Toggle-able (can enable/disable)
* * **`sora-2`**: Automatically included with no extra credits
diff --git a/src/types/index.ts b/src/types/index.ts
index c3b1b99..cee8407 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -74,6 +74,10 @@ export { V1AutoSubtitleGeneratorCreateBodyStyle } from "./v1-auto-subtitle-gener
export { V1AutoSubtitleGeneratorCreateBodyStyleCustomConfig } from "./v1-auto-subtitle-generator-create-body-style-custom-config";
export { V1AutoSubtitleGeneratorCreateBodyStyleTemplateEnum } from "./v1-auto-subtitle-generator-create-body-style-template-enum";
export { V1AutoSubtitleGeneratorCreateResponse } from "./v1-auto-subtitle-generator-create-response";
+export { V1BodySwapCreateBody } from "./v1-body-swap-create-body";
+export { V1BodySwapCreateBodyAssets } from "./v1-body-swap-create-body-assets";
+export { V1BodySwapCreateBodyResolutionEnum } from "./v1-body-swap-create-body-resolution-enum";
+export { V1BodySwapCreateResponse } from "./v1-body-swap-create-response";
export { V1FaceDetectionCreateBody } from "./v1-face-detection-create-body";
export { V1FaceDetectionCreateBodyAssets } from "./v1-face-detection-create-body-assets";
export { V1FaceDetectionCreateResponse } from "./v1-face-detection-create-response";
diff --git a/src/types/v1-ai-image-generator-create-body-model-enum.ts b/src/types/v1-ai-image-generator-create-body-model-enum.ts
index 27b071c..c828f9b 100644
--- a/src/types/v1-ai-image-generator-create-body-model-enum.ts
+++ b/src/types/v1-ai-image-generator-create-body-model-enum.ts
@@ -22,7 +22,7 @@
* - `nano-banana-2` - from 100 credits/image
* - Supported resolutions: 640px, 1k, 2k, 4k
* - Available for tiers: free, creator, pro, business
- * - Image count allowed: 1, 2, 3, 4
+ * - Image count allowed: 1, 4, 9, 16
* - `nano-banana-pro` - from 150 credits/image
* - Supported resolutions: 1k, 2k, 4k
* - Available for tiers: creator, pro, business
diff --git a/src/types/v1-ai-image-generator-create-body.ts b/src/types/v1-ai-image-generator-create-body.ts
index f6ad3ae..80c648a 100644
--- a/src/types/v1-ai-image-generator-create-body.ts
+++ b/src/types/v1-ai-image-generator-create-body.ts
@@ -43,7 +43,7 @@ export type V1AiImageGeneratorCreateBody = {
* - `nano-banana-2` - from 100 credits/image
* - Supported resolutions: 640px, 1k, 2k, 4k
* - Available for tiers: free, creator, pro, business
- * - Image count allowed: 1, 2, 3, 4
+ * - Image count allowed: 1, 4, 9, 16
* - `nano-banana-pro` - from 150 credits/image
* - Supported resolutions: 1k, 2k, 4k
* - Available for tiers: creator, pro, business
diff --git a/src/types/v1-body-swap-create-body-assets.ts b/src/types/v1-body-swap-create-body-assets.ts
new file mode 100644
index 0000000..436814c
--- /dev/null
+++ b/src/types/v1-body-swap-create-body-assets.ts
@@ -0,0 +1,80 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * Person image and scene image for body swap
+ */
+export type V1BodySwapCreateBodyAssets = {
+ /**
+ * Image of the person to place into the scene. This value is either
+ * - a direct URL to the video file
+ * - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls).
+ *
+ * See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details.
+ *
+ */
+ personFilePath: string;
+ /**
+ * Target scene image (background). This value is either
+ * - a direct URL to the video file
+ * - `file_path` field from the response of the [upload urls API](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls).
+ *
+ * See the [file upload guide](https://docs.magichour.ai/api-reference/files/generate-asset-upload-urls#input-file) for details.
+ *
+ */
+ sceneFilePath: string;
+};
+
+/**
+ * @internal
+ * V1BodySwapCreateBodyAssets without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1BodySwapCreateBodyAssets = {
+ person_file_path: string;
+ scene_file_path: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1BodySwapCreateBodyAssets
+ */
+const SchemaIn$V1BodySwapCreateBodyAssets: z.ZodType<
+ V1BodySwapCreateBodyAssets, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ person_file_path: z.string(),
+ scene_file_path: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ person_file_path: "personFilePath",
+ scene_file_path: "sceneFilePath",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1BodySwapCreateBodyAssets
+ */
+const SchemaOut$V1BodySwapCreateBodyAssets: z.ZodType<
+ External$V1BodySwapCreateBodyAssets, // output type of this zod object
+ z.ZodTypeDef,
+ V1BodySwapCreateBodyAssets // the object to be transformed
+> = z
+ .object({
+ personFilePath: z.string(),
+ sceneFilePath: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ personFilePath: "person_file_path",
+ sceneFilePath: "scene_file_path",
+ });
+ });
+
+export const Schemas$V1BodySwapCreateBodyAssets = {
+ in: SchemaIn$V1BodySwapCreateBodyAssets,
+ out: SchemaOut$V1BodySwapCreateBodyAssets,
+};
diff --git a/src/types/v1-body-swap-create-body-resolution-enum.ts b/src/types/v1-body-swap-create-body-resolution-enum.ts
new file mode 100644
index 0000000..aff29d3
--- /dev/null
+++ b/src/types/v1-body-swap-create-body-resolution-enum.ts
@@ -0,0 +1,4 @@
+/**
+ * Output resolution. Determines credits charged for the run.
+ */
+export type V1BodySwapCreateBodyResolutionEnum = "1k" | "2k" | "4k" | "640px";
diff --git a/src/types/v1-body-swap-create-body.ts b/src/types/v1-body-swap-create-body.ts
new file mode 100644
index 0000000..143e0a2
--- /dev/null
+++ b/src/types/v1-body-swap-create-body.ts
@@ -0,0 +1,85 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+import {
+ External$V1BodySwapCreateBodyAssets,
+ Schemas$V1BodySwapCreateBodyAssets,
+ V1BodySwapCreateBodyAssets,
+} from "./v1-body-swap-create-body-assets";
+
+/**
+ * V1BodySwapCreateBody
+ */
+export type V1BodySwapCreateBody = {
+ /**
+ * Person image and scene image for body swap
+ */
+ assets: V1BodySwapCreateBodyAssets;
+ /**
+ * Give your image a custom name for easy identification.
+ */
+ name?: string | undefined;
+ /**
+ * Output resolution. Determines credits charged for the run.
+ */
+ resolution: "1k" | "2k" | "4k" | "640px";
+};
+
+/**
+ * @internal
+ * V1BodySwapCreateBody without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1BodySwapCreateBody = {
+ assets: External$V1BodySwapCreateBodyAssets;
+ name?: string | undefined;
+ resolution: "1k" | "2k" | "4k" | "640px";
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1BodySwapCreateBody
+ */
+const SchemaIn$V1BodySwapCreateBody: z.ZodType<
+ V1BodySwapCreateBody, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ assets: Schemas$V1BodySwapCreateBodyAssets.in,
+ name: z.string().optional(),
+ resolution: z.enum(["1k", "2k", "4k", "640px"]),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ name: "name",
+ resolution: "resolution",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1BodySwapCreateBody
+ */
+const SchemaOut$V1BodySwapCreateBody: z.ZodType<
+ External$V1BodySwapCreateBody, // output type of this zod object
+ z.ZodTypeDef,
+ V1BodySwapCreateBody // the object to be transformed
+> = z
+ .object({
+ assets: Schemas$V1BodySwapCreateBodyAssets.out,
+ name: z.string().optional(),
+ resolution: z.enum(["1k", "2k", "4k", "640px"]),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ assets: "assets",
+ name: "name",
+ resolution: "resolution",
+ });
+ });
+
+export const Schemas$V1BodySwapCreateBody = {
+ in: SchemaIn$V1BodySwapCreateBody,
+ out: SchemaOut$V1BodySwapCreateBody,
+};
diff --git a/src/types/v1-body-swap-create-response.ts b/src/types/v1-body-swap-create-response.ts
new file mode 100644
index 0000000..5d8d852
--- /dev/null
+++ b/src/types/v1-body-swap-create-response.ts
@@ -0,0 +1,81 @@
+import { zodTransform } from "make-api-request-js";
+import * as z from "zod";
+
+/**
+ * Success
+ */
+export type V1BodySwapCreateResponse = {
+ /**
+ * The amount of credits deducted from your account to generate the image. We charge credits right when the request is made.
+ *
+ * If an error occurred while generating the image(s), credits will be refunded and this field will be updated to include the refund.
+ */
+ creditsCharged: number;
+ /**
+ * Deprecated: Previously represented the number of frames (original name of our credit system) used for image generation. Use 'credits_charged' instead.
+ */
+ frameCost: number;
+ /**
+ * Unique ID of the image. Use it with the [Get image Project API](https://docs.magichour.ai/api-reference/image-projects/get-image-details) to fetch status and downloads.
+ */
+ id: string;
+};
+
+/**
+ * @internal
+ * V1BodySwapCreateResponse without any key transformation, this is what
+ * we expect to come in as network data
+ */
+export type External$V1BodySwapCreateResponse = {
+ credits_charged: number;
+ frame_cost: number;
+ id: string;
+};
+
+/**
+ * Takes network data, validates it, and transforms keys to match typescript object V1BodySwapCreateResponse
+ */
+const SchemaIn$V1BodySwapCreateResponse: z.ZodType<
+ V1BodySwapCreateResponse, // output type of this zod object
+ z.ZodTypeDef,
+ unknown
+> = z
+ .object({
+ credits_charged: z.number().int(),
+ frame_cost: z.number().int(),
+ id: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ credits_charged: "creditsCharged",
+ frame_cost: "frameCost",
+ id: "id",
+ });
+ });
+
+/**
+ * @internal
+ * Takes typescript data, validates it, and maps keys to match the expected external object External$V1BodySwapCreateResponse
+ */
+const SchemaOut$V1BodySwapCreateResponse: z.ZodType<
+ External$V1BodySwapCreateResponse, // output type of this zod object
+ z.ZodTypeDef,
+ V1BodySwapCreateResponse // the object to be transformed
+> = z
+ .object({
+ creditsCharged: z.number().int(),
+ frameCost: z.number().int(),
+ id: z.string(),
+ })
+ .transform((obj) => {
+ return zodTransform(obj, {
+ creditsCharged: "credits_charged",
+ frameCost: "frame_cost",
+ id: "id",
+ });
+ });
+
+export const Schemas$V1BodySwapCreateResponse = {
+ in: SchemaIn$V1BodySwapCreateResponse,
+ out: SchemaOut$V1BodySwapCreateResponse,
+};
diff --git a/src/types/v1-image-to-video-create-body.ts b/src/types/v1-image-to-video-create-body.ts
index 8a501f1..a3a3a7a 100644
--- a/src/types/v1-image-to-video-create-body.ts
+++ b/src/types/v1-image-to-video-create-body.ts
@@ -27,7 +27,7 @@ export type V1ImageToVideoCreateBody = {
* * **`ltx-2`**: Automatically included with no extra credits
* * **`wan-2.2`**: Not supported
* * **`seedance`**: Not supported
- * * **`seedance-2.0`**: Not supported
+ * * **`seedance-2.0`**: Automatically included with no extra credits
* * **`kling-2.5`**: Automatically included with no extra credits
* * **`kling-3.0`**: Toggle-able (can enable/disable)
* * **`sora-2`**: Automatically included with no extra credits
diff --git a/src/types/v1-text-to-video-create-body.ts b/src/types/v1-text-to-video-create-body.ts
index f33f5a2..54e692f 100644
--- a/src/types/v1-text-to-video-create-body.ts
+++ b/src/types/v1-text-to-video-create-body.ts
@@ -35,7 +35,7 @@ export type V1TextToVideoCreateBody = {
* * **`ltx-2`**: Automatically included with no extra credits
* * **`wan-2.2`**: Not supported
* * **`seedance`**: Not supported
- * * **`seedance-2.0`**: Not supported
+ * * **`seedance-2.0`**: Automatically included with no extra credits
* * **`kling-2.5`**: Automatically included with no extra credits
* * **`kling-3.0`**: Toggle-able (can enable/disable)
* * **`sora-2`**: Automatically included with no extra credits
diff --git a/test/v1-body-swap.test.ts b/test/v1-body-swap.test.ts
new file mode 100644
index 0000000..9938cbd
--- /dev/null
+++ b/test/v1-body-swap.test.ts
@@ -0,0 +1,37 @@
+import { Client, Environment } from "magic-hour";
+
+describe("tests client.v1.bodySwap.create", () => {
+ test.concurrent(
+ "POST /v1/body-swap | testId: success_all_params | Success test with response schema validation. Expects status code 200",
+ async () => {
+ const client = new Client({
+ token: "API_TOKEN",
+ environment: Environment.MockServer,
+ });
+ // Get both raw response for status and parsed response for data
+ const [rawResponse, response] = await Promise.all([
+ client.v1.bodySwap
+ .create({
+ assets: {
+ personFilePath: "api-assets/id/1234.png",
+ sceneFilePath: "api-assets/id/5678.png",
+ },
+ name: "My Body Swap image",
+ resolution: "1k",
+ })
+ .asResponse(),
+ client.v1.bodySwap.create({
+ assets: {
+ personFilePath: "api-assets/id/1234.png",
+ sceneFilePath: "api-assets/id/5678.png",
+ },
+ name: "My Body Swap image",
+ resolution: "1k",
+ }),
+ ]);
+ expect(rawResponse.status).toBe(200); // Exact status code match
+ // Response body automatically validated by Zod schema during deserialization
+ expect(response).toBeDefined();
+ },
+ );
+});