Easy Labs
SDKsPython

Authentication

How to authenticate requests from your server.

Every request from the SDK is authenticated with a single secret API key sent on the x-easy-api-key header. The SDK never reads keys from the filesystem or environment on its own — you pass the key explicitly to Client(api_key=...) so you can keep secret management in one place.

Two key prefixes route automatically:

PrefixEnvironmentBase URL
sk_test_Sandboxhttps://sandbox-api.itseasy.co/v1/api
sk_live_Productionhttps://api.itseasy.co/v1/api

Use sandbox keys for development and CI. Production keys see real money.

Initializing with an API key

import os
from easylabs import Client

# Sandbox — sk_test_... routes to sandbox automatically.
client = Client(api_key=os.environ["EASY_API_KEY"])

# Production — sk_live_... routes to production automatically.
prod = Client(api_key=os.environ["EASY_LIVE_KEY"])

# Self-hosted / pinned-CI: bypass routing entirely.
local = Client(
    api_key="sk_test_...",
    internal_api_url="http://localhost:4000/v1/api",
)

The constructor calls GET /validate-key synchronously. If the key is malformed, revoked, or doesn't match the target environment, it raises easylabs.AuthenticationError before returning the client. That gives you a guaranteed-good client object on success — no surprise 401s on the first real request.

Rotating keys

Keys can be rotated without downtime by running two clients side-by-side during the cutover:

old = Client(api_key=os.environ["EASY_API_KEY"])
new = Client(api_key=os.environ["EASY_API_KEY_NEXT"])

# Route new traffic through `new`; let in-flight requests on `old` finish.
# Once the dashboard confirms zero requests on the old key, revoke it.

There's no per-key state on the client, so swapping at runtime (e.g. re-binding a module-level singleton) is also safe.

Multi-tenant authentication

The SDK is designed for one key per Client instance. For multi-tenant servers, build a small factory keyed by tenant:

from functools import lru_cache
from easylabs import Client

@lru_cache(maxsize=512)
def client_for(tenant_id: str) -> Client:
    api_key = lookup_api_key(tenant_id)        # your own resolver
    return Client(api_key=api_key)

Client is cheap to keep around (one httpx.Client + a few resource namespaces), but __init__ does perform a /validate-key round-trip, so caching avoids the network hop on every request.

On this page