Generate

Custom LoRAs

Upload your own LoRA once, then use it in any generation request.

Upload your own LoRA, then pass its ID as adapter in a normal generation request. Custom LoRAs are available through the REST API.

Generating with a custom LoRA costs extra. See Pricing for the rates.

Supported today
ModelMiniMax-H3 only, for now
T2V, I2VLoRAs trained on the FL2VA base
R2VLoRAs trained on the Ref2VA base
Resolution480p, 768p, 1080p
Duration1-15 seconds
Request modeAsynchronous only (no sync mode, no long video)

Prepare your LoRA

An upload is one .safetensors weights file plus two small JSON configs: one describing the file, and one describing how to sample with it. Each upload has one of each, sent as the adapter_config and inference_config objects in the reserve request.

adapter_config

Says which base the LoRA was trained on and how its tensors are laid out.

{
  "base_model": "minimax-h3",
  "base_partition": "FL2VA",
  "export_format": "h3-native-v1",
  "scaling": { "mode": "unit" }
}
FieldValues
base_modelminimax-h3
base_partitionFL2VA for T2V and I2V, Ref2VA for R2V. To use one LoRA for both, upload it once per base.
scaling{"mode": "unit"} for no scaling, {"mode": "alpha_over_rank", "alpha": <number>}, or {"mode": "alpha_tensor_over_rank"} when the file stores an .alpha tensor per layer.
export_formatHow your file names its tensors, see below.

Each layer needs a .lora_A.weight and .lora_B.weight pair in BF16.

export_formatUse when your tensor names look like
machgen-h3-direct-v1diffusion_model. prefix, separate Q/K/V (common for ComfyUI exports)
h3-native-v1Native H3 module names, no prefix
h3-comfy-packed-v1One packed QKV projection per block (ComfyUI packed exports)
h3-diffusers-hyperflow-v1Diffusers-style split Q/K/V with HyperFlow time embedders

inference_config

Sets the default strength, an optional trigger phrase, and the sampling recipe your LoRA was trained for.

{
  "default_strength": 1.0,
  "trigger_prompt": "",
  "sampling": {
    "num_inference_steps": 6,
    "guidance_scale": 1.0,
    "video_shift": 12.0,
    "audio_shift": 3.0,
    "sampler": "euler",
    "sigma_grid": "linspace"
  }
}
  • trigger_prompt is added to the start of every prompt for you. Leave it empty if your LoRA has no trigger word.
  • guidance_scale must be 1.0.
  • sampler is euler or res_multistep. sigma_grid is linspace, simple or fixed.
  • res_multistep needs sigma_grid: "simple". simple and fixed need audio_shift: 3.0.
  • A fixed grid also needs fixed_sigmas: steps + 1 values decreasing from 1 to 0.

Upload

Uploading takes three calls: reserve, send the file, commit.

1. Reserve

POST /api/v0/adapters?model=MiniMax-H3 with the file's size and MD5 (md5sum on Linux, md5 -q on macOS) and both configs.

curl -X POST "https://api.machgen.ai/api/v0/adapters?model=MiniMax-H3" \
  -H "Authorization: Bearer $MACHGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "size_bytes": 779849816,
    "md5": "<md5 of the file>",
    "adapter_config": {...},
    "inference_config": {...}
  }'

The response carries your LoRA's ID, which starts with up-, and a one-time upload_url:

{
  "adapter": "up-3f9c0e5b7a2d41e8c6f0b9a1d2e3c4b5a6f7e8d9",
  "model": "MiniMax-H3",
  "status": "pending",
  "size_bytes": 779849816,
  "created_at": 1791820800,
  "expires_at": 1792425600,
  "chunk_size": 8388608,
  "upload_url": "https://storage.googleapis.com/upload/..."
}

The ID is derived from the file and its configs, so reserving the same upload again returns the same ID. Each reserve hands out a fresh upload_url and retires the previous one, so always send the file to the latest URL.

upload_url is null when the file already arrived; go straight to commit. If status is already ready, there is nothing left to do.

2. Send the file

