{"record":{"id":"d9827624491b1321","repo":"vercel/ai","slug":"cartesia-realtime-client-secrets-must-expire-betwe","errorCode":null,"errorMessage":"Cartesia realtime client secrets must expire between 1 and 3600 seconds.","messagePattern":"Cartesia realtime client secrets must expire between 1 and 3600 seconds\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cartesia/src/cartesia-realtime-model.ts","lineNumber":77,"sourceCode":"\n  constructor(\n    modelId: CartesiaRealtimeModelId,\n    config: CartesiaRealtimeModelConfig,\n  ) {\n    this.modelId = modelId;\n    this.provider = config.provider;\n    this.config = config;\n  }\n\n  async doCreateClientSecret(\n    options: RealtimeModelV4ClientSecretOptions,\n  ): Promise<RealtimeModelV4ClientSecretResult> {\n    const expiresIn = options.expiresAfterSeconds;\n    if (\n      expiresIn != null &&\n      (!Number.isInteger(expiresIn) || expiresIn <= 0 || expiresIn > 3600)\n    ) {\n      throw new Error(\n        'Cartesia realtime client secrets must expire between 1 and 3600 seconds.',\n      );\n    }\n\n    const sessionConfig = options.sessionConfig;\n    const language = sessionConfig?.inputAudioTranscription?.language;\n    if (language != null && language !== 'en') {\n      throw new Error('Cartesia Ink 2 currently supports English only.');\n    }\n\n    const fetchFn = this.config.fetch ?? fetch;\n    const response = await fetchFn(`${this.config.baseURL}/access-token`, {\n      method: 'POST',\n      headers: {\n        ...this.config.headers(),\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/cartesia/src/cartesia-realtime-model.ts#L59-L95","documentation":"doCreateClientSecret validates options.expiresAfterSeconds client-side before requesting an access token. If the value is provided but is not a positive integer within 1–3600, a plain Error is thrown. This mirrors the Cartesia API's own constraint on client-secret lifetimes.","triggerScenarios":"cartesia.realtime(modelId).doCreateClientSecret({ expiresAfterSeconds: 0 | -5 | 4000 | 12.5 | ... }) — any non-integer, non-positive, or >3600 value.","commonSituations":"Computing the expiry in minutes and passing seconds incorrectly (e.g. 7200 instead of 3600); passing 0 thinking it means 'no expiry' instead of omitting the field; passing a string parsed value that isn't an integer.","solutions":["Set expiresAfterSeconds to an integer between 1 and 3600, or omit the option entirely for the default expiry.","Clamp/validate the value before calling: Math.min(3600, Math.max(1, seconds)).","If 0 was intended as 'no expiry', remove the expiresAfterSeconds field instead."],"exampleFix":"// before\nconst secret = await model.doCreateClientSecret({ expiresAfterSeconds: 7200 });\n\n// after\nconst secret = await model.doCreateClientSecret({ expiresAfterSeconds: 3600 });","handlingStrategy":"validation","validationCode":"function isValidExpiry(s: unknown): boolean {\n  return s == null || (Number.isInteger(s) && s > 0 && s <= 3600);\n}\n// call before requesting the secret\nif (!isValidExpiry(opts.expiresAfterSeconds)) throw new RangeError('expiresAfterSeconds must be 1-3600 or omitted');","typeGuard":"function isValidExpirySeconds(v: unknown): v is number | undefined {\n  return v === undefined || (typeof v === 'number' && Number.isInteger(v) && v > 0 && v <= 3600);\n}","tryCatchPattern":"try {\n  const secret = await model.doCreateClientSecret(options);\n} catch (e) {\n  if (e instanceof RangeError || /1 and 3600 seconds/.test(String(e.message))) {\n    // clamp and retry once\n    const clamped = Math.min(3600, Math.max(1, Math.floor(Number(options.expiresAfterSeconds) || 3600)));\n    return model.doCreateClientSecret({ ...options, expiresAfterSeconds: clamped });\n  }\n  throw e;\n}","preventionTips":["Remember the unit is seconds, not minutes or milliseconds.","Omit expiresAfterSeconds rather than passing 0 when you want the default.","Clamp user/config-derived values to [1, 3600] before calling."],"tags":["validation","realtime","argument-error"],"backgroundTag":"invalid-parameter-range","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}