{"record":{"id":"2928ed18aa49f037","repo":"Billionmail/BillionMail","slug":"lipsync-api-error-d-s","errorCode":null,"errorMessage":"lipsync API error %d: %s","messagePattern":"lipsync API error (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/lipsync.go","lineNumber":125,"sourceCode":"\t\tVideoURL:       videoURL,\n\t\tSynergizeAudio: true,\n\t}\n\n\thttpReq, err := BuildLipSyncRequest(cfg, req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\thttpReq = httpReq.WithContext(ctx)\n\n\tresp, err := cfg.doHTTP(httpReq)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"lipsync API call: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn \"\", fmt.Errorf(\"lipsync API error %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result LipSyncResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode lipsync response: %w\", err)\n\t}\n\treturn result.ID, nil\n}\n\n// CheckLipSyncStatus checks the status of a lip sync job.\nfunc CheckLipSyncStatus(ctx context.Context, cfg LipSyncConfig, jobID string) (*LipSyncResponse, error) {\n\thttpReq, err := BuildLipSyncStatusRequest(cfg, jobID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thttpReq = httpReq.WithContext(ctx)\n\n\tresp, err := cfg.doHTTP(httpReq)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/lipsync.go#L107-L143","documentation":"SubmitLipSync returns this when the lipsync provider responds with any status other than 200 or 201. The response body is read and embedded verbatim in the message, so the provider's own error text (auth errors, validation messages, quota info) is included after 'lipsync API error <code>: '. This is a server-side rejection, not a transport failure.","triggerScenarios":"Calling SubmitLipSync with an invalid/expired API key (401/403), malformed request body rejected by the provider (400/422), insufficient credits or quota (402/429), provider 5xx outage, or a provider API version change that altered the endpoint contract.","commonSituations":"API key rotated in provider dashboard but not updated in env/config; source/target asset URLs the provider cannot fetch; account out of credits; provider introduced new required field or deprecated an endpoint; rate limiting during bulk submissions.","solutions":["Read the embedded provider message after 'lipsync API error <code>: ' — it states the exact cause (auth, validation, quota)","For 401/403, rotate/verify the x-api-key value in your lipsync config/env","For 400/422, compare your request payload against the provider's current API docs (schema drift after version updates)","For 429/5xx, add retry with exponential backoff and rate limiting on submission volume","Check account credits/plan limits on the provider dashboard for 402/429"],"exampleFix":"// before\nid, err := SubmitLipSync(ctx, cfg, ...) // error: lipsync API error 401: {\"detail\":\"Invalid API key\"}\n// after\nid, err := SubmitLipSync(ctx, cfg, ...)\nif err != nil {\n    var apiErr *LipSyncAPIError // wrap status+body in a typed error at the call site\n    if errors.As(err, &apiErr) && apiErr.StatusCode == 401 {\n        log.Error(\"lipsync auth failed: check LIPSYNC_API_KEY\")\n    }\n}","handlingStrategy":"try-catch","validationCode":"// before calling\nif strings.TrimSpace(cfg.APIKey) == \"\" {\n    return fmt.Errorf(\"lipsync API key not configured\")\n}","typeGuard":"func isLipSyncAPIError(err error) (status int, body string, ok bool) {\n    var e *LipSyncAPIError\n    if errors.As(err, &e) {\n        return e.StatusCode, e.Body, true\n    }\n    return 0, \"\", false\n}","tryCatchPattern":"id, err := SubmitLipSync(ctx, cfg, ...)\nif err != nil {\n    if status, body, ok := isLipSyncAPIError(err); ok {\n        switch {\n        case status == 401 || status == 403:\n            // alert: invalid/rotated API key\n        case status == 429:\n            // backoff and retry later\n        case status >= 500:\n            // provider outage; retry with backoff\n        default:\n            // 4xx: log provider body for validation fix\n        }\n    }\n    return err\n}","preventionTips":["Rotate provider API keys via a single env/config source; alert on 401s","Validate request payloads against the provider's current API schema in CI","Track credit/quota levels and rate-limit bulk submissions","Monitor the provider status page; treat 5xx bursts as an outage signal"],"tags":["http","api-error","lipsync","third-party"],"backgroundTag":"http-non-2xx-response","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}