AlistGo/alist · error
access_token expired: provide a refresh_token together with
Error message
access_token expired: provide a refresh_token together with clientID/clientSecret, or switch to client_credentials mode
What it means
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).
Source
Thrown at drivers/123_open/client.go:19
package _123Open
import (
"context"
"errors"
"fmt"
"net/http"
"sync"
"time"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/op"
pan123 "github.com/okatu-loli/go-123pan"
)
// tokenRefreshMargin is how long before expiry a token is proactively renewed.
const tokenRefreshMargin = 10 * time.Minute
var errNoRefreshCredentials = errors.New("access_token expired: provide a refresh_token together with clientID/clientSecret, or switch to client_credentials mode")
// newSDKClient builds the SDK client for the configured authentication mode.
func (d *Open123) newSDKClient() (*pan123.Client, error) {
opts := []pan123.Option{
pan123.WithHTTPClient(&http.Client{Timeout: 60 * time.Second}),
pan123.WithUserAgent("AList/" + conf.Version),
}
switch d.AuthMode {
case AuthToken:
if d.AccessToken == "" {
return nil, errors.New("access_token is required in token mode")
}
c := pan123.NewWithToken(d.AccessToken, opts...)
// expiry is unknown for an externally issued token; refresh on demand
c.SetToken(d.AccessToken, d.tokenExpiry())
return c, nil
case AuthClientCredentials, "":
if d.ClientID == "" || d.ClientSecret == "" {View on GitHub (pinned to 843d9dc814)
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.
Example fix
// before: plain link, origin returns 403
link := &model.Link{URL: u}
// after: propagate needed auth header
link := &model.Link{
URL: u,
Header: http.Header{"Authorization": []string{"Bearer " + token}},
} Defensive patterns
Strategy: retry
Validate before calling
// optional pre-flight availability check
if resp, err := base.RestyClient.R().SetContext(ctx).Head(link.URL); err == nil && resp.StatusCode() >= 400 {
// do not attempt full read; surface broken-link error early
} Type guard
func isBrokenStatusErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "read url failed: status=")
} Try / catch
err := readStrmTarget(ctx, link)
if isBrokenStatusErr(err) {
if isTransientStatus(err) {
// retry with backoff (429/5xx)
} else {
// mark strm entry stale; do not retry (401/403/404/410)
}
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- oss: chunkNum invalid
- missing cookie or qrcode account
- {message}
- missing cookie or qrcode account
- not get file info
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/f6cb563c6b67fc5f.
Report an issue: GitHub.