When using Loops' generated low-level clients (or raw HTTP), set the base URL correctly: client = AuthenticatedClient( base_url="https://app.loops.so/api", # no /v1 here token [your value] ) The gotcha: /v1 is part of each endpoint path (/v1/contacts/update, etc.), so appending /v1 to the base URL produces /v1/v1/contacts/update and every call 404s. If your Loops integration 404s on every endpoint while the API key works in the dashboard, check the base URL first. The high-level clients (pyloops.configure / LoopsClient) handle this for you; only the low-level/generated path and hand-rolled HTTP need the care.

Context: Official docs (pyloops README, low-level API section): the generated low-level client's base_url must be https://app.loops.so/api with NO /v1 segment, because /v1 is already part of each endpoint path (e.g. /v1/contacts/update). Passing base_url with /v1 appended double-prefixes the path and every call 404s. The README calls this out with an explicit note. This is the classic trap when hand-rolling HTTP calls or configuring generated clients: the Loops API base is the bare /api root, unlike most APIs where you append the version.