{"record":{"id":"820c8089723a161f","repo":"MHSanaei/3x-ui","slug":"node-has-no-api-token-configured","errorCode":null,"errorMessage":"node has no API token configured","messagePattern":"node has no API token configured","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/web/runtime/remote.go","lineNumber":181,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"invalid node port %d\", r.node.Port)\n\t}\n\tbp := r.node.BasePath\n\tif !strings.HasSuffix(bp, \"/\") {\n\t\tbp += \"/\"\n\t}\n\tu := &url.URL{\n\t\tScheme: scheme,\n\t\tHost:   net.JoinHostPort(addr, strconv.Itoa(r.node.Port)),\n\t\tPath:   bp,\n\t}\n\treturn u.String(), nil\n}\n\nfunc (r *Remote) do(ctx context.Context, method, path string, body any) (*envelope, error) {\n\t// mtls nodes authenticate via the client certificate, so a bearer token is\n\t// optional for them; every other mode still requires one.\n\tif r.node.ApiToken == \"\" && r.node.TlsVerifyMode != \"mtls\" {\n\t\treturn nil, errors.New(\"node has no API token configured\")\n\t}\n\n\tbase, err := r.baseURL()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttarget := base + strings.TrimPrefix(path, \"/\")\n\n\tvar (\n\t\tbodyBytes   []byte\n\t\tcontentType string\n\t)\n\tswitch b := body.(type) {\n\tcase nil:\n\tcase url.Values:\n\t\tbodyBytes = []byte(b.Encode())\n\t\tcontentType = \"application/x-www-form-urlencoded\"\n\tdefault:","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/runtime/remote.go#L163-L199","documentation":"Remote.do refuses to send when the node record has no ApiToken AND TlsVerifyMode is not 'mtls'. Every node-auth mode except mutual TLS uses a bearer token, so an empty token means the request cannot be authenticated and would surface later as a confusing 401. Failing client-side with this explicit error pinpoints the missing configuration. mtls nodes are exempt because the client certificate itself carries identity.","triggerScenarios":"Creating a node without pasting its API token (empty string saved); token field cleared during an edit; switching a node from mtls to token mode without adding the token.","commonSituations":"New node setup where the operator skipped the token step; copy-paste dropping the token; node templates defaulting to empty token.","solutions":["Copy the API token from the remote panel and save it on the node record","If the node genuinely uses mutual TLS, set TlsVerifyMode='mtls' so certificate auth is used instead","Validate at node-save time that token is non-empty unless mode is mtls, so the error surfaces in the form rather than at dispatch"],"exampleFix":"// before\nnode.ApiToken = \"\"\nnode.TlsVerifyMode = \"\"\n\n// after\nnode.ApiToken = remoteToken // from remote panel\n// or: node.TlsVerifyMode = \"mtls\" with client cert configured","handlingStrategy":"validation","validationCode":"if node.ApiToken == \"\" && node.TlsVerifyMode != \"mtls\" {\n    return errors.New(\"save an API token on the node, or switch it to mtls auth\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := remote.Do(ctx, method, path, body); err != nil {\n    if strings.Contains(err.Error(), \"no API token configured\") {\n        // fill node.ApiToken from the remote panel and retry once\n    }\n}","preventionTips":["Require a non-empty token at node save time unless TlsVerifyMode is mtls","Copy tokens directly from the remote panel UI to avoid paste errors","After switching a node off mtls, always (re)set the bearer token"],"tags":["node","authentication","configuration","runtime"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}