> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Environment Variables

> SDK and integration configuration: names, defaults, resolution order, and common traps

# Environment Variables

This page lists only the settings **meaningful to external developers** — the ones you can set in your own service that actually change behaviour. Platform-internal service configuration (database connections, object storage credentials, model keys) is not listed; Profy operates those and you neither see nor need them.

## SDK configuration

### Auth and address

| Variable         | Purpose                               | Default                |
| ---------------- | ------------------------------------- | ---------------------- |
| `PROFY_API_KEY`  | Platform API key, shaped `sk-pro-...` | none (required)        |
| `PROFY_BASE_URL` | API base URL                          | `https://api.profy.cn` |

Resolution order puts **constructor arguments ahead of environment variables**:

```python theme={null}
Profy(api_key="sk-pro-xxx")        # explicit wins
Profy()                            # falls back to PROFY_API_KEY
```

A missing `PROFY_API_KEY` raises at construction rather than degrading to anonymous:

```
Profy: api_key is required. Pass it directly or set the PROFY_API_KEY
environment variable.
```

<Note>
  A trailing slash on `PROFY_BASE_URL` is stripped automatically, so `https://api.profy.cn/` and `https://api.profy.cn` behave identically. For private deployments or local development this is the only setting you change; nothing else in the SDK differs.
</Note>

### OAuth applications

Third-party apps acting on behalf of many Profy users use application credentials rather than an API key:

| Variable           | Purpose                          | Default                |
| ------------------ | -------------------------------- | ---------------------- |
| `PROFY_APP_ID`     | OAuth application client\_id     | none (required)        |
| `PROFY_APP_SECRET` | OAuth application client\_secret | none (required)        |
| `PROFY_BASE_URL`   | as above, shared                 | `https://api.profy.cn` |

Missing either one raises:

```
ProfyApp: client_id and client_secret are required. Pass them directly or
set PROFY_APP_ID / PROFY_APP_SECRET environment variables.
```

The default scope on authorization requests is `events:write`.

<Warning>
  `PROFY_APP_SECRET` is a **server-side credential**. The moment it appears in a browser bundle, a mobile app, or any client code it is effectively public — the authorization-code exchange must happen on your server.
</Warning>

## Deliberately not environment variables

A few settings are intentionally excluded, and the reasons matter:

| Setting           | How it is passed                                               | Why                                                                                             |
| ----------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| End-user identity | `X-End-User-Id` header, or the client's `end_user_id` argument | One process usually serves many end users; a process-level variable guarantees cross-user bleed |
| Timeouts          | `timeout` constructor argument                                 | Defaults are 300s total, 10s connect; sane timeouts vary enormously per call                    |
| Model selection   | Request body parameter                                         | It is a per-call decision, not deployment configuration                                         |

<Note>
  End-user identity is the one most often misread. It looks "fixed per deployment," but one API key fronting many users is the normal case — so it has to vary per request, and an environment variable would freeze it. A single-user script can set `end_user_id` once on the client; a server-side integration passes it per call.
</Note>

## Proxies and networking

The SDK's internal HTTP client sets **`trust_env=False` explicitly**, which means:

* `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` are **not** read
* `NO_PROXY` likewise has no effect
* System CA bundles and `.netrc` do not participate

<Warning>
  This is the easiest trap to fall into: you have a proxy configured, `curl` works, `requests` works, and the SDK still connects directly.

  It is not a defect. Honouring the environment would break direct file uploads (presigned URLs), where two competing Authorization mechanisms collide — the SDK's Bearer header makes object storage ignore the query-string signature and return 400. To avoid that near-unattributable failure, the SDK uniformly ignores environment proxies.

  If you need a proxy, pass a custom HTTP client through the constructor rather than relying on environment variables.
</Warning>

## MCP integration

The remote MCP server needs no environment variables at all — only a config file:

```json .mcp.json theme={null}
{
  "mcpServers": {
    "profy": {
      "type": "streamable-http",
      "url": "https://mcp.profy.cn/mcp"
    }
  }
}
```

