# beeps documentation
beeps routes alerts from monitoring systems to on-call engineers and AI agents.
---
# What is beeps?
> Source: https://beeps.dev/docs/
beeps routes alerts from your monitoring systems to on-call engineers and AI agents. When an incident occurs, beeps can notify humans and kick off automated triage at the same time — or sequentially, however you want to set it up.
## The Flow: How Alerts Move Through the System
Here's what happens when an incident occurs:
```
Monitoring System (Sentry, Datadog, etc.)
↓
Sends webhook to beeps
↓
Alert Created
↓
Relay (routing pipeline)
↓
Relay Rules (ordered actions)
├─ [Group: agents] → AI Agent starts working
│ (uses Integration credentials)
│
└─ [Group: humans] → Find on-call user (from Schedule)
→ Send notification (via Contact Methods)
```
## Core concepts
### Relays
Relays are alert routing pipelines. When an alert arrives, the relay runs it through an ordered set of rules. Think of a relay as "what should happen when this alert fires." You'll typically have one per service or environment ("Production Alerts", "Staging Alerts").
Set `externalKey` on relays so your config scripts are idempotent.
### Relay rules
Rules are the individual actions inside a relay. Three types:
- `schedule_notify` — notify the on-call user from a schedule
- `webhook` — POST to an external URL (Slack, PagerDuty, etc.)
- `agent` — invoke an AI agent (Devin, Cursor)
Rules have a `group` and an `order`. Same group = sequential (order 1 runs, then 2, then 3). Different groups = parallel. This is how you control whether agents and humans get notified at the same time or one after the other:
```typescript
// Parallel — different groups, both fire immediately
Rule 1: { group: "agents", order: 1, ruleType: "agent" }
Rule 2: { group: "humans", order: 1, ruleType: "schedule_notify" }
// Sequential — same group, agent tries first
Rule 1: { group: "default", order: 1, ruleType: "agent" }
Rule 2: { group: "default", order: 2, ruleType: "schedule_notify" }
```
### Schedules
Schedules define on-call rotations — who is on-call and when it rotates. A schedule becomes active at `startAt`, then hands off on a daily or weekly cadence using `handoffDay` and `handoffTime` (UTC). If you omit `startAt`, beeps starts the schedule immediately. Members rotate in the order they were added.
### Contact methods
Each user needs at least one contact method (email or SMS) to receive notifications. We recommend both. Phone numbers should be in E.164 format (`+14155551234`).
### Integrations
Integrations store API credentials for AI agents (Devin, Cursor). The keys are encrypted and never returned in API responses. Don't confuse these with observability integrations — agent integrations tell beeps how to talk to agents, while observability integrations are webhooks that monitoring tools use to send alerts into beeps.
### Webhooks
Each relay gets a webhook URL (`https://hooks.beeps.dev/{webhookKey}`) that monitoring systems POST to. When a request comes in, beeps detects the provider, normalizes the payload, creates an alert, and kicks off the relay.
### Alerts
Alerts are incidents created from webhook payloads — things like "High CPU usage on web-01" with a severity of `critical`. They carry a `title`, `severity` (critical/high/medium/low/info), optional `message`, and arbitrary `metadata` JSON for context like server names or error rates.
## Alert Lifecycle
1. **Created** - Alert arrives via webhook
2. **Responded** - Someone (human or AI) is working on it
3. **Assigned** - Explicitly assigned to a specific user
4. **Resolved** - Issue is fixed
Responding to an alert cancels any pending relay executions, preventing duplicate notifications.
## Typical Setup Order
When configuring beeps for the first time, follow this order:
1. **Create a Relay** - The routing pipeline
2. **Create a Schedule** - Define on-call rotation
3. **Add Schedule Members** - Add team members to rotation
4. **Add Contact Methods** - Configure email/SMS for users
5. **Create Integration** (optional) - Set up AI agent credentials
6. **Create Relay Rules** - Define what happens when alerts arrive
7. **Test** - Send a test alert to verify everything works
**Ready to get started?** Follow the [Quickstart](/docs/quickstart) to set up relays, schedules, notifications, and AI agents.
## Next Steps
- [Quickstart](/docs/quickstart) - Set up humans and AI agents
- [Best Practices](/docs/best-practices/) - Configuration guidelines
- [Relays](/docs/relays/overview/) - Alert routing in depth
- [Schedules](/docs/schedules/overview/) - On-call rotations
## Questions?
**Q: What's the difference between Agent Integrations and Observability Integrations?**
A: Agent Integrations store credentials for AI agents (Devin, Cursor Cloud Agent) so beeps can invoke them. Observability Integrations are webhooks that monitoring systems (Sentry, Datadog) use to send alerts into beeps.
**Q: Can I have multiple relays?**
A: Yes. You might have separate relays for production vs staging, or different relays for different services.
**Q: What happens if no one is on-call?**
A: The `schedule_notify` rule won't send notifications. Make sure your schedule always has at least one member.
**Q: Can I test without sending real alerts?**
A: Yes — just POST a test payload to your webhook URL, or use the SDK to create test alerts programmatically.
---
# Quickstart
> Source: https://beeps.dev/docs/quickstart/
import { Tabs, TabItem } from "@astrojs/starlight/components";
Set up a relay, schedule, AI agent, and parallel routing. Pick whichever surface you want to drive it from.
New to beeps? Read [Core Concepts](/docs/) first to understand how the pieces fit together.
## Prerequisites
- A [beeps](https://beeps.dev) account.
- An access token from **Settings → Access Tokens** (looks like `bat_xxxxxxxx`).
## Pick your surface
The same eight-step setup is available three ways. The CLI is the fastest path to your first alert. Pick it unless you have a reason not to.
Install + authenticate:
```bash
npm install -g @beepsdev/cli
export BEEPS_ACCESS_TOKEN="bat_your_token_here"
```
### 1. Create a relay
```bash
beeps relay create --name "production relay" \
--external-key production::relay \
--description "routes critical production incidents" \
--json
```
The response includes a relay id (`rly_…`) and a webhook URL. Save both; you'll need them in steps 2 and 8.
### 2. Create an on-call schedule
```bash
beeps schedule create \
--name "Primary On-Call" \
--relay-id rly_REPLACE_ME \
--type weekly \
--handoff-day monday \
--handoff-time 09:00 \
--external-key primary::schedule \
--json
```
`startAt` is omitted on purpose. beeps defaults it to now, so you can test alerts immediately.
### 3. Add team members
```bash
beeps schedule add-member --schedule-id sch_REPLACE_ME --email alice@example.com
beeps schedule add-member --schedule-id sch_REPLACE_ME --email bob@example.com
```
Members rotate in the order they're added.
### 4. Contact methods (one-time, in the dashboard)
Your signup email is auto-added as a verified contact method. To add SMS or another email, go to **Settings → Profile** in the dashboard. SMS requires verification via a confirmation text.
:::tip[Stopping here is fine]
If you don't need AI agents, skip to step 7 and create only the schedule_notify rule (the `humans` group).
:::
### 5. Set up an AI agent integration
Get a [Cursor cloud agent API token](https://cursor.com/dashboard?tab=cloud-agents). **Enable Privacy Mode in Cursor account settings.** Without it, every API call returns 403.
```bash
export CURSOR_API_KEY="cursor_api_key_here"
beeps integration create \
--provider cursor \
--name "Cursor Agent" \
--api-key-env CURSOR_API_KEY \
--json
```
### 6. Create parallel relay rules
```bash
# AI agent rule
beeps relay rule create --relay-id rly_REPLACE_ME \
--name "AI Agent Auto-Triage" \
--rule-type agent \
--group agents \
--order 1 \
--external-key agents::cursor \
--config '{"agentType":"cursor","integrationId":"int_REPLACE_ME","repository":"https://github.com/your-org/your-repo","autoCreatePr":true}'
# Human notify rule
beeps relay rule create --relay-id rly_REPLACE_ME \
--name "Notify On-Call Engineer" \
--rule-type schedule_notify \
--group humans \
--order 1 \
--external-key humans::primary \
--config '{"scheduleId":"sch_REPLACE_ME"}'
```
Different groups (`agents` vs `humans`) run in parallel.
### 7. Verify
```bash
beeps relay list
beeps relay rule list --relay-id rly_REPLACE_ME
beeps schedule on-call --schedule-id sch_REPLACE_ME
```
### 8. Fire a test alert
Use the webhook URL from step 1:
```bash
curl -X POST https://hooks.beeps.dev/YOUR_WEBHOOK_KEY \
-H "Content-Type: application/json" \
-d '{"title":"Test Alert","message":"Testing the on-call system","severity":"high"}'
```
You should see Cursor start triaging at the same time the on-call engineer gets notified.
```bash
beeps alert list --active
```
The CLI tab above is faster for first-time setup. Use the SDK when you want this in your application code or in a [config-as-code](/docs/cli/config/) file.
### 1. Install and initialize
```bash
npm install @beepsdev/sdk
```
```bash
pnpm add @beepsdev/sdk
```
```bash
bun add @beepsdev/sdk
```
```bash
export BEEPS_ACCESS_TOKEN="bat_your_access_token_here"
```
Create `beeps.config.ts`:
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
```
### 2. Create a relay
```typescript
const relay = await client.relay.create({
name: "production relay",
description: "routes critical production incidents",
externalKey: "production::relay",
});
```
:::tip[Use externalKey for idempotency]
Always set `externalKey` on relays, schedules, and rules. This makes your config safe to re-run without creating duplicates. Think of `externalKey` as a stable identifier you control, while `id` is generated by beeps.
:::
### 3. Create a schedule
```typescript
const schedule = await client.schedule.create({
name: "Primary On-Call",
relayId: relay.id,
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00", // UTC
externalKey: "primary::schedule",
});
```
Omitting `startAt` is intentional. The schedule starts immediately, so you can test right away.
### 4. Add team members
```typescript
await client.schedule.addMember(schedule.id, { email: "alice@example.com" });
await client.schedule.addMember(schedule.id, { email: "bob@example.com" });
```
Members rotate in the order they're added.
### 5. Contact methods (one-time, in the dashboard)
Signup email is auto-added as a verified contact method. Add SMS or extra emails at **Settings → Profile**.
### 6. AI agent integration
Get a [Cursor cloud agent API token](https://cursor.com/dashboard?tab=cloud-agents). **Enable Privacy Mode in Cursor account settings.** Without it, every API call returns 403.
```typescript
const integration = await client.integration.create({
name: "Cursor Agent",
provider: "cursor",
apiKey: process.env.CURSOR_API_KEY,
});
```
### 7. Create parallel relay rules
```typescript
// AI agent
await client.relay.rules.create(relay.id, {
name: "AI Agent Auto-Triage",
externalKey: "agents::cursor",
ruleType: "agent",
group: "agents",
order: 1,
config: {
agentType: "cursor",
integrationId: integration.id,
repository: "https://github.com/your-org/your-repo",
autoCreatePr: true,
},
});
// Human notify
await client.relay.rules.create(relay.id, {
name: "Notify On-Call Engineer",
externalKey: "humans::primary::schedule-notify",
ruleType: "schedule_notify",
group: "humans",
order: 1,
config: { scheduleId: schedule.id },
});
```
Different groups run in parallel.
### 8. Fire a test alert
```bash
curl -X POST https://hooks.beeps.dev/YOUR_WEBHOOK_KEY \
-H "Content-Type: application/json" \
-d '{"title":"Test Alert","message":"Testing","severity":"high"}'
```
For a full runnable config file (`beeps.config.ts`) and CI deploy via `beeps relay apply`, see [Config as Code](/docs/config-as-code/).
Drive setup from Claude Code, Codex, or any MCP-compatible client. Best when you live in your editor and don't want to context-switch.
### Connect the MCP server
```bash
claude mcp add --transport http --scope user beeps https://mcp.beeps.dev/mcp
```
```bash
codex mcp add beeps --url https://mcp.beeps.dev/mcp
codex mcp login beeps --scopes "openid,profile,email,offline_access,beeps.tools.read,beeps.tools.write"
```
From here, just talk to your agent.
### 1. Create a relay
```text
Create a relay called "production relay" with externalKey "production::relay".
```
The agent calls `beeps_create_relay` and reports back the relay id and webhook URL. Save the webhook URL for step 8.
### 2. Create a schedule
```text
Create a weekly schedule called "Primary On-Call" on the production relay,
handoff Monday at 09:00, externalKey "primary::schedule".
```
### 3. Add team members
```text
Add alice@example.com and bob@example.com to the primary schedule.
```
### 4. Contact methods (one-time, in the dashboard)
Open **Settings → Profile** to add SMS or extra emails. Your signup email is already verified.
### 5. AI agent integration (one-time, outside MCP)
MCP can't create the Cursor credential. That's the only step that needs the CLI or dashboard. Get a [Cursor cloud agent API token](https://cursor.com/dashboard?tab=cloud-agents) (**enable Privacy Mode in Cursor account settings**), then either:
```bash
export CURSOR_API_KEY="cursor_api_key_here"
beeps integration create --provider cursor --name "Cursor Agent" --api-key-env CURSOR_API_KEY --json
```
…or use **Settings → Integrations → New integration** in the dashboard. Either way, grab the returned integration id (`int_…`) so you can reference it in the next step.
### 6. Create parallel relay rules
```text
On the production relay, create two rules in parallel:
1. An "agent" rule named "AI Agent Auto-Triage", group "agents", using my
Cursor integration int_REPLACE_ME, repository
"https://github.com/your-org/your-repo", autoCreatePr true.
2. A "schedule_notify" rule named "Notify On-Call Engineer", group "humans",
pointing at the Primary On-Call schedule.
```
Different groups fire in parallel.
### 7. Verify
```text
List the rules on the production relay and tell me who's on call right now.
```
### 8. Fire a test alert
```bash
curl -X POST https://hooks.beeps.dev/YOUR_WEBHOOK_KEY \
-H "Content-Type: application/json" \
-d '{"title":"Test Alert","message":"Testing","severity":"high"}'
```
Then back in your editor:
```text
Show me active alerts.
```
## Next steps
- [Config as Code](/docs/config-as-code/) — check `beeps.config.ts` into your repo and deploy via `beeps relay apply` in CI.
- [Relay Rules](/docs/relays/rules/) — escalation, webhooks, sequential vs parallel.
- [Schedules](/docs/schedules/manage/) — overrides, multiple rotations, secondary schedules.
- [Agent Integrations](/docs/agent-integrations/overview/) — Devin, Cursor, Codex, Claude, AWS DevOps, OpenCode, Zup.
- [Observability Integrations](/docs/observability-integrations/overview/) — Sentry, Datadog, Prometheus, generic webhooks.
- [MCP Server](/docs/using-beeps/mcp-server/) — full tool list for ongoing alert triage from your editor.
## Troubleshooting
- [Alerts not appearing](/docs/troubleshooting/#alerts-are-not-appearing)
- [No one receiving notifications](/docs/troubleshooting/#alerts-appear-but-no-one-is-notified)
- [AI agent not responding](/docs/troubleshooting/#ai-agent-never-responds)
- [Rules not executing](/docs/troubleshooting/#relay-rules-not-firing)
---
# Relays Overview
> Source: https://beeps.dev/docs/relays/overview/
Relays are alert routing pipelines. They receive alerts via webhooks and process them through ordered rules to notify people, call external APIs, or invoke AI agents.
## Relay Rules
Each relay has rules that execute in order. Rule types:
- **schedule_notify** - Notify the on-call user from a schedule
- **webhook** - Call an external URL (Slack, PagerDuty, etc.)
- **agent** - Invoke an AI agent (Devin, Cursor Cloud Agent)
Rules are organized into groups. Rules in the same group run sequentially; rules in different groups run in parallel. See [Best Practices](/docs/best-practices/#parallel-vs-sequential-execution) for examples.
## Common Use Cases
### Direct Schedule Notification
Route all alerts directly to your on-call schedule:
```typescript
const relay = await client.relay.create({
name: "Production Alerts",
description: "Critical production incidents",
externalKey: "production-relay", // Idempotent - safe to re-run
});
await client.relay.rules.create(relay.id, {
name: "Notify on-call engineer",
externalKey: "notify-oncall", // Idempotent rule creation
ruleType: "schedule_notify",
config: {
scheduleId: "sch_xyz123",
},
});
```
### Escalation with AI Agent
Try an AI agent first, then escalate to humans:
```typescript
await client.relay.rules.create(relay.id, {
name: "Try AI agent first",
externalKey: "agent-first", // Idempotent
order: 1,
ruleType: "agent",
config: {
agentType: "devin",
integrationId: "int_abc123",
},
});
await client.relay.rules.create(relay.id, {
name: "Escalate to on-call",
externalKey: "escalate-oncall", // Idempotent
order: 2,
ruleType: "schedule_notify",
config: {
scheduleId: "sch_xyz123",
},
});
```
### Webhook Integration
Send alerts to external systems:
```typescript
await client.relay.rules.create(relay.id, {
name: "Post to Slack",
externalKey: "slack-webhook", // Idempotent
ruleType: "webhook",
config: {
endpoint: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
method: "POST",
headers: {
"Content-Type": "application/json",
},
payload: {
text: "New alert received",
},
},
});
```
---
# Managing Relays
> Source: https://beeps.dev/docs/relays/manage/
import { Tabs, TabItem, Code } from '@astrojs/starlight/components';
Create and manage relays using the SDK, API, and CLI.
## Creating a Relay
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
const relay = await client.relay.create({
name: "Production Alerts",
description: "Routes critical production incidents",
externalKey: "prod-alerts", // Optional: use for easier API access
});
console.log(`Created relay: ${relay.id}`);
```
### Safe Method (No Exceptions)
```typescript
const result = await client.relay.createSafe({
name: "Production Alerts",
description: "Routes critical production incidents",
});
if (result.error) {
console.error("Failed to create relay:", result.error.message);
} else {
console.log(`Created relay: ${result.data.id}`);
}
```
```bash
curl -X POST https://api.beeps.dev/v0/relay \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Alerts",
"description": "Routes critical production incidents",
"externalKey": "prod-alerts"
}'
```
**Response:**
```json
{
"relay": {
"id": "rly_abc123",
"organizationId": "org_xyz789",
"name": "Production Alerts",
"description": "Routes critical production incidents",
"externalKey": "prod-alerts",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
}
```
```bash
beeps relay create --name "Production Alerts" --external-key prod-alerts --description "Routes critical production incidents"
```
Use `--json` to get structured output:
```bash
beeps relay create --name "Production Alerts" --external-key prod-alerts --description "Routes critical production incidents" --json
```
**Tool:** `beeps_create_relay`
```json
{
"name": "Production Alerts",
"description": "Routes critical production incidents",
"externalKey": "prod-alerts"
}
```
## Listing Relays
```typescript
const relays = await client.relay.list();
console.log(`Found ${relays.length} relays:`);
relays.forEach((relay) => {
console.log(`- ${relay.name} (${relay.id})`);
});
```
```bash
curl -X GET https://api.beeps.dev/v0/relay \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"relays": [
{
"id": "rly_abc123",
"organizationId": "org_xyz789",
"name": "Production Alerts",
"description": "Routes critical production incidents",
"externalKey": "prod-alerts",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
]
}
```
```bash
beeps relay list
```
Use `--json` to get structured output:
```bash
beeps relay list --json
```
**Tool:** `beeps_list_relays`
```json
{}
```
## Input Types
### CreateRelayInput
```typescript
type CreateRelayInput = {
name: string; // Required: Human-readable relay name
description?: string; // Optional: Relay description
externalKey?: string; // Optional: Custom identifier for API access
};
```
### Relay
```typescript
type Relay = {
id: string; // Unique relay identifier (e.g., "rly_abc123")
organizationId: string; // Organization this relay belongs to
name: string; // Relay name
description: string; // Relay description
externalKey: string | null; // Custom external identifier
createdAt: string; // ISO 8601 timestamp
updatedAt: string; // ISO 8601 timestamp
};
```
## Best Practices
Always set `externalKey` for idempotent configuration. See [Best Practices](/docs/best-practices/) for details.
## Error Handling
The SDK throws errors by default. Use the `*Safe` methods for error handling without exceptions:
```typescript
const result = await client.relay.createSafe({
name: "New Relay",
});
if (result.error) {
console.error(`Error: ${result.error.message}`);
} else {
console.log(`Success: ${result.data.id}`);
}
```
For validation failures, the SDK formats a short human-readable message and also keeps structured field-level issues on `error.details`:
```typescript
import { ValidationError } from "@beepsdev/sdk";
try {
await client.relay.rules.create(relay.id, {
name: "Notify on-call engineer",
ruleType: "schedule_notify",
config: {
schduleId: "sch_primary", // typo
},
});
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.message);
console.error(error.details?.issues);
}
}
```
The message will look like:
```text
Invalid config for relay rule `schedule_notify`
- `config.scheduleId` is required.
- `config.schduleId` Unknown field. Did you mean `scheduleId`?
```
---
# Relay Rules
> Source: https://beeps.dev/docs/relays/rules/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Relay rules define how alerts are processed and routed. Rules execute in order based on their `order` property.
## Key Concepts
### Groups: Parallel vs Sequential Execution
The `group` field controls whether rules run simultaneously or sequentially:
- **Same group** = sequential (order 1, then 2, then 3)
- **Different groups** = parallel (run at the same time)
See [Best Practices - Parallel vs Sequential](/docs/best-practices/#parallel-vs-sequential-execution) for examples.
### Idempotency
Set `externalKey` on relay rules to make configuration idempotent and safe to re-run. See [Best Practices](/docs/best-practices/#idempotent-configuration).
## Rule Types
### Schedule Notify
Notify team members based on a schedule:
```typescript
const rule = await client.relay.rules.create(relayId, {
name: "Notify on-call engineer",
ruleType: "schedule_notify",
order: 1,
config: {
scheduleId: "sch_xyz123",
},
enabled: true,
});
```
```bash
curl -X POST https://api.beeps.dev/v0/relay/rly_abc123/rules \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Notify on-call engineer",
"ruleType": "schedule_notify",
"order": 1,
"config": {
"scheduleId": "sch_xyz123"
},
"enabled": true
}'
```
### Webhook
Call an external webhook:
```typescript
const rule = await client.relay.rules.create(relayId, {
name: "Post to Slack",
ruleType: "webhook",
order: 2,
config: {
endpoint: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
method: "POST",
headers: {
"Content-Type": "application/json",
},
payload: {
text: "Alert received",
severity: "high",
},
timeout: 10000, // Optional: timeout in ms (default 30000)
},
});
```
```bash
curl -X POST https://api.beeps.dev/v0/relay/rly_abc123/rules \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Post to Slack",
"ruleType": "webhook",
"order": 2,
"config": {
"endpoint": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"payload": {
"text": "Alert received"
}
}
}'
```
### AI Agent
Invoke an AI agent to handle the alert:
Supported `agentType` values: `"claude"`, `"codex"`, `"cursor"`, `"opencode"`, `"devin"`, `"aws_devops"`, `"zup"`. See [Agent Integrations](/docs/agent-integrations/overview/) for a full rundown of each.
```typescript
const rule = await client.relay.rules.create(relayId, {
name: "Invoke Devin agent",
ruleType: "agent",
order: 1,
config: {
agentType: "devin", // or "cursor", "aws_devops", "opencode"
integrationId: "int_abc123", // References stored credentials
endpoint: "https://api.devin.ai/v3/organizations/sessions", // Optional override
config: {
// Agent-specific configuration
priority: "high",
},
pollInterval: 30000, // Check status every 30s (default)
maxPollAttempts: 120, // Give up after 120 attempts (default)
},
});
```
#### Cursor Agent
Cursor requires a `repository` URL and can auto-create PRs:
```typescript
const cursorRule = await client.relay.rules.create(relayId, {
name: "Cursor Auto-Fix",
ruleType: "agent",
order: 1,
config: {
agentType: "cursor",
integrationId: "int_cursor123",
repository: "https://github.com/org/repo", // Required for Cursor
autoCreatePr: true, // Default: true
autoBranch: true, // Let Cursor auto-name the branch
pollInterval: 30000,
maxPollAttempts: 120,
},
});
```
#### AWS DevOps Agent
Needs the Agent Space webhook URL as `endpoint`, plus the Agent Space ID and region so beeps can poll the backlog:
```typescript
const awsRule = await client.relay.rules.create(relayId, {
name: "AWS DevOps investigation",
ruleType: "agent",
order: 1,
config: {
agentType: "aws_devops",
integrationId: "int_aws123",
endpoint: "https://webhook.devopsagent.us-east-1.amazonaws.com/invoke/abc",
awsAgentSpaceId: "space-abc123",
awsRegion: "us-east-1",
awsAuthVersion: "hmac_v1", // or "bearer_v2"
pollInterval: 60000,
maxPollAttempts: 60,
},
});
```
#### OpenCode
Point `endpoint` at your running OpenCode server. `openCodeModel` is optional. If you leave it off, the server's default model is used:
```typescript
const openCodeRule = await client.relay.rules.create(relayId, {
name: "OpenCode investigation",
ruleType: "agent",
order: 1,
config: {
agentType: "opencode",
integrationId: "int_oc123",
endpoint: "https://opencode.internal.corp:4096",
openCodeModel: {
providerID: "anthropic",
modelID: "claude-sonnet-4-20250514",
},
pollInterval: 15000,
maxPollAttempts: 200,
},
});
```
```bash
curl -X POST https://api.beeps.dev/v0/relay/rly_abc123/rules \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoke Devin agent",
"ruleType": "agent",
"order": 1,
"config": {
"agentType": "devin",
"integrationId": "int_abc123",
"pollInterval": 30000,
"maxPollAttempts": 120
}
}'
```
#### Cursor Agent
```bash
curl -X POST https://api.beeps.dev/v0/relay/rly_abc123/rules \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Cursor Auto-Fix",
"ruleType": "agent",
"order": 1,
"config": {
"agentType": "cursor",
"integrationId": "int_cursor123",
"repository": "https://github.com/org/repo",
"autoCreatePr": true,
"pollInterval": 30000,
"maxPollAttempts": 120
}
}'
```
## Validation Errors
Rule configs are validated strictly. If a required field is missing or a field name is misspelled, the SDK throws a `ValidationError` with a short message and field-level issues.
```typescript
import { ValidationError } from "@beepsdev/sdk";
try {
await client.relay.rules.create(relayId, {
name: "Notify on-call engineer",
ruleType: "schedule_notify",
order: 1,
config: {
schduleId: "sch_xyz123", // typo
},
});
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.message);
console.error(error.details?.issues);
}
}
```
Example SDK message:
```text
Invalid config for relay rule `schedule_notify`
- `config.scheduleId` is required.
- `config.schduleId` Unknown field. Did you mean `scheduleId`?
```
The API returns the same validation failure as structured JSON:
```json
{
"error": {
"code": "validation_failed",
"message": "Invalid config for relay rule `schedule_notify`",
"issues": [
{
"path": ["config", "scheduleId"],
"code": "missing_required",
"message": "is required."
},
{
"path": ["config", "schduleId"],
"code": "unknown_field",
"message": "Unknown field.",
"suggestion": "scheduleId"
}
]
}
}
```
When present, the response also includes an `X-Request-ID` header you can include in support requests.
## Managing Rules
### List Rules
```typescript
// List all rules
const allRules = await client.relay.rules.list(relayId);
// Filter by enabled status
const enabledRules = await client.relay.rules.list(relayId, {
enabled: true,
});
// Filter by rule type
const webhookRules = await client.relay.rules.list(relayId, {
ruleType: "webhook",
});
```
```bash
# List all rules
curl -X GET https://api.beeps.dev/v0/relay/rly_abc123/rules \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Filter by enabled status
curl -X GET "https://api.beeps.dev/v0/relay/rly_abc123/rules?enabled=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# Filter by rule type
curl -X GET "https://api.beeps.dev/v0/relay/rly_abc123/rules?ruleType=webhook" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### Get a Specific Rule
```typescript
const rule = await client.relay.rules.get(relayId, ruleId);
console.log(`Rule: ${rule.name}, Order: ${rule.order}`);
```
```bash
curl -X GET https://api.beeps.dev/v0/relay/rly_abc123/rules/rul_xyz789 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### Update a Rule
```typescript
const updatedRule = await client.relay.rules.update(relayId, ruleId, {
name: "Updated rule name",
enabled: false,
config: {
// Updated config
},
});
```
```bash
curl -X PUT https://api.beeps.dev/v0/relay/rly_abc123/rules/rul_xyz789 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated rule name",
"enabled": false
}'
```
### Delete a Rule
```typescript
await client.relay.rules.delete(relayId, ruleId);
console.log("Rule deleted successfully");
```
```bash
curl -X DELETE https://api.beeps.dev/v0/relay/rly_abc123/rules/rul_xyz789 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### Reorder Rules
```typescript
const reorderedRules = await client.relay.rules.reorder(relayId, {
rules: [
{ id: "rul_first", order: 1 },
{ id: "rul_second", order: 2 },
{ id: "rul_third", order: 3 },
],
});
reorderedRules.forEach((rule) => {
console.log(`${rule.order}: ${rule.name}`);
});
```
```bash
curl -X PUT https://api.beeps.dev/v0/relay/rly_abc123/rules/reorder \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rules": [
{"id": "rul_first", "order": 1},
{"id": "rul_second", "order": 2},
{"id": "rul_third", "order": 3}
]
}'
```
## Rule Groups
Organize related rules using the `group` property:
```typescript
await client.relay.rules.create(relayId, {
name: "Primary notification",
ruleType: "schedule_notify",
group: "primary",
order: 1,
config: { scheduleId: "sch_primary" },
});
await client.relay.rules.create(relayId, {
name: "Backup notification",
ruleType: "schedule_notify",
group: "backup",
order: 2,
config: { scheduleId: "sch_backup" },
});
```
## Type Definitions
### CreateRelayRuleInput
```typescript
type CreateRelayRuleInput = {
externalKey?: string;
name: string;
ruleType: RelayRuleType;
group?: string;
order?: number;
config: RelayRuleConfig;
enabled?: boolean;
};
```
### RelayRule
```typescript
type RelayRule = {
id: string;
organizationId: string;
relayId: string;
group: string;
externalKey: string | null;
name: string;
order: number;
ruleType: RelayRuleType;
config: Record;
enabled: boolean;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
## Best Practices
See [Best Practices](/docs/best-practices/) for relay rule configuration guidelines.
---
# Relay API Reference
> Source: https://beeps.dev/docs/relays/api/
Complete API reference for the Relay resource in the beeps SDK.
## SDK Client
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
baseURL: "https://api.beeps.dev", // Optional
timeoutMs: 30000, // Optional: default 30s
retries: {
// Optional
attempts: 3,
backoffMs: 1000,
},
});
```
## Relay Methods
### `client.relay.create(input)`
Create a new relay.
**Parameters:**
- `input: CreateRelayInput`
**Returns:** `Promise`
**Example:**
```typescript
const relay = await client.relay.create({
name: "Production Alerts",
description: "Routes critical production incidents",
externalKey: "prod-alerts",
});
```
### `client.relay.list()`
List all relays in your organization.
**Returns:** `Promise`
**Example:**
```typescript
const relays = await client.relay.list();
```
### `client.relay.lint(relayId, input?)`
Validate relay rules, schedules, and contact methods without side effects.
**Parameters:**
- `relayId: string`
- `input?: RelayLintInput`
**Returns:** `Promise`
**Example:**
```typescript
const result = await client.relay.lint("rly_abc123", {
simulateAt: "2025-01-01T00:00:00Z",
coverageDays: 7,
});
if (result.issues.length > 0) {
console.log(result.issues);
}
```
### `client.relay.simulate(relayId, input)`
Preview relay execution without sending notifications.
**Parameters:**
- `relayId: string`
- `input: RelaySimulationInput`
**Returns:** `Promise`
**Example:**
```typescript
const result = await client.relay.simulate("rly_abc123", {
simulateAt: "2025-01-01T00:00:00Z",
payload: {
action: "triggered",
},
source: "sentry",
});
console.log(result.groups);
```
### `client.relay.createSafe(input)`
Create a new relay without throwing exceptions.
**Parameters:**
- `input: CreateRelayInput`
**Returns:** `Promise>`
**Example:**
```typescript
const result = await client.relay.createSafe({
name: "Production Alerts",
});
if (result.error) {
console.error(result.error.message);
} else {
console.log(result.data.id);
}
```
For validation failures, `result.error` may be a `ValidationError` with field-level issues on `error.details?.issues`.
## Relay Rules Methods
### `client.relay.rules.create(relayId, input)`
Create a new rule for a relay.
**Parameters:**
- `relayId: string`
- `input: CreateRelayRuleInput`
**Returns:** `Promise`
**Example:**
```typescript
const rule = await client.relay.rules.create("rly_abc123", {
name: "Notify on-call",
ruleType: "schedule_notify",
order: 1,
config: {
scheduleId: "sch_xyz789",
},
});
```
If rule validation fails, the SDK throws a `ValidationError` with a formatted message and structured issues in `error.details?.issues`. The API returns the same issues in `error.issues` with code `validation_failed`.
### `client.relay.rules.list(relayId, params?)`
List rules for a relay.
**Parameters:**
- `relayId: string`
- `params?: ListRelayRulesParams`
- `enabled?: boolean` - Filter by enabled status
- `ruleType?: RelayRuleType` - Filter by rule type
**Returns:** `Promise`
**Example:**
```typescript
const allRules = await client.relay.rules.list("rly_abc123");
const enabledRules = await client.relay.rules.list("rly_abc123", {
enabled: true,
});
```
### `client.relay.rules.get(relayId, ruleId)`
Get a specific rule.
**Parameters:**
- `relayId: string`
- `ruleId: string`
**Returns:** `Promise`
**Example:**
```typescript
const rule = await client.relay.rules.get("rly_abc123", "rul_xyz789");
```
### `client.relay.rules.update(relayId, ruleId, input)`
Update a rule.
**Parameters:**
- `relayId: string`
- `ruleId: string`
- `input: UpdateRelayRuleInput`
**Returns:** `Promise`
**Example:**
```typescript
const updatedRule = await client.relay.rules.update(
"rly_abc123",
"rul_xyz789",
{
name: "Updated rule name",
enabled: false,
}
);
```
### `client.relay.rules.delete(relayId, ruleId)`
Delete a rule.
**Parameters:**
- `relayId: string`
- `ruleId: string`
**Returns:** `Promise<{ success: boolean }>`
**Example:**
```typescript
await client.relay.rules.delete("rly_abc123", "rul_xyz789");
```
### `client.relay.rules.reorder(relayId, input)`
Reorder rules.
**Parameters:**
- `relayId: string`
- `input: ReorderRelayRulesInput`
**Returns:** `Promise`
**Example:**
```typescript
const reordered = await client.relay.rules.reorder("rly_abc123", {
rules: [
{ id: "rul_first", order: 1 },
{ id: "rul_second", order: 2 },
],
});
```
## Safe Methods
All mutation methods have corresponding `*Safe` variants that return `Result` instead of throwing:
- `client.relay.lintSafe(relayId, input?)`
- `client.relay.simulateSafe(relayId, input)`
- `client.relay.rules.listSafe(relayId, params?)`
- `client.relay.rules.createSafe(relayId, input)`
- `client.relay.rules.getSafe(relayId, ruleId)`
- `client.relay.rules.updateSafe(relayId, ruleId, input)`
- `client.relay.rules.deleteSafe(relayId, ruleId)`
- `client.relay.rules.reorderSafe(relayId, input)`
## Types
### RelayRuleType
```typescript
type RelayRuleType =
| "schedule_notify"
| "webhook"
| "agent"
| "external_api" // deprecated
| "wait" // deprecated
| "conditional" // deprecated
| "escalate"; // deprecated
```
### ScheduleNotifyConfig
```typescript
type ScheduleNotifyConfig = {
scheduleId: string;
notificationMethod?: "email" | "sms";
};
```
### WebhookConfig
```typescript
type WebhookConfig = {
endpoint: string;
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
headers?: Record;
payload?: Record;
timeout?: number; // Request timeout in ms (default 30000)
};
```
### AgentConfig
```typescript
type AgentConfig = {
agentType: "devin" | "cursor";
integrationId?: string; // References stored credentials
endpoint?: string; // Override endpoint if needed
config?: Record; // Agent-specific config
pollInterval?: number; // How often to poll in ms (default 30000)
maxPollAttempts?: number; // Give up after N attempts (default 120)
};
```
### RelayLintIssue
```typescript
type RelayLintIssue = {
severity: "error" | "warning" | "info";
code: string;
message: string;
relayId?: string;
ruleId?: string;
scheduleId?: string;
details?: Record;
};
```
### RelayLintResult
```typescript
type RelayLintResult = {
issues: RelayLintIssue[];
};
```
### RelaySimulationResult
```typescript
type RelaySimulationResult = {
relayId: string;
simulateAt: string;
alertPreview: {
title: string;
message?: string;
severity: "critical" | "high" | "medium" | "low" | "info";
source: string;
externalId?: string;
metadata?: Record;
};
groups: Array<{
groupName: string;
steps: Array<{
ruleId: string;
ruleType: "schedule_notify" | "webhook" | "agent" | string;
name: string;
enabled: boolean;
}>;
}>;
issues: RelayLintIssue[];
};
```
## HTTP Endpoints
### Create Relay
```
POST /v0/relay
```
**Request Body:**
```json
{
"name": "Production Alerts",
"description": "Routes critical production incidents",
"externalKey": "prod-alerts"
}
```
### List Relays
```
GET /v0/relay
```
### Create Relay Rule
```
POST /v0/relay/:relayId/rules
```
### List Relay Rules
```
GET /v0/relay/:relayId/rules
GET /v0/relay/:relayId/rules?enabled=true
GET /v0/relay/:relayId/rules?ruleType=webhook
```
### Lint Relay
```
POST /v0/relay/:relayId/lint
```
**Request Body:**
```json
{
"simulateAt": "2025-01-01T00:00:00Z",
"coverageDays": 7
}
```
**Response:**
```json
{
"issues": [
{
"severity": "warning",
"code": "schedule.assignment_missing_contact_method",
"message": "Schedule assignment has no verified contact method",
"scheduleId": "sch_123",
"details": {
"userId": "usr_456"
}
}
]
}
```
### Simulate Relay
```
POST /v0/relay/:relayId/simulate
```
**Request Body:**
```json
{
"simulateAt": "2025-01-01T00:00:00Z",
"payload": {
"action": "triggered"
},
"source": "sentry"
}
```
**Response:**
```json
{
"relayId": "rly_123",
"simulateAt": "2025-01-01T00:00:00Z",
"alertPreview": {
"title": "Test Alert",
"severity": "medium",
"source": "sentry"
},
"groups": [],
"issues": []
}
```
### Get Relay Rule
```
GET /v0/relay/:relayId/rules/:ruleId
```
### Update Relay Rule
```
PUT /v0/relay/:relayId/rules/:ruleId
```
### Delete Relay Rule
```
DELETE /v0/relay/:relayId/rules/:ruleId
```
### Reorder Relay Rules
```
PUT /v0/relay/:relayId/rules/reorder
```
## Authentication
All API requests require authentication using your access token:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.beeps.dev/v0/relay
```
---
# Schedules Overview
> Source: https://beeps.dev/docs/schedules/overview/
Schedules define on-call rotations. Each schedule becomes active at `startAt`, then rotates team members on a daily or weekly cadence using `handoffDay` and `handoffTime`. If you omit `startAt` when creating a schedule, beeps starts it immediately.
## Schedule Types
### Daily Rotation
Rotates every day at a specified time:
```typescript
const schedule = await client.schedule.create({
name: "Daily On-Call",
relayId: "rly_abc123",
type: "daily",
handoffDay: "monday", // Not used for daily schedules
handoffTime: "09:00", // Rotates at 9:00 AM every day
externalKey: "daily-oncall", // Idempotent - safe to re-run
});
```
### Weekly Rotation
Rotates every week on a specified day and time:
```typescript
const schedule = await client.schedule.create({
name: "Weekly On-Call",
relayId: "rly_abc123",
type: "weekly",
handoffDay: "monday", // Rotates every Monday
handoffTime: "09:00", // at 9:00 AM
externalKey: "weekly-oncall", // Idempotent - safe to re-run
});
```
## Schedule Members
Add team members to the schedule to participate in the rotation:
```typescript
await client.schedule.addMember(schedule.id, {
userId: "usr_alice",
});
await client.schedule.addMember(schedule.id, {
userId: "usr_bob",
});
```
Members rotate in the order they were added.
New members usually join at the next handoff boundary so the current on-call assignment is not interrupted. If the schedule has not started yet, they become active at `startAt`. If the schedule is active but currently has no active members, they become active immediately.
## On-Call Status
Check who is currently on-call:
```typescript
const onCall = await client.schedule.getOnCall(schedule.id);
console.log(`${onCall.email} is on-call`);
```
## Assignments
View upcoming or past assignments to understand the rotation:
```typescript
// Get the next 14 days of assignments
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "days",
count: 14,
});
assignments.forEach((assignment) => {
console.log(
`Assignment ${assignment.assignmentNumber}: ${assignment.userId} from ${assignment.startDate} to ${assignment.endDate}`
);
});
```
`assignmentNumber` is the generated assignment index for the schedule timeline. It is not a permanent member position.
## Schedule Start
Set `startAt` when you want a schedule to begin in the future:
```typescript
const schedule = await client.schedule.create({
name: "Planned Cutover",
relayId: "rly_abc123",
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
startAt: "2025-03-15T09:00:00.000Z",
});
```
If you omit `startAt`, the server defaults it to the current time.
## Schedule Overrides
Overrides let you temporarily replace the on-call user without changing the rotation itself. They're useful for vacation coverage, sick days, or mid-shift handoffs.
An override applies to a specific time window (up to 30 days) and expires automatically. While active, `getOnCall` returns the override user instead of the regularly scheduled person. Overrides cannot overlap on the same schedule.
```typescript
const override = await client.schedule.createOverride(schedule.id, {
userId: "usr_bob",
startAt: "2025-03-15T09:00:00.000Z",
endAt: "2025-03-22T09:00:00.000Z",
reason: "Covering for Alice — vacation",
});
```
See [Schedule Overrides](/docs/schedules/overrides/) for the full management guide.
## Common Use Cases
### Basic On-Call Rotation
Create a simple weekly rotation:
```typescript
const relay = await client.relay.create({
name: "Production Alerts",
});
const schedule = await client.schedule.create({
name: "Weekly On-Call",
relayId: relay.id,
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
});
await client.schedule.addMember(schedule.id, { userId: "usr_alice" });
await client.schedule.addMember(schedule.id, { userId: "usr_bob" });
await client.schedule.addMember(schedule.id, { userId: "usr_charlie" });
await client.relay.rules.create(relay.id, {
name: "Notify on-call engineer",
ruleType: "schedule_notify",
config: {
scheduleId: schedule.id,
},
});
```
### Multi-Tier On-Call
Create primary and backup schedules:
```typescript
const primarySchedule = await client.schedule.create({
name: "Primary On-Call",
relayId: relay.id,
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
});
const backupSchedule = await client.schedule.create({
name: "Backup On-Call",
relayId: relay.id,
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
});
await client.relay.rules.create(relay.id, {
name: "Notify primary first",
order: 1,
ruleType: "schedule_notify",
config: { scheduleId: primarySchedule.id },
});
await client.relay.rules.create(relay.id, {
name: "Escalate to backup",
order: 2,
ruleType: "schedule_notify",
config: { scheduleId: backupSchedule.id },
});
```
### Follow-the-Sun Rotation
Create different schedules for different time zones:
```typescript
const usSchedule = await client.schedule.create({
name: "US Business Hours",
relayId: relay.id,
type: "daily",
handoffDay: "monday",
handoffTime: "08:00", // 8 AM US time
});
const euSchedule = await client.schedule.create({
name: "EU Business Hours",
relayId: relay.id,
type: "daily",
handoffDay: "monday",
handoffTime: "08:00", // 8 AM EU time
});
```
---
# Managing Schedules
> Source: https://beeps.dev/docs/schedules/manage/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Create schedules and add team members to on-call rotations.
## Creating a Schedule
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
const schedule = await client.schedule.create({
name: "Weekly On-Call",
relayId: "rly_abc123", // Schedule must belong to a relay
type: "weekly", // or "daily"
handoffDay: "monday", // Day of week for weekly schedules
handoffTime: "09:00", // Time when rotation occurs (HH:MM format)
// startAt: "2025-03-15T09:00:00.000Z", // Optional: omit to start immediately
externalKey: "prod-oncall", // Optional: custom identifier
});
console.log(`Created schedule: ${schedule.id}`);
```
### Daily Schedule
```typescript
const dailySchedule = await client.schedule.create({
name: "Daily On-Call",
relayId: "rly_abc123",
type: "daily",
handoffDay: "monday", // Not used for daily schedules
handoffTime: "09:00", // Rotates every day at this time
});
```
### Weekly Schedule
```typescript
const weeklySchedule = await client.schedule.create({
name: "Weekly On-Call",
relayId: "rly_abc123",
type: "weekly",
handoffDay: "friday", // Rotates every Friday
handoffTime: "17:00", // at 5:00 PM
});
```
If you omit `startAt`, beeps starts the schedule immediately. Set it explicitly when you want the schedule to begin in the future.
```bash
curl -X POST https://api.beeps.dev/v0/schedule \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly On-Call",
"relayId": "rly_abc123",
"type": "weekly",
"handoffDay": "monday",
"handoffTime": "09:00",
"externalKey": "prod-oncall"
}'
```
**Response:**
```json
{
"schedule": {
"id": "sch_xyz789",
"organizationId": "org_abc123",
"relayId": "rly_abc123",
"name": "Weekly On-Call",
"type": "weekly",
"handoffDay": "monday",
"handoffTime": "09:00",
"startAt": "2025-01-15T10:30:00Z",
"externalKey": "prod-oncall",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"deletedAt": null
}
}
```
Schedules are created via config-as-code. See [CLI Config-as-Code](/docs/cli/config/).
```bash
beeps relay apply
```
**Tool:** `beeps_create_schedule`
```json
{
"name": "Weekly On-Call",
"relayId": "rly_abc123",
"type": "weekly",
"handoffDay": "monday",
"handoffTime": "09:00"
}
```
## Listing Schedules
```typescript
const schedules = await client.schedule.list();
console.log(`Found ${schedules.length} schedules:`);
schedules.forEach((schedule) => {
console.log(`- ${schedule.name} (${schedule.type})`);
});
```
```bash
curl -X GET https://api.beeps.dev/v0/schedule \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"schedules": [
{
"id": "sch_xyz789",
"organizationId": "org_abc123",
"relayId": "rly_abc123",
"name": "Weekly On-Call",
"type": "weekly",
"handoffDay": "monday",
"handoffTime": "09:00",
"startAt": "2025-01-15T10:30:00Z",
"externalKey": "prod-oncall",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"deletedAt": null
}
]
}
```
```bash
beeps schedule list
```
For JSON output:
```bash
beeps schedule list --json
```
**Tool:** `beeps_list_schedules`
```json
{}
```
## Adding Schedule Members
Members are added to the rotation in the order they're added. New members usually join at the next handoff boundary so the current on-call assignment is not interrupted. If the schedule has not started yet, they become active at `startAt`. If the schedule is active but currently has no active members, they become active immediately. You can add members by email address or user ID.
```typescript
// Add by email address
const member1 = await client.schedule.addMember(schedule.id, {
email: "alice@example.com",
});
console.log(`Added ${member1.userId} to schedule`);
const member2 = await client.schedule.addMember(schedule.id, {
email: "bob@example.com",
});
console.log(`Added ${member2.userId} to schedule`);
// Or add by user ID
const member3 = await client.schedule.addMember(schedule.id, {
userId: "usr_charlie",
});
```
```bash
# Add by email
curl -X POST https://api.beeps.dev/v0/schedule/sch_xyz789/members \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com"
}'
# Or by user ID
curl -X POST https://api.beeps.dev/v0/schedule/sch_xyz789/members \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "usr_alice"
}'
```
**Response:**
```json
{
"member": {
"id": "scm_abc123",
"scheduleId": "sch_xyz789",
"userId": "usr_alice",
"createdAt": "2025-01-15T10:35:00Z",
"effectiveAt": "2025-01-20T09:00:00Z",
"removedAt": null
}
}
```
**Tool:** `beeps_add_schedule_member`
```json
{
"scheduleId": "sch_xyz789",
"email": "alice@example.com"
}
```
## Getting Current On-Call User
```typescript
const onCall = await client.schedule.getOnCall(schedule.id);
console.log(`Current on-call: ${onCall.email}`);
console.log(`User ID: ${onCall.userId}`);
console.log(`Assignment #: ${onCall.assignmentNumber}`);
```
### Safe Method
```typescript
const result = await client.schedule.getOnCallSafe(schedule.id);
if (result.error) {
console.error("Failed to get on-call:", result.error.message);
} else {
console.log(`On-call: ${result.data.email}`);
}
```
```bash
curl -X GET https://api.beeps.dev/v0/schedule/sch_xyz789/on-call \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"onCall": {
"userId": "usr_alice",
"email": "alice@example.com",
"assignmentNumber": 7,
"scheduleId": "sch_xyz789"
}
}
```
```bash
beeps schedule on-call --schedule-id sch_xyz789
```
For JSON output:
```bash
beeps schedule on-call --schedule-id sch_xyz789 --json
```
**Tool:** `beeps_get_on_call`
```json
{
"scheduleId": "sch_xyz789"
}
```
## Type Definitions
### CreateScheduleInput
```typescript
type CreateScheduleInput = {
name: string; // Schedule name
relayId: string; // Parent relay ID
type: "daily" | "weekly"; // Rotation frequency
handoffDay:
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday"
| "sunday"; // Day of week (used for weekly schedules)
handoffTime: string; // Time of day in HH:MM format (24-hour)
startAt?: string | Date; // Optional: omit to start immediately
externalKey?: string; // Optional custom identifier
};
```
### Schedule
```typescript
type Schedule = {
id: string;
organizationId: string;
relayId: string;
name: string;
type: "daily" | "weekly";
handoffDay:
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday"
| "sunday";
handoffTime: string;
startAt: string;
externalKey: string | null;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
### AddScheduleMemberInput
```typescript
type AddScheduleMemberInput =
| { userId: string } // Add by user ID
| { email: string }; // Add by email address
```
### ScheduleMember
```typescript
type ScheduleMember = {
id?: string;
scheduleId: string;
userId: string;
createdAt?: string;
effectiveAt?: string;
removedAt?: string | null;
};
```
### BeepsUser
```typescript
type BeepsUser = {
userId: string; // User ID of the person currently on-call
email: string; // Email address
assignmentNumber: number | null; // Current generated assignment index (null when schedule has no members)
scheduleId: string; // Schedule ID
isOverride?: boolean; // True if user is on-call via schedule override
overrideId?: string; // ID of the schedule override (if applicable)
};
```
## Best Practices
Always set `externalKey` for idempotent configuration. All times are in UTC. See [Best Practices](/docs/best-practices/#schedules) for details.
## Common Patterns
### Setting Up a New Team
```typescript
const relay = await client.relay.create({
name: "Backend Team Alerts",
});
const schedule = await client.schedule.create({
name: "Backend Weekly Rotation",
relayId: relay.id,
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
});
const teamEmails = ["alice@example.com", "bob@example.com", "charlie@example.com"];
for (const email of teamEmails) {
await client.schedule.addMember(schedule.id, { email });
}
await client.relay.rules.create(relay.id, {
name: "Notify on-call engineer",
ruleType: "schedule_notify",
config: {
scheduleId: schedule.id,
},
});
```
### Checking Who's On Call
```typescript
const onCall = await client.schedule.getOnCall(schedule.id);
console.log(`${onCall.email} is currently on-call`);
```
---
# Schedule Overrides
> Source: https://beeps.dev/docs/schedules/overrides/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Schedule overrides let you temporarily swap who is on-call. Use them for vacation coverage, sick days, or handing off to someone else mid-shift. An override takes effect for a specific time window and automatically expires when the window ends.
Overrides cannot overlap with each other on the same schedule. The maximum duration is 30 days.
## Creating an Override
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
const override = await client.schedule.createOverride("sch_xyz789", {
userId: "usr_bob",
startAt: "2025-03-15T09:00:00.000Z",
endAt: "2025-03-22T09:00:00.000Z",
reason: "Alice on vacation, Bob covering",
});
console.log(`Override created: ${override.id}`);
console.log(`${override.userId} on-call from ${override.startAt} to ${override.endAt}`);
```
You can also pass `Date` objects:
```typescript
const override = await client.schedule.createOverride("sch_xyz789", {
userId: "usr_bob",
startAt: new Date("2025-03-15T09:00:00.000Z"),
endAt: new Date("2025-03-22T09:00:00.000Z"),
reason: "Alice on vacation, Bob covering",
});
```
```bash
curl -X POST https://api.beeps.dev/v0/schedule/sch_xyz789/overrides \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "usr_bob",
"startAt": "2025-03-15T09:00:00.000Z",
"endAt": "2025-03-22T09:00:00.000Z",
"reason": "Alice on vacation, Bob covering"
}'
```
**Response:**
```json
{
"override": {
"id": "ovr_abc123",
"organizationId": "org_xyz789",
"scheduleId": "sch_xyz789",
"userId": "usr_bob",
"startAt": "2025-03-15T09:00:00.000Z",
"endAt": "2025-03-22T09:00:00.000Z",
"reason": "Alice on vacation, Bob covering",
"createdByUserId": "usr_alice",
"createdAt": "2025-03-10T14:00:00Z",
"updatedAt": "2025-03-10T14:00:00Z",
"canceledAt": null,
"deletedAt": null
}
}
```
```bash
beeps schedule override create \
--schedule-id sch_xyz789 \
--user-id usr_bob \
--start-at 2025-03-15T09:00:00.000Z \
--end-at 2025-03-22T09:00:00.000Z \
--reason "Alice on vacation, Bob covering"
```
Add `--json` for machine-readable output:
```bash
beeps schedule override create \
--schedule-id sch_xyz789 \
--user-id usr_bob \
--start-at 2025-03-15T09:00:00.000Z \
--end-at 2025-03-22T09:00:00.000Z \
--reason "Alice on vacation, Bob covering" \
--json
```
**Tool:** `beeps_set_override`
```json
{
"scheduleId": "sch_xyz789",
"userId": "usr_bob",
"startAt": "2025-03-15T09:00:00.000Z",
"endAt": "2025-03-22T09:00:00.000Z",
"reason": "Alice on vacation, Bob covering"
}
```
## Listing Overrides
```typescript
const overrides = await client.schedule.listOverrides("sch_xyz789");
overrides.forEach((override) => {
console.log(`${override.userId}: ${override.startAt} - ${override.endAt}`);
if (override.reason) {
console.log(` Reason: ${override.reason}`);
}
});
```
Filter by date range to find overrides within a specific window:
```typescript
const overrides = await client.schedule.listOverrides("sch_xyz789", {
startAt: "2025-03-01T00:00:00.000Z",
endAt: "2025-03-31T23:59:59.999Z",
});
```
```bash
curl -X GET "https://api.beeps.dev/v0/schedule/sch_xyz789/overrides" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
With date range filtering:
```bash
curl -X GET "https://api.beeps.dev/v0/schedule/sch_xyz789/overrides?startAt=2025-03-01T00:00:00.000Z&endAt=2025-03-31T23:59:59.999Z" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"overrides": [
{
"id": "ovr_abc123",
"scheduleId": "sch_xyz789",
"userId": "usr_bob",
"startAt": "2025-03-15T09:00:00.000Z",
"endAt": "2025-03-22T09:00:00.000Z",
"reason": "Alice on vacation, Bob covering",
"createdByUserId": "usr_alice",
"canceledAt": null
}
]
}
```
```bash
beeps schedule override list --schedule-id sch_xyz789
```
With date range filtering:
```bash
beeps schedule override list \
--schedule-id sch_xyz789 \
--start-at 2025-03-01T00:00:00.000Z \
--end-at 2025-03-31T23:59:59.999Z
```
Add `--json` for machine-readable output:
```bash
beeps schedule override list --schedule-id sch_xyz789 --json
```
## Updating an Override
Change the time window or reason for an existing override. All fields are optional—only include what you want to change.
```typescript
const updated = await client.schedule.updateOverride(
"sch_xyz789",
"ovr_abc123",
{
endAt: "2025-03-25T09:00:00.000Z",
reason: "Extended coverage — Alice returning Thursday",
},
);
console.log(`Override now ends at ${updated.endAt}`);
```
```bash
curl -X PATCH https://api.beeps.dev/v0/schedule/sch_xyz789/overrides/ovr_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"endAt": "2025-03-25T09:00:00.000Z",
"reason": "Extended coverage — Alice returning Thursday"
}'
```
**Response:**
```json
{
"override": {
"id": "ovr_abc123",
"endAt": "2025-03-25T09:00:00.000Z",
"reason": "Extended coverage — Alice returning Thursday",
"updatedAt": "2025-03-12T10:00:00Z"
}
}
```
```bash
beeps schedule override update \
--schedule-id sch_xyz789 \
--override-id ovr_abc123 \
--end-at 2025-03-25T09:00:00.000Z \
--reason "Extended coverage"
```
Add `--json` for machine-readable output:
```bash
beeps schedule override update \
--schedule-id sch_xyz789 \
--override-id ovr_abc123 \
--end-at 2025-03-25T09:00:00.000Z \
--reason "Extended coverage" \
--json
```
**Tool:** `beeps_update_override`
```json
{
"scheduleId": "sch_xyz789",
"overrideId": "ovr_abc123",
"endAt": "2025-03-25T09:00:00.000Z",
"reason": "Extended coverage"
}
```
## Canceling an Override
Cancel an override to restore the normal rotation. The on-call user reverts to whoever the schedule assigns.
```typescript
const canceled = await client.schedule.cancelOverride("sch_xyz789", "ovr_abc123");
console.log(`Override canceled at ${canceled.canceledAt}`);
```
```bash
curl -X DELETE https://api.beeps.dev/v0/schedule/sch_xyz789/overrides/ovr_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"override": {
"id": "ovr_abc123",
"canceledAt": "2025-03-12T10:00:00Z"
}
}
```
```bash
beeps schedule override cancel --schedule-id sch_xyz789 --override-id ovr_abc123
```
Add `--json` for machine-readable output:
```bash
beeps schedule override cancel --schedule-id sch_xyz789 --override-id ovr_abc123 --json
```
**Tool:** `beeps_cancel_override`
```json
{
"scheduleId": "sch_xyz789",
"overrideId": "ovr_abc123"
}
```
## Type Definitions
### CreateScheduleOverrideInput
```typescript
type CreateScheduleOverrideInput = {
userId: string;
startAt: string | Date;
endAt: string | Date;
reason?: string;
};
```
### UpdateScheduleOverrideInput
```typescript
type UpdateScheduleOverrideInput = {
startAt?: string | Date;
endAt?: string | Date;
reason?: string;
};
```
### ListScheduleOverridesParams
```typescript
type ListScheduleOverridesParams = {
startAt?: string | Date;
endAt?: string | Date;
};
```
### ScheduleOverride
```typescript
type ScheduleOverride = {
id: string;
organizationId: string;
scheduleId: string;
userId: string;
startAt: string;
endAt: string;
reason: string | null;
createdByUserId: string;
createdAt: string;
updatedAt: string;
canceledAt: string | null;
deletedAt: string | null;
};
```
## Common Patterns
### Vacation Coverage
```typescript
const vacationStart = new Date("2025-06-01T00:00:00.000Z");
const vacationEnd = new Date("2025-06-14T00:00:00.000Z");
const override = await client.schedule.createOverride(schedule.id, {
userId: "usr_bob",
startAt: vacationStart,
endAt: vacationEnd,
reason: "Covering for Alice — vacation June 1-14",
});
```
### Temporary Handoff
```typescript
const now = new Date();
const fourHoursLater = new Date(now.getTime() + 4 * 60 * 60 * 1000);
const override = await client.schedule.createOverride(schedule.id, {
userId: "usr_charlie",
startAt: now,
endAt: fourHoursLater,
reason: "Handing off for a few hours",
});
```
### Checking Who Is Actually On-Call
The `getOnCall` method already accounts for overrides. If an override is active, the returned user will have `isOverride: true` and `overrideId` set:
```typescript
const onCall = await client.schedule.getOnCall(schedule.id);
if (onCall.isOverride) {
console.log(`${onCall.email} is on-call via override ${onCall.overrideId}`);
} else {
console.log(`${onCall.email} is on-call (assignment #${onCall.assignmentNumber})`);
}
```
---
# Schedule Assignments
> Source: https://beeps.dev/docs/schedules/assignments/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Assignments show who is on-call during specific time periods. Use assignments to plan coverage, understand rotation patterns, and track historical data.
## Understanding Assignments
Each assignment represents:
- **Assignment Number**: The generated assignment index since the schedule started
- **User ID**: Who is on-call for this assignment
- **Start Date**: When this assignment begins (ISO 8601 format)
- **End Date**: When this assignment ends (ISO 8601 format)
## Viewing Assignments
You can retrieve assignments in three ways:
### By Number of Assignments
Get a specific number of upcoming assignments:
```typescript
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "assignments",
count: 5, // Get next 5 assignments
});
assignments.forEach((assignment) => {
console.log(
`Assignment ${assignment.assignmentNumber}: ${assignment.userId}`
);
console.log(` ${assignment.startDate} → ${assignment.endDate}`);
});
```
```bash
curl -X GET "https://api.beeps.dev/v0/schedule/sch_xyz789/assignments?type=assignments&count=5" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps schedule assignments --schedule-id sch_xyz789 --count 5
```
For JSON output:
```bash
beeps schedule assignments --schedule-id sch_xyz789 --count 5 --json
```
**Tool:** `beeps_get_schedule_assignments`
```json
{
"scheduleId": "sch_xyz789",
"count": 5
}
```
### By Number of Days
Get all assignments covering a specific number of days:
```typescript
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "days",
count: 14, // Get assignments for next 14 days
});
console.log(`Assignments covering next 14 days: ${assignments.length}`);
```
```bash
curl -X GET "https://api.beeps.dev/v0/schedule/sch_xyz789/assignments?type=days&count=14" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps schedule assignments --schedule-id sch_xyz789 --type days --count 14
```
For JSON output:
```bash
beeps schedule assignments --schedule-id sch_xyz789 --type days --count 14 --json
```
**Tool:** `beeps_get_schedule_assignments`
```json
{
"scheduleId": "sch_xyz789",
"type": "days",
"count": 14
}
```
### Until a Specific Date
Get all assignments up to a specific date:
```typescript
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "until",
date: "2025-12-31T23:59:59.999Z", // Get assignments until end of year
});
console.log(`Assignments until 2025-12-31: ${assignments.length}`);
```
```bash
curl -X GET "https://api.beeps.dev/v0/schedule/sch_xyz789/assignments?type=until&date=2025-12-31T23:59:59.999Z" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps schedule assignments --schedule-id sch_xyz789 --type until --date 2025-12-31T23:59:59.999Z
```
For JSON output:
```bash
beeps schedule assignments --schedule-id sch_xyz789 --type until --date 2025-12-31T23:59:59.999Z --json
```
**Tool:** `beeps_get_schedule_assignments`
```json
{
"scheduleId": "sch_xyz789",
"type": "until",
"date": "2025-12-31T23:59:59.999Z"
}
```
## API Response
```json
{
"assignments": [
{
"userId": "usr_alice",
"startDate": "2025-01-20T09:00:00Z",
"endDate": "2025-01-27T09:00:00Z",
"assignmentNumber": 7
},
{
"userId": "usr_bob",
"startDate": "2025-01-27T09:00:00Z",
"endDate": "2025-02-03T09:00:00Z",
"assignmentNumber": 8
},
{
"userId": "usr_charlie",
"startDate": "2025-02-03T09:00:00Z",
"endDate": "2025-02-10T09:00:00Z",
"assignmentNumber": 9
}
]
}
```
## Type Definitions
### GetAssignmentsParams
```typescript
type GetAssignmentsParams =
| {
type?: "assignments"; // Default
count?: number; // Number of assignments (optional)
}
| {
type: "until";
date: string | Date; // ISO 8601 date or JavaScript Date
}
| {
type: "days";
count: number; // Number of days
};
```
### ScheduleAssignment
```typescript
type ScheduleAssignment = {
userId: string; // User on-call for this assignment
startDate: string; // ISO 8601 timestamp
endDate: string; // ISO 8601 timestamp
assignmentNumber: number; // Generated assignment index since schedule start
};
```
## Common Use Cases
### Show Who's On-Call This Week
```typescript
const onCall = await client.schedule.getOnCall(schedule.id);
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "days",
count: 7,
});
const currentAssignment = assignments.find(
(a) => a.assignmentNumber === onCall.assignmentNumber
);
if (currentAssignment) {
console.log(`${onCall.email} is on-call until ${currentAssignment.endDate}`);
}
```
### Display Rotation Calendar
```typescript
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "days",
count: 30,
});
console.log("On-Call Calendar (Next 30 Days):\n");
assignments.forEach((assignment) => {
const start = new Date(assignment.startDate).toLocaleDateString();
const end = new Date(assignment.endDate).toLocaleDateString();
console.log(`${start} - ${end}: ${assignment.userId}`);
});
```
### Find Next Assignment for a User
```typescript
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "assignments",
count: 10,
});
const userId = "usr_alice";
const nextAssignment = assignments.find((a) => a.userId === userId);
if (nextAssignment) {
console.log(`${userId}'s next on-call starts: ${nextAssignment.startDate}`);
} else {
console.log(`${userId} not found in next 10 assignments`);
}
```
### Calculate On-Call Load
```typescript
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "days",
count: 90,
});
const userCounts = new Map();
assignments.forEach((assignment) => {
const count = userCounts.get(assignment.userId) || 0;
userCounts.set(assignment.userId, count + 1);
});
console.log("On-Call Load (Next 90 Days):");
userCounts.forEach((count, userId) => {
console.log(`${userId}: ${count} assignments`);
});
```
### Vacation Planning
```typescript
function formatDate(date: Date): string {
return date.toISOString().split("T")[0];
}
const vacationStart = new Date("2025-03-15");
const vacationEnd = new Date("2025-03-22");
const assignments = await client.schedule.getAssignments(schedule.id, {
type: "until",
date: formatDate(vacationEnd),
});
const conflictingAssignments = assignments.filter((assignment) => {
const start = new Date(assignment.startDate);
const end = new Date(assignment.endDate);
return (
assignment.userId === "usr_alice" &&
start <= vacationEnd &&
end >= vacationStart
);
});
if (conflictingAssignments.length > 0) {
console.log("⚠️ On-call assignments during vacation:");
conflictingAssignments.forEach((a) => {
console.log(` ${a.startDate} → ${a.endDate}`);
});
} else {
console.log("✓ No conflicts with vacation dates");
}
```
## Best Practices
All dates are in UTC—convert to local time zones for display. Cache assignments to reduce API calls since they don't change frequently.
---
# Schedule API Reference
> Source: https://beeps.dev/docs/schedules/api/
Complete API reference for the Schedule resource in the beeps SDK.
If you omit `startAt` when creating a schedule, the server defaults it to the current time so the schedule starts immediately.
## Schedule Methods
### `client.schedule.create(input)`
Create a new schedule.
**Parameters:**
- `input: CreateScheduleInput`
**Returns:** `Promise`
**Example:**
```typescript
const schedule = await client.schedule.create({
name: "Weekly On-Call",
relayId: "rly_abc123",
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
externalKey: "prod-oncall",
});
```
### `client.schedule.list()`
List all schedules in your organization.
**Returns:** `Promise`
**Example:**
```typescript
const schedules = await client.schedule.list();
```
### `client.schedule.addMember(scheduleId, input)`
Add a team member to a schedule rotation. New members usually join at the next handoff boundary so the current on-call assignment is not interrupted. If the schedule has not started yet, they become active at `startAt`. If the schedule is active but currently has no active members, they become active immediately.
**Parameters:**
- `scheduleId: string`
- `input: AddScheduleMemberInput`
**Returns:** `Promise`
**Examples:**
```typescript
// Add by email address
const member = await client.schedule.addMember("sch_xyz789", {
email: "alice@example.com",
});
// Or add by user ID
const member = await client.schedule.addMember("sch_xyz789", {
userId: "usr_alice",
});
// Alice joins based on the schedule timing
```
### `client.schedule.listMembers(scheduleId)`
List active members of a schedule rotation.
**Parameters:**
- `scheduleId: string`
**Returns:** `Promise`
**Example:**
```typescript
const members = await client.schedule.listMembers("sch_xyz789");
```
### `client.schedule.listMembersSafe(scheduleId)`
List active schedule members without throwing exceptions.
**Parameters:**
- `scheduleId: string`
**Returns:** `Promise>`
**Example:**
```typescript
const result = await client.schedule.listMembersSafe("sch_xyz789");
if (result.error) {
console.error("Error:", result.error.message);
} else {
console.log(`Members: ${result.data.length}`);
}
```
### `client.schedule.removeMember(scheduleId, userId)`
Remove a team member from a schedule rotation. The member will remain on-call through the end of their current shift, then be removed at the next rotation boundary. Cannot remove a member if it would leave the schedule with no active members.
**Parameters:**
- `scheduleId: string`
- `userId: string`
**Returns:** `Promise`
**Example:**
```typescript
const member = await client.schedule.removeMember("sch_xyz789", "usr_alice");
// Alice completes her current shift, then leaves the rotation
```
### `client.schedule.getAssignments(scheduleId, params?)`
Get schedule assignments.
**Parameters:**
- `scheduleId: string`
- `params?: GetAssignmentsParams`
**Returns:** `Promise`
**Examples:**
```typescript
// Get next 5 assignments
const assignments = await client.schedule.getAssignments("sch_xyz789", {
type: "assignments",
count: 5,
});
// Get assignments for next 14 days
const assignments = await client.schedule.getAssignments("sch_xyz789", {
type: "days",
count: 14,
});
// Get assignments until specific date (using Date object)
const assignments = await client.schedule.getAssignments("sch_xyz789", {
type: "until",
date: new Date("2025-12-31T23:59:59.999Z"),
});
// Or using ISO 8601 string with Z suffix (UTC timezone required)
const assignments = await client.schedule.getAssignments("sch_xyz789", {
type: "until",
date: "2025-12-31T23:59:59.999Z",
});
```
### `client.schedule.getOnCall(scheduleId)`
Get the user currently on-call for a schedule.
**Parameters:**
- `scheduleId: string`
**Returns:** `Promise`
**Example:**
```typescript
const onCall = await client.schedule.getOnCall("sch_xyz789");
console.log(`${onCall.email} is on-call`);
```
### `client.schedule.getOnCallSafe(scheduleId)`
Get the user currently on-call without throwing exceptions.
**Parameters:**
- `scheduleId: string`
**Returns:** `Promise>`
**Example:**
```typescript
const result = await client.schedule.getOnCallSafe("sch_xyz789");
if (result.error) {
console.error("Error:", result.error.message);
} else {
console.log(`On-call: ${result.data.email}`);
}
```
## Override Methods
### `client.schedule.createOverride(scheduleId, input)`
Create a temporary on-call override. The override user takes over for the specified time window.
**Parameters:**
- `scheduleId: string`
- `input: CreateScheduleOverrideInput`
**Returns:** `Promise`
**Example:**
```typescript
const override = await client.schedule.createOverride("sch_xyz789", {
userId: "usr_bob",
startAt: "2025-03-15T09:00:00.000Z",
endAt: "2025-03-22T09:00:00.000Z",
reason: "Vacation coverage",
});
```
### `client.schedule.listOverrides(scheduleId, params?)`
List overrides for a schedule, optionally filtered by date range.
**Parameters:**
- `scheduleId: string`
- `params?: ListScheduleOverridesParams`
**Returns:** `Promise`
**Example:**
```typescript
const overrides = await client.schedule.listOverrides("sch_xyz789");
// With date range filter
const overrides = await client.schedule.listOverrides("sch_xyz789", {
startAt: "2025-03-01T00:00:00.000Z",
endAt: "2025-03-31T23:59:59.999Z",
});
```
### `client.schedule.updateOverride(scheduleId, overrideId, input)`
Update an existing override. All fields are optional.
**Parameters:**
- `scheduleId: string`
- `overrideId: string`
- `input: UpdateScheduleOverrideInput`
**Returns:** `Promise`
**Example:**
```typescript
const updated = await client.schedule.updateOverride("sch_xyz789", "ovr_abc123", {
endAt: "2025-03-25T09:00:00.000Z",
reason: "Extended coverage",
});
```
### `client.schedule.cancelOverride(scheduleId, overrideId)`
Cancel an override. The normal rotation resumes.
**Parameters:**
- `scheduleId: string`
- `overrideId: string`
**Returns:** `Promise`
**Example:**
```typescript
const canceled = await client.schedule.cancelOverride("sch_xyz789", "ovr_abc123");
```
## Types
### CreateScheduleInput
```typescript
type CreateScheduleInput = {
name: string;
relayId: string;
type: "daily" | "weekly";
handoffDay:
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday"
| "sunday";
handoffTime: string; // HH:MM format (24-hour)
startAt?: string | Date;
externalKey?: string;
};
```
### Schedule
```typescript
type Schedule = {
id: string;
organizationId: string;
relayId: string;
name: string;
type: "daily" | "weekly";
handoffDay:
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday"
| "sunday";
handoffTime: string;
startAt: string;
externalKey: string | null;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
### AddScheduleMemberInput
```typescript
type AddScheduleMemberInput =
| { userId: string }
| { email: string };
```
### ScheduleMember
```typescript
type ScheduleMember = {
id?: string;
scheduleId: string;
userId: string;
createdAt?: string;
effectiveAt?: string;
removedAt?: string | null;
};
```
### GetAssignmentsParams
```typescript
type GetAssignmentsParams =
| { type?: "assignments"; count?: number }
| { type: "until"; date: string | Date }
| { type: "days"; count: number };
```
**Note:** When using `type: "until"`, the `date` parameter must be either:
- A JavaScript `Date` object (automatically converted to UTC ISO 8601 format)
- An ISO 8601 timestamp string with Z suffix (e.g., `"2025-12-31T23:59:59.999Z"`)
The SDK validates that string dates include the Z suffix to ensure UTC timezone consistency. Dates without timezone indicators will be rejected with a clear error message.
### ScheduleAssignment
```typescript
type ScheduleAssignment = {
userId: string;
startDate: string; // ISO 8601 timestamp
endDate: string; // ISO 8601 timestamp
assignmentNumber: number;
};
```
### BeepsUser
```typescript
type BeepsUser = {
userId: string;
email: string;
assignmentNumber: number | null; // Generated assignment index, null when schedule has no members
scheduleId: string;
isOverride?: boolean; // True if user is on-call via schedule override
overrideId?: string; // ID of the schedule override (if applicable)
};
```
### CreateScheduleOverrideInput
```typescript
type CreateScheduleOverrideInput = {
userId: string;
startAt: string | Date;
endAt: string | Date;
reason?: string;
};
```
### UpdateScheduleOverrideInput
```typescript
type UpdateScheduleOverrideInput = {
startAt?: string | Date;
endAt?: string | Date;
reason?: string;
};
```
### ListScheduleOverridesParams
```typescript
type ListScheduleOverridesParams = {
startAt?: string | Date;
endAt?: string | Date;
};
```
### ScheduleOverride
```typescript
type ScheduleOverride = {
id: string;
organizationId: string;
scheduleId: string;
userId: string;
startAt: string;
endAt: string;
reason: string | null;
createdByUserId: string;
createdAt: string;
updatedAt: string;
canceledAt: string | null;
deletedAt: string | null;
};
```
## HTTP Endpoints
### Create Schedule
```
POST /v0/schedule
```
**Request Body:**
```json
{
"name": "Weekly On-Call",
"relayId": "rly_abc123",
"type": "weekly",
"handoffDay": "monday",
"handoffTime": "09:00",
"externalKey": "prod-oncall"
}
```
If `startAt` is omitted, the server sets it to the current time.
### List Schedules
```
GET /v0/schedule
```
### Add Schedule Member
```
POST /v0/schedule/:scheduleId/members
```
Adds a member to the rotation. They will join at the next rotation boundary. Pass either `userId` or `email`.
**Request Body:**
```json
{
"email": "alice@example.com"
}
```
Or by user ID:
```json
{
"userId": "usr_alice"
}
```
### List Schedule Members
```
GET /v0/schedule/:scheduleId/members
```
### Remove Schedule Member
```
DELETE /v0/schedule/:scheduleId/members/:userId
```
Removes a member from the rotation. They will complete their current shift and leave at the next rotation boundary. Cannot remove a member if it would leave the schedule with no active members.
### Get Assignments
```
GET /v0/schedule/:scheduleId/assignments
GET /v0/schedule/:scheduleId/assignments?type=assignments&count=5
GET /v0/schedule/:scheduleId/assignments?type=days&count=14
GET /v0/schedule/:scheduleId/assignments?type=until&date=2025-12-31T23:59:59.999Z
```
**Note:** The `date` parameter for `type=until` must be a valid ISO 8601 timestamp with Z suffix (UTC timezone). Dates without the Z suffix will be rejected with a validation error.
### Get Current On-Call User
```
GET /v0/schedule/:scheduleId/on-call
```
### Create Override
```
POST /v0/schedule/:scheduleId/overrides
```
**Request Body:**
```json
{
"userId": "usr_bob",
"startAt": "2025-03-15T09:00:00.000Z",
"endAt": "2025-03-22T09:00:00.000Z",
"reason": "Vacation coverage"
}
```
**Note:** `startAt` and `endAt` must be valid ISO 8601 timestamps with Z suffix (UTC timezone).
### List Overrides
```
GET /v0/schedule/:scheduleId/overrides
GET /v0/schedule/:scheduleId/overrides?startAt=2025-03-01T00:00:00.000Z&endAt=2025-03-31T23:59:59.999Z
```
### Update Override
```
PATCH /v0/schedule/:scheduleId/overrides/:overrideId
```
**Request Body:**
```json
{
"endAt": "2025-03-25T09:00:00.000Z",
"reason": "Extended coverage"
}
```
### Cancel Override
```
DELETE /v0/schedule/:scheduleId/overrides/:overrideId
```
## Authentication
All API requests require authentication using your access token:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.beeps.dev/v0/schedule
```
---
# Contact Methods Overview
> Source: https://beeps.dev/docs/contact-methods/overview/
Contact methods define how users receive notifications. Each user can have multiple email and SMS contact methods. When a relay rule notifies an on-call user, it uses their configured contact methods.
## Automatic Email Contact Method
When you sign up for beeps, your signup email is automatically added as a verified contact method. This means you'll start receiving notifications immediately without any extra setup.
## Managing Contact Methods
Contact methods are managed through the beeps web UI at **Settings > Profile**. From there you can:
- Add additional email addresses
- Add SMS phone numbers (E.164 format, e.g. `+14155551234`)
- Remove contact methods you no longer need
- Resend verification for unverified SMS numbers
## Transport Types
### Email
Email contact methods are automatically verified when added.
### SMS
SMS contact methods require verification. After adding a phone number, you'll receive a text message and must reply **YES** to confirm.
## Verification Status
Contact methods have a `verified` status:
- **Verified**: Confirmed to be working and deliverable
- **Unverified**: Not yet confirmed
Unverified contact methods receive notifications, but delivery is not guaranteed. Verify methods for reliable delivery.
## Listing Contact Methods via SDK
You can list and delete contact methods programmatically:
```typescript
const methods = await client.contactMethod.list({
userId: "usr_alice",
});
console.log(`Alice has ${methods.length} contact methods`);
const emailMethods = methods.filter((m) => m.transport === "email");
const smsMethods = methods.filter((m) => m.transport === "sms");
console.log(`Emails: ${emailMethods.length}, SMS: ${smsMethods.length}`);
```
### Cleaning Up Old Contact Methods
Remove outdated contact information:
```typescript
const methods = await client.contactMethod.list({
userId: "usr_alice",
});
const oldEmail = methods.find(
(m) => m.value === "old-email@example.com"
);
if (oldEmail) {
await client.contactMethod.delete(oldEmail.id, {
userId: "usr_alice",
});
}
```
## Best Practices
See [Best Practices - Contact Methods](/docs/best-practices/#contact-methods) for configuration guidelines.
---
# Managing Contact Methods
> Source: https://beeps.dev/docs/contact-methods/manage/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Contact methods are created and managed through the beeps web UI at **Settings > Profile**. Your signup email is automatically added as a verified contact method.
The SDK and API provide read and delete access for programmatic management.
## Listing Contact Methods
```typescript
const methods = await client.contactMethod.list({
userId: "usr_alice",
});
console.log(`Found ${methods.length} contact methods`);
methods.forEach((method) => {
console.log(`- ${method.transport}: ${method.value}`);
console.log(` Verified: ${method.verified}`);
});
```
### Filter by Transport Type
```typescript
const methods = await client.contactMethod.list({
userId: "usr_alice",
});
const emailMethods = methods.filter((m) => m.transport === "email");
const smsMethods = methods.filter((m) => m.transport === "sms");
console.log(`Emails: ${emailMethods.length}`);
console.log(`SMS: ${smsMethods.length}`);
```
```bash
curl -X GET "https://api.beeps.dev/v0/contact-methods?userId=usr_alice" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"contactMethods": [
{
"id": "cm_abc123",
"userId": "usr_alice",
"transport": "email",
"value": "alice@example.com",
"verified": true,
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"deletedAt": null
},
{
"id": "cm_def456",
"userId": "usr_alice",
"transport": "sms",
"value": "+14155551234",
"verified": false,
"createdAt": "2025-01-15T11:00:00Z",
"updatedAt": "2025-01-15T11:00:00Z",
"deletedAt": null
}
]
}
```
## Deleting a Contact Method
```typescript
const result = await client.contactMethod.delete("cm_abc123", {
userId: "usr_alice",
});
console.log("Deleted successfully:", result.success);
```
### Safe Method
```typescript
const result = await client.contactMethod.deleteSafe("cm_abc123", {
userId: "usr_alice",
});
if (result.error) {
console.error("Failed to delete:", result.error.message);
} else {
console.log("Deleted:", result.data.success);
}
```
```bash
curl -X DELETE "https://api.beeps.dev/v0/contact-methods/cm_abc123?userId=usr_alice" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"success": true
}
```
## Type Definitions
### ContactMethod
```typescript
type ContactMethod = {
id: string;
userId: string;
transport: string;
value: string;
verified: boolean;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
### ListContactMethodsParams
```typescript
type ListContactMethodsParams = {
userId: string;
};
```
### DeleteContactMethodParams
```typescript
type DeleteContactMethodParams = {
userId: string;
};
```
## Troubleshooting
**No one receiving notifications?** See [Troubleshooting - No One Is Notified](/docs/troubleshooting/#alerts-appear-but-no-one-is-notified).
---
# Contact Methods API Reference
> Source: https://beeps.dev/docs/contact-methods/api/
Complete API reference for the Contact Method resource in the beeps SDK.
## Contact Method Methods
### `client.contactMethod.list(params)`
List all contact methods for a user.
**Parameters:**
- `params: ListContactMethodsParams`
**Returns:** `Promise`
**Example:**
```typescript
const methods = await client.contactMethod.list({
userId: "usr_alice",
});
```
### `client.contactMethod.delete(id, params)`
Delete a contact method.
**Parameters:**
- `id: string` - Contact method ID
- `params: DeleteContactMethodParams`
**Returns:** `Promise<{ success: boolean }>`
**Example:**
```typescript
await client.contactMethod.delete("cm_abc123", {
userId: "usr_alice",
});
```
## Safe Methods
All methods have corresponding `*Safe` variants that return `Result` instead of throwing:
### `client.contactMethod.listSafe(params)`
**Returns:** `Promise>`
### `client.contactMethod.deleteSafe(id, params)`
**Returns:** `Promise>`
## Types
### ContactMethodTransport
```typescript
type ContactMethodTransport = "email" | "sms";
```
### ContactMethod
```typescript
type ContactMethod = {
id: string;
userId: string;
transport: string;
value: string;
verified: boolean;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
### ListContactMethodsParams
```typescript
type ListContactMethodsParams = {
userId: string;
};
```
### DeleteContactMethodParams
```typescript
type DeleteContactMethodParams = {
userId: string;
};
```
## HTTP Endpoints
### List Contact Methods
```
GET /v0/contact-methods?userId=usr_alice
```
**Query Parameters:**
- `userId` (required): User ID to list contact methods for
### Delete Contact Method
```
DELETE /v0/contact-methods/:id?userId=usr_alice
```
**Query Parameters:**
- `userId` (required): User ID (for authorization)
## Authentication
All API requests require authentication using your access token:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.beeps.dev/v0/contact-methods?userId=usr_alice
```
---
# Alerts Overview
> Source: https://beeps.dev/docs/alerts/overview/
Alerts are incidents received via webhooks from your monitoring systems. They have a severity level (critical, high, medium, low, info), flow through relays, and can be responded to, assigned, and resolved.
## Alert Lifecycle
1. **Created**: Alert is received via webhook or integration
2. **Routed**: Alert flows through relay rules
3. **Notified**: On-call users are notified via their contact methods
4. **Responded**: One or more responders signal they're on it
5. **Assigned**: Alert is assigned to a specific user (optional)
6. **Resolved**: Alert is marked as resolved
Multiple responders can join the same alert. Each responder tracks their own status (`on_it`, `done`, `dropped`) independently.
## Alert Severity Levels
Alerts have five severity levels:
- **Critical**: Immediate attention required, service down
- **High**: Important issue requiring prompt attention
- **Medium**: Non-urgent issue that should be addressed
- **Low**: Minor issue with minimal impact
- **Info**: Informational notification, no action required
```typescript
const alerts = await client.alert.list();
const criticalAlerts = alerts.filter((a) => a.severity === "critical");
console.log(`${criticalAlerts.length} critical alerts`);
```
## Alert States
### Active Alerts
Alerts that haven't been resolved:
```typescript
const activeAlerts = await client.alert.listActive();
console.log(`${activeAlerts.length} alerts need attention`);
```
### Resolved Alerts
Alerts that have been resolved:
```typescript
const resolvedAlerts = await client.alert.listResolved();
console.log(`${resolvedAlerts.length} alerts resolved`);
```
## Common Operations
### Responding to an Alert
Signal that you're working on an alert:
```typescript
const responder = await client.alert.onIt("alr_abc123", {
userId: "usr_alice",
});
console.log(`${responder.userId} is on it (joined ${responder.joinedAt})`);
```
### Assigning an Alert
Assign an alert to a specific user:
```typescript
const assigned = await client.alert.assign("alr_abc123", "usr_bob");
console.log(`Assigned to ${assigned.assignedToUserId}`);
```
### Resolving an Alert
Mark an alert as resolved:
```typescript
const resolved = await client.alert.resolve("alr_abc123");
console.log(`Resolved at ${resolved.resolvedAt}`);
```
## Alert Metadata
Alerts can include arbitrary metadata for context:
```typescript
const alert = await client.alert.get("alr_abc123");
console.log("Alert metadata:", alert.metadata);
// Example: { server: "web-01", region: "us-west", errorRate: 15.3 }
```
## Common Use Cases
### Monitoring Active Alerts
```typescript
const active = await client.alert.listActive();
console.log(`Active Alerts: ${active.length}\n`);
active.forEach((alert) => {
console.log(`[${alert.severity.toUpperCase()}] ${alert.title}`);
console.log(` Source: ${alert.source}`);
if (alert.assignedToUserId) {
console.log(` Assigned to: ${alert.assignedToUserId}`);
}
console.log();
});
```
### On-Call Dashboard
```typescript
const active = await client.alert.listActive();
const byUser = new Map();
active.forEach((alert) => {
if (alert.assignedToUserId) {
const count = byUser.get(alert.assignedToUserId) || 0;
byUser.set(alert.assignedToUserId, count + 1);
}
});
console.log("Alerts by User:");
byUser.forEach((count, userId) => {
console.log(`${userId}: ${count} alerts`);
});
```
### Alert Workflow
```typescript
const alertId = "alr_abc123";
const userId = "usr_alice";
const alert = await client.alert.get(alertId);
console.log(`Working on: ${alert.title}`);
const responder = await client.alert.onIt(alertId, { userId });
console.log("On it");
await client.alert.assign(alertId, userId);
console.log("Assigned to self");
console.log("Investigating...");
await client.alert.updateResponderStatus(alertId, responder.id, {
status: "done",
});
console.log("Responder done");
await client.alert.resolve(alertId);
console.log("Resolved");
```
---
# Managing Alerts
> Source: https://beeps.dev/docs/alerts/manage/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Manage alerts throughout their lifecycle.
## Listing All Alerts
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
const alerts = await client.alert.list();
console.log(`Found ${alerts.length} total alerts`);
alerts.forEach((alert) => {
console.log(`[${alert.severity}] ${alert.title}`);
});
```
```bash
curl -X GET https://api.beeps.dev/v0/alerts \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"alerts": [
{
"id": "alr_abc123",
"organizationId": "org_xyz789",
"webhookId": "wh_def456",
"title": "High CPU usage on web-01",
"message": "CPU usage has exceeded 90% for 5 minutes",
"severity": "high",
"source": "monitoring",
"externalId": "mon-12345",
"metadata": {
"server": "web-01",
"cpu": 92.5
},
"assignedToUserId": null,
"resolvedAt": null,
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
]
}
```
```bash
beeps alert list
beeps alert list --json
```
**Tool:** `beeps_list_alerts`
```json
{
"status": "all"
}
```
## Listing Active Alerts
```typescript
const activeAlerts = await client.alert.listActive();
console.log(`${activeAlerts.length} active alerts need attention`);
```
### Safe Method
```typescript
const result = await client.alert.listActiveSafe();
if (result.error) {
console.error("Failed to list alerts:", result.error.message);
} else {
console.log(`Active alerts: ${result.data.length}`);
}
```
```bash
curl -X GET https://api.beeps.dev/v0/alerts/active \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps alert list --active
beeps alert list --active --json
```
**Tool:** `beeps_list_alerts`
```json
{
"status": "active"
}
```
Or use the dedicated tool:
**Tool:** `beeps_list_active_alerts`
```json
{}
```
## Listing Resolved Alerts
```typescript
const resolvedAlerts = await client.alert.listResolved();
console.log(`${resolvedAlerts.length} alerts have been resolved`);
```
```bash
curl -X GET https://api.beeps.dev/v0/alerts/resolved \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps alert list --resolved
```
**Tool:** `beeps_list_alerts`
```json
{
"status": "resolved"
}
```
## Getting a Specific Alert
```typescript
const alert = await client.alert.get("alr_abc123");
console.log(`Title: ${alert.title}`);
console.log(`Severity: ${alert.severity}`);
console.log(`Status: ${alert.resolvedAt ? "Resolved" : "Active"}`);
```
```bash
curl -X GET https://api.beeps.dev/v0/alerts/alr_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps alert get --alert-id alr_abc123
beeps alert get --alert-id alr_abc123 --json
```
**Tool:** `beeps_get_alert`
```json
{
"alertId": "alr_abc123"
}
```
## Responding to an Alert
Signal that you're working on an alert. This creates a responder record.
```typescript
const responder = await client.alert.onIt("alr_abc123", {
userId: "usr_alice",
});
console.log(`${responder.userId} is on it (status: ${responder.status})`);
console.log(`Joined at ${responder.joinedAt}`);
```
If `userId` is not provided, the authenticated user is used:
```typescript
const responder = await client.alert.onIt("alr_abc123");
```
### Safe Method
```typescript
const result = await client.alert.onItSafe("alr_abc123", {
userId: "usr_alice",
});
if (result.error) {
console.error("Failed to respond:", result.error.message);
} else {
console.log(`Responder joined: ${result.data.id}`);
}
```
```bash
curl -X POST https://api.beeps.dev/v0/alerts/alr_abc123/on-it \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "usr_alice"
}'
```
**Response:**
```json
{
"responder": {
"id": "rsp_def456",
"alertId": "alr_abc123",
"organizationId": "org_xyz789",
"responderType": "user",
"userId": "usr_alice",
"status": "on_it",
"joinedAt": "2025-01-15T10:35:00Z",
"completedAt": null
}
}
```
```bash
beeps alert on-it --alert-id alr_abc123
beeps alert on-it --alert-id alr_abc123 --user-id usr_alice
```
**Tool:** `beeps_on_it`
```json
{
"alertId": "alr_abc123"
}
```
With a specific user:
```json
{
"alertId": "alr_abc123",
"userId": "usr_alice"
}
```
## Listing Responders
```typescript
const responders = await client.alert.listResponders("alr_abc123");
responders.forEach((r) => {
console.log(`${r.userId || r.integrationId} — ${r.status}`);
if (r.prUrl) {
console.log(` PR: ${r.prUrl}`);
}
});
```
### Safe Method
```typescript
const result = await client.alert.listRespondersSafe("alr_abc123");
if (result.error) {
console.error("Failed to list responders:", result.error.message);
} else {
console.log(`${result.data.length} responders`);
}
```
```bash
curl -X GET https://api.beeps.dev/v0/alerts/alr_abc123/responders \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"responders": [
{
"id": "rsp_def456",
"alertId": "alr_abc123",
"organizationId": "org_xyz789",
"responderType": "user",
"userId": "usr_alice",
"status": "on_it",
"joinedAt": "2025-01-15T10:35:00Z",
"completedAt": null
}
]
}
```
```bash
beeps alert responders --alert-id alr_abc123
beeps alert responders --alert-id alr_abc123 --json
```
**Tool:** `beeps_list_alert_responders`
```json
{
"alertId": "alr_abc123"
}
```
## Updating Responder Status
Mark a responder as `done` or `dropped`. You can optionally attach a PR URL when completing work.
```typescript
const updated = await client.alert.updateResponderStatus(
"alr_abc123",
"rsp_def456",
{
status: "done",
prUrl: "https://github.com/org/repo/pull/42",
},
);
console.log(`Responder status: ${updated.status}`);
console.log(`Completed at: ${updated.completedAt}`);
```
### Safe Method
```typescript
const result = await client.alert.updateResponderStatusSafe(
"alr_abc123",
"rsp_def456",
{ status: "done" },
);
if (result.error) {
console.error("Failed to update:", result.error.message);
} else {
console.log("Responder marked as done");
}
```
```bash
curl -X PATCH https://api.beeps.dev/v0/alerts/alr_abc123/responders/rsp_def456 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "done",
"prUrl": "https://github.com/org/repo/pull/42"
}'
```
**Response:**
```json
{
"responder": {
"id": "rsp_def456",
"status": "done",
"prUrl": "https://github.com/org/repo/pull/42",
"completedAt": "2025-01-15T11:00:00Z"
}
}
```
**Tool:** `beeps_update_responder_status`
```json
{
"alertId": "alr_abc123",
"responderId": "rsp_def456",
"status": "done",
"prUrl": "https://github.com/org/repo/pull/42"
}
```
## Assigning an Alert
```typescript
const assigned = await client.alert.assign("alr_abc123", "usr_bob");
console.log(`Alert assigned to ${assigned.assignedToUserId}`);
```
### Safe Method
```typescript
const result = await client.alert.assignSafe("alr_abc123", "usr_bob");
if (result.error) {
console.error("Failed to assign:", result.error.message);
} else {
console.log(`Assigned to ${result.data.assignedToUserId}`);
}
```
```bash
curl -X POST https://api.beeps.dev/v0/alerts/alr_abc123/assign \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "usr_bob"
}'
```
**Response:**
```json
{
"alert": {
"id": "alr_abc123",
"assignedToUserId": "usr_bob",
"updatedAt": "2025-01-15T10:40:00Z"
}
}
```
```bash
beeps alert assign --alert-id alr_abc123 --user-id usr_bob
```
**Tool:** `beeps_assign_alert`
```json
{
"alertId": "alr_abc123",
"userId": "usr_bob"
}
```
## Resolving an Alert
```typescript
const resolved = await client.alert.resolve("alr_abc123");
console.log(`Alert resolved at ${resolved.resolvedAt}`);
```
### Safe Method
```typescript
const result = await client.alert.resolveSafe("alr_abc123");
if (result.error) {
console.error("Failed to resolve:", result.error.message);
} else {
console.log("Alert resolved");
}
```
```bash
curl -X POST https://api.beeps.dev/v0/alerts/alr_abc123/resolve \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"alert": {
"id": "alr_abc123",
"resolvedAt": "2025-01-15T10:45:00Z",
"updatedAt": "2025-01-15T10:45:00Z"
}
}
```
```bash
beeps alert resolve --alert-id alr_abc123
```
**Tool:** `beeps_resolve_alert`
```json
{
"alertId": "alr_abc123"
}
```
## Type Definitions
### Alert
```typescript
type Alert = {
id: string;
organizationId: string;
webhookId: string;
title: string;
message: string | null;
severity: AlertSeverity;
source: string;
externalId: string | null;
metadata: Record | null;
assignedToUserId: string | null;
resolvedAt: string | null;
resolvedBy: ResolutionSource | null;
resolvedByUserId: string | null;
resolvedByProvider: string | null;
createdAt: string;
updatedAt: string;
};
```
### AlertSeverity
```typescript
type AlertSeverity = "critical" | "high" | "medium" | "low" | "info";
```
### ResolutionSource
```typescript
type ResolutionSource = "user" | "monitoring_system";
```
### AlertResponder
```typescript
type AlertResponder = {
id: string;
alertId: string;
organizationId: string;
responderType: ResponderType;
userId: string | null;
integrationId: string | null;
agentSessionId: string | null;
status: ResponderStatus;
prUrl: string | null;
joinedAt: string;
completedAt: string | null;
};
```
### ResponderType
```typescript
type ResponderType = "user" | "agent";
```
### ResponderStatus
```typescript
type ResponderStatus = "on_it" | "done" | "dropped";
```
### OnItInput
```typescript
type OnItInput = {
userId?: string;
};
```
### UpdateResponderStatusInput
```typescript
type UpdateResponderStatusInput = {
status: "done" | "dropped";
prUrl?: string;
};
```
## Common Patterns
### Complete Alert Workflow
```typescript
const alertId = "alr_abc123";
const userId = "usr_alice";
const alert = await client.alert.get(alertId);
console.log(`New alert: ${alert.title}`);
const responder = await client.alert.onIt(alertId, { userId });
console.log(`On it: ${responder.status}`);
await client.alert.assign(alertId, userId);
console.log("Assigned to self");
console.log("Working on the issue...");
await client.alert.updateResponderStatus(alertId, responder.id, {
status: "done",
prUrl: "https://github.com/org/repo/pull/42",
});
console.log("Responder done");
await client.alert.resolve(alertId);
console.log("Resolved");
```
### Filter Alerts by Severity
```typescript
const active = await client.alert.listActive();
const critical = active.filter((a) => a.severity === "critical");
const high = active.filter((a) => a.severity === "high");
console.log(`Critical: ${critical.length}`);
console.log(`High: ${high.length}`);
```
### Alert Dashboard
```typescript
const active = await client.alert.listActive();
const stats = {
total: active.length,
assigned: active.filter((a) => a.assignedToUserId).length,
critical: active.filter((a) => a.severity === "critical").length,
};
console.log("Alert Dashboard:");
console.log(`Total Active: ${stats.total}`);
console.log(`Assigned: ${stats.assigned}`);
console.log(`Critical: ${stats.critical}`);
```
### My Assigned Alerts
```typescript
const userId = "usr_alice";
const active = await client.alert.listActive();
const myAlerts = active.filter((a) => a.assignedToUserId === userId);
console.log(`You have ${myAlerts.length} assigned alerts:`);
myAlerts.forEach((alert) => {
console.log(`- [${alert.severity}] ${alert.title}`);
});
```
## Troubleshooting
**Alerts not appearing?** See [Troubleshooting - Alerts Not Appearing](/docs/troubleshooting/#alerts-are-not-appearing).
---
# Alert API Reference
> Source: https://beeps.dev/docs/alerts/api/
Complete API reference for the Alert resource in the beeps SDK.
## Alert Methods
### `client.alert.list()`
List all alerts in your organization.
**Returns:** `Promise`
**Example:**
```typescript
const alerts = await client.alert.list();
```
### `client.alert.listActive()`
List all active (unresolved) alerts.
**Returns:** `Promise`
**Example:**
```typescript
const activeAlerts = await client.alert.listActive();
```
### `client.alert.listResolved()`
List all resolved alerts.
**Returns:** `Promise`
**Example:**
```typescript
const resolvedAlerts = await client.alert.listResolved();
```
### `client.alert.get(alertId)`
Get a specific alert by ID.
**Parameters:**
- `alertId: string`
**Returns:** `Promise`
**Example:**
```typescript
const alert = await client.alert.get("alr_abc123");
```
### `client.alert.onIt(alertId, input?)`
Signal that a user is responding to an alert. Creates a responder record. If `userId` is not provided, the authenticated user is used.
**Parameters:**
- `alertId: string`
- `input?: OnItInput`
**Returns:** `Promise`
**Example:**
```typescript
const responder = await client.alert.onIt("alr_abc123", {
userId: "usr_alice",
});
// Or as the authenticated user
const responder = await client.alert.onIt("alr_abc123");
```
### `client.alert.listResponders(alertId)`
List all responders for an alert.
**Parameters:**
- `alertId: string`
**Returns:** `Promise`
**Example:**
```typescript
const responders = await client.alert.listResponders("alr_abc123");
```
### `client.alert.listAgents(alertId)`
List agent jobs associated with an alert.
**Parameters:**
- `alertId: string`
**Returns:** `Promise`
**Example:**
```typescript
const jobs = await client.alert.listAgents("alr_abc123");
```
### `client.alert.getFixContext(alertId)`
Get a foreground fix context bundle for an alert, including responders, agent jobs, and suggested next actions.
**Parameters:**
- `alertId: string`
**Returns:** `Promise`
**Example:**
```typescript
const fixContext = await client.alert.getFixContext("alr_abc123");
console.log(fixContext.nextActions);
```
### `client.alert.updateResponderStatus(alertId, responderId, input)`
Update a responder's status to `done` or `dropped`. Optionally attach a PR URL.
**Parameters:**
- `alertId: string`
- `responderId: string`
- `input: UpdateResponderStatusInput`
**Returns:** `Promise`
**Example:**
```typescript
const updated = await client.alert.updateResponderStatus(
"alr_abc123",
"rsp_def456",
{ status: "done", prUrl: "https://github.com/org/repo/pull/42" },
);
```
### `client.alert.resolve(alertId)`
Mark an alert as resolved.
**Parameters:**
- `alertId: string`
**Returns:** `Promise`
**Example:**
```typescript
const alert = await client.alert.resolve("alr_abc123");
```
### `client.alert.assign(alertId, userId)`
Assign an alert to a user.
**Parameters:**
- `alertId: string`
- `userId: string`
**Returns:** `Promise`
**Example:**
```typescript
const alert = await client.alert.assign("alr_abc123", "usr_bob");
```
## Safe Methods
All methods have corresponding `*Safe` variants that return `Result` instead of throwing:
- `client.alert.listSafe()`
- `client.alert.listActiveSafe()`
- `client.alert.listResolvedSafe()`
- `client.alert.getSafe(alertId)`
- `client.alert.onItSafe(alertId, input?)`
- `client.alert.listRespondersSafe(alertId)`
- `client.alert.listAgentsSafe(alertId)`
- `client.alert.getFixContextSafe(alertId)`
- `client.alert.updateResponderStatusSafe(alertId, responderId, input)`
- `client.alert.resolveSafe(alertId)`
- `client.alert.assignSafe(alertId, userId)`
**Example:**
```typescript
const result = await client.alert.onItSafe("alr_abc123", {
userId: "usr_alice",
});
if (result.error) {
console.error(result.error.message);
} else {
console.log(`Responder joined: ${result.data.id}`);
}
```
## Types
### AlertSeverity
```typescript
type AlertSeverity = "critical" | "high" | "medium" | "low" | "info";
```
### Alert
```typescript
type Alert = {
id: string;
organizationId: string;
webhookId: string;
title: string;
message: string | null;
severity: AlertSeverity;
source: string;
externalId: string | null;
metadata: Record | null;
assignedToUserId: string | null;
resolvedAt: string | null;
resolvedBy: ResolutionSource | null;
resolvedByUserId: string | null;
resolvedByProvider: string | null;
createdAt: string;
updatedAt: string;
};
```
### ResolutionSource
```typescript
type ResolutionSource = "user" | "monitoring_system";
```
### AlertResponder
```typescript
type AlertResponder = {
id: string;
alertId: string;
organizationId: string;
responderType: ResponderType;
userId: string | null;
integrationId: string | null;
agentSessionId: string | null;
status: ResponderStatus;
prUrl: string | null;
joinedAt: string;
completedAt: string | null;
};
```
### ResponderType
```typescript
type ResponderType = "user" | "agent";
```
### ResponderStatus
```typescript
type ResponderStatus = "on_it" | "done" | "dropped";
```
### OnItInput
```typescript
type OnItInput = {
userId?: string;
};
```
### UpdateResponderStatusInput
```typescript
type UpdateResponderStatusInput = {
status: "done" | "dropped";
prUrl?: string;
};
```
## HTTP Endpoints
### List All Alerts
```
GET /v0/alerts
```
### List Active Alerts
```
GET /v0/alerts/active
```
### List Resolved Alerts
```
GET /v0/alerts/resolved
```
### Get Alert
```
GET /v0/alerts/:alertId
```
### Respond to Alert (On It)
```
POST /v0/alerts/:alertId/on-it
```
**Request Body:**
```json
{
"userId": "usr_alice"
}
```
### List Responders
```
GET /v0/alerts/:alertId/responders
```
### List Agent Jobs for Alert
```
GET /v0/alerts/:alertId/agents
```
### Get Fix Context
```
GET /v0/alerts/:alertId/fix-context
```
### Update Responder Status
```
PATCH /v0/alerts/:alertId/responders/:responderId
```
**Request Body:**
```json
{
"status": "done",
"prUrl": "https://github.com/org/repo/pull/42"
}
```
### Resolve Alert
```
POST /v0/alerts/:alertId/resolve
```
### Assign Alert
```
POST /v0/alerts/:alertId/assign
```
**Request Body:**
```json
{
"userId": "usr_bob"
}
```
## Authentication
All API requests require authentication using your access token:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.beeps.dev/v0/alerts
```
---
# Alert Storms Overview
> Source: https://beeps.dev/docs/alert-storms/overview/
An alert storm happens when a single root cause (e.g., a database going down) triggers many different alerts — API timeouts, connection refused errors, query failures, and so on. Without storm detection, each unique alert title would dispatch its own agent, leading to multiple agents fixing symptoms instead of the root cause.
## How Storm Detection Works
Storm detection is purely count-based. No AI is involved in the detection step — it simply counts alerts per relay within a time window.
### Normal Flow (No Storm)
When alerts arrive at a normal rate:
1. Alert arrives via webhook
2. Relay rules execute
3. Agent rule dispatches a coding agent
4. Agent investigates and creates a PR
### Storm Flow
When a burst of alerts arrives:
1. First 2 alerts dispatch agents immediately (configurable via `maxImmediateDispatches`)
2. Once the alert count exceeds the threshold within the time window, a storm is detected
3. Subsequent alerts are held — no additional agents are dispatched
4. After a debounced delay, an AI triage step analyzes all storm alerts
5. The triage identifies the root cause alert
6. A single agent is dispatched for the root cause with full storm context
## Storm Lifecycle
| Status | Description |
|--------|-------------|
| `collecting` | Storm detected, alerts are being collected |
| `triaging` | AI is analyzing the alerts to find the root cause |
| `dispatched` | Root cause identified, agent dispatched |
| `resolved` | Storm resolved (future enhancement) |
## Relationship to Alert Deduplication
Storm detection is a layer above the existing same-title dedup:
- **Same-title dedup** prevents duplicate agents for alerts with identical (normalized) titles
- **Storm detection** prevents excessive agents when many _different_ alert titles fire from a shared root cause
Both checks run in sequence. Storm detection runs first — if it holds an alert, the same-title dedup is skipped entirely.
## Configuration
Storm behavior is configured per agent rule. See [Tuning Storm Detection](/docs/alert-storms/tuning/) for configuration options.
```typescript
const rule = {
ruleType: "agent",
config: {
agentType: "devin",
integrationId: "int_abc123",
stormThreshold: 5,
stormWindowSeconds: 60,
maxImmediateDispatches: 2,
},
};
```
## Fail-Open Design
Storm detection is designed to never block alerts:
- If the storm detection database query fails, the alert proceeds to normal dispatch
- If the AI triage fails, the earliest high-severity alert is selected as root cause
- Existing alert routing, notifications, and acknowledgment are unaffected by storm detection
---
# Tuning Storm Detection
> Source: https://beeps.dev/docs/alert-storms/tuning/
Storm detection is configured per agent rule, so each relay can have independent thresholds tuned to its alert volume and criticality.
## Configuration Options
### `stormThreshold`
The number of alerts within the time window that triggers a storm.
- **Type:** `number`
- **Default:** `5`
- **Range:** `2` to `50`
```typescript
{
stormThreshold: 5
}
```
### `stormWindowSeconds`
The time window (in seconds) used to count alerts for storm detection.
- **Type:** `number`
- **Default:** `60`
- **Range:** `10` to `300`
```typescript
{
stormWindowSeconds: 60
}
```
### `maxImmediateDispatches`
The number of agents dispatched immediately before storm hold kicks in. These first agents start working right away while the storm is still being evaluated.
- **Type:** `number`
- **Default:** `2`
- **Range:** `0` to `5`
```typescript
{
maxImmediateDispatches: 2
}
```
Set to `0` to hold all dispatches during a storm. Set to a higher number if you want more agents working in parallel before triage.
## Examples
### High-Traffic Relay
For a relay that receives many alerts normally, increase the threshold to avoid false storm detection:
```typescript
{
agentType: "devin",
integrationId: "int_abc123",
stormThreshold: 15,
stormWindowSeconds: 120,
maxImmediateDispatches: 3,
}
```
### Critical Relay
For a relay where every alert matters and storms should be detected quickly:
```typescript
{
agentType: "cursor",
integrationId: "int_def456",
repository: "https://github.com/org/critical-service",
stormThreshold: 3,
stormWindowSeconds: 30,
maxImmediateDispatches: 1,
}
```
## Debounced Triage Window
When a storm is detected, the triage job doesn't fire immediately. Instead, it uses a debounced delay to wait for more alerts to arrive:
- Each new alert pushes the triage job forward by **15 seconds**
- The maximum delay is capped at **90 seconds** from when the storm was first detected
- This ensures late-arriving alerts (which may include the actual root cause) are included in triage
For example, if a storm is detected at `T=0`:
- Alert at `T=5s` pushes triage to `T=20s`
- Alert at `T=10s` pushes triage to `T=25s`
- Alert at `T=20s` pushes triage to `T=35s`
- No more alerts arrive — triage fires at `T=35s`
If alerts keep arriving, triage fires at most at `T=90s` regardless.
## AI Triage
After the debounce window, AI analyzes all collected storm alerts. The triage considers:
1. **Timing** — earlier alerts are more likely to be the root cause
2. **Infrastructure level** — lower-level alerts (database, network) rank higher than application-level alerts
3. **Severity** — higher severity alerts with specific error details are more informative
4. **Error specificity** — alerts with stack traces, connection errors, or specific error codes are preferred
5. **Causal relationships** — patterns where one failure causes others (e.g., DB down causing API timeouts)
The identified root cause alert is then dispatched to a coding agent with the full storm context, so the agent understands it needs to fix the underlying issue rather than a symptom.
---
# Integrations Overview
> Source: https://beeps.dev/docs/observability-integrations/overview/
Point your monitoring tools at a relay webhook URL and beeps handles the rest.
## Getting your webhook URL
Each relay has a webhook URL in the format `https://hooks.beeps.dev/YOUR_WEBHOOK_ID`. Grab it from your relay in the dashboard and paste it into your monitoring provider's webhook settings.
## Supported providers
- [Sentry](/docs/observability-integrations/sentry/)
- [Datadog](/docs/observability-integrations/datadog/)
- [Axiom](/docs/observability-integrations/axiom/)
- [Custom webhooks](/docs/observability-integrations/custom/) — anything that can POST JSON
## How it works
1. Add your relay webhook URL to your monitoring provider
2. Configure alert rules in the provider (what triggers a webhook)
3. When a webhook fires, beeps creates an alert and runs your relay rules
## Testing
Fire a test alert from your monitoring tool and check the dashboard. If nothing shows up, see [Troubleshooting](/docs/troubleshooting/#alerts-are-not-appearing).
---
# Sentry
> Source: https://beeps.dev/docs/observability-integrations/sentry/
Configure Sentry to send error alerts to beeps via webhooks.
## Prerequisites
- Active Sentry account and project
- beeps relay webhook URL
## Setup Steps
1. In Sentry, navigate to **Settings** → **Integrations**
2. Search for and select **Webhooks**
3. Click **Add to Project** or **Configure**
4. Enter your beeps webhook URL: `https://hooks.beeps.dev/YOUR_WEBHOOK_ID`
5. Select the events you want to trigger alerts:
- Error events
- Issue state changes
- New releases
6. Save the webhook configuration
## Alert Rules
Create alert rules to control when notifications are sent:
1. Go to **Alerts** → **Create Alert**
2. Choose **Issues** or **Errors**
3. Set your conditions (e.g., "When an event is seen more than 100 times in 1 hour")
4. Under **Actions**, select **Send a notification via an integration**
5. Choose your beeps webhook
6. Save the rule
## What happens next
Once Sentry fires the webhook, beeps creates an alert and runs it through your relay's rules. What happens from there depends on how you've set up your [relay rules](/docs/relays/rules/) — notify on-call, invoke an agent, post to Slack, or any combination.
## Next Steps
- [Configure Relay Rules](/docs/relays/rules/) to route alerts to humans and AI agents
- [Create Schedules](/docs/schedules/overview/) to define on-call rotations
- [Add Contact Methods](/docs/contact-methods/overview/) for email and SMS notifications
---
# Datadog
> Source: https://beeps.dev/docs/observability-integrations/datadog/
Configure Datadog monitors to send alerts to beeps via webhooks.
## Prerequisites
- Active Datadog account
- beeps relay webhook URL
## Setup Steps
### Create Webhook Integration
1. In Datadog, navigate to **Integrations** → **Webhooks**
2. Click **New** to create a webhook
3. Name your webhook (e.g., "beeps Production")
4. Enter URL: `https://hooks.beeps.dev/YOUR_WEBHOOK_ID`
5. Optionally customize the payload template (see below)
6. Save the webhook
### Configure Monitors
Add the webhook to your monitors:
1. Create or edit a monitor
2. In the **Notify your team** section, add `@webhook-beeps-dev-production` (using your webhook name)
3. Customize the message with monitor details
4. Save the monitor
## Alert Renotification
Configure monitors to re-notify if not resolved:
1. In monitor settings, enable **Renotify if this monitor stays in alert state**
2. Set interval (e.g., every 60 minutes)
3. Include in message: `This is a reminder that the alert is still active`
## What happens next
When a Datadog monitor triggers, beeps normalizes the payload into an alert and runs your relay rules. Renotification webhooks from Datadog create follow-up alerts if the original is still active.
## Next Steps
- [Configure Relay Rules](/docs/relays/rules/) to route alerts to humans and AI agents
- [Create Schedules](/docs/schedules/overview/) to define on-call rotations
- [Add Contact Methods](/docs/contact-methods/overview/) for email and SMS notifications
---
# Axiom
> Source: https://beeps.dev/docs/observability-integrations/axiom/
Configure Axiom monitors to send alerts to beeps via webhooks.
## Prerequisites
- Active Axiom account
- beeps relay webhook URL
## Setup Steps
1. In Axiom, navigate to **Monitors**
2. Create a new monitor or edit an existing one
3. Configure your query and alert conditions
4. In **Notifications**, select **Webhook**
5. Enter URL: `https://hooks.beeps.dev/YOUR_WEBHOOK_ID`
6. Choose notification frequency and threshold
7. Save the monitor
## Webhook Payload
Axiom sends webhooks with this structure:
```json
{
"monitor": {
"id": "mon_abc123",
"name": "High 5xx Error Rate",
"description": "Alert when 5xx errors exceed threshold",
"query": "['http-logs'] | where status >= 500"
},
"alert": {
"state": "triggered",
"value": 25,
"threshold": 10,
"operator": ">"
},
"timestamp": "2024-10-27T10:30:00Z",
"queryUrl": "https://app.axiom.co/...",
"dataset": "http-logs"
}
```
## Notification Settings
### Frequency Options
- **Every time**: Alert on every evaluation that meets threshold
- **Once**: Alert only once when condition is first met
- **Every X minutes**: Rate limit notifications
### Alert Resolution
Configure when to send "resolved" notifications:
```typescript
{
"sendResolved": true,
"resolutionThreshold": {
"operator": "<=",
"value": 5
}
}
```
## What happens next
When an Axiom monitor fires, beeps creates an alert from the payload and runs your relay rules. If you've configured `sendResolved: true`, Axiom will also send a resolution webhook that beeps can use to auto-resolve the alert.
## Next Steps
- [Configure Relay Rules](/docs/relays/rules/) to route alerts to humans and AI agents
- [Create Schedules](/docs/schedules/overview/) to define on-call rotations
- [Add Contact Methods](/docs/contact-methods/overview/) for email and SMS notifications
---
# Custom Webhooks
> Source: https://beeps.dev/docs/observability-integrations/custom/
Send alerts to beeps from any system using HTTP POST requests.
## Webhook Format
beeps relays accept webhook POST requests at:
```
https://hooks.beeps.dev/YOUR_WEBHOOK_ID
```
## Basic Example
Send a simple alert:
```bash
curl -X POST https://hooks.beeps.dev/YOUR_WEBHOOK_ID \
-H "Content-Type: application/json" \
-d '{
"title": "Database Connection Failed",
"message": "Unable to connect to production database",
"severity": "critical"
}'
```
## Payload Structure
### Required Fields
- `title` (string): Short alert title (max 255 characters)
### Optional Fields
- `message` (string): Detailed alert description
- `severity` (string): Alert severity level
- `low` / `info`
- `medium` / `warning`
- `high` / `error`
- `critical` / `fatal`
- `source` (string): System generating the alert
- `timestamp` (string): ISO 8601 timestamp (defaults to current time)
- `metadata` (object): Additional key-value pairs
### Complete Example
```json
{
"title": "High API Error Rate",
"message": "Production API error rate exceeded 5% threshold. Current rate: 8.2%",
"severity": "critical",
"source": "monitoring-system",
"timestamp": "2024-10-27T10:30:00Z",
"metadata": {
"environment": "production",
"service": "api",
"region": "us-east-1",
"error_rate": 8.2,
"threshold": 5.0,
"dashboard_url": "https://dashboard.example.com/api",
"runbook_url": "https://wiki.example.com/runbooks/high-error-rate"
}
}
```
## Language Examples
### Python
```python
import requests
from datetime import datetime
def send_alert(title, message, severity="high"):
webhook_url = "https://hooks.beeps.dev/YOUR_WEBHOOK_ID"
payload = {
"title": title,
"message": message,
"severity": severity,
"source": "python-app",
"timestamp": datetime.utcnow().isoformat() + "Z",
"metadata": {
"host": "app-server-01",
"pid": 12345
}
}
response = requests.post(webhook_url, json=payload)
response.raise_for_status()
return response.json()
send_alert(
"Database Connection Failed",
"Could not connect to PostgreSQL after 3 retries",
severity="critical"
)
```
### Node.js
```javascript
async function sendAlert(title, message, severity = "high") {
const webhookUrl = "https://hooks.beeps.dev/YOUR_WEBHOOK_ID";
const payload = {
title,
message,
severity,
source: "nodejs-app",
timestamp: new Date().toISOString(),
metadata: {
host: process.env.HOSTNAME,
pid: process.pid,
},
};
const response = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Failed to send alert: ${response.statusText}`);
}
return response.json();
}
await sendAlert(
"High Memory Usage",
"Application memory usage exceeded 90%",
"warning",
);
```
### Go
```go
package main
import (
"bytes"
"encoding/json"
"net/http"
"time"
)
type Alert struct {
Title string `json:"title"`
Message string `json:"message"`
Severity string `json:"severity"`
Source string `json:"source"`
Timestamp string `json:"timestamp"`
Metadata map[string]interface{} `json:"metadata"`
}
func sendAlert(title, message, severity string) error {
webhookURL := "https://hooks.beeps.dev/YOUR_WEBHOOK_ID"
alert := Alert{
Title: title,
Message: message,
Severity: severity,
Source: "go-app",
Timestamp: time.Now().UTC().Format(time.RFC3339),
Metadata: map[string]interface{}{
"host": "app-server-01",
"version": "1.2.3",
},
}
payload, err := json.Marshal(alert)
if err != nil {
return err
}
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(payload))
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func main() {
sendAlert(
"Service Unavailable",
"Health check failed for payment service",
"critical",
)
}
```
### Ruby
```ruby
require 'net/http'
require 'json'
require 'uri'
def send_alert(title, message, severity: 'high')
webhook_url = URI('https://hooks.beeps.dev/YOUR_WEBHOOK_ID')
payload = {
title: title,
message: message,
severity: severity,
source: 'ruby-app',
timestamp: Time.now.utc.iso8601,
metadata: {
host: `hostname`.strip,
ruby_version: RUBY_VERSION
}
}
http = Net::HTTP.new(webhook_url.host, webhook_url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(webhook_url)
request['Content-Type'] = 'application/json'
request.body = payload.to_json
response = http.request(request)
raise "Failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
end
send_alert(
'Cache Miss Rate High',
'Redis cache miss rate exceeded 50%',
severity: 'warning'
)
```
### Bash
```bash
#!/bin/bash
WEBHOOK_URL="https://hooks.beeps.dev/YOUR_WEBHOOK_ID"
send_alert() {
local title="$1"
local message="$2"
local severity="${3:-high}"
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d @- < {
return new Promise((resolve, reject) => {
const options = {
host: domain,
port: 443,
method: "GET",
rejectUnauthorized: false,
agent: false,
};
const req = https.request(options, (res) => {
const cert = res.socket.getPeerCertificate();
if (!cert || Object.keys(cert).length === 0) {
reject(new Error("No certificate found"));
return;
}
const validTo = new Date(cert.valid_to);
const now = new Date();
const daysRemaining = Math.floor(
(validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
);
resolve({
domain,
validTo,
daysRemaining,
issuer: cert.issuer.O || "Unknown",
subject: cert.subject.CN || domain,
});
});
req.on("error", reject);
req.end();
});
}
async function sendAlert(cert: CertificateInfo) {
const severity = cert.daysRemaining <= 7 ? "critical" : "warning";
const payload = {
title: `SSL Certificate Expiring Soon: ${cert.domain}`,
message: `SSL certificate for ${cert.domain} expires in ${cert.daysRemaining} days (${cert.validTo.toISOString()})`,
severity,
source: "ssl-monitor",
timestamp: new Date().toISOString(),
metadata: {
domain: cert.domain,
days_remaining: cert.daysRemaining,
expiration_date: cert.validTo.toISOString(),
issuer: cert.issuer,
subject: cert.subject,
},
};
try {
const response = await fetch(WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
console.error(`Failed to send alert: ${response.statusText}`);
} else {
console.log(`Alert sent for ${cert.domain}`);
}
} catch (error) {
console.error(`Error sending alert:`, error);
}
}
async function checkAllCertificates() {
console.log(`Checking ${DOMAINS.length} certificates...`);
for (const domain of DOMAINS) {
try {
const cert = await checkCertificate(domain);
console.log(
`${domain}: ${cert.daysRemaining} days remaining (expires ${cert.validTo.toLocaleDateString()})`
);
if (cert.daysRemaining <= WARNING_DAYS) {
await sendAlert(cert);
}
} catch (error) {
console.error(`Error checking ${domain}:`, error);
await fetch(WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: `SSL Certificate Check Failed: ${domain}`,
message: `Unable to retrieve certificate: ${error.message}`,
severity: "high",
source: "ssl-monitor",
timestamp: new Date().toISOString(),
metadata: {
domain,
error: error.message,
},
}),
});
}
}
}
checkAllCertificates();
setInterval(checkAllCertificates, 24 * 60 * 60 * 1000);
```
**Usage:**
```bash
# Install dependencies
npm install
# Run the monitor
npx tsx ssl-monitor.ts
# Or run as a cron job (daily at 9 AM)
0 9 * * * cd /path/to/monitor && npx tsx ssl-monitor.ts
```
## Response Format
Successful webhook submission returns:
```json
{
"success": true,
"alert_id": "alr_abc123",
"received_at": "2024-10-27T10:30:00Z"
}
```
## Error Handling
### HTTP Status Codes
- `200 OK`: Alert received successfully
- `400 Bad Request`: Invalid payload format
- `401 Unauthorized`: Invalid webhook ID
- `429 Too Many Requests`: Rate limit exceeded
- `500 Internal Server Error`: Server error
### Example Error Response
```json
{
"success": false,
"error": "Invalid payload: 'title' is required",
"code": "INVALID_PAYLOAD"
}
```
## Best Practices
1. **Include Context**: Add relevant metadata for triage
2. **Use Severity**: Properly categorize alert urgency
3. **Add Timestamps**: Include when issue occurred
4. **Link to Resources**: Add dashboard/runbook URLs
5. **Handle Errors**: Implement retry logic with backoff
6. **Rate Limit**: Batch alerts when possible
7. **Validate Payloads**: Check data before sending
8. **Use HTTPS**: Always use secure connections
## Rate Limits
- **Default**: 100 requests per minute per webhook
- **Burst**: Up to 10 requests per second
- Exceeding limits returns `429 Too Many Requests`
## What happens next
Once beeps receives a POST, it creates an alert and runs your [relay rules](/docs/relays/rules/). The response includes the `alert_id` so you can track it programmatically.
## Next Steps
- [Configure Relay Rules](/docs/relays/rules/) to route alerts to humans and AI agents
- [Create Schedules](/docs/schedules/overview/) to define on-call rotations
- [Add Contact Methods](/docs/contact-methods/overview/) for email and SMS notifications
---
# Agent Integrations Overview
> Source: https://beeps.dev/docs/agent-integrations/overview/
An agent integration stores the credentials beeps needs to invoke an AI agent: an API key plus any provider-specific settings. Relay rules of `ruleType: "agent"` reference an integration; when a matching alert fires, beeps invokes the agent, polls until it finishes, and writes the result (usually a PR URL) onto the alert responder.
## How it works
1. An alert fires on one of your relays.
2. A relay rule of `ruleType: "agent"` matches and points at an integration.
3. Beeps invokes the agent: calling its API, opening a self-hosted session, or triggering a GitHub workflow.
4. Beeps polls the agent until it reaches a terminal state.
5. The alert responder is updated: `done` (with `prUrl` when the agent opened one) on success, or `dropped` on failure.
Storm-detection holds, alert deduplication, polling intervals, and max-attempts apply to every provider. They're configured on the relay rule, not the integration. See [Relay Rules](/docs/relays/rules/) for the full rule shape.
## Supported providers
| Provider | How it runs | Output |
|---|---|---|
| [Claude](/docs/agent-integrations/claude/) | GitHub Actions | PR URL |
| [OpenAI Codex](/docs/agent-integrations/codex/) | GitHub Actions | PR URL |
| [Cursor](/docs/agent-integrations/cursor/) | Cloud API | PR URL |
| [OpenCode](/docs/agent-integrations/opencode/) | Self-hosted HTTP | File diff |
| [Devin](/docs/agent-integrations/devin/) | Cloud API | PR URL |
| [AWS DevOps Agent](/docs/agent-integrations/aws-devops/) | Agent Space (HMAC webhook + AWS SDK) | Investigation report |
| [Zup](/docs/agent-integrations/zup/) | Self-hosted HTTP | Investigation report |
## Where to next
- Pick a provider above for setup instructions.
- [Manage integrations](/docs/agent-integrations/manage/): CRUD via SDK, API, or CLI.
- [Integration API reference](/docs/agent-integrations/api/): full endpoint and type reference.
- [Best Practices — Agent Integrations](/docs/best-practices/#agent-integrations): configuration guidelines.
## MCP Clients
To connect beeps tools in Claude Code or Codex, see [MCP Setup for Claude Code and Codex](/docs/using-beeps/mcp-server/).
---
# Managing Integrations
> Source: https://beeps.dev/docs/agent-integrations/manage/
import { Tabs, TabItem } from '@astrojs/starlight/components';
Create, update, and manage integrations for AI agents.
## Creating an Integration
```typescript
import { BeepsClient } from "@beepsdev/sdk";
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
const integration = await client.integration.create({
name: "Production Agent",
provider: "devin",
apiKey: process.env.AGENT_API_KEY,
// metadata shape is provider-specific — see the per-provider page
});
console.log(`Created integration: ${integration.id}`);
```
For provider-specific `metadata` and rule-config fields, see the page for that provider under [Agent Integrations](/docs/agent-integrations/overview/).
### Safe Method (No Exceptions)
```typescript
const result = await client.integration.createSafe({
name: "Production Agent",
provider: "devin",
apiKey: process.env.AGENT_API_KEY,
});
if (result.error) {
console.error("Failed to create integration:", result.error.message);
} else {
console.log(`Created: ${result.data.id}`);
}
```
```bash
curl -X POST https://api.beeps.dev/integrations \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Devin Production",
"provider": "devin",
"apiKey": "devin_api_key_here",
"metadata": {
"environment": "production"
}
}'
```
**Response:**
```json
{
"integration": {
"id": "int_abc123",
"organizationId": "org_xyz789",
"name": "Devin Production",
"provider": "devin",
"apiKey": "***",
"metadata": {
"environment": "production"
},
"createdBy": "usr_alice",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"deletedAt": null
}
}
```
```bash
beeps integration create \
--provider devin \
--name "Devin Production" \
--api-key-env DEVIN_API_KEY
```
Add `--json` for machine-readable output:
```bash
beeps integration create \
--provider devin \
--name "Devin Production" \
--api-key-env DEVIN_API_KEY \
--json
```
## Listing Integrations
```typescript
const integrations = await client.integration.list();
console.log(`Found ${integrations.length} integrations:`);
integrations.forEach((integration) => {
console.log(`- ${integration.name} (${integration.provider})`);
});
```
### Filter by Provider
```typescript
const integrations = await client.integration.list();
const claudeIntegrations = integrations.filter((i) => i.provider === "claude");
console.log(`Claude integrations: ${claudeIntegrations.length}`);
```
```bash
curl -X GET https://api.beeps.dev/integrations \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"integrations": [
{
"id": "int_abc123",
"organizationId": "org_xyz789",
"name": "Devin Production",
"provider": "devin",
"apiKey": "***",
"metadata": {
"environment": "production"
},
"createdBy": "usr_alice",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"deletedAt": null
}
]
}
```
```bash
beeps integration list
```
Add `--json` for machine-readable output:
```bash
beeps integration list --json
```
**Tool:** `beeps_list_integrations`
```json
{}
```
## Getting a Specific Integration
```typescript
const integration = await client.integration.get("int_abc123");
console.log(`Name: ${integration.name}`);
console.log(`Provider: ${integration.provider}`);
console.log(`Metadata:`, integration.metadata);
```
```bash
curl -X GET https://api.beeps.dev/integrations/int_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
```bash
beeps integration get --integration-id int_abc123
```
Add `--json` for machine-readable output:
```bash
beeps integration get --integration-id int_abc123 --json
```
## Updating an Integration
```typescript
const updated = await client.integration.update("int_abc123", {
name: "Production Agent (Updated)",
apiKey: process.env.NEW_API_KEY,
// metadata shape is provider-specific — see the per-provider page
});
console.log("Integration updated");
```
### Update Name Only
```typescript
const updated = await client.integration.update("int_abc123", {
name: "New Integration Name",
});
```
### Rotate API Key
```typescript
const updated = await client.integration.update("int_abc123", {
apiKey: process.env.NEW_API_KEY,
});
console.log("API key rotated");
```
```bash
curl -X PUT https://api.beeps.dev/integrations/int_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Devin Production (Updated)",
"apiKey": "new_api_key_here"
}'
```
```bash
beeps integration update \
--integration-id int_abc123 \
--name "Devin Production (Updated)" \
--api-key-env NEW_DEVIN_API_KEY
```
Add `--json` for machine-readable output:
```bash
beeps integration update \
--integration-id int_abc123 \
--name "Devin Production (Updated)" \
--api-key-env NEW_DEVIN_API_KEY \
--json
```
## Deleting an Integration
```typescript
await client.integration.delete("int_abc123");
console.log("Integration deleted successfully");
```
### Safe Method
```typescript
const result = await client.integration.deleteSafe("int_abc123");
if (result.error) {
console.error("Failed to delete:", result.error.message);
} else {
console.log("Deleted:", result.data.success);
}
```
```bash
curl -X DELETE https://api.beeps.dev/integrations/int_abc123 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
**Response:**
```json
{
"success": true
}
```
```bash
beeps integration delete --integration-id int_abc123
```
Add `--json` for machine-readable output:
```bash
beeps integration delete --integration-id int_abc123 --json
```
## Type Definitions
### IntegrationProvider
```typescript
type IntegrationProvider =
| "devin"
| "cursor"
| "zup"
| "aws_devops"
| "opencode"
| "codex"
| "claude";
```
### CreateIntegrationInput
```typescript
type CreateIntegrationInput = {
name: string; // Integration name
provider: IntegrationProvider; // Service provider
apiKey: string; // API key (stored securely)
metadata?: Record; // Provider-specific config
};
```
### UpdateIntegrationInput
```typescript
type UpdateIntegrationInput = {
name?: string; // Update name
apiKey?: string; // Rotate API key
metadata?: Record; // Update metadata
};
```
### Integration
```typescript
type Integration = {
id: string;
organizationId: string;
name: string;
provider: IntegrationProvider;
apiKey: string; // Always returns "***" for security
metadata: Record | null;
createdBy: string; // User who created it
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
## Common Patterns
### Complete Setup with Relay Rule
```typescript
const integration = await client.integration.create({
name: "Devin Auto-Triage",
provider: "devin",
apiKey: process.env.DEVIN_API_KEY,
});
const relay = await client.relay.create({
name: "Production Alerts",
});
await client.relay.rules.create(relay.id, {
name: "AI triage first",
ruleType: "agent",
order: 1,
config: {
agentType: "devin",
integrationId: integration.id,
pollInterval: 30000,
},
});
await client.relay.rules.create(relay.id, {
name: "Escalate to humans",
ruleType: "schedule_notify",
order: 2,
config: {
scheduleId: "sch_oncall",
},
});
```
### Key Rotation
```typescript
const integrations = await client.integration.list();
for (const integration of integrations) {
if (integration.provider === "devin") {
await client.integration.update(integration.id, {
apiKey: process.env.NEW_DEVIN_KEY,
});
console.log(`Rotated key for ${integration.name}`);
}
}
```
### Environment-Specific Integrations
```typescript
const environments = ["development", "staging", "production"];
for (const env of environments) {
await client.integration.create({
name: `Devin - ${env}`,
provider: "devin",
apiKey: process.env[`DEVIN_${env.toUpperCase()}_KEY`],
metadata: {
environment: env,
},
});
}
```
## Troubleshooting
**AI agent not responding?** See [Troubleshooting - AI Agent Never Responds](/docs/troubleshooting/#ai-agent-never-responds).
---
# Integration API Reference
> Source: https://beeps.dev/docs/agent-integrations/api/
Complete API reference for the Integration resource in the beeps SDK.
## Integration Methods
### `client.integration.create(input)`
Create a new integration.
**Parameters:**
- `input: CreateIntegrationInput`
**Returns:** `Promise`
**Example:**
```typescript
const integration = await client.integration.create({
name: "Devin Production",
provider: "devin",
apiKey: process.env.DEVIN_API_KEY,
metadata: {
environment: "production",
},
});
```
### `client.integration.list()`
List all integrations in your organization.
**Returns:** `Promise`
**Example:**
```typescript
const integrations = await client.integration.list();
```
### `client.integration.get(integrationId)`
Get a specific integration by ID.
**Parameters:**
- `integrationId: string`
**Returns:** `Promise`
**Example:**
```typescript
const integration = await client.integration.get("int_abc123");
```
### `client.integration.update(integrationId, input)`
Update an integration.
**Parameters:**
- `integrationId: string`
- `input: UpdateIntegrationInput`
**Returns:** `Promise`
**Example:**
```typescript
const updated = await client.integration.update("int_abc123", {
name: "Updated Name",
apiKey: process.env.NEW_API_KEY,
});
```
### `client.integration.delete(integrationId)`
Delete an integration.
**Parameters:**
- `integrationId: string`
**Returns:** `Promise<{ success: boolean }>`
**Example:**
```typescript
await client.integration.delete("int_abc123");
```
## Safe Methods
All methods have corresponding `*Safe` variants that return `Result` instead of throwing:
- `client.integration.listSafe()`
- `client.integration.createSafe(input)`
- `client.integration.getSafe(integrationId)`
- `client.integration.updateSafe(integrationId, input)`
- `client.integration.deleteSafe(integrationId)`
**Example:**
```typescript
const result = await client.integration.createSafe({
name: "Devin",
provider: "devin",
apiKey: process.env.DEVIN_API_KEY,
});
if (result.error) {
console.error(result.error.message);
} else {
console.log(result.data.id);
}
```
## Types
### IntegrationProvider
```typescript
type IntegrationProvider =
| "devin"
| "cursor"
| "zup"
| "aws_devops"
| "opencode"
| "codex"
| "claude";
```
### CreateIntegrationInput
```typescript
type CreateIntegrationInput = {
name: string;
provider: IntegrationProvider;
apiKey: string;
metadata?: Record;
};
```
### UpdateIntegrationInput
```typescript
type UpdateIntegrationInput = {
name?: string;
apiKey?: string;
metadata?: Record;
};
```
### Integration
```typescript
type Integration = {
id: string;
organizationId: string;
name: string;
provider: IntegrationProvider;
apiKey: string; // Always returns "***" for security
metadata: Record | null;
createdBy: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
};
```
## HTTP Endpoints
### Create Integration
```
POST /v0/integrations
```
**Request Body:**
```json
{
"name": "Devin Production",
"provider": "devin",
"apiKey": "devin_api_key_here",
"metadata": {
"environment": "production"
}
}
```
### List Integrations
```
GET /v0/integrations
```
### Get Integration
```
GET /v0/integrations/:integrationId
```
### Update Integration
```
PUT /v0/integrations/:integrationId
```
**Request Body:**
```json
{
"name": "Updated Name",
"apiKey": "new_api_key_here",
"metadata": {
"version": "v2"
}
}
```
### Delete Integration
```
DELETE /v0/integrations/:integrationId
```
## Authentication
All API requests require authentication using your access token:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.beeps.dev/v0/integrations
```
---
# Claude
> Source: https://beeps.dev/docs/agent-integrations/claude/
The [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview) is a local Node.js / Python library, with no remote API to call. Beeps integrates with Claude by triggering a `workflow_dispatch` GitHub Actions run in your repository; the workflow runs Claude (via `anthropics/claude-code-action` or the SDK directly), opens a PR with the changes, and beeps polls the workflow run and resolves the PR by branch.
## Prerequisites
- A GitHub repository where Claude can run and open PRs.
- A workflow file in that repo (template below).
- An `ANTHROPIC_API_KEY` stored as a GitHub Actions secret in the repo.
- A fine-grained GitHub PAT (or GitHub App installation token) with these permissions on the target repo:
- `actions: write` (to trigger the workflow)
- `actions: read` (to poll the workflow run)
- `pull-requests: read` (to resolve the PR URL)
## Provider-side setup
### 1. Add the workflow file
Drop this into `.github/workflows/claude-beeps.yml`. The `run-name` line is required so beeps can locate the run it triggered:
```yaml
name: Claude (Beeps)
run-name: "Beeps claude run ${{ inputs.dispatchId }}"
on:
workflow_dispatch:
inputs:
prompt:
required: true
type: string
branch:
required: true
type: string
dispatchId:
required: true
type: string
alertId:
required: false
type: string
jobs:
claude:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action@v1
with:
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: ${{ inputs.prompt }}
- name: Open PR with changes
uses: peter-evans/create-pull-request@v6
with:
branch: ${{ inputs.branch }}
title: "Claude: ${{ inputs.alertId }}"
body: |
Triggered by beeps alert `${{ inputs.alertId }}`.
${{ inputs.prompt }}
commit-message: "fix: claude response for ${{ inputs.alertId }}"
```
### 2. Create the GitHub PAT
Create a fine-grained PAT scoped to the repo with the permissions listed in Prerequisites. Save it; you'll paste it into beeps as `apiKey`.
## Beeps-side setup
Create the integration with the PAT and repo coordinates:
```typescript
const claudeIntegration = await client.integration.create({
name: "Claude - acme/monorepo",
provider: "claude",
apiKey: process.env.GITHUB_PAT,
metadata: {
githubOwner: "acme",
githubRepo: "monorepo",
workflowFileName: "claude-beeps.yml",
defaultBranch: "main",
branchPrefix: "claude-beeps",
},
});
```
Reference it from a relay rule. Workflow runs typically take a few minutes, so push `pollInterval` higher than the cloud-API agents:
```typescript
await client.relay.rules.create(relayId, {
name: "Claude Auto-Fix",
ruleType: "agent",
config: {
agentType: "claude",
integrationId: claudeIntegration.id,
pollInterval: 60000,
maxPollAttempts: 60,
},
});
```
## Configuration reference
### Integration metadata
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `githubOwner` | string | yes | — | GitHub org or user that owns the repo |
| `githubRepo` | string | yes | — | Repo name |
| `workflowFileName` | string | yes | — | Workflow filename, e.g. `claude-beeps.yml` |
| `defaultBranch` | string | no | `main` | Branch to dispatch the workflow against |
| `branchPrefix` | string | no | `claude-beeps` | Prefix for the branch name beeps generates per dispatch |
### Rule config
The rule needs `agentType: "claude"` and `integrationId`. The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## Troubleshooting
Beeps reports "workflow run not found". Your workflow file is missing the `run-name: "Beeps claude run ${{ inputs.dispatchId }}"` line. Without it, beeps falls back to "most recent workflow_dispatch run" matching, which is flaky under concurrency.
The PR URL never appears on the alert responder. Beeps resolves the PR by querying `GET /repos/{owner}/{repo}/pulls?head={owner}:{branch}`, so your workflow needs to open the PR using the `inputs.branch` value beeps passed in (not a branch the workflow chose itself).
---
# OpenAI Codex
> Source: https://beeps.dev/docs/agent-integrations/codex/
The [Codex SDK](https://developers.openai.com/codex/sdk) is a local Node.js library that shells out to the `codex` binary, with no remote API to call. Beeps integrates with Codex by triggering a `workflow_dispatch` GitHub Actions run in your repository; the workflow uses `openai/codex-action` to run Codex, opens a PR, and beeps polls the workflow run and resolves the PR by branch.
## Prerequisites
- A GitHub repository where Codex can run and open PRs.
- A workflow file in that repo (template below).
- An `OPENAI_API_KEY` stored as a GitHub Actions secret in the repo. (Codex Web / Codex Cloud requires ChatGPT login and is not supported here; only the API-key path is.)
- A fine-grained GitHub PAT with these permissions on the target repo:
- `actions: write` (to trigger the workflow)
- `actions: read` (to poll the workflow run)
- `pull-requests: read` (to resolve the PR URL)
## Provider-side setup
### 1. Add the workflow file
Drop this into `.github/workflows/codex-beeps.yml`. The `run-name` line is required so beeps can locate the run it triggered:
```yaml
name: Codex (Beeps)
run-name: "Beeps codex run ${{ inputs.dispatchId }}"
on:
workflow_dispatch:
inputs:
prompt:
required: true
type: string
branch:
required: true
type: string
dispatchId:
required: true
type: string
alertId:
required: false
type: string
jobs:
codex:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
prompt: ${{ inputs.prompt }}
- name: Open PR with changes
uses: peter-evans/create-pull-request@v6
with:
branch: ${{ inputs.branch }}
title: "Codex: ${{ inputs.alertId }}"
body: |
Triggered by beeps alert `${{ inputs.alertId }}`.
${{ inputs.prompt }}
commit-message: "fix: codex response for ${{ inputs.alertId }}"
```
### 2. Create the GitHub PAT
Create a fine-grained PAT scoped to the repo with the permissions listed in Prerequisites. Save it; you'll paste it into beeps as `apiKey`.
## Beeps-side setup
Create the integration with the PAT and repo coordinates:
```typescript
const codexIntegration = await client.integration.create({
name: "Codex - acme/monorepo",
provider: "codex",
apiKey: process.env.GITHUB_PAT,
metadata: {
githubOwner: "acme",
githubRepo: "monorepo",
workflowFileName: "codex-beeps.yml",
defaultBranch: "main",
branchPrefix: "codex-beeps",
},
});
```
Reference it from a relay rule. Workflow runs typically take a few minutes, so push `pollInterval` higher than the cloud-API agents:
```typescript
await client.relay.rules.create(relayId, {
name: "Codex Auto-Fix",
ruleType: "agent",
config: {
agentType: "codex",
integrationId: codexIntegration.id,
pollInterval: 60000,
maxPollAttempts: 60,
},
});
```
## Configuration reference
### Integration metadata
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `githubOwner` | string | yes | — | GitHub org or user that owns the repo |
| `githubRepo` | string | yes | — | Repo name |
| `workflowFileName` | string | yes | — | Workflow filename, e.g. `codex-beeps.yml` |
| `defaultBranch` | string | no | `main` | Branch to dispatch the workflow against |
| `branchPrefix` | string | no | `codex-beeps` | Prefix for the branch name beeps generates per dispatch |
### Rule config
The rule needs `agentType: "codex"` and `integrationId`. The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## Troubleshooting
Beeps reports "workflow run not found". Your workflow file is missing the `run-name: "Beeps codex run ${{ inputs.dispatchId }}"` line. Without it, beeps falls back to "most recent workflow_dispatch run" matching, which is flaky under concurrency.
The PR URL never appears on the alert responder. Beeps resolves the PR by querying `GET /repos/{owner}/{repo}/pulls?head={owner}:{branch}`, so your workflow needs to open the PR using the `inputs.branch` value beeps passed in.
---
# Cursor
> Source: https://beeps.dev/docs/agent-integrations/cursor/
import { Aside } from "@astrojs/starlight/components";
[Cursor's cloud agents](https://cursor.com/dashboard?tab=cloud-agents) are a hosted API: Cursor clones your repo, runs the agent, and opens a PR. Beeps creates an agent run when an alert fires, polls Cursor for status, and writes the PR URL onto the alert responder.
## Prerequisites
- A Cursor account with cloud agents enabled.
- **Privacy Mode enabled** in account settings (see callout above).
- A GitHub repository Cursor can clone. The `repository` URL goes on the relay rule, not the integration.
## Provider-side setup
1. Sign in to [cursor.com/dashboard](https://cursor.com/dashboard?tab=cloud-agents).
2. Enable **Privacy Mode** in account settings.
3. Generate an API key from the same dashboard.
4. Save the API key; you'll paste it into beeps as `apiKey`.
## Beeps-side setup
Create the integration:
```typescript
const cursorIntegration = await client.integration.create({
name: "Cursor Agent",
provider: "cursor",
apiKey: process.env.CURSOR_API_KEY,
});
```
Reference it from a relay rule. Cursor requires a `repository` URL on the rule. That's the repo Cursor will clone and open the PR against:
```typescript
await client.relay.rules.create(relayId, {
name: "Cursor Auto-Fix",
ruleType: "agent",
config: {
agentType: "cursor",
integrationId: cursorIntegration.id,
repository: "https://github.com/org/repo",
autoCreatePr: true,
pollInterval: 30000,
maxPollAttempts: 120,
},
});
```
## Configuration reference
### Integration metadata
Cursor doesn't use `metadata`. Only `apiKey` is required.
### Rule config
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `agentType` | `"cursor"` | yes | — | Selects the Cursor agent |
| `integrationId` | string | yes | — | The Cursor integration's id |
| `repository` | string (URL) | yes | — | GitHub repo URL Cursor will clone |
| `autoCreatePr` | boolean | no | `true` | Whether the agent should open a PR |
| `branchName` | string | no | — | Override the branch name |
| `autoBranch` | boolean | no | `true` | Let Cursor pick the branch name |
The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## API version
Beeps targets the Cursor Cloud Agents **v1 API** (`https://api.cursor.com/v1/agents`). The legacy `v0` endpoint had a different request and response shape; customers still pinned to `v0` via an explicit `config.endpoint` override should plan to migrate, since `v0` is no longer documented.
## Troubleshooting
### All requests return 403 Forbidden
Privacy Mode is not enabled on your Cursor account. Toggle it on in settings and retry.
### Repository required error on dispatch
The `repository` URL must be on the rule config, not the integration metadata.
### `Cursor API error: response missing agent.id`
You're on a `config.endpoint` that returns a v0-shaped response. Either remove the `endpoint` override (recommended) or update it to a v1 path.
---
# OpenCode
> Source: https://beeps.dev/docs/agent-integrations/opencode/
[OpenCode](https://opencode.ai/) is a self-hosted AI coding agent. You run the OpenCode server on your own infrastructure; beeps connects to it over HTTP, creates a session per alert, and sends a prompt. OpenCode produces a file diff (it does not open a PR by itself).
## Prerequisites
- A running OpenCode server reachable from beeps. See the [OpenCode server docs](https://opencode.ai/docs/server/) for installation.
- HTTP Basic auth credentials for the server.
- The server's URL.
## Provider-side setup
1. Install and run OpenCode following [opencode.ai/docs/server](https://opencode.ai/docs/server/).
2. Configure HTTP Basic auth on your server. The password becomes `apiKey` in beeps; the username goes in metadata (default `opencode`).
3. Note the server URL. You'll pass it on the relay rule as `endpoint`.
## Beeps-side setup
Create the integration with the basic-auth password as `apiKey`:
```typescript
const openCodeIntegration = await client.integration.create({
name: "OpenCode - internal",
provider: "opencode",
apiKey: process.env.OPENCODE_SERVER_PASSWORD,
metadata: {
username: "opencode",
},
});
```
Reference it from a relay rule. OpenCode needs an `endpoint` pointing at your server. The optional `openCodeModel` lets you pick which model the session runs:
```typescript
await client.relay.rules.create(relayId, {
name: "OpenCode investigation",
ruleType: "agent",
config: {
agentType: "opencode",
integrationId: openCodeIntegration.id,
endpoint: "https://opencode.internal.corp:4096",
openCodeModel: {
providerID: "anthropic",
modelID: "claude-sonnet-4-20250514",
},
pollInterval: 15000,
maxPollAttempts: 200,
},
});
```
## Configuration reference
### Integration metadata
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `username` | string | no | `opencode` | HTTP Basic auth username |
### Rule config
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `agentType` | `"opencode"` | yes | — | Selects the OpenCode agent |
| `integrationId` | string | yes | — | The OpenCode integration's id |
| `endpoint` | string (URL) | yes | — | Base URL of the OpenCode server |
| `openCodeModel.providerID` | string | no | server default | Provider id, e.g. `anthropic` |
| `openCodeModel.modelID` | string | no | server default | Model id, e.g. `claude-sonnet-4-20250514` |
The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## Troubleshooting
401 Unauthorized on session creation. Beeps sends `Authorization: Basic `. Confirm both the username (in metadata) and password (`apiKey`) match the server's basic-auth config.
Session completes but no PR is created. OpenCode produces a file diff, not a PR. Check the session's diff endpoint on your server, or wire your own commit/PR step downstream.
---
# Devin
> Source: https://beeps.dev/docs/agent-integrations/devin/
[Devin](https://devin.ai/) is a hosted AI software engineering agent. Beeps creates a Devin session when an alert fires, sends a prompt built from the alert, polls Devin until the session finishes, and surfaces the PR URL on the alert responder.
## Prerequisites
- A Devin account with API access.
- A Devin **service-user token** (`cog_*` prefix).
## Provider-side setup
1. In the Devin web app, navigate to `https://app.devin.ai/org//settings/org-service-users`. Replace `` with your Devin org.
2. Create a service user and generate its token. It will look like `cog_...`.
3. Save the token; you'll paste it into beeps as `apiKey`.
Beeps verifies the token against Devin's API at integration-creation time, so a bad token is caught immediately rather than failing on the first alert.
## Beeps-side setup
Create the integration:
```typescript
const devinIntegration = await client.integration.create({
name: "Devin Production",
provider: "devin",
apiKey: process.env.DEVIN_SERVICE_TOKEN, // cog_*
});
```
Beeps captures the Devin org id from the token and stores it on the integration metadata. You don't need to provide it yourself.
Reference the integration from a relay rule:
```typescript
await client.relay.rules.create(relayId, {
name: "Devin Auto-Triage",
ruleType: "agent",
config: {
agentType: "devin",
integrationId: devinIntegration.id,
pollInterval: 30000,
maxPollAttempts: 120,
},
});
```
## Configuration reference
### Rule config
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `agentType` | `"devin"` | yes | — | Selects the Devin agent |
| `integrationId` | string | yes | — | The Devin integration's id |
| `endpoint` | string (URL) | no | composed from the integration's org id | Override only for Devin Enterprise / self-hosted; provide a full session-create URL including the org path |
The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## Troubleshooting
### `invalid_devin_token` at integration creation
The token was rejected by Devin. Confirm it starts with `cog_`, hasn't been revoked, and was generated from `https://app.devin.ai/org//settings/org-service-users`.
### Session stuck "waiting for user"
Devin sometimes pauses for human input or approval mid-session. Beeps marks the job `completed` with a `blocked` result so the alert isn't left dangling. Open the Devin session URL (logged when beeps creates the session) to unblock it.
### Session ended with "quota_exceeded"
When your Devin account hits a credit, quota, or billing limit, the session terminates and beeps surfaces it as **`quota_exceeded`** on the alert responder. Possible underlying reasons:
- `out_of_credits` — purchased credit pool is empty.
- `out_of_quota` / `no_quota_allocation` — plan-level quota exhausted.
- `usage_limit_exceeded` / `org_usage_limit_exceeded` — per-user or per-org cap hit.
- `total_session_limit_exceeded` — concurrent session cap hit.
- `payment_declined` — billing failure on file.
Top up or adjust the limit at https://app.devin.ai/settings/usage, then retry the failed alert (or wait for the next one). Beeps doesn't auto-retry quota failures.
---
# AWS DevOps Agent
> Source: https://beeps.dev/docs/agent-integrations/aws-devops/
[AWS DevOps Agent](https://docs.aws.amazon.com/devopsagent/) is AWS's incident-investigation agent, hosted in an Agent Space. The integration is two-sided: beeps POSTs the alert to your Agent Space's webhook URL to start an investigation, then polls task status through the AWS SDK using IAM credentials. The output is an investigation report, not a PR.
## Prerequisites
- An AWS account with [DevOps Agent](https://docs.aws.amazon.com/devopsagent/) enabled and at least one Agent Space configured.
- The Agent Space's webhook URL.
- The webhook authentication secret. Either an HMAC signing secret (`hmac_v1`) or a bearer token (`bearer_v2`), depending on how your Agent Space is configured.
- AWS IAM credentials with the `aidevops:*` permissions listed in the [DevOps Agent IAM docs](https://docs.aws.amazon.com/devopsagent/latest/userguide/aws-devops-agent-security-devops-agent-iam-permissions.html).
- Optionally: a cross-account `roleArn` for STS AssumeRole.
## Provider-side setup
### 1. Configure the Agent Space
Follow the [AWS DevOps Agent setup guide](https://docs.aws.amazon.com/devopsagent/latest/userguide/getting-started.html) to create an Agent Space. Note:
- The Agent Space ID (e.g. `space-abc123`).
- The Agent Space webhook URL (e.g. `https://webhook.devopsagent.us-east-1.amazonaws.com/invoke/abc123`).
- Whether the webhook uses HMAC v1 or bearer v2 auth.
### 2. Get the auth secret
- **HMAC v1**: copy the signing secret from the Agent Space configuration. Beeps will sign each request with HMAC-SHA256.
- **Bearer v2**: copy the bearer token from the Agent Space configuration. Beeps will send it as `Authorization: Bearer `.
### 3. Provision IAM credentials
Create an IAM user (or, preferred for cross-account, a role) with the `aidevops:*` permissions referenced in the [DevOps Agent IAM docs](https://docs.aws.amazon.com/devopsagent/latest/userguide/aws-devops-agent-security-devops-agent-iam-permissions.html). Beeps uses the access key + secret to call the DevOps Agent SDK (`ListBacklogTasksCommand`, `ListJournalRecordsCommand`) when polling.
If you're going cross-account, create a role in the target account with those permissions and a trust policy allowing the IAM user in your beeps integration account to assume it. Provide the role ARN as `roleArn` in metadata; beeps will assume it via STS for each poll.
## Beeps-side setup
The webhook secret goes in `apiKey`. Everything else (AWS creds, region, agent space, auth version, optional role) goes in `metadata`:
```typescript
const awsDevOpsIntegration = await client.integration.create({
name: "AWS DevOps Agent - prod",
provider: "aws_devops",
apiKey: process.env.AWS_DEVOPS_WEBHOOK_SECRET,
metadata: {
awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
awsRegion: "us-east-1",
agentSpaceId: "space-abc123",
authVersion: "hmac_v1", // or "bearer_v2"
// roleArn: "arn:aws:iam::123456789012:role/beeps-devops-agent",
},
});
```
Reference it from a relay rule. Point the rule at the Agent Space webhook URL via `endpoint`, and pass the Agent Space ID and region so beeps knows where to poll:
```typescript
await client.relay.rules.create(relayId, {
name: "AWS DevOps investigation",
ruleType: "agent",
config: {
agentType: "aws_devops",
integrationId: awsDevOpsIntegration.id,
endpoint: "https://webhook.devopsagent.us-east-1.amazonaws.com/invoke/abc123",
awsAgentSpaceId: "space-abc123",
awsRegion: "us-east-1",
awsAuthVersion: "hmac_v1",
awsPriority: "HIGH",
pollInterval: 60000,
maxPollAttempts: 60,
},
});
```
## Configuration reference
### Integration metadata
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `awsAccessKeyId` | string | yes | — | IAM access key id |
| `awsSecretAccessKey` | string | yes | — | IAM secret access key |
| `awsRegion` | string | yes | — | AWS region of the Agent Space, e.g. `us-east-1` |
| `agentSpaceId` | string | yes | — | Agent Space identifier |
| `authVersion` | `"hmac_v1"` \| `"bearer_v2"` | yes | — | Webhook signature scheme |
| `roleArn` | string | no | — | IAM role to assume via STS for polling (cross-account) |
The `apiKey` field on the integration holds the webhook secret (the HMAC signing secret or bearer token, depending on `authVersion`).
### Rule config
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `agentType` | `"aws_devops"` | yes | — | Selects the AWS DevOps agent |
| `integrationId` | string | yes | — | The integration's id |
| `endpoint` | string (URL) | yes | — | Agent Space webhook URL |
| `awsAgentSpaceId` | string | yes | — | Repeated here so polling can locate it without re-reading the integration |
| `awsRegion` | string | yes | — | Repeated here for the same reason |
| `awsAuthVersion` | `"hmac_v1"` \| `"bearer_v2"` | yes | — | Must match the integration's `authVersion` |
| `awsPriority` | `"CRITICAL"` \| `"HIGH"` \| `"MEDIUM"` \| `"LOW"` | no | — | Priority sent on the webhook payload |
| `awsService` | string | no | — | Service tag added to the investigation |
The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## Troubleshooting
Webhook returns 401/403. Check that `authVersion` on both the integration metadata and the rule config matches what the Agent Space expects. HMAC and bearer use different request signing; they're not interchangeable.
Polling fails with `AccessDenied`. The IAM credentials need `aidevops:*` (see the IAM permissions doc above). For cross-account, also confirm the trust policy on the assumed role allows your IAM user.
`roleArn` set but polling still uses the base credentials. Beeps assumes the role via STS only if `roleArn` is present in metadata. Confirm the field is set on the integration (not the rule).
---
# Zup
> Source: https://beeps.dev/docs/agent-integrations/zup/
[Zup](https://zup.dev) is a self-hosted incident-investigation agent. You run Zup on your own infrastructure; beeps creates a run when an alert fires and polls until the investigation finishes. The output is an investigation report (findings, situation assessment, recommendations), not a PR.
## Prerequisites
- A running Zup server reachable from beeps. See the [Zup docs](https://zup.dev/docs) for installation and configuration.
- A bearer token for authenticating to the Zup server.
- The Zup server's URL.
## Provider-side setup
1. Install and run Zup following [zup.dev/docs](https://zup.dev/docs).
2. Configure an API token on the Zup server. Save it; you'll paste it into beeps as `apiKey`.
3. Note the server URL. You'll pass it on the relay rule as `endpoint`. Default during local development is `http://localhost:3000/api/v0/runs`.
## Beeps-side setup
Create the integration with the bearer token as `apiKey`:
```typescript
const zupIntegration = await client.integration.create({
name: "Zup - internal",
provider: "zup",
apiKey: process.env.ZUP_API_TOKEN,
});
```
Reference it from a relay rule. Zup needs an `endpoint` pointing at your server:
```typescript
await client.relay.rules.create(relayId, {
name: "Zup investigation",
ruleType: "agent",
config: {
agentType: "zup",
integrationId: zupIntegration.id,
endpoint: "https://zup.internal.corp/api/v0/runs",
pollInterval: 30000,
maxPollAttempts: 120,
},
});
```
## Configuration reference
### Integration metadata
Zup doesn't use `metadata`. Only `apiKey` is required.
### Rule config
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `agentType` | `"zup"` | yes | — | Selects the Zup agent |
| `integrationId` | string | yes | — | The Zup integration's id |
| `endpoint` | string (URL) | no | `http://localhost:3000/api/v0/runs` | Base URL of your Zup server's runs endpoint |
The shared polling and storm-control fields are documented in [Relay Rules](/docs/relays/rules/).
## Troubleshooting
401 Unauthorized when starting a run. Beeps sends `Authorization: Bearer `. Confirm the token in `apiKey` matches what the Zup server is configured to accept.
Run completes but no findings show up. Check the run's status in your Zup server. Zup returns `status: "completed"` only when the investigation actually finishes; partial or failed runs return different statuses and beeps marks the alert responder `dropped` accordingly.
---
# Members API Reference
> Source: https://beeps.dev/docs/members/api/
import { Tabs, TabItem } from '@astrojs/starlight/components';
List organization members to resolve emails to user IDs when managing schedules.
## List Members
### `client.member.list()`
List all members in your organization.
**Returns:** `Promise`
**Example:**
```typescript
const members = await client.member.list();
```
### `client.member.listSafe()`
List members without throwing exceptions.
**Returns:** `Promise>`
**Example:**
```typescript
const result = await client.member.listSafe();
if (result.error) {
console.error("Error:", result.error.message);
} else {
console.log(`Members: ${result.data.length}`);
}
```
### HTTP Endpoint
```
GET /v0/members
```
All API requests require authentication using your access token:
```bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.beeps.dev/v0/members
```
```bash
beeps member list
```
Add `--json` for machine-readable output:
```bash
beeps member list --json
```
**Tool:** `beeps_list_members`
```json
{}
```
## Types
### OrganizationMember
```typescript
type OrganizationMember = {
userId: string;
organizationId: string;
role: string;
email: string;
};
```
---
# Troubleshooting
> Source: https://beeps.dev/docs/troubleshooting/
Solutions for common beeps issues.
## Alerts are not appearing
If you're sending alerts but they're not showing up:
1. Check the webhook URL — make sure you're POSTing to the right relay webhook
2. Check the payload — `title` is required, `severity` is recommended
3. Check the response — a `200` with an `alert_id` means beeps received it. A `401` means the webhook key is wrong.
4. For Sentry/Datadog/Axiom, check their delivery logs to see if the webhook is actually firing
Quick test from your terminal:
```bash
curl -X POST https://hooks.beeps.dev/YOUR_WEBHOOK_ID \
-H "Content-Type: application/json" \
-d '{"title": "Test Alert", "severity": "high"}'
```
## Alerts appear but no one is notified
Check these in order:
1. **Does the on-call user have contact methods?** Open the dashboard and check. Users without email or SMS configured won't get anything.
2. **Is someone actually on-call?** If your schedule has no members, `schedule_notify` rules have nothing to do.
3. **Are the relay rules enabled?** Rules are created with `enabled: true` by default, but check that someone didn't disable them.
4. **Is it the right rule type?** A `webhook` rule posts to a URL — it won't send email/SMS. You need a `schedule_notify` rule for that.
## No one is on-call
The schedule probably has no members. Add at least one user to the rotation. If there are members but no one shows as on-call, check these next:
1. `startAt` may be in the future. A schedule is inactive until its start time.
2. `handoffTime` and `handoffDay` may not be what you expect. All times are UTC.
## AI agent never responds
1. **Check the integration exists** — the agent rule references an `integrationId`. Make sure that integration hasn't been deleted.
2. **Check the access token** — test the Devin/Cursor access token directly against their API to confirm it's valid and has the right permissions.
3. **Check the rule is enabled** — disabled rules are skipped.
4. **Check the rule's group and order** — if the agent rule is in the same group as a `schedule_notify` rule with a lower order, and a human responds first, the relay may stop before reaching the agent rule.
## Relay rules not firing
1. **Is the relay receiving alerts?** Check the dashboard for alerts on this relay. If there are none, the webhook isn't being called.
2. **Are the rules enabled?** Disabled rules are skipped entirely.
3. **Check the rule config** — each rule type has required fields:
- `schedule_notify` needs a `scheduleId`
- `webhook` needs an `endpoint` and `method`
- `agent` needs an `agentType` and `integrationId`
Invalid rule config now returns field-level validation issues, including unknown fields and typo suggestions when available.
4. **Check groups and order** — rules in the same group run sequentially by `order`. If an earlier rule in the same group causes a response, later rules may not fire.
## Getting help
If none of the above helps, reach out with:
- Your organization ID
- The relay ID and rule IDs involved
- An example alert payload
- The error message or unexpected behavior you're seeing
- The `X-Request-ID` response header if you have it
---
# Best Practices
> Source: https://beeps.dev/docs/best-practices/
## Idempotent Configuration
Always set `externalKey` on relays, schedules, and relay rules. This makes operations idempotent—you can safely re-run setup scripts without creating duplicates.
```typescript
// First run: creates relay
const relay = await client.relay.create({
name: "Production Alerts",
externalKey: "prod-alerts-v1",
});
// Second run: updates existing relay (no duplicate)
const sameRelay = await client.relay.create({
name: "Production Alerts (Updated)",
externalKey: "prod-alerts-v1", // Same key = update
});
```
Use a consistent naming convention for external keys:
```typescript
// Recommended: namespace::resource::identifier
"myco::relay::production"
"myco::schedule::primary-weekly"
"myco::rule::notify-oncall"
```
## Parallel vs Sequential Execution
The `group` field on relay rules controls execution flow:
- **Same group** = sequential (order 1, then 2, then 3)
- **Different groups** = parallel (run simultaneously)
```typescript
// Parallel: AI and humans notified at the same time
{ group: "agents", order: 1, ruleType: "agent" }
{ group: "humans", order: 1, ruleType: "schedule_notify" }
// Sequential: AI tries first, then escalates to humans
{ group: "default", order: 1, ruleType: "agent" }
{ group: "default", order: 2, ruleType: "schedule_notify" }
```
**Use parallel when:** You want the fastest possible response time.
**Use sequential when:** You want AI to attempt resolution before interrupting humans.
## Relays
- **Descriptive names**: Use names that indicate purpose ("Production Alerts", not "Relay 1")
- **Separate by service**: Create different relays for different services or environments
- **Test rules disabled**: Create rules with `enabled: false`, test, then enable
## Schedules
- **UTC times**: All times are in UTC. Account for your team's time zones when setting `handoffTime`
- **Default start**: Omit `startAt` when you want a schedule to start immediately. Set it explicitly for planned cutovers.
- **Member order matters**: Members rotate in the order they're added
- **Weekly over daily**: Weekly rotations are generally better for work-life balance
- **Always have coverage**: Ensure your schedule has at least one member at all times
## Contact Methods
- **Multiple channels**: Add both email and SMS for each on-call engineer
- **E.164 format**: Use international format for phone numbers (+14155551234)
- **Verify methods**: Verified contact methods have higher deliverability
- **Keep updated**: Review contact information when team members change roles
## Agent Integrations
- **Environment separation**: Use separate integrations for dev/staging/prod
- **Rotate keys regularly**: Update access tokens periodically for security
- **Descriptive metadata**: Store environment and purpose in metadata
## Testing
1. Create relays and rules with `enabled: false`
2. Send test alerts to verify webhook connectivity
3. Use `client.relay.simulate()` to preview routing
4. Use `client.relay.lint()` to validate coverage
5. Enable rules once verified
---
# Terminal
> Source: https://beeps.dev/docs/using-beeps/terminal/
import { Tabs, TabItem } from "@astrojs/starlight/components";
beeps-terminal is a keyboard-driven TUI dashboard that gives you a live view of active alerts, alert details, and your on-call roster — all from the terminal.
## Installation
```bash
npm install -g beeps-terminal
```
Then run:
```bash
beeps-terminal
```
```bash
npx beeps-terminal
```
## Configuration
beeps-terminal needs one environment variable:
| Variable | Required | Description |
|----------|----------|-------------|
| `BEEPS_ACCESS_TOKEN` | Yes | Your access token from organization settings (looks like `bat_xxxxxxxx`) |
```bash
export BEEPS_ACCESS_TOKEN="bat_your_access_token_here"
beeps-terminal
```
## Layout
The dashboard has a 3-panel layout that adapts to your terminal size:
```
┌──────────────────────┬──────────────────────┐
│ │ │
│ Active Alerts │ Alert Detail / │
│ │ Recent Deploys │
│ (scrollable list │ │
│ of all active ├──────────────────────┤
│ alerts) │ │
│ │ On-Call Roster │
│ │ │
└──────────────────────┴──────────────────────┘
```
- **Left pane** — scrollable list of active alerts with status, severity, source, and assignment info. Updates automatically.
- **Top-right pane** — shows recent deploy activity by default. Press `enter` on an alert to expand its full detail (ID, status, severity, timestamps, metadata).
- **Bottom-right pane** — on-call roster showing who's on-call for each schedule, their active alert count, rotation end time, and who's up next.
## Keybindings
| Key | Action |
|-----|--------|
| `↑` / `↓` | Navigate alerts |
| `enter` | Expand alert detail |
| `escape` | Collapse detail |
| `a` | Acknowledge alert |
| `r` | Resolve alert |
| `e` | Escalate alert |
| `q` / `ctrl+c` | Quit |
---
# MCP Server
> Source: https://beeps.dev/docs/using-beeps/mcp-server/
import { Tabs, TabItem } from "@astrojs/starlight/components";
The beeps MCP server lets you triage, fix, and resolve alerts without leaving your editor.
## Setup
```bash
claude mcp add --transport http --scope user beeps https://mcp.beeps.dev/mcp
```
Verify:
```bash
claude mcp list
claude mcp get beeps
```
#### Optional: Pre-Registered OAuth Client
If your deployment disables open dynamic client registration, add a client id/secret:
```bash
claude mcp add --transport http --scope user --client-id --client-secret beeps https://mcp.beeps.dev/mcp
```
```bash
codex mcp add beeps --url https://mcp.beeps.dev/mcp
```
Run OAuth login:
```bash
codex mcp login beeps --scopes "openid,profile,email,offline_access,beeps.tools.read,beeps.tools.write"
```
Verify:
```bash
codex mcp list
codex mcp get beeps
```
## Tools
### Triage
**`beeps_list_active_alerts`** — List active unresolved alerts with optional severity filter.
```text
Show me all active alerts
```
```text
Are there any critical alerts right now?
```
**`beeps_list_alert_responders`** — List alert responders and their status.
```text
Who's responding to alert al_abc123?
```
**`beeps_get_fix_status`** — Get current fix progress by alert jobs or a specific job.
```text
What's the fix status on alert al_abc123?
```
### Act
**`beeps_on_it`** — Mark an alert as being worked by a responder.
```text
I'm on it for alert al_abc123
```
**`beeps_fix_here`** — Load complete alert fix context for foreground remediation.
```text
Load the fix context for alert al_abc123 and help me resolve it
```
### Resolve
**`beeps_update_responder_status`** — Mark responder as done or dropped and optionally attach a PR URL.
```text
Mark my response to al_abc123 as done with PR https://github.com/org/repo/pull/42
```
**`beeps_resolve_alert`** — Resolve an alert.
```text
Resolve alert al_abc123
```
### Scheduling
**`beeps_get_on_call`** — Get the currently on-call user for a schedule.
```text
Who's on call for the platform schedule?
```
**`beeps_set_override`** — Set a temporary schedule override.
```text
Override the platform schedule to put me on call from now until 6pm
```
**`beeps_update_override`** — Update the time window or reason of an override.
```text
Extend the current override until Monday
```
**`beeps_cancel_override`** — Cancel an override to restore normal rotation.
```text
Cancel the override on the platform schedule
```
**`beeps_get_schedule_assignments`** — Get upcoming schedule assignments.
```text
Show the next 5 on-call assignments for the platform schedule
```
### Configuration
**`beeps_list_relays`** — List all relays.
```text
What relays do we have?
```
**`beeps_list_webhooks`** — List webhooks for a relay.
```text
What webhook URLs does the production relay have?
```
**`beeps_create_relay`** — Create a new relay.
```text
Create a relay called "staging alerts"
```
**`beeps_list_schedules`** — List all schedules.
```text
Show me all schedules
```
**`beeps_create_schedule`** — Create a schedule.
```text
Create a weekly schedule on the production relay, handoff Monday at 9am
```
**`beeps_list_schedule_members`** — List members of a schedule.
```text
Who's in the primary on-call rotation?
```
**`beeps_add_schedule_member`** — Add a member to a rotation.
```text
Add alice@example.com to the primary schedule
```
**`beeps_remove_schedule_member`** — Remove a member from a rotation.
```text
Remove Bob from the primary schedule
```
**`beeps_list_relay_rules`** — List rules for a relay.
```text
What rules are configured on the production relay?
```
**`beeps_create_relay_rule`** — Create a relay rule.
```text
Add a schedule_notify rule to the production relay for the primary schedule
```
**`beeps_delete_relay_rule`** — Delete a relay rule.
```text
Delete the webhook rule on the staging relay
```
**`beeps_assign_alert`** — Assign an alert to a user.
```text
Assign alert alr_abc123 to Alice
```
**`beeps_list_integrations`** — List integrations.
```text
What integrations are configured?
```
**`beeps_list_members`** — List organization members.
```text
Who's in the org?
```
**`beeps_get_alert`** — Get full details of a specific alert.
```text
Show me the details of alert alr_abc123
```
## Rate limits
Per-actor, sliding 1-minute window:
| Tier | Limit |
|---|---|
| Read tools (e.g. `beeps_list_alerts`) | 120 / minute |
| Write tools (e.g. `beeps_on_it`, `beeps_resolve_alert`) | 20 / minute |
| Per-IP fallback (unauth, mixed) | 200 / minute |
When you exceed a limit the server returns `429 Too Many Requests` with a `Retry-After` header (seconds until the window resets). The body is `{ "error": "rate_limit_exceeded" }`.
## Troubleshooting
### `429 rate_limit_exceeded`
You hit a per-minute cap (see [Rate limits](#rate-limits)). Wait the number of seconds in `Retry-After` and retry. If you're consistently hitting the limit, batch your reads with `beeps_list_alerts` rather than per-alert `beeps_get_alert` calls.
### `403 missing required scope`
Re-run `codex mcp login beeps` with both `beeps.tools.read` and `beeps.tools.write`. For Claude Code, remove/re-add the server and complete OAuth consent again.
### `Organization context required`
If the user belongs to multiple organizations, set an active org in the web app first. For Claude Code, you can also set a fixed header when adding the server:
```bash
claude mcp add --transport http --scope user -H "X-Organization-Id: " beeps https://mcp.beeps.dev/mcp
```
### `explicit write confirmation required`
Write tools require confirmation metadata by default. Ask the agent to include MCP `_meta`:
```json
{
"beeps/confirmWrite": true
}
```
For self-hosted environments, this policy can be disabled with `BEEPS_REQUIRE_WRITE_CONFIRMATION=false`.
---
# Config as Code
> Source: https://beeps.dev/docs/config-as-code/
import { Tabs, TabItem } from "@astrojs/starlight/components";
Once you've gone through the [Quickstart](/docs/quickstart/) and have a working setup, manage it as code. A single `beeps.config.ts` file is the source of truth, checked into your repo, and deployed via CI.
## Full SDK config
This is the complete `beeps.config.ts` that wires up the quickstart end-to-end. Because every entity sets `externalKey`, the file is idempotent: safe to re-run without creating duplicates. Treat it like a database migration.
```typescript
import { BeepsClient } from "@beepsdev/sdk";
async function configure() {
const client = new BeepsClient({
accessToken: process.env.BEEPS_ACCESS_TOKEN,
});
// 1. Create relay
const relay = await client.relay.create({
name: "production relay",
description: "routes critical production incidents",
externalKey: "production::relay",
});
// 2. Create schedule
const schedule = await client.schedule.create({
name: "Primary On-Call",
relayId: relay.id,
type: "weekly",
handoffDay: "monday",
handoffTime: "09:00",
// startAt omitted -> schedule starts immediately
externalKey: "primary::schedule",
});
// 3. Add team members by email
await client.schedule.addMember(schedule.id, { email: "alice@example.com" });
await client.schedule.addMember(schedule.id, { email: "bob@example.com" });
// 4. Contact methods are managed in Settings → Profile in the dashboard.
// 5. Create AI integration
const integration = await client.integration.create({
name: "Cursor Production Agent",
provider: "cursor",
apiKey: process.env.CURSOR_API_KEY,
externalKey: "agents::cursor::integration",
});
// 6. Create relay rules (parallel: agents + humans)
await client.relay.rules.create(relay.id, {
name: "AI Agent Auto-Triage",
externalKey: "agents::cursor",
ruleType: "agent",
group: "agents",
order: 1,
config: {
agentType: "cursor",
integrationId: integration.id,
repository: "https://github.com/your-org/your-repo",
autoCreatePr: true,
},
enabled: true,
});
await client.relay.rules.create(relay.id, {
name: "Notify On-Call Engineer",
externalKey: "humans::primary::schedule-notify",
ruleType: "schedule_notify",
group: "humans",
order: 1,
config: { scheduleId: schedule.id },
enabled: true,
});
// 7. Verify
const rules = await client.relay.rules.list(relay.id);
console.log("\nRelay Rules:");
rules.forEach((rule) => {
console.log(` [${rule.group}] ${rule.name}`);
console.log(` Type: ${rule.ruleType}`);
console.log(` Enabled: ${rule.enabled}`);
});
}
configure().catch(console.error);
```
Run it:
```bash
export BEEPS_ACCESS_TOKEN="bat_your_token_here"
export CURSOR_API_KEY="cursor_api_key_here"
npx tsx beeps.config.ts
```
## Deploy via CI
Treat `beeps.config.ts` like infrastructure. Every change goes through PR review.
### GitHub Actions
```yaml
name: Deploy beeps config
on:
push:
branches: [main]
paths: [beeps.config.ts]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm install @beepsdev/sdk
- run: npx tsx beeps.config.ts
env:
BEEPS_ACCESS_TOKEN: ${{ secrets.BEEPS_ACCESS_TOKEN }}
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
```
For a CLI-driven plan/apply flow that previews diffs before applying (à la Terraform), see [CLI Config-as-Code](/docs/cli/config/).
## Understanding parallel execution
```
Alert Received
|
Relay: "production relay"
|
|-- [agents] AI Agent Auto-Triage --> Cursor starts working
|
|-- [humans] Notify On-Call Engineer --> Email/SMS sent
```
Rules with the same `group` run sequentially in `order` order. Rules in different groups run in parallel.
## Common customizations
### Sequential instead of parallel
To run AI first, then escalate to humans only if the AI doesn't resolve in time, put both rules in the same group:
```typescript
await client.relay.rules.create(relay.id, {
name: "Try AI first",
ruleType: "agent",
group: "default",
order: 1,
config: {
agentType: "cursor",
integrationId: integration.id,
repository: "https://github.com/your-org/your-repo",
},
});
await client.relay.rules.create(relay.id, {
name: "Escalate to humans",
ruleType: "schedule_notify",
group: "default",
order: 2,
config: { scheduleId: schedule.id },
});
```
### Add a secondary schedule
```typescript
await client.relay.rules.create(relay.id, {
name: "Escalate to backup",
ruleType: "schedule_notify",
group: "humans",
order: 2,
config: { scheduleId: backupSchedule.id },
});
```
### Webhook fan-out
```typescript
await client.relay.rules.create(relay.id, {
name: "Post to Slack ops channel",
ruleType: "webhook",
group: "notifications",
order: 1,
config: {
endpoint: "https://hooks.slack.com/services/...",
method: "POST",
headers: { "Content-Type": "application/json" },
},
});
```
See [Relay Rules](/docs/relays/rules/) for the full rule type reference.
---
# CLI
> Source: https://beeps.dev/docs/using-beeps/cli/
import { Tabs, TabItem } from "@astrojs/starlight/components";
The beeps CLI lets you manage relays, schedules, alerts, and integrations directly from the command line — or define your entire on-call setup as code.
## Installation
```bash
npm install -g @beepsdev/cli
```
```bash
npx @beepsdev/cli --help
```
## Authentication
Set your access token as an environment variable:
```bash
export BEEPS_ACCESS_TOKEN="bat_your_access_token_here"
```
Or use a JSON config file (must be `chmod 600`, owned by the current user, and not a symlink):
```json
{
"accessToken": "bat_your_access_token_here",
"baseUrl": "https://api.beeps.dev/v0"
}
```
```bash
beeps relay list --config ./beeps.cli.json
# or set once via environment variable
export BEEPS_CLI_CONFIG="./beeps.cli.json"
```
Resolution order (highest priority first): config file > `BEEPS_ACCESS_TOKEN` environment variable.
Passing an access token on the command line is not supported — argv is visible to other local users via `ps` and `/proc//cmdline`.
## Global Options
These flags are available on every command:
| Flag | Description |
|------|-------------|
| `--base-url ` | Custom API endpoint |
| `--config ` | Path to a JSON config file |
| `--timeout ` | Request timeout in milliseconds (default: 10000) |
| `--retries ` | Number of retry attempts (default: 2) |
| `--json` | Output machine-readable JSON |
| `--help` | Show usage info |
## Quick Commands
### See who's on call
```bash
beeps schedule on-call --schedule-id sch_abc123
```
### List active alerts
```bash
beeps alert list --active
```
### Respond to an alert
```bash
beeps alert on-it --alert-id alt_abc123
```
### Resolve an alert
```bash
beeps alert resolve --alert-id alt_abc123
```
### List relays
```bash
beeps relay list
```
### Simulate a relay
```bash
beeps relay simulate --relay-id rly_abc123 --simulate-at 2025-03-01T00:00:00Z
```
### Define your setup as code
```bash
beeps relay export -o beeps.config.ts # export current setup
beeps relay plan -f beeps.config.ts # preview changes
beeps relay apply -f beeps.config.ts # apply changes
```
## Command Reference
```
beeps
├── alert
│ ├── list [--active] [--resolved]
│ ├── get --alert-id
│ ├── on-it --alert-id [--user-id ]
│ ├── assign --alert-id --user-id
│ ├── resolve --alert-id
│ ├── responders --alert-id
│ ├── agents --alert-id
│ └── fix-context --alert-id
├── relay
│ ├── list
│ ├── create --name [--external-key ]
│ ├── lint --relay-id [--coverage-days ]
│ ├── simulate --relay-id --simulate-at
│ ├── plan -f
│ ├── apply -f [--prune] [--dry-run]
│ ├── export -o
│ └── rule
│ ├── list --relay-id
│ ├── get --relay-id --rule-id
│ ├── create --relay-id --name --rule-type
│ └── delete --relay-id --rule-id
├── schedule
│ ├── list
│ ├── create --name --relay-id --type ...
│ ├── on-call --schedule-id
│ ├── assignments --schedule-id [--count ]
│ ├── add-member --schedule-id (--email | --user-id )
│ ├── remove-member --schedule-id --user-id
│ └── override
│ ├── create --schedule-id --user-id --start-at --end-at
│ ├── list --schedule-id
│ ├── update --schedule-id --override-id
│ └── cancel --schedule-id --override-id
├── integration
│ ├── list
│ ├── get --integration-id
│ ├── create --provider --name --access-token-env
│ ├── update --integration-id
│ └── delete --integration-id
├── webhook
│ └── list --relay-id
├── agent-job
│ ├── list
│ ├── get --job-id
│ └── status --job-id
├── member
│ └── list
└── config
└── lint -f
```
## Reference
- [Config-as-Code](/docs/cli/config) — define relays, schedules, and rules in TypeScript
- [Relay Commands](/docs/cli/relay) — manage relays and rules directly
- [Schedule Commands](/docs/cli/schedule) — manage schedules, members, and overrides
- [Alert Commands](/docs/cli/alert) — triage, respond to, and resolve alerts
- [Integration Commands](/docs/cli/integration) — connect AI agents and notification services
All commands support `--json` for machine-readable output and `--help` for usage info.