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

> ## Agent Instructions
> Phaseo Gateway base URL: https://api.phaseo.app/v1. Send `Authorization: Bearer $PHASEO_API_KEY` with every request.
> Model IDs look like `provider/model`, for example `openai/gpt-6-astra`. Do not guess IDs: list them with `GET /v1/models` or check https://phaseo.app/models. `phaseo/free` routes to an available free model and needs no wallet balance.
> For new text integrations prefer `POST /v1/responses`. `POST /v1/chat/completions` (OpenAI format) and `POST /v1/messages` (Anthropic format) suit existing code.
> The full API contract is https://phaseo.app/docs/openapi/v1/openapi.yaml. Official SDKs: `@phaseo/sdk` (TypeScript) and `phaseo` (Python), plus Go, C#, PHP, Ruby and Rust.
> Search these docs over MCP at https://phaseo.app/docs/mcp, and install Phaseo skills with `npx skills add https://phaseo.app/docs`. If a page is wrong or out of date, report it with the MCP `submit_feedback` tool.
> Manage Phaseo accounts with the `phaseo` CLI (`npm install -g @phaseo/cli`, then `phaseo login` or `phaseo login --device-code`), the `/v1` Management API or the MCP server at `https://mcp.phaseo.app/mcp?access=manage`. Do not send users to the dashboard for account tasks. A person is still needed for sign-up, approving a sign-in, paying the first top-up on the checkout link that `phaseo credits add` returns when no card is saved, confirming each saved-card top-up from the emailed link, and account security (password, MFA, passkeys). Create API keys with `phaseo keys create --name <name> --json`; never guess one. Credit top-ups need `phaseo login --scopes billing:write` first. Task map: https://phaseo.app/docs/v1/developers/manage-from-an-agent.md.

# Workspace Notifications

> Send workspace alerts to email, chat channels, or an HTTPS webhook.

Use notification destinations to send enabled workspace alerts to the tools your team already monitors. Destinations are shared by the workspace and can be tested before you rely on them for operational alerts.

## Add a destination

1. Open **Gateway → Settings → Notifications**.
2. Select **Add destination**.
3. Enter a name and choose one or more channels.
4. Add the destination details and optional mentions.
5. Select **Send test** for each configured channel.
6. Create the destinations after every test succeeds.

From the CLI, run `phaseo notifications destinations create`, check it with `phaseo notifications destinations test <id>`, then choose which alerts it receives with `phaseo notifications routes set <event-kind> --destination-ids <id>`.

Custom webhooks get a signing secret when you create them. Copy it from the dialog that opens; it is shown only once.

Phaseo supports:

* email addresses and distribution lists
* Discord channels through a bot token
* Discord webhooks
* Slack incoming webhooks
* Microsoft Teams workflow URLs
* custom HTTPS webhooks

Credentials and webhook URLs are encrypted before storage. Keep channel credentials scoped to notification delivery and rotate them if they are exposed.

## Enable product alerts

The notifications page lists the alert types available to your workspace. Enable **Model Deprecation Alerts** to receive notice before a model used by the workspace is retired.

