> ## 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.

# Endpoint Reference

> Every /v1/* endpoint: path, method, auth, and purpose

# Endpoint Reference

The Platform API lives under the `/v1` prefix. Base URL:

```
https://api.profy.cn
```

This page is the complete endpoint index. Full request and response shapes live on each API detail page.

## Authentication

Every request except a small number of public endpoints carries an API key:

```http theme={null}
Authorization: Bearer sk-pro-xxxxxxxx
```

In multi-user deployments you must also identify the end user:

```http theme={null}
X-End-User-Id: your-app-user-123
```

<Warning>
  `X-End-User-Id` is not decorative. When calling `/v1/chat/*`, `/v1/agents/run`, `/v1/sessions*`, or `/v1/files*` with an API key, omitting it returns **400** with this exact message:

  ```
  Missing required header: X-End-User-Id. Send a stable id for the terminal
  user this request acts on; it isolates their sessions, files and memory
  from other users of your API key.
  ```

  Failing loudly beats merging silently: one API key usually fronts many end users, and session history, sandbox files, and memory are all partitioned by this header. Without it everyone shares one partition — cross-user bleed costs far more than one extra header.

  OAuth app callers are exempt, because the authorization flow already established identity.
</Warning>

## All endpoints

### Runs and sessions

| Method   | Path                         | Purpose                                   |
| -------- | ---------------------------- | ----------------------------------------- |
| `POST`   | `/v1/agents/run`             | Run an expert once (SSE stream)           |
| `POST`   | `/v1/chat/completions`       | OpenAI-compatible chat completions        |
| `POST`   | `/v1/sessions`               | Create a session                          |
| `GET`    | `/v1/sessions`               | List sessions                             |
| `GET`    | `/v1/sessions/:id`           | Get session detail                        |
| `DELETE` | `/v1/sessions/:id`           | Delete a session                          |
| `POST`   | `/v1/sessions/:id/adopt`     | Adopt a session into the current caller   |
| `GET`    | `/v1/sessions/:id/events`    | Subscribe to session events (SSE)         |
| `POST`   | `/v1/sessions/:id/events`    | Send an event (continue the conversation) |
| `POST`   | `/v1/sessions/:id/interrupt` | Interrupt a running session               |

`agents/run` and `sessions/:id/events` share the same execution path. The only difference is whether state persists:

* One-shot tasks use `agents/run` — stateless
* Multi-turn conversations use `sessions` — the server keeps context

### Agents

| Method  | Path                                     | Purpose                          |
| ------- | ---------------------------------------- | -------------------------------- |
| `POST`  | `/v1/agents`                             | Create an agent draft            |
| `GET`   | `/v1/agents`                             | List your agents                 |
| `GET`   | `/v1/agents/:identifier`                 | Get agent detail                 |
| `PATCH` | `/v1/agents/:identifier`                 | Update agent configuration       |
| `POST`  | `/v1/agents/:identifier/publish`         | Submit for review and publishing |
| `GET`   | `/v1/agents/:identifier/profile`         | Get the public profile           |
| `GET`   | `/v1/agents/:identifier/opening-message` | Get the opening message          |

<Note>
  `publish` **submits for review**; it does not go live directly. When a published agent ships a new version the old one stays online while the new one queues for review — so a successful publish does not mean users see the change yet. Confirm with `get_submission_status` (MCP) or the pending-submission field on the agent detail.
</Note>

### Environments (sandbox templates)

| Method   | Path                   | Purpose                               |
| -------- | ---------------------- | ------------------------------------- |
| `POST`   | `/v1/environments`     | Create a reusable sandbox environment |
| `GET`    | `/v1/environments`     | List environments                     |
| `GET`    | `/v1/environments/:id` | Get environment detail                |
| `PATCH`  | `/v1/environments/:id` | Update an environment                 |
| `DELETE` | `/v1/environments/:id` | Delete an environment                 |

### Files

| Method   | Path                   | Purpose                   |
| -------- | ---------------------- | ------------------------- |
| `POST`   | `/v1/files/upload-url` | Get a direct-upload URL   |
| `POST`   | `/v1/files`            | Register an uploaded file |
| `DELETE` | `/v1/files/:id`        | Delete a file             |

Uploads are **two-step**: get a direct URL, PUT the bytes to object storage, then register. Large files never traverse the API server and are not bound by request body limits.

Limits are listed in [Limits](/en/documentation/reference/limits).

### Usage and models

| Method | Path         | Auth         | Purpose                           |
| ------ | ------------ | ------------ | --------------------------------- |
| `GET`  | `/v1/meters` | **Public**   | Metering units and rate structure |
| `GET`  | `/v1/models` | Required     | Available models                  |
| `POST` | `/v1/events` | Events scope | Report custom metering events     |

`GET /v1/meters` is the only unauthenticated `/v1` endpoint — pricing has to be visible before anyone signs in.

## OAuth scopes

Third-party apps integrate over OAuth and are granted scopes.

<Note>
  Session and environment operations **reuse `agents:write` rather than minting `sessions:*` / `environments:*`**. They all mean "act as this app on this app's agents." Splitting them out would add two options nobody understands to the consent screen without creating a real permission boundary.
</Note>

## Request example

<CodeGroup>
  ```bash cURL theme={null}
  curl -N https://api.profy.cn/v1/agents/run \
    -H "Authorization: Bearer $PROFY_API_KEY" \
    -H "X-End-User-Id: user-123" \
    -H "Content-Type: application/json" \
    -d '{
      "agent": "my-expert",
      "message": "Summarise the three key findings in this quarterly report"
    }'
  ```

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

  async with Profy(
      api_key=os.environ["PROFY_API_KEY"],
      end_user_id="user-123",
  ) as client:
      async for event in client.agents.run_stream("my-expert", "Summarise the report"):
          if event.type == "output.text.delta":
              print(event.delta, end="", flush=True)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch("https://api.profy.cn/v1/agents/run", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PROFY_API_KEY}`,
      "X-End-User-Id": "user-123",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      agent: "my-expert",
      message: "Summarise the three key findings in this quarterly report",
    }),
  });

  const reader = res.body!.getReader();
  // Parse SSE chunks; the event vocabulary is on the SSE Events page
  ```
</CodeGroup>

## Streaming responses

`agents/run`, `chat/completions`, and `sessions/:id/events` all return `text/event-stream`.

Event names use dotted hierarchical naming (aligned with the OpenAI Responses API). The full vocabulary is in [SSE Events](/en/developers/reference/sse-events).

<Warning>
  The stream terminator is `data: [DONE]`. It is **not** a JSON event — calling `JSON.parse` on it throws. Your parse loop must check for that line first.
</Warning>

## Known boundaries

| Item                | Status                                                                                              |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| Webhook callbacks   | **Not shipped.** Webhook examples in the docs are forward-looking design; no callbacks fire today   |
| Python SDK version  | `1.0.0` in-repo, `0.1.0` on PyPI — a version drift. The repo source is authoritative                |
| MCP `install_skill` | **A stub**: returns success without installing. See [MCP Tools](/en/developers/reference/mcp-tools) |

<Warning>
  Without these three lines you would spend hours debugging "why don't webhooks fire" or "why didn't the skill take effect" when the root cause is that the feature is not wired yet. Admitting the gap is cheaper than letting you chase something that does not exist.
</Warning>

## Related pages

<CardGroup cols={2}>
  <Card title="SSE Events" icon="bolt" href="/en/developers/reference/sse-events">
    The complete streaming event vocabulary
  </Card>

  <Card title="Error Codes" icon="triangle-exclamation" href="/en/developers/error-codes">
    Every business code and status derivation
  </Card>

  <Card title="Authentication" icon="key" href="/en/developers/authentication">
    API keys and OAuth
  </Card>

  <Card title="Environment Variables" icon="sliders" href="/en/developers/reference/environment-variables">
    SDK and integration configuration
  </Card>
</CardGroup>

<Note>
  Verified 2026-08-11. Sources: `services/core/src/routes/platform-api/` (`index.ts`, `agents.ts`, `sessions.ts`, `environments.ts`, `files.ts`), `services/core/src/index.ts` (`/v1` mount).
</Note>