Authentication reuses your IDE's existing login session (Bearer token or cookie); no secret travels through the environment. The tool list is in [MCP Tools](/en/developers/reference/mcp-tools).

## Full example

<CodeGroup>
  ```bash .env theme={null}
  PROFY_API_KEY=sk-pro-xxxxxxxxxxxxxxxx
  # Only needed for private deployments or local development
  # PROFY_BASE_URL=https://api.example.internal
  ```

  ```python Python theme={null}
  import os
  from profy import Profy

  # Three forms, in decreasing order of "leave it to the environment":
  # 1) Environment only (simplest, but end-user identity must go per call)
  async with Profy() as client:
      await client.agents.run("my-expert", "Hello", end_user_id="user-123")

  # 2) Explicit key plus a client-level end user (single-user script)
  async with Profy(api_key=os.environ["PROFY_API_KEY"], end_user_id="user-123") as client:
      await client.agents.run("my-expert", "Hello")

  # 3) Fully explicit (multi-tenant service, base_url to a private deployment)
  async with Profy(
      api_key=os.environ["PROFY_API_KEY"],
      base_url=os.environ.get("PROFY_BASE_URL", "https://api.profy.cn"),
  ) as client:
      await client.agents.run("my-expert", "Hello", end_user_id=request.user_id)
  ```

  ```python OAuth app theme={null}
  import os
  from profy import ProfyApp

  app = ProfyApp()   # reads PROFY_APP_ID / PROFY_APP_SECRET

  url = app.authorization_url(
      redirect_uri="https://your.app/callback",
      scope="events:write",
      state=csrf_token,
  )
  ```
</CodeGroup>

## Boundaries and failure modes

| Symptom                                        | Cause                               | Fix                                                                          |
| ---------------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------- |
| `api_key is required` at construction          | `PROFY_API_KEY` unset or misspelled | Check the name and whether the process loaded `.env`                         |
| Every request 400 complaining about a header   | End-user identity not sent          | Set `end_user_id` on the client or pass it per call                          |
| Proxy configured but SDK connects directly     | SDK sets `trust_env=False`          | Pass a custom HTTP client; do not rely on the environment                    |
| Private deployment traffic hits the public API | `PROFY_BASE_URL` never applied      | Ensure the environment loads **before** the client is constructed            |
| `ProfyApp` complains about client\_id          | Only `PROFY_API_KEY` was set        | App credentials and API keys are separate; neither substitutes for the other |

<Note>
  The fourth row is especially common: many `.env` loaders inject variables at import time, after a module-level client has already been constructed. Create clients inside a function rather than at module top level.
</Note>

## Verify your configuration

```python theme={null}
import os
from profy import Profy

print("key set:", bool(os.environ.get("PROFY_API_KEY")))
print("base url:", os.environ.get("PROFY_BASE_URL", "https://api.profy.cn (default)"))

async with Profy(end_user_id="verify-1") as client:
    resp = await client.agents.run("your-expert", "Reply with one word")
    print(resp)
```

If this runs, your key, base URL, and end-user identity are all correct. Any one of them being wrong surfaces here rather than in production.

## Related pages

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/en/developers/authentication">
    API keys versus OAuth
  </Card>

  <Card title="Endpoint Reference" icon="list" href="/en/developers/reference/endpoints">
    Every `/v1/*` endpoint
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/en/developers/error-codes">
    Codes returned by configuration errors
  </Card>

  <Card title="MCP Tools" icon="plug" href="/en/developers/reference/mcp-tools">
    IDE integration
  </Card>
</CardGroup>

<Note>
  Verified 2026-08-11. Sources: `sdk/python/profy/client.py` (`DEFAULT_BASE_URL`, `_DEFAULT_TIMEOUT`, `trust_env=False`, `_end_user_headers`), `sdk/python/profy/app.py` (`PROFY_APP_ID` / `PROFY_APP_SECRET` / `DEFAULT_SCOPE`), `services/core/src/routes/platform-api/caller.ts` (end-user header gate).
</Note>
