HeyPuter/puter · error · HttpError

field_invalid

field_invalid

Error message

Invalid model: ${modelId}. Expected: ${expected.join(', ')}

What it means

ElevenLabsTTSProvider gates the model on the cost table, not the advertised model list: if modelId (after defaulting to eleven_multilingual_v2) is not a key in ELEVENLABS_TTS_COSTS, it throws HTTP 400 (legacyCode field_invalid) with fields.key='model', fields.expected, and fields.got. Rationale (per source comment): an id we cannot price is an id we cannot bill for, but the vendor still bills.

Source

Thrown at src/backend/drivers/ai-tts/providers/elevenlabs/ElevenLabsTTSProvider.ts:225

            return { url: SAMPLE_AUDIO_URL, content_type: 'audio' };
        }

        if (typeof text !== 'string' || !text.trim()) {
            throw new HttpError(400, 'Missing required field: text', {
                legacyCode: 'field_required',
                fields: { key: 'text' },
            });
        }

        const voiceId = voiceArg || this.defaultVoiceId;
        const modelId = modelArg || DEFAULT_MODEL;

        // Gate on the cost table rather than the advertised model list: an id
        // we can't price is an id we can't bill for, and the vendor bills us
        // for it either way.
        if (!Object.hasOwn(ELEVENLABS_TTS_COSTS, modelId)) {
            const expected = Object.keys(ELEVENLABS_TTS_COSTS);
            throw new HttpError(
                400,
                `Invalid model: ${modelId}. Expected: ${expected.join(', ')}`,
                {
                    legacyCode: 'field_invalid',
                    fields: { key: 'model', expected, got: modelId },
                },
            );
        }

        const desiredFormat =
            output_format || response_format || DEFAULT_OUTPUT_FORMAT;

        const actor = Context.get('actor')!;
        const usageKey = `elevenlabs:${modelId}:character`;
        const ucentsPerChar = ELEVENLABS_TTS_COSTS[modelId];
        const totalCost = ucentsPerChar * text.length;

        const usageAllowed = await this.meteringService.hasEnoughCredits(

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Use a model present in the cost table: eleven_multilingual_v2, eleven_flash_v2_5, eleven_turbo_v2_5, eleven_v3 (read fields.expected from the error).
  2. Omit model to accept the default (eleven_multilingual_v2).
  3. Call list_engines({ provider: 'elevenlabs' }) to see supported/priced model ids.
  4. If you need a new model priced, add it to ELEVENLABS_TTS_COSTS in costs.js.

Example fix

// before
await driver.synthesize({ text: 'hi', provider: 'elevenlabs', model: 'eleven_monolingual_v1' });
// after
await driver.synthesize({ text: 'hi', provider: 'elevenlabs', model: 'eleven_multilingual_v2' });
Defensive patterns

Strategy: validation

Validate before calling

// Gate on the priced model set, mirroring ELEVENLABS_TTS_COSTS keys.
const PRICED_MODELS = ['eleven_multilingual_v2', 'eleven_flash_v2_5', 'eleven_turbo_v2_5', 'eleven_v3'];
function synthesizeElevenLabs(text, model = 'eleven_multilingual_v2') {
  if (!PRICED_MODELS.includes(model)) {
    throw new Error(`Unsupported model: ${model}. Use one of: ${PRICED_MODELS.join(', ')}`);
  }
  return driver.synthesize({ text, provider: 'elevenlabs', model });
}

Type guard

const PRICED_MODELS = ['eleven_multilingual_v2', 'eleven_flash_v2_5', 'eleven_turbo_v2_5', 'eleven_v3'] as const;
type ElevenLabsModel = typeof PRICED_MODELS[number];

const isElevenLabsModel = (v: unknown): v is ElevenLabsModel =>
  typeof v === 'string' && (PRICED_MODELS as readonly string[]).includes(v);

Prevention

When it happens

Trigger: Passing model: 'eleven_monolingual_v1' (not in the cost table) or any model id absent from ELEVENLABS_TTS_COSTS, including deprecated or preview ids that ElevenLabs accepts but Puter cannot price.

Common situations: Using an old/deprecated model id; passing a brand-new ElevenLabs model before the cost table is updated; typo in the model id; assuming a model is supported because ElevenLabs lists it, when Puter's cost table hasn't added it.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/a25f89c8a622cf93. Report an issue: GitHub.