{"record":{"id":"f6cb563c6b67fc5f","repo":"AlistGo/alist","slug":"access-token-expired-provide-a-refresh-token-toge","errorCode":null,"errorMessage":"access_token expired: provide a refresh_token together with clientID/clientSecret, or switch to client_credentials mode","messagePattern":"access_token expired: provide a refresh_token together with clientID/clientSecret, or switch to client_credentials mode","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/123_open/client.go","lineNumber":19,"sourceCode":"package _123Open\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/alist-org/alist/v3/internal/conf\"\n\t\"github.com/alist-org/alist/v3/internal/op\"\n\tpan123 \"github.com/okatu-loli/go-123pan\"\n)\n\n// tokenRefreshMargin is how long before expiry a token is proactively renewed.\nconst tokenRefreshMargin = 10 * time.Minute\n\nvar errNoRefreshCredentials = errors.New(\"access_token expired: provide a refresh_token together with clientID/clientSecret, or switch to client_credentials mode\")\n\n// newSDKClient builds the SDK client for the configured authentication mode.\nfunc (d *Open123) newSDKClient() (*pan123.Client, error) {\n\topts := []pan123.Option{\n\t\tpan123.WithHTTPClient(&http.Client{Timeout: 60 * time.Second}),\n\t\tpan123.WithUserAgent(\"AList/\" + conf.Version),\n\t}\n\tswitch d.AuthMode {\n\tcase AuthToken:\n\t\tif d.AccessToken == \"\" {\n\t\t\treturn nil, errors.New(\"access_token is required in token mode\")\n\t\t}\n\t\tc := pan123.NewWithToken(d.AccessToken, opts...)\n\t\t// expiry is unknown for an externally issued token; refresh on demand\n\t\tc.SetToken(d.AccessToken, d.tokenExpiry())\n\t\treturn c, nil\n\tcase AuthClientCredentials, \"\":\n\t\tif d.ClientID == \"\" || d.ClientSecret == \"\" {","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/123_open/client.go#L1-L37","documentation":"Returned when the HTTP GET for an strm link's URL (after optional absolutization) completes but returns a status >= 400. The transport worked; the remote origin refused the request (401/403 auth, 404 gone, 410 removed, 429 throttled, 5xx broken).","triggerScenarios":"Fetching content of a .strm target whose backing file was moved or deleted (404), requires expired credentials (401/403), rate limits the client (429), or the origin server errors (5xx).","commonSituations":"Expired signed URLs inside .strm files; media servers moved/renamed their paths; credentials rotated without regenerating .strm files; aggressive parallel opens hitting origin rate limits.","solutions":["Verify the URL in the .strm file still resolves in a browser or curl with the same headers.","For 401/403, add the required credentials/headers to the link's Header so the fetch includes them, or fix the target's access settings.","For 429/5xx, retry with backoff and reduce concurrent stream opens.","Regenerate the .strm file against the current location of the media."],"exampleFix":"// before: plain link, origin returns 403\nlink := &model.Link{URL: u}\n\n// after: propagate needed auth header\nlink := &model.Link{\n\tURL:    u,\n\tHeader: http.Header{\"Authorization\": []string{\"Bearer \" + token}},\n}","handlingStrategy":"retry","validationCode":"// optional pre-flight availability check\nif resp, err := base.RestyClient.R().SetContext(ctx).Head(link.URL); err == nil && resp.StatusCode() >= 400 {\n\t// do not attempt full read; surface broken-link error early\n}","typeGuard":"func isBrokenStatusErr(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"read url failed: status=\")\n}","tryCatchPattern":"err := readStrmTarget(ctx, link)\nif isBrokenStatusErr(err) {\n\tif isTransientStatus(err) {\n\t\t// retry with backoff (429/5xx)\n\t} else {\n\t\t// mark strm entry stale; do not retry (401/403/404/410)\n\t}\n}","preventionTips":["Attach required auth headers to the Link when the origin needs them.","Periodically validate .strm targets (batch HEAD checks) and prune dead entries.","Retry only 429/5xx; treat 401/403/404/410 as permanent."],"tags":["strm","http","upstream","content"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}