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
- Decode response.Message: 'InvalidAccessKeyId'/'SignatureDoesNotMatch'/'Forbidden' all point to credentials — verify the AccessKeyId, AccessKeySecret, and that the key is enabled and has NLS permissions.
- 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.
- Ensure the NLS service and the specific appKey are activated in the Aliyun console for that region.
- 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.
- 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
- Validate all four credential fields (accessKeyId, accessKeySecret, appKey, region) at startup and fail the boot if any is missing.
- Restrict region to the supported AliyunNlsRegion set so an internal/public endpoint mismatch cannot produce a bad signature.
- Keep host clocks NTP-synced so the signed Timestamp stays inside Aliyun's allowed skew window.
- Log (never echo the secret) the CreateToken response Message on failure so the provider's reason is always available.
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
- Aliyun NLS credentials are incomplete.
- Failed to create token: ${JSON.stringify(response) || 'Unkno
- Nano Banana API Key not configured
- Nano Banana API Error
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/c3d89cf3e63bc76f.
Report an issue: GitHub.