PUT the weights to upload_url. Treat the URL like a password: anyone holding it can write your file. It stays valid for 7 days.

curl -X PUT -H "Content-Type: application/octet-stream" \
  --upload-file my_lora.safetensors "$UPLOAD_URL"

For large files, upload_url also accepts resumable uploads in pieces that are multiples of chunk_size, so an interrupted transfer can pick up where it stopped.

3. Commit

Checks the file and makes the LoRA ready to use.

curl -X POST "https://api.machgen.ai/api/v0/adapters/$LORA_ID/commit" \
  -H "Authorization: Bearer $MACHGEN_API_KEY"

It returns the same shape as reserve, with status: "ready". Commit is safe to repeat.

Generate

Add your LoRA's ID as adapter in a normal MiniMax-H3 request. Use an FL2VA LoRA for T2V or I2V, and a Ref2VA LoRA for R2V.

curl https://api.machgen.ai/api/v0/generate \
  -H "Authorization: Bearer $MACHGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-H3",
    "task_type": "T2V",
    "adapter": "up-3f9c0e5b7a2d41e8c6f0b9a1d2e3c4b5a6f7e8d9",
    "prompt": "A paper boat drifting down a rainy street at dusk",
    "video_config": {"duration_secs": 5, "height": 768, "aspect_ratio": "16:9"}
  }'
  • The number of steps comes from your LoRA's inference_config. Leave infer_steps out of the request.
  • Your trigger_prompt, if set, is added to the prompt automatically.

The first generation with a newly uploaded LoRA takes longer while the LoRA is prepared.

Manage

Each LoRA is either pending (reserved, not committed yet) or ready (usable in requests). A pending upload expires 7 days after it was reserved. A ready LoRA stays until you delete it.

# All your LoRAs, plus how much of your storage you've used
curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
  https://api.machgen.ai/api/v0/adapters

# One LoRA
curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
  https://api.machgen.ai/api/v0/adapters/$LORA_ID

# Delete a LoRA (safe to repeat)
curl -X DELETE -H "Authorization: Bearer $MACHGEN_API_KEY" \
  https://api.machgen.ai/api/v0/adapters/$LORA_ID

The list response:

{
  "adapters": [
    {
      "adapter": "up-3f9c0e5b7a2d41e8c6f0b9a1d2e3c4b5a6f7e8d9",
      "model": "MiniMax-H3",
      "status": "ready",
      "size_bytes": 779849816,
      "created_at": 1791820800
    }
  ],
  "used_bytes": 779851204,
  "max_bytes": 15000000000,
  "used_count": 1,
  "max_count": 100
}

New requests with a deleted LoRA are refused right away. LoRAs are private to your account: nobody else can see or use them.

LimitValue
Weights file5 GB
Each config1 MB
Storage per account15 GB
LoRAs per account100

Troubleshooting

You seeWhat it meansWhat to do
409 on commit: weights have not been uploaded yetThe file didn't finish uploading, or a newer reserve replaced its URLReserve again, send the file to the new upload_url, then commit
400 on commit: size or MD5 does not matchThe file differs from what you reservedReserve again (same ID, new upload_url) and send the correct file
400 on commit: invalid safetensors headerThe file isn't a valid LoRA safetensors fileCheck the file and export it again
409 changed concurrently, retryTwo identical requests ran at the same timeRetry the call
409 storage quota exceededYour account is at 15 GB or 100 LoRAsDelete LoRAs you no longer need
404 adapter upload not foundWrong ID, deleted, or a pending upload older than 7 daysUpload again
400 No adapter named ... in your accountThe LoRA isn't ready, was deleted, or the ID has a typoCheck the ID with GET /api/v0/adapters
400 This adapter requires the ... task partitionAn FL2VA LoRA on R2V, or a Ref2VA LoRA on T2V / I2VUse a LoRA trained for that task type
400 infer_steps must match the uploaded inference_configThe request set a different step countLeave infer_steps out
Task fails with "Invalid customer H3 adapter"The file's tensors don't match MiniMax H3 or the chosen export_formatCheck export_format, scaling and base_partition, then upload again