chenhg5/cc-connect · error
resource API status=
Error message
resource API status=%d body=%q
What it means
The Feishu resource API responded with a non-2xx HTTP status code during a single-GET download. The library reads up to 4 KiB of the response body and includes it in the error so the API's error message (e.g. invalid token, file not found, permission denied) is visible to the developer.
Solutions
- Read the body quoted in the error — Feishu's code/msg fields identify the exact cause (invalid token, not found, permission)
- Refresh the tenant_access_token and ensure the app has im:resource / im:message:readonly scopes
- Verify message_id and file_key belong to the same message and that the bot is a member of the chat
- For 5xx/429, retry with backoff; for 401, re-authenticate; for 404, ask the sender to re-share the file
- Check that the token type matches the API (tenant_access_token vs user_access_token)
Defensive patterns
Strategy: try-catch
Validate before calling
// keep token fresh and scopes granted before downloading
token, err := p.ensureFreshTenantToken(ctx)
if err != nil { return nil, fmt.Errorf("auth for resource download: %w", err) } Try / catch
data, err := p.resourceSingleGet(ctx, token, msgID, fileKey, resType)
if err != nil {
if strings.Contains(err.Error(), "resource API status=") {
// parse status + Feishu code from the quoted body:
// 401/99991663 -> refresh token; 404 -> re-request file; 5xx -> backoff retry
return classifyFeishuAPIError(err)
}
return err
} Prevention
- Refresh tenant_access_token proactively before expiry
- Grant the app im:resource / im:message:readonly scopes in the Feishu developer console
- Keep the bot in the chat whose message resources you download
- Apply backoff on 429/5xx instead of immediate retries
When it happens
Trigger: resourceSingleGet receives a status outside 200-299 from the resource endpoint — expired/invalid tenant_access_token (401/99991663-style errors), wrong message_id or file_key (404), missing im:resource permission, or Feishu server error (5xx).
Common situations: Token expired or fetched for the wrong tenant/app; file_key copied from a message in another chat or an expired resource; app lacks the im:message/im:resource scopes; Feishu incident causing 5xx; rate limiting returning 429.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/424dcbc116cfbc29.
Report an issue: GitHub.
Appendix: source
Thrown at platform/feishu/resource_download.go:269
// for small files and as a fallback when the size probe fails. We honour
// resourceMaxBytes via Content-Length + body cap so a misbehaving server
// can't blow up memory.
func (p *Platform) resourceSingleGet(ctx context.Context, token, messageID, fileKey, resType string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.resourceURL(messageID, fileKey, resType), nil)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
resp, err := p.resourceDownloadHTTP.Do(req)
if err != nil {
return nil, fmt.Errorf("resource request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4*1024))
return nil, fmt.Errorf("resource API status=%d body=%q", resp.StatusCode, strings.TrimSpace(string(body)))
}
if cl := resp.ContentLength; cl > p.resourceMaxBytes {
return nil, fmt.Errorf("resource too large: Content-Length=%d exceeds cap %d", cl, p.resourceMaxBytes)
}
// LimitReader caps the body too in case the server lies about
// Content-Length; we read up to cap+1 bytes to detect the lie.
data, err := io.ReadAll(io.LimitReader(resp.Body, p.resourceMaxBytes+1))
if err != nil {
return nil, fmt.Errorf("read resource: %w", err)
}
if int64(len(data)) > p.resourceMaxBytes {
return nil, fmt.Errorf("resource too large: body exceeds cap %d", p.resourceMaxBytes)
}
slog.Debug(p.tag()+": resource downloaded (single GET)",
"file_key", fileKey, "type", resType, "size", len(data))
return data, nilView on GitHub (pinned to 4000b2338a)