{"record":{"id":"3dac99731d670598","repo":"chenhg5/cc-connect","slug":"api-returned-status-d-s","errorCode":null,"errorMessage":"api returned status %d: %s","messagePattern":"api returned status (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"platform/dingtalk/dingtalk.go","lineNumber":761,"sourceCode":"\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bodyBytes))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := p.httpClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"do request: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn \"\", fmt.Errorf(\"api returned status %d: %s\", resp.StatusCode, body)\n\t}\n\n\tvar tokenResp struct {\n\t\tAccessToken string `json:\"accessToken\"`\n\t\tExpireIn    int    `json:\"expireIn\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode response: %w\", err)\n\t}\n\n\tif tokenResp.AccessToken == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty accessToken in response\")\n\t}\n\n\t// Cache token with 5 minutes buffer before expiry.\n\t// When the server omits expireIn (or sends 0/negative), fall back to the\n\t// documented DingTalk default (7200s = 2h) — without this, tokenExpiry\n\t// would land at time.Now() and every subsequent getAccessToken() would","sourceCodeStart":743,"sourceCodeEnd":779,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L743-L779","documentation":"The DingTalk accessToken endpoint responded with a non-200 HTTP status; getAccessToken reads the response body (which usually contains a JSON error description) and includes both the status code and body in this error. This is an authentication/configuration failure at the DingTalk API level, not a transport issue.","triggerScenarios":"Any token-requiring call when DingTalk rejects the token request: wrong appKey/appSecret (typically 400 with badRequest), app disabled or permissions revoked, or DingTalk server-side errors (5xx).","commonSituations":"Typo or stale value in clientID/clientSecret in config.toml, rotated secret not updated in the running daemon, app deleted or disabled in the DingTalk developer console, or IP allowlist changes on the DingTalk app.","solutions":["Read the embedded body in the error — DingTalk's message (e.g. badRequest / appKey invalid) tells you the exact cause.","Verify appKey and appSecret against the DingTalk developer console; update config.toml and restart cc-connect.","Confirm the app is enabled, published, and the robot is active.","Check IP allowlist configuration on the DingTalk app if your server IP changed.","For 5xx statuses, wait and retry — this is a DingTalk-side outage."],"exampleFix":"// before: silent retry loop against a bad secret\nfor {\n    tok, err := p.getAccessToken()\n    if err != nil { time.Sleep(time.Second); continue }\n}\n\n// after: fail fast on auth errors, retry only server errors\ntok, err := p.getAccessToken()\nif err != nil {\n    if strings.Contains(err.Error(), \"status 4\") {\n        return fmt.Errorf(\"dingtalk auth failed, check appKey/appSecret: %w\", err)\n    }\n    return fmt.Errorf(\"dingtalk token: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if len(p.clientID) < 8 || len(p.clientSecret) < 8 {\n    return errors.New(\"dingtalk: appKey/appSecret look invalid before any API call\")\n}","typeGuard":null,"tryCatchPattern":"tok, err := p.getAccessToken()\nif err != nil {\n    if strings.Contains(err.Error(), \"api returned status 4\") {\n        // stop retrying; alert operator to fix credentials\n        return fmt.Errorf(\"dingtalk auth rejected — check appKey/appSecret: %w\", err)\n    }\n    return err // 5xx: retryable\n}","preventionTips":["Store credentials in config.toml via a tested template; validate at startup.","Update config immediately after any secret rotation and restart the daemon.","Keep the DingTalk app published/enabled with correct IP allowlist entries.","Alert on 4xx token failures; they never resolve by retrying."],"tags":["auth","dingtalk","http","api","credentials"],"backgroundTag":"api-error-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}