**Model replacement notices** are sent when [retired model routing](https://phaseo.app/docs/v1/guides/model-retirement.md#route-requests-to-a-replacement-automatically) is on and a replacement model answers the workspace's requests for a model no provider serves any more. One notice is sent per model and replacement, the first time a replacement answers successfully. The workspace owner is always emailed; choose destinations to send the notice to your team as well.

Alerts are delivered to the configured destinations in addition to any built-in recipient behavior shown in the dashboard.

## Spend reports

Workspace owners get a spend report email every Monday at 09:00 in the workspace billing time zone, covering the previous Monday-to-Sunday week. Change it under **Spend alerts → Workspace spend settings** on the notifications page: daily (09:00, for the day before), weekly, monthly (on the 1st, for the month before) or off, and whether admins get it too. Periods without spend are skipped.

Each report shows spend and the change from the period before, the top keys, models, members and departments, this month's forecast, the balance runway, and the period's spend alerts and blocks. Anyone who gets it can unsubscribe from the link in the email; that stops only their copy of that workspace's report. Destinations routed to **Spend Reports** get one summary per report.

The same settings are `spend.report_cadence` and `spend.report_include_admins` in the [notification settings API](https://phaseo.app/docs/v1/api-reference/endpoint/notifications-settings-update.md).

> [!NOTE]
>
> Spend report emails are being rolled out. Until they are switched on, your choice is saved but no report is sent.

## Mentions

Optional mention fields let an alert notify specific recipients:

* Discord accepts user IDs and role IDs.
* Slack accepts user IDs and user-group IDs.
* Microsoft Teams accepts Microsoft 365 email addresses or Entra object IDs.

Use mentions sparingly. A high-volume workspace alert should normally go to a monitored channel rather than paging a broad group.

## Test and remove destinations

Use **Send test** beside an existing destination after rotating credentials or changing channel configuration. Remove destinations that are no longer monitored so alerts do not appear delivered to an abandoned channel.

Custom webhooks must use HTTPS and should validate the request before processing it. Return a successful response promptly and move slower work to a background job.

## Verify webhook signatures

Phaseo signs every custom webhook delivery, including tests sent from a saved destination, with the same scheme as [video and batch webhooks](https://phaseo.app/docs/v1/guides/async-video-and-batch.md#receive-updates). Each request carries these headers:

```http theme={null}
POST /your/endpoint HTTP/1.1
content-type: application/json
user-agent: Phaseo-Notifications/1.0
x-phaseo-event-id: 6f1c0f5e-3f0b-4a39-9a51-2f4f2c1d9e10
x-phaseo-event-type: rate_limit_throttled
x-phaseo-timestamp: 1792569600
x-phaseo-signature: 5d2a…e41f
```

`x-phaseo-signature` is the hexadecimal HMAC-SHA256 of the `x-phaseo-timestamp` value, a literal period (`.`), and the **unmodified request body**, keyed with the destination's signing secret (`whsec_…`). `x-phaseo-timestamp` is in Unix seconds.

To verify a delivery:

1. Read the raw request body before parsing it as JSON.
2. Recompute the signature and compare it in constant time.
3. Reject timestamps more than five minutes from your clock.
4. Deduplicate on `x-phaseo-event-id`. Failed deliveries are retried, and each attempt is signed again with a new timestamp.

```js Node.js theme={null}
import crypto from "node:crypto";

// rawBody is the request body exactly as received, as a string.
export function verifyPhaseoSignature(rawBody, headers, secret, toleranceSeconds = 300) {
  const timestamp = headers["x-phaseo-timestamp"];
  const signature = headers["x-phaseo-signature"];
  if (!timestamp || !signature) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signature, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

```python Python theme={null}
import hashlib
import hmac
import time

# raw_body is the request body exactly as received, as bytes.
def verify_phaseo_signature(raw_body: bytes, headers, secret: str, tolerance_seconds: int = 300) -> bool:
    timestamp = headers.get("x-phaseo-timestamp", "")
    signature = headers.get("x-phaseo-signature", "")
    if not timestamp.isdigit() or not signature:
        return False
    if abs(time.time() - int(timestamp)) > tolerance_seconds:
        return False
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

### Generate or rotate a secret

The secret is shown once, when the destination is created. To replace it, select **Rotate secret** beside the destination in **Settings → Notifications**, or call [`POST /v1/notifications/destinations/{id}/rotate-secret`](https://phaseo.app/docs/v1/api-reference/endpoint/notifications-destination-rotate-secret). The next delivery is signed with the new secret and the old one stops working, so update your receiver straight away.

Custom webhooks created before signing was available have no secret and are still delivered unsigned. They show **Unsigned** in Settings. Select **Generate secret**, add the secret to your receiver, then send a test to check verification. Tests sent from the **Add destination** sheet, before the destination is saved, are unsigned because no secret exists yet.

Email, Slack, Discord and Microsoft Teams destinations are not signed: those services authenticate the webhook URL or bot token themselves.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.