{"record":{"id":"8a9b19ca17320933","repo":"cloudflare/cloudflared","slug":"errunauthorized","errorCode":"ErrUnauthorized","errorMessage":"unauthorized","messagePattern":"unauthorized","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cfapi/base_client.go","lineNumber":24,"sourceCode":"\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/rs/zerolog\"\n\t\"golang.org/x/net/http2\"\n)\n\nconst (\n\tdefaultTimeout  = 15 * time.Second\n\tjsonContentType = \"application/json\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"unauthorized\")\n\tErrBadRequest   = errors.New(\"incorrect request parameters\")\n\tErrNotFound     = errors.New(\"not found\")\n\tErrAPINoSuccess = errors.New(\"API call failed\")\n)\n\ntype RESTClient struct {\n\tbaseEndpoints *baseEndpoints\n\tauthToken     string\n\tuserAgent     string\n\tclient        http.Client\n\tlog           *zerolog.Logger\n}\n\ntype baseEndpoints struct {\n\taccountLevel  url.URL\n\tzoneLevel     url.URL\n\taccountRoutes url.URL\n\taccountVnets  url.URL","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/cfapi/base_client.go#L6-L42","documentation":"ErrUnauthorized is a sentinel error in cloudflared's cfapi REST client returned by statusCodeToError when the Cloudflare API responds with HTTP 401 (Unauthorized) or 403 (Forbidden). It means the request was rejected by authentication/authorization before any resource-level work happened.","triggerScenarios":"Any cfapi REST call (tunnel, VM, config endpoints) where the API token/service token is missing, expired, revoked, or lacks scopes for the endpoint; cloudflared receiving 401/403 from api.cloudflare.com during tunnel management operations.","commonSituations":"Expired or rotated API tokens left in config, tunnel tokens copied incorrectly, an account whose token lacks the Cloudflare Tunnel edit permission, clock skew invalidating tokens, or hitting an endpoint the token's policy does not cover.","solutions":["Regenerate the API/service token and update the configuration (TUNNEL_TOKEN or credentials file).","Verify the token's policy includes the needed permissions (e.g. Account: Cloudflare Tunnel:Edit).","Check account/zone IDs in the request match the token's account scope.","Retry after confirming clock sync (NTP) if using time-bound tokens."],"exampleFix":"// before\nclient, err := cfapi.NewRESTClient(..., \"stale-token\", ...)\n// after\nclient, err := cfapi.NewRESTClient(..., os.Getenv(\"TUNNEL_API_TOKEN\"), ...) // token freshly rotated and scoped","handlingStrategy":"try-catch","validationCode":"if os.Getenv(\"TUNNEL_API_TOKEN\") == \"\" {\n    return errors.New(\"TUNNEL_API_TOKEN must be set before calling the cfapi client\")\n}","typeGuard":"import \"errors\"\n\nfunc IsUnauthorized(err error) bool {\n    return errors.Is(err, ErrUnauthorized)\n}","tryCatchPattern":"resp, err := client.GetTunnel(ctx, accountID, tunnelID)\nif err != nil {\n    if errors.Is(err, ErrUnauthorized) {\n        // refresh token / re-authenticate before retrying\n        return fmt.Errorf(\"check API token and scopes: %w\", err)\n    }\n    return err\n}","preventionTips":["Rotate tokens on a schedule and update the daemon's credentials","Scope the token policy to Cloudflare Tunnel Edit for the right account","Keep server clocks NTP-synced to avoid token validation failures"],"tags":["go","cloudflare","api","auth"],"backgroundTag":"authentication-required","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}