moeru-ai/airi · critical · Error

Failed to create Aliyun NLS token: ${response.Message || 'un

Error message

Failed to create Aliyun NLS token: ${response.Message || 'unknown error'}

What it means

Thrown by createAliyunNlsToken() (session.ts:125) after the signed POST to the Aliyun NLS CreateToken endpoint returns a body whose Token.Id is not a string or Token.ExpireTime is not a number. The Aliyun RPC response carries a Message field on failure; the code appends it (or 'unknown error') so the caller sees the provider's reason. This is the only path for credential/signature/region problems with the Aliyun NLS token minting step that precedes every transcription session.

Source

Thrown at server/apps/api/src/routes/audio-transcription-stream/session.ts:125

    RegionId: credentials.region,
    SignatureMethod: 'HMAC-SHA1',
    SignatureNonce: randomUUID(),
    SignatureVersion: '1.0',
    Timestamp: aliyunTimestamp(new Date()),
    Version: '2019-02-28',
  }
  const canonicalQuery = canonicalizeQuery(params)
  const signature = encodeURIComponent(signStringToBase64(createStringToSign('POST', '/', canonicalQuery), credentials.accessKeySecret))
  const endpoint = nlsMetaEndpointFromRegion(credentials.region).toString().replace(/\/$/, '')
  const response = await ofetch<{
    Token?: { ExpireTime?: number, Id?: string }
    Message?: string
  }>(`${endpoint}/?Signature=${signature}&${canonicalQuery}`, { method: 'POST' })

  if (typeof response.Token?.Id === 'string' && typeof response.Token?.ExpireTime === 'number')
    return { token: response.Token.Id, expiresAt: response.Token.ExpireTime * 1000 }

  throw new Error(`Failed to create Aliyun NLS token: ${response.Message || 'unknown error'}`)
}

function sse(payload: { delta: string, type: 'transcript.text.delta' | 'transcript.text.done' }): Uint8Array {
  return encoder.encode(`data: ${JSON.stringify(payload)}\n\n`)
}

function createClientEvent(credentials: AliyunNlsCredentials, name: 'StartTranscription' | 'StopTranscription', sessionId: string, payload?: AliyunNlsStartPayload) {
  return JSON.stringify({
    header: {
      appkey: credentials.appKey,
      message_id: randomUUID().replaceAll('-', ''),
      task_id: sessionId,
      namespace: 'SpeechTranscriber',
      name,
    },
    payload,
  })
}

View on GitHub (pinned to 27111382b4)

Solutions

  1. Decode response.Message: 'InvalidAccessKeyId'/'SignatureDoesNotMatch'/'Forbidden' all point to credentials — verify the AccessKeyId, AccessKeySecret, and that the key is enabled and has NLS permissions.
  2. Confirm credentials.region is one of the supported AliyunNlsRegion values (cn-shanghai / cn-beijing / cn-shenzhen, with or without -internal) and matches where the NLS app/appKey is provisioned — do not mix a public region key with an -internal endpoint or vice versa.
  3. Ensure the NLS service and the specific appKey are activated in the Aliyun console for that region.
  4. Check for clock skew: the signed Timestamp (session.ts:98-99,111) must be within Aliyun's allowed window — sync the host clock (NTP) if the server drifts.
  5. Enable request/response logging of the CreateToken call (status + Message) to capture the provider's exact reason instead of 'unknown error'.

Example fix

// before: signing against the wrong endpoint for the region/keys
const endpoint = nlsMetaEndpointFromRegion(credentials.region)
// credentials.region = 'cn-shanghai-internal' but running outside the VPC
// -> Failed to create Aliyun NLS token: ...

// after: use a public region when egress is outside Aliyun VPC
const credentials = {
  accessKeyId: process.env.ALIYUN_NLS_ACCESS_KEY_ID,
  accessKeySecret: process.env.ALIYUN_NLS_ACCESS_KEY_SECRET,
  appKey: process.env.ALIYUN_NLS_APP_KEY,
  region: process.env.ALIYUN_NLS_REGION ?? 'cn-shanghai', // not the -internal variant
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Fail fast in composition: require the four credential fields before the
// transcription route ever calls createAliyunNlsToken.
function assertAliyunNlsCredentials(c: Partial<AliyunNlsCredentials> | undefined): asserts c is AliyunNlsCredentials {
  if (!c?.accessKeyId || !c?.accessKeySecret || !c?.appKey || !c?.region) {
    throw new Error('Aliyun NLS credentials are not fully configured (accessKeyId/accessKeySecret/appKey/region).')
  }
}
// also: validate region is one of the supported enum values to avoid endpoint mismatch

Try / catch

// In the route handler, map token-creation failures to a clear 5xx with the
// provider Message, and surface clock/region hints without leaking the secret.
try {
  token = await createAliyunNlsToken(credentials)
}
catch (error) {
  const msg = errorMessageFromValue(error)
  // SignatureDoesNotMatch / InvalidAccessKeyId -> 503 (config), others -> 502
  const status = /Signature|AccessKey|Forbidden/i.test(msg) ? 503 : 502
  throw new ApiError(status, 'ALIYUN_NLS_TOKEN_FAILED', msg)
}

Prevention

When it happens

Trigger: Invalid or disabled Aliyun AccessKeyId/AccessKeySecret; the key lacks permission for the NLS CreateToken action; the signature is wrong because the region, endpoint, or canonical query string is mismatched (e.g. internal vs public region mismatch via nlsMetaEndpointFromRegion); the appKey/NLS project is not enabled for the account; clock skew between the server and Aliyun makes the signed Timestamp invalid.

Common situations: Fresh deployment where the Aliyun NLS env vars were not set or were copied from another region; a rotated AccessKey whose secret was not updated in the API's env; using a cn-*-internal region endpoint from outside the VPC; the NLS service was never activated for the Aliyun account; container/system clock drift breaks the signature Timestamp.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/c3d89cf3e63bc76f. Report an issue: GitHub.