<@ULVA73B9P> I have a Prefect 3.4.9 self hosted se...
# ask-marvin
n
@Marvin I have a Prefect 3.4.9 self hosted server. I have correctly setup the auth string and I can connect. But if I try to create a new flow using the api/flows endpoint, I get an unauthorized error. Why?
m
thought for 241.8 seconds
Short answer: because POST /api/flows is a protected endpoint and your request is missing (or the proxy is stripping) the HTTP Basic Authorization header that matches your server’s auth_string. “I can connect” usually refers to health/version endpoints which are intentionally unauthenticated. What to check and how to fix: - Include HTTP Basic on every protected request: - Header must be: Authorization: Basic <base64(username:password)> - The value you base64-encode is literally user:pass from your server’s server.api.auth_string. Examples - curl
Copy code
AUTH=$(printf '%s' 'admin:pass' | base64)
  curl -X POST http://<host>:4200/api/flows \
    -H "Authorization: Basic $AUTH" \
    -H "Content-Type: application/json" \
    -d '{"name": "my-flow"}'
- Python requests
Copy code
import requests
  r = <http://requests.post|requests.post>(
      "http://<host>:4200/api/flows",
      json={"name": "my-flow"},
      auth=("admin", "pass")  # sets Basic Authorization automatically
  )
  print(r.status_code, r.text)
Common pitfalls that cause 401 even when “auth is set”: - Setting the auth string on the server but not sending it with the client request. Environment variables like PREFECT_API_AUTH_STRING help Prefect’s Python client, but raw HTTP tools (curl/Postman) must send the header explicitly. - Using a Bearer token (PREFECT_API_KEY) instead of Basic auth. Bearer is for Prefect Cloud; self-hosted Server expects Basic. - Reverse proxies/load balancers stripping the Authorization header. Ensure your proxy forwards it. - Small formatting issues: wrong base64 value (must be base64 of exactly user:pass), trailing spaces/newlines, wrong username/password. Docs and references: - Prefect 3 auth for self-hosted server: Security settings (Server auth) - Your server’s OpenAPI docs (helpful to test endpoints): http//&lt;host&gt;4200/docs If you share the exact request you’re sending (headers minus secrets) and whether you’re behind a proxy, I can pinpoint the issue quickly.
n
@Marvin I'm using the Python Prefect SDK with the create_flow function on my client object
m
thought for 180.5 seconds
Got it — since you’re using the Python SDK’s client, the 401 is almost always because the client is not sending HTTP Basic auth to your self-hosted Server (even if you can “connect” for unprotected endpoints). Key points for Prefect 3.x Server: - Server expects HTTP Basic auth via
auth_string
(username:password), not a Bearer API key. - If
PREFECT_API_KEY
is set anywhere (env/profile), it takes precedence and the client will send Bearer auth — which Server will reject with 401. How to fix and reliably create a flow Option A — pass auth_string explicitly
Copy code
import asyncio
from prefect.client.orchestration import PrefectClient
from prefect.client.schemas.actions import FlowCreate

async def main():
    client = PrefectClient(
        api="http://<host>:4200/api",
        auth_string="admin:pass",  # match your server.api.auth_string
    )
    await client.create_flow(FlowCreate(name="my-flow"))

asyncio.run(main())
Option B — use environment variables (no code changes)
Copy code
export PREFECT_API_URL=http://<host>:4200/api
export PREFECT_API_AUTH_STRING=admin:pass
# Make sure PREFECT_API_KEY is NOT set anywhere
prefect config view --show-sources
Then:
Copy code
import asyncio
from prefect.client.orchestration import get_client
from prefect.client.schemas.actions import FlowCreate

async def main():
    async with get_client() as client:
        await client.create_flow(FlowCreate(name="my-flow"))

asyncio.run(main())
Common causes of 401 with PrefectClient.create_flow - PREFECT_API_KEY is set in your environment/profile, so the client uses Bearer auth instead of Basic. Remove it or switch to a clean profile. - Check with:
Copy code
prefect config view --show-sources
- If you see
PREFECT_API_KEY
, remove it from your profiles.toml or unset it in the environment. - The reverse proxy is stripping the
Authorization
header. If you’re routing through a proxy, make sure it forwards
Authorization
. - Small mismatches: wrong
auth_string
value, trailing spaces/newlines, or using the wrong API base (it should be
http://<host>:4200/api
). Docs - Server auth for self-hosted: Security settings (Server auth) - Python client reference: PrefectClient API If you can share how you’re constructing the client (code snippet) and a quick
prefect config view --show-sources
(redact secrets), I can pinpoint exactly what’s causing the 401.
n
@Marvin I'm running this code on a CICD pipeline. I have set the auth string using env variable, the PREFECT_API_KEY env variable is not set, and if I run this code on my local machine it works as expected
m
thought for 45.3 seconds
Thanks — that helps narrow it down. Given it works locally but not in CI, the most common cause is that your CI job is not pointing at your self-hosted Server at all. In Prefect 3.x the SDK defaults to Prefect Cloud if
PREFECT_API_URL
is not set, and Cloud will return 401 without a Bearer token. Locally you likely have a profile or env var set for `PREFECT_API_URL`; in CI you only set the auth string. What to do in your CI job - Set BOTH of these environment variables: -
PREFECT_API_URL=http://<host>:4200/api
-
PREFECT_API_AUTH_STRING=admin:pass
- Ensure
PREFECT_API_KEY
is NOT set anywhere (env or profile). - Avoid wrapping the auth string in quotes in the secret value (no leading/trailing quotes or whitespace). - If you’re behind a proxy, make sure it does not strip the
Authorization
header. For extra safety in code, you can pass these explicitly:
Copy code
import os, asyncio
from prefect.client.orchestration import PrefectClient
from prefect.client.schemas.actions import FlowCreate

async def main():
    client = PrefectClient(
        api=os.environ["PREFECT_API_URL"],              # e.g. <http://host:4200/api>
        auth_string=os.environ["PREFECT_API_AUTH_STRING"],  # e.g. admin:pass
        server_type="server",                            # force server mode detection
    )
    await client.create_flow(FlowCreate(name="my-flow"))

asyncio.run(main())
Quick CI diagnostics - Print the active config at runtime (redact secrets) to confirm the URL and auth source:
Copy code
prefect config view --show-sources
- From the CI runner, try a basic call to verify the target and auth:
Copy code
python - <<'PY'
  import os, requests, base64
  url = os.environ.get("PREFECT_API_URL", "") + "/flows"
  auth = os.environ["PREFECT_API_AUTH_STRING"]
  b = base64.b64encode(auth.encode()).decode()
  r = requests.get(url, headers={"Authorization": f"Basic {b}"})
  print(r.status_code, r.url, r.text[:200])
  PY
If you’ve set
PREFECT_API_URL
and still get 401 in CI: - Check for trailing newlines/whitespace in
PREFECT_API_AUTH_STRING
(many CI systems can add them). - Confirm the CI network/proxy isn’t stripping
Authorization
. - Verify Prefect versions are the same locally and in CI (3.4.9 SDK against 3.4.9 server is good). Docs - Server auth (self-hosted): Security settings (Server auth) - PrefectClient reference: PrefectClient API If you share your CI step env setup (redacting secrets) and the output of
prefect config view --show-sources
from CI, I can help pinpoint the exact issue.