Skip to content

Authentication

methodic authenticates to Chronicle using a Bearer token in Authorization: Bearer. The Chronicle constructor takes the token as api_key regardless of its actual format — the server's middleware figures out which kind it is.

Configuring the client

You can pass server_url + api_key to the constructor directly, but most code — including every Chronicle skill — uses Chronicle.from_env(), which resolves credentials from the ambient environment so you never hard-code a key.

from methodic import Chronicle

chronicle = Chronicle.from_env()

from_env() resolves each setting from the first source that provides it, highest precedence first:

  1. Explicit keyword argumentsChronicle.from_env(api_key="sk_...").
  2. Environment variables (below).
  3. YAML files under ~/.methodic/config.yaml then credentials.yaml (or a single file via $CHRONICLE_CONFIG).
  4. Built-in defaultsserver_url falls back to https://api.methodiclabs.ai.

api_key has no default; if no source provides one, from_env() raises ChronicleConfigError before any network call.

Environment variables

Variable Required Default Meaning
CHRONICLE_API_KEY yes Bearer token (sk_user_…, sk_agent_…, sk_worker_…, or an Auth0 JWT)
CHRONICLE_SERVER_URL no https://api.methodiclabs.ai Base URL of the Chronicle server
CHRONICLE_TIMEOUT no 30 Per-request timeout, seconds
CHRONICLE_MAX_UPLOAD_WORKERS no 2 Threads for async asset uploads
CHRONICLE_ORGANIZATION_ID no Default organization for calls that take an organization_id (resolve yours via chronicle.me.scopes()); pass methodic.PERSONAL on a call to override back to personal scope
CHRONICLE_ORGANIZATION_SLUG no The same default-organization setting named by slug instead of id — resolved to the id via /v1/me/scopes on first use. Set this or CHRONICLE_ORGANIZATION_ID, not both
CHRONICLE_CONFIG no ~/.methodic/config.yaml + credentials.yaml Path to a single YAML config file (overrides the ~/.methodic/ files)
export CHRONICLE_SERVER_URL="https://api.methodiclabs.ai"
export CHRONICLE_API_KEY="sk_user_..."

Chronicle-managed menlo_park workers receive CHRONICLE_SERVER_URL and CHRONICLE_API_KEY automatically — the daemon injects them into the container, so Chronicle.from_env() just works inside a run.

Config files

For interactive use, keep your settings under ~/.methodic/ instead of exporting variables. The secret is split into its own file so it can be permissioned and rotated separately:

~/.methodic/config.yaml — non-secret settings:

server_url: https://api.methodiclabs.ai
timeout: 30              # optional
max_upload_workers: 2    # optional
organization_id: 1111…   # optional — default org for org-scoped calls
                         # (experiments/datasets create under this org unless
                         # the call passes organization_id, or methodic.PERSONAL
                         # to force personal scope)
organization_slug: acme  # …or name the same default org by slug instead of id
                         # (resolved to the id via /v1/me/scopes on first use).
                         # Set one of organization_id / organization_slug, not both.

~/.methodic/credentials.yaml — the API key, on its own:

api_key: sk_user_...

Both files are optional and may hold any subset of settings (a single credentials.yaml with just api_key is enough). credentials.yaml wins over config.yaml on any overlap, environment variables win over both, and explicit keyword arguments win over everything. To load explicit files and ignore the environment entirely, use the same split:

chronicle = Chronicle.from_file(config="config.yaml", credentials="creds.yaml")

Either argument is optional (a combined file can be passed as config). To point from_env() at a single combined file instead, set CHRONICLE_CONFIG.

Logging in from the terminal

methodic auth login writes the ~/.methodic files for you instead of hand-editing them:

$ methodic auth login
Credential (API key or JWT, input hidden):
Organizations you belong to:
  1. acme — Acme Research
Default organization (number or slug; Enter to keep current): 1
Logged in as Jane Doe (https://api.methodiclabs.ai).
Wrote ~/.methodic/credentials.yaml (chmod 600).
Default organization: 2f1c… (recorded in ~/.methodic/config.yaml).

The stored credential can be an sk_... API key or an Auth0 JWT — the server routes by prefix, so a JWT lets an agent bootstrap and then mint its own appropriately-restricted keys via chronicle.api_keys. It verifies the credential live (--no-verify to skip), resolves --organization <id-or-slug> against your memberships, clears the default with --personal, and persists --server-url for self-hosted servers. config.yaml is merge-written (hand-added settings survive a re-login); credentials.yaml is overwritten (re-running login is key rotation). methodic auth status prints what the SDK would resolve, with the key masked.

Keep keys out of version control

credentials.yaml holds a live credential. Keep it under ~/.methodic/ (consider chmod 600), never in a checked-in working tree.

Token formats

Format Holder Lifetime
Auth0 access token (RS256 JWT) Interactive researcher / web UI Auth0 session lifetime
sk_user_... Researcher API key, minted via POST /api-keys with an Auth0 token Configurable, default 90 days
sk_agent_... An agent or developer Configurable, default 90 days
sk_worker_... A worker on a provisioned instance; minted by Chronicle during provisioning Tied to the run

Provisioning paths

Chronicle-managed runners (menlo-park)

The agent calls POST /experiments/{id}/variations/{n}/runs/{r}/provision. Chronicle spins up the instance, mints a sk_worker_ key scoped to that run, and injects it as CHRONICLE_API_KEY:

import os
from methodic import Chronicle

chronicle = Chronicle(
    server_url=os.environ["CHRONICLE_SERVER_URL"],
    api_key=os.environ["CHRONICLE_API_KEY"],
)
run = chronicle.run(
    os.environ["CHRONICLE_EXPERIMENT_ID"],
    int(os.environ["CHRONICLE_VARIATION"]),
    int(os.environ["CHRONICLE_RUN"]),
)

Self-managed runners

The agent mints a long-lived sk_agent_ key once, then your runner uses it for any number of runs:

curl -X POST https://chronicle.example.com/api-keys \
  -H "Authorization: Bearer $AUTH0_TOKEN" \
  -d '{"description": "my custom runner", "scopes": [...]}'

The response includes the full key — Chronicle only ever returns it once. Store it somewhere safe (a secret manager).

Researcher (interactive)

Hand-paste an Auth0 access token from the web UI dev tools, or grab one programmatically via Auth0's password grant (CI accounts). Same constructor:

chronicle = Chronicle(server_url="https://api.methodiclabs.ai", api_key=jwt)

Revocation

API keys are revocable immediately via DELETE /api-keys/{id}. Chronicle's auth middleware uses an LRU cache for performance but reflects revocations within seconds. If methodic starts getting AuthenticationError (401), the key is no longer valid — there is no automatic refresh.