{"record":{"id":"533c2f1680472fb6","repo":"langflow-ai/langflow","slug":"api-key-required","errorCode":null,"errorMessage":"API key required","messagePattern":"API key required","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"src/backend/base/langflow/api/v1/a2a.py","lineNumber":132,"sourceCode":"\n    Uses ``check_key`` directly, NOT ``api_key_security``: under AUTO_LOGIN the latter\n    returns the superuser for a *missing* key, which would silently bypass this gate.\n    \"\"\"\n    # Short writable session (check_key flushes usage counters), closed before\n    # dispatch so no lock is held across the up-to-300s run.\n    async with session_scope() as session:\n        auth_type = await folder_auth_type(flow, session)\n        if auth_type == \"none\":\n            return  # public agent\n        if auth_type not in (\"apikey\", \"oauth\"):\n            # Protected folder with a scheme A2A can't enforce: fail closed, never public.\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=f\"A2A access is disabled for this agent: unsupported folder auth type {auth_type!r}.\",\n            )\n        api_key = request.headers.get(A2A_APIKEY_HEADER)\n        if not api_key:\n            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=\"API key required\")\n        user = await check_key(session, api_key)\n        # Same message for invalid and wrong-owner: don't reveal a key is valid for another user.\n        if user is None or user.id != flow.user_id:\n            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=\"Invalid API key\")\n\n\nclass _FlowContextBuilder(DefaultServerCallContextBuilder):\n    \"\"\"Carry the per-request flow_id into the shared executor via call-context state.\"\"\"\n\n    def build(self, request: Request) -> ServerCallContext:\n        context = super().build(request)\n        # Canonicalize to the same string form the resume guard uses (str(UUID(...))), so the durable\n        # store scope (_task_scope / _push_config_scope) and the checkpoint/resume guard agree even for\n        # a non-canonical UUID in the path (uppercase or hyphenless, both valid to the UUID route\n        # converter). Without this, the same task addressed via two encodings lands in two scopes.\n        context.state[\"flow_id\"] = str(UUID(request.path_params[\"flow_id\"]))\n        return context\n","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/a2a.py#L114-L150","documentation":"Raised by _enforce_a2a_auth when the flow's folder uses apikey/oauth auth but the JSON-RPC request carries no x-api-key header (A2A_APIKEY_HEADER). Because the flow runs as its owner, an unauthenticated run would be a run under the owner's identity, so the request is rejected with 401 before dispatch. Note it deliberately uses check_key, not api_key_security, to avoid AUTO_LOGIN silently substituting the superuser for a missing key.","triggerScenarios":"POST /api/v1/a2a/{flow_id}/jsonrpc (message/send, message/stream, etc.) to a flow in an apikey or oauth folder without an x-api-key header, e.g. a raw a2a-sdk client that only sets Authorization or no credentials at all.","commonSituations":"Testing the public-agent path against a flow that was later moved into an apikey folder; A2A clients that put credentials in a different header or a JSON-RPC field; assuming the agent card's security scheme is optional.","solutions":["Send the flow owner's Langflow API key in the x-api-key header on every JSON-RPC request","Confirm the flow's folder auth_type — if it should be public, set the folder auth to 'none'","Use the exact header name the card advertises (x-api-key), not Authorization: Bearer"],"exampleFix":"# before\nclient = Client(url)  # no credentials\n# after\nclient = Client(httpx_client=httpx.AsyncClient(headers={\"x-api-key\": OWNER_API_KEY}))","handlingStrategy":"validation","validationCode":"def has_owner_api_key configured() -> bool:\n    key = os.environ.get(\"FLOW_OWNER_API_KEY\", \"\")\n    return bool(key) and len(key) >= 20  # cheap presence check, no network call","typeGuard":null,"tryCatchPattern":"try:\n    resp = await client.send_message(flow_id, payload)\nexcept A2AClientError as e:\n    if \"API key required\" in str(e):\n        client = rebuild_client_with_x_api_key(os.environ[\"FLOW_OWNER_API_KEY\"])\n        resp = await client.send_message(flow_id, payload)\n    else:\n        raise","preventionTips":["Configure the x-api-key header once in the shared httpx client, not per call","Check the flow's folder auth_type before assuming the public path","Name the env var after the owning account to avoid grabbing the wrong key"],"tags":["a2a","auth","http-401","api-key"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}