HeyGen 把自家的文本转语音模型 HeyGen Voice Turbo 放进 API 免费用,期限到 2026 年 11 月 30 日。免费的是一个单独的模型名 heygen-voice-1-turbo-free,写成 heygen-voice-1-turbo 或者不写 model,都照常计费。注册账号、建个 API Key 就能调,官方说不用绑信用卡。

消息出自 HeyGen 10 月 21 日更新的博客 HeyGen Voice Turbo: The #1-ranked voice model, now free in your API,具体规则写在开发者文档的 Text to Speech 和 HeyGen Voice 两页。下面的条款是 2026 年 10 月 22 日对着这几页核的。

三个模型名,只有一个不要钱

两个语音接口 POST /v3/models/audio/tts 和 POST /v3/models/audio/tts/stream 共用同一个请求体,靠 model 字段区分档位。文档给的对照是这样的:

model能用的音色文档的说法
heygen-voice-1(默认)公共音色、即时克隆、专业克隆标准速度,收费
heygen-voice-1-turbo公共音色、即时克隆更快,收费
heygen-voice-1-turbo-free公共音色、即时克隆Turbo 速度,免费,容量共享且有限,不保证响应时间和可用性

文档原话是 “These requests are free and do not require a credit balance”,同时补了一句 “Requests to the default model or paid Turbo are still charged”。也就是说免费只认这一个名字,账户里没有余额也能调。专业克隆的音色不在免费范围里,传了会回 400 invalid_parameter,param 指向 model。

免费请求被拒时也不会切到付费档。文档写的是 “A free request never switches to a paid model”,想用付费 Turbo 得自己重新发一个 model 为 heygen-voice-1-turbo 的请求。

限流有两层

第一层是你自己的请求上限,每个接口默认每个工作区成员每分钟 30 次,个别账户的上限可能不同。第二层是所有 Turbo Free 用户共用的一池容量,池子满了,哪怕你没到 30 次也会收到 429 rate_limit_exceeded。响应里带 Retry-After 就按它等,没带就指数退避。超时或者服务端出错时回的是 500。流式接口的这些错误都发生在第一段音频之前。

文本一次最多 5000 个字符。同步接口返回一个 44.1 kHz 单声道 PCM16 的 WAV 链接和时长,请求会一直挂着直到文件生成好,不用轮询。

想用自己的声音,可以从一段录音做即时克隆,预览期内创建即时音色免费。免费套餐的工作区另有创建限额,默认滚动 24 小时内克隆 20 次、音色设计 20 次,工作区成员共享。不想克隆的话,文档示例直接用公共音色 Nadine,voice_id 是 83548de556df48ba8c09c42a57c51d85。

用 Spring Boot 接一下

这里没用 SDK,直接用 Spring Boot 4.1.1 的 RestClient。鉴权走 X-Api-Key 请求头,Key 从环境变量读:

heygen.api-key=${HEYGEN_API_KEY:}
heygen.model=heygen-voice-1-turbo-free
heygen.voice-id=83548de556df48ba8c09c42a57c51d85
server.port=8080

客户端把 429 单独拎出来处理,有 Retry-After 就照着睡,没有就从 2 秒开始翻倍,最多试 3 次。其他错误原样抛出去:

package com.example.heygen;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClient;

@Component
public class HeyGenTtsClient {

    public record TtsRequest(String model,
                             @JsonProperty("voice_id") String voiceId,
                             String text,
                             String language) {}

    public record TtsData(@JsonProperty("audio_url") String audioUrl, double duration) {}

    public record TtsResponse(TtsData data) {}

    private final RestClient restClient;
    private final String model;
    private final String voiceId;

    public HeyGenTtsClient(RestClient.Builder builder,
                           @Value("${heygen.api-key}") String apiKey,
                           @Value("${heygen.model}") String model,
                           @Value("${heygen.voice-id}") String voiceId) {
        RestClient.Builder b = builder.baseUrl("https://api.heygen.com");
        if (!apiKey.isBlank()) {
            b = b.defaultHeader("X-Api-Key", apiKey);
        }
        this.restClient = b.build();
        this.model = model;
        this.voiceId = voiceId;
    }

    public TtsData speak(String text) throws InterruptedException {
        TtsRequest body = new TtsRequest(model, voiceId, text, "en");
        long waitSeconds = 2;
        for (int attempt = 1; ; attempt++) {
            try {
                return restClient.post()
                        .uri("/v3/models/audio/tts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(body)
                        .retrieve()
                        .body(TtsResponse.class)
                        .data();
            } catch (HttpClientErrorException e) {
                if (e.getStatusCode() != HttpStatus.TOO_MANY_REQUESTS || attempt >= 3) {
                    throw e;
                }
                String retryAfter = e.getResponseHeaders() == null ? null
                        : e.getResponseHeaders().getFirst("Retry-After");
                long sleep = retryAfter != null ? Long.parseLong(retryAfter) : waitSeconds;
                Thread.sleep(sleep * 1000);
                waitSeconds *= 2;
            }
        }
    }
}

再挂一个接口,把 HeyGen 的错误状态码和响应体原样透出来,方便看问题:

@RestController
public class TtsController {

    private final HeyGenTtsClient client;

    public TtsController(HeyGenTtsClient client) {
        this.client = client;
    }

    @GetMapping("/tts")
    public ResponseEntity<?> tts(@RequestParam String text) throws InterruptedException {
        try {
            return ResponseEntity.ok(client.speak(text));
        } catch (HttpStatusCodeException e) {
            return ResponseEntity.status(e.getStatusCode())
                    .body(e.getResponseBodyAsString());
        }
    }
}

依赖只有 spring-boot-starter-web 和 spring-boot-starter-restclient,在 JDK 21 下打包后启动:

2026-10-22T08:12:20.249+08:00  INFO 851245 --- [           main] c.example.heygen.HeyGenTtsApplication    : Starting HeyGenTtsApplication v0.0.1-SNAPSHOT using Java 21.0.12.1 with PID 851245
2026-10-22T08:12:21.717+08:00  INFO 851245 --- [           main] o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 8080 (http) with context path '/'
2026-10-22T08:12:21.727+08:00  INFO 851245 --- [           main] c.example.heygen.HeyGenTtsApplication    : Started HeyGenTtsApplication in 2.035 seconds (process running for 2.635)

这次手上没有 HeyGen 账号的 Key,所以没有拿到真实音频,只把请求真实发到了 api.heygen.com。不设 HEYGEN_API_KEY 时:

{"error":{"request_id":"728457cf52c047d7a4d8f5ab62dca253","code":"unauthorized","message":"API key or OAuth token required","doc_url":"https://developers.heygen.com/docs/error-codes#unauthorized"}}
HTTP 401

随便填一个假 Key 时:

{"error":{"request_id":"948ced4784054d05af6f72bfda1d5f9d","code":"unauthorized","message":"Invalid credentials","doc_url":"https://developers.heygen.com/docs/error-codes#unauthorized"}}
HTTP 401

两次都是 401,message 不同:前一个是没带凭证,后一个是凭证无效。文档写明 Turbo Free 用 API Key 或 OAuth 调用,不带凭证的请求会被直接拒绝。

拿到真 Key 后,成功响应长这样(取自官方文档示例):

{
  "data": {
    "audio_url": "https://files.heygen.ai/generated/model-speech.wav",
    "duration": 3.42
  }
}

按官方说法,免费期到 2026 年 11 月 30 日,政策有变化前会提前通知。之前也介绍过用 Spring Boot 集成免费的 EdgeTTS 和 集成 MiniMax、CosyVoice 实现文本转语音。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。