# Interactive API Reference (/docs/api/endpoints)
View the [interactive API reference](/docs/api/endpoints) for complete endpoint documentation with request/response schemas and a try-it playground.
# API Reference (/docs/api)
The Bounce House API is a REST API with JSON request and response bodies.
Browse the full [interactive API reference](/docs/api/endpoints) for complete endpoint documentation with request/response schemas, or read the overview below.
## Base URL [#base-url]
```
https://api.bouncehouse.cloud
```
## Authentication [#authentication]
All requests require a Bearer token with a `bh_` prefix:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/dashboard-summary \
-H "Authorization: Bearer bh_your_key"
```
API keys use the `bh_` prefix. The full key is only shown once at creation — store it securely. Keys are identified by their first 7 characters (e.g. `bh_abc1`) and validated via SHA-256 hash.
Create API keys in the [dashboard settings](https://bouncehouse.cloud/settings) or via the API:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/api-keys \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"label": "ci-pipeline"}'
```
## Quick start [#quick-start]
```bash
# 1. Create an account and get an API key
curl -X POST https://api.bouncehouse.cloud/api/accounts \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "..."}'
# Response includes your API key (bh_ prefix, shown only once)
# 2. Create an email receiver
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/receivers \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"channel": "email", "label": "test", "ttl_seconds": 3600}'
# 3. Wait for a message (long-poll, no polling needed)
curl "https://api.bouncehouse.cloud/api/accounts/me/receivers/{id}/wait?timeout=30" \
-H "Authorization: Bearer bh_your_key"
# 4. Add a domain for monitoring
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/domains \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
```
## Rate limits [#rate-limits]
API requests are rate-limited to 100 requests/minute per API key. Current limits are returned in response headers:
* `X-RateLimit-Limit` — requests allowed per window
* `X-RateLimit-Remaining` — requests remaining
* `X-RateLimit-Reset` — window reset (UTC timestamp)
Exceeding the limit returns 429 with a `Retry-After` header.
## What's in the REST API vs. Data API [#whats-in-the-rest-api-vs-data-api]
After PR #329 the dashboard reads tenant rows directly from
[Neon's Data API](https://neon.tech/docs/data-api/get-started) (a
PostgREST surface) over the `bouncehouse` schema, with row-level
security scoping every query to the calling tenant. The Bearer-token
REST API documented here keeps the surface that needs server-side
coordination:
* **Account lifecycle and credentials** — account create, API-key
issue, addresses sign, account update / delete.
* **Domains** — register / delete, DMARC sources + guidance, live
DNS lookup, DNS recommendations apply / dismiss, MX setup &
verify, reporting-address (HMAC-signed), live health recompute.
* **Receivers** — create (HMAC-signs the address), delete (releases
SMS pool), and `/wait` long-poll on Redis pub/sub.
* **Sending / DNS / auth providers** — create with server-side
encryption, fanout-URL admin, domain-link writes, setup
acknowledgement.
* **Outbound webhooks** — `POST /event-subscriptions/{id}/test`
signs and dispatches a synthetic event.
* **Deliveries** — manual retry (re-queues background dispatch).
* **Preferences** — registry-merged defaults, per-key blast-radius
preview, multi-layer resolver, group + override writes.
* **Events / messages** — registered-types enum, daily volume,
type breakdown, current-month usage, message delete (audit
event).
* **Alerts** — acknowledge / unacknowledge with audit row.
* **Suppression** — `GET /check` with classification.
* **Webhook ingress** — email, provider, auth-provider, SMS, Polar.
* **Admin / maintenance / benchmarks** — cross-tenant operational
surfaces.
Tenant **list / detail reads** (domains, messages, events,
receivers, deliveries, suppression entries, auth-provider configs,
sending-provider configs, DNS connections, event subscriptions,
DMARC reports + records, health-score history, group + override
rows) are served by Data API, not the REST API. Hit
`https://.data-api.neon.tech/rest/v1/` with the
short-lived JWT issued by Better Auth instead.
## OpenAPI spec [#openapi-spec]
The full OpenAPI specification covers only the REST API surface
above:
```
https://api.bouncehouse.cloud/openapi.json
```
## Errors [#errors]
All errors return JSON with a `detail` field:
```json
{ "detail": "Domain not found" }
```
| Status | Meaning |
| ------ | -------------------------------------------- |
| 400 | Bad request (invalid format) |
| 401 | Unauthorized (missing/invalid API key) |
| 404 | Not found |
| 409 | Conflict (duplicate label, already exists) |
| 410 | Gone (receiver expired) |
| 422 | Validation error |
| 429 | Rate limited (`Retry-After` header included) |
| 503 | Service unavailable |
# Domains (/docs/domains)
When your email lands in someone's spam folder, it's usually because your domain's DNS records aren't set up correctly. Email providers like Gmail, Outlook, and Yahoo check your domain's authentication records (SPF, DKIM, and DMARC) every time you send a message, and if something is misconfigured, your email gets downgraded or rejected entirely. Worse, anyone can send email that looks like it came from your domain unless your DMARC policy tells receiving servers to block unauthorized senders.
The challenge is that these problems are invisible until they cause damage. Your sending provider won't tell you that your SPF record has too many DNS lookups, or that a third-party service is sending unauthenticated email as your domain, or that your DMARC policy is set to "none" and isn't actually protecting you. Bounce House fills this gap: it continuously parses the DMARC reports that major email providers send back about your domain's email, audits your DNS configuration, scores your overall email health, and tells you exactly what to fix. Connect a DNS provider like Cloudflare or Route 53, and those fixes can be applied with a single API call.
## Add a domain [#add-a-domain]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/domains \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"domain": "example.com"}'
```
Returns: `{id, domain_name, verified, created_at}`
## Set up DMARC reporting [#set-up-dmarc-reporting]
Add a TXT record to start receiving DMARC aggregate reports:
```
_dmarc.example.com TXT "v=DMARC1; p=none; rua=mailto:{signed-id}@{shard}-bo.ing-bo.ing"
```
The signed address is generated when you add the domain. Reports from Gmail, Outlook, Yahoo, and other providers flow in automatically via Cloudflare Email Routing, with no SMTP server to manage.
## DMARC analysis [#dmarc-analysis]
Once reports start arriving, Bounce House parses them and gives you a breakdown of every IP address sending email as your domain: how many messages it sent, whether SPF and DKIM passed, whether they were properly aligned with your domain, and how the receiving server handled them.
This surfaces problems that are otherwise invisible: a marketing tool sending as your domain without DKIM signing, an old server still using an IP that's no longer in your SPF record, or a forwarding service that breaks alignment. Each sending source is classified so you can see at a glance which senders are authorized and which aren't.
```bash
# List aggregate reports
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dmarc \
-H "Authorization: Bearer bh_your_key"
# Get detailed per-source records from a specific report
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dmarc/{report_id} \
-H "Authorization: Bearer bh_your_key"
```
Each record includes `source_ip`, `count`, `spf_result`, `dkim_result`, `disposition`, `spf_aligned`, `dkim_aligned`, and `header_from`.
## Sending sources [#sending-sources]
See every IP that has sent email as your domain, aggregated across all reports:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/sources \
-H "Authorization: Bearer bh_your_key"
```
Each source shows `total_messages`, `spf_pass`, `dkim_pass`, and a `classification` (aligned or unaligned). This is the fastest way to identify unauthorized senders.
## Policy guidance [#policy-guidance]
DMARC policies control what happens when authentication fails: `p=none` just monitors, `p=quarantine` sends failures to spam, and `p=reject` blocks them entirely. Moving from `none` to `reject` protects your domain from spoofing, but doing it too early (before all your legitimate senders are properly authenticated) will cause your own email to be blocked.
Bounce House tracks your alignment rate over time and tells you when it's safe to tighten your policy:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dmarc/policy-guidance \
-H "Authorization: Bearer bh_your_key"
```
Returns your `current_policy`, `recommended_policy`, `alignment_rate`, whether you're `ready_to_enforce`, and specific `guidance` on what to do next.
## DNS records [#dns-records]
Fetch current SPF, DKIM, DMARC, and MX records:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dns \
-H "Authorization: Bearer bh_your_key"
```
## Health score [#health-score]
Get a 0-100 health score with findings and recommendations:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/health \
-H "Authorization: Bearer bh_your_key"
```
Returns `{score, findings, recommendations}`. The score reflects SPF validity (lookup limits, sender coverage), DKIM key publication and alignment, DMARC policy enforcement, and MX configuration.
Track score changes over time:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/health/history \
-H "Authorization: Bearer bh_your_key"
```
## DNS recommendations [#dns-recommendations]
Get specific DNS changes to improve your configuration:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dns/recommendations \
-H "Authorization: Bearer bh_your_key"
```
Each recommendation includes `record_type`, `record_name`, `record_value`, `action`, and `status`.
## One-click DNS fixes [#one-click-dns-fixes]
Connect a DNS provider (Cloudflare or Route 53) to apply recommendations directly:
```bash
# Connect DNS provider
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dns/provider \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"provider": "cloudflare", "zone_id": "...", "api_token": "..."}'
# Approve a recommendation
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dns/recommendations/{id}/approve \
-H "Authorization: Bearer bh_your_key"
# Apply it (creates/updates the DNS record)
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/domains/example.com/dns/recommendations/{id}/apply \
-H "Authorization: Bearer bh_your_key"
```
Provider credentials are encrypted at rest.
## Forward operational email [#forward-operational-email]
Route abuse, postmaster, and custom-tagged email to Bounce House using your email provider's forwarding rules:
```
abuse@example.com → example-com_abuse_{hmac}@{account-name}.{shard}-bo.ing-bo.ing
postmaster@example.com → example-com_postmaster_{hmac}@{account-name}.{shard}-bo.ing-bo.ing
security@example.com → example-com_security_{hmac}@{account-name}.{shard}-bo.ing-bo.ing
```
Built-in tags (`abuse`, `postmaster`) get standard handlers. Custom tags get processing rules you define via [tag routes](/docs/tag-routes).
Generate a signed address for any tag:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/addresses \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"tag": "security"}'
```
# Events & Alerts (/docs/events)
Email infrastructure problems tend to be silent. A DNS record gets misconfigured during a migration, and no one notices until customers complain they're not receiving emails. A new marketing tool starts sending as your domain without proper authentication, and your deliverability degrades over weeks before anyone connects the dots. Bounce rates creep up on one provider, but the team responsible uses a different one and doesn't see it.
Bounce House watches for these problems and surfaces them as events. When your domain health score drops, bounces spike, a DNS record changes, or a webhook delivery fails, you get an event you can query, filter, and act on. Critical events become alerts that require acknowledgement, so your team can track who investigated what. You can subscribe webhook endpoints to specific event types with domain and tag filtering, so alerts reach the right people and systems automatically.
## Query events [#query-events]
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/events?event_type=health_score_changed&limit=50" \
-H "Authorization: Bearer bh_your_key"
```
Returns `[{id, event_type, payload, created_at}, ...]`
**Parameters:**
* `event_type` — filter to a specific type
* `since` — ISO timestamp
* `limit` — max 200, default 50
Discover available event types:
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/events/types \
-H "Authorization: Bearer bh_your_key"
```
## Alerts [#alerts]
Alerts are events that require attention. Query and acknowledge them:
```bash
# Get unacknowledged alerts
curl "https://api.bouncehouse.cloud/api/accounts/me/alerts?acknowledged=false" \
-H "Authorization: Bearer bh_your_key"
# Acknowledge an alert
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/alerts/{event_id}/acknowledge \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"acknowledged_by": "ops@example.com", "note": "Investigated — false positive"}'
# Unacknowledge
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/alerts/{event_id}/acknowledge \
-H "Authorization: Bearer bh_your_key"
```
## Event subscriptions [#event-subscriptions]
Push events to your own endpoints:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/event-subscriptions \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/bouncehouse",
"event_types": ["health_score_changed", "bounce_spike"],
"domain_filter": "example.com",
"tag_filter": "abuse",
"signing_secret": ""
}'
```
**Fields:**
* `url` — your webhook endpoint
* `event_types` — list of types to subscribe to (omit for all events)
* `domain_filter` — only events for this domain
* `tag_filter` — only events for this tag
* `signing_secret` — we sign payloads with this (encrypted at rest)
Test a subscription:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/event-subscriptions/{id}/test \
-H "Authorization: Bearer bh_your_key"
```
Returns `{status, status_code}`.
## Delivery tracking [#delivery-tracking]
Track webhook delivery status and retry failures:
```bash
# List deliveries
curl "https://api.bouncehouse.cloud/api/accounts/me/deliveries?status=failed" \
-H "Authorization: Bearer bh_your_key"
# Manually retry a failed delivery
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/deliveries/{id}/retry \
-H "Authorization: Bearer bh_your_key"
```
Delivery status: `pending`, `delivered`, `failed`, `retrying`. Each delivery tracks `attempts`, `max_attempts`, `last_status_code`, `last_error`, and `next_retry_at`.
# Flow Completions (/docs/flow-completions)
Receiving a verification email is only half the job. The email contains a link that must be clicked to activate the account, and that link often leads to a page with a button, a form, or a CAPTCHA. In a manual test, a human clicks through it. In a CI pipeline or an AI agent workflow, the test stalls because there's nothing to operate the browser.
Flow completions bridge this gap. They provide a server-side browser agent that navigates to the URL from a received message, clicks elements, fills forms, submits pages, and captures screenshots as evidence of what happened. Combined with receivers (to capture the email) and TOTP (to handle any 2FA challenge along the way), flow completions let you automate an entire signup-to-verified flow without human interaction.
## Start a flow [#start-a-flow]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/flow-completions \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/verify?token=abc123",
"instructions": "Click the verify button",
"max_duration_seconds": 60
}'
```
Returns `{id, url, status, instructions, page_state, evidence_urls, created_at}`.
The response includes `page_state`, which describes what the browser sees on the page: the page title, current URL, visible links, form fields, and buttons. This lets your automation decide what action to take next based on the actual page content, rather than hardcoding selectors that break when the UI changes.
**Status values:** `active`, `completed`, `failed`, `timeout`, `cancelled`
## Take actions [#take-actions]
Once a flow is active, send browser actions:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/flow-completions/{id}/actions \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"action": "click", "selector": "button.verify", "timeout": 5000}'
```
Each action returns its result and an updated `page_state`, so you can chain actions based on what changed.
**Available actions:**
| Action | Parameters | What it does |
| ------------ | --------------------- | ------------------------------------ |
| `navigate` | `selector` (URL) | Navigate to a URL |
| `click` | `selector` | Click an element by CSS selector |
| `fill` | `selector`, `value` | Fill a form field |
| `submit` | `selector` | Submit a form |
| `screenshot` | | Capture the current page as an image |
| `get_dom` | | Return all DOM elements on the page |
| `wait` | `selector`, `timeout` | Wait for an element to appear |
## Evidence collection [#evidence-collection]
When a flow completes, fails, or is cancelled, Bounce House automatically captures evidence:
* **Final screenshot** — image of the page at completion
* **HAR recording** — full HTTP Archive of every request the browser made during the flow, useful for debugging network issues or verifying that the right requests were sent
Evidence URLs are returned in the `evidence_urls` field on the flow completion response. On failure, evidence is captured before the browser session is closed, so you can see exactly what the page looked like when things went wrong.
## Complete a flow [#complete-a-flow]
Mark a flow as successfully completed:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/flow-completions/{id}/complete \
-H "Authorization: Bearer bh_your_key"
```
This captures a final screenshot and HAR recording, closes the browser session, and sets the status to `completed`.
## Cancel a flow [#cancel-a-flow]
Cancel an active flow and release the browser session:
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/flow-completions/{id} \
-H "Authorization: Bearer bh_your_key"
```
Captures evidence before closing.
## Get flow status [#get-flow-status]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/flow-completions/{id} \
-H "Authorization: Bearer bh_your_key"
```
## List flows [#list-flows]
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/flow-completions?status=active&limit=50" \
-H "Authorization: Bearer bh_your_key"
```
Filter by `status` (active, completed, failed, timeout, cancelled).
## List actions [#list-actions]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/flow-completions/{id}/actions \
-H "Authorization: Bearer bh_your_key"
```
Returns the full history of actions taken during the flow, each with its result and any error.
## Rate limits and security [#rate-limits-and-security]
* Max concurrent flows: 3 per account
* Max flows per hour: 20 per account
* Max duration per flow: 120 seconds (configurable per-flow, capped by account limit)
* URL allowlist: optionally restrict which URLs flows can navigate to
URLs are validated before the browser session starts. If a URL allowlist is configured, only URLs matching the allowlist are permitted.
# CI Email Testing (/docs/guides/ci-email-testing)
Use Bounce House receivers to verify real email delivery in your CI pipeline — no mock SMTP servers, no shared inboxes, no flaky polling loops.
## The problem [#the-problem]
Testing email flows in CI is hard:
* Local SMTP catchers (Mailpit) can't receive real email from production providers
* Shared test inboxes create race conditions between parallel test runs
* Polling for email with retries is slow and flaky
## The solution [#the-solution]
Create an ephemeral receiver per test run, send to it, and wait:
```typescript
import { describe, it, expect } from "vitest";
const API = "https://api.bouncehouse.cloud";
const headers = {
Authorization: `Bearer ${process.env.BOUNCEHOUSE_API_KEY}`,
"Content-Type": "application/json",
};
describe("signup flow", () => {
it("sends a verification email", async () => {
// 1. Create a receiver
const receiver = await fetch(`${API}/api/accounts/me/receivers`, {
method: "POST",
headers,
body: JSON.stringify({
channel: "email",
label: "signup-test",
ttl_seconds: 3600,
}),
}).then((r) => r.json());
// 2. Trigger signup with the receiver's email address
await signupUser({ email: receiver.address });
// 3. Wait for the verification email (blocks, no polling)
const message = await fetch(`${API}/api/accounts/me/receivers/${receiver.id}/wait?timeout=30`, {
headers,
}).then((r) => r.json());
// 4. Assert
expect(message.subject).toContain("Verify your email");
expect(message.extracted.links).toHaveLength(1);
// 5. Clean up
await fetch(`${API}/api/accounts/me/receivers/${receiver.id}`, {
method: "DELETE",
headers,
});
});
});
```
## Key benefits [#key-benefits]
* **Isolation** — each test run gets its own email address, no cross-contamination
* **No polling** — the wait endpoint blocks until a message arrives or the timeout expires
* **Real delivery** — tests actual email sending through your production provider
* **Content extraction** — OTPs, verification links, and structured content are extracted automatically
# Playwright Integration (/docs/guides/playwright-integration)
Combine Bounce House receivers with Playwright to test full user flows that involve email — signup verification, password reset, invitation links, and more.
## Setup [#setup]
Install the Bounce House client alongside Playwright:
```bash
npm install -D @playwright/test
```
Set your API key as an environment variable:
```bash
export BOUNCEHOUSE_API_KEY=bh_your_key
```
## Example: password reset flow [#example-password-reset-flow]
```typescript
import { test, expect } from "@playwright/test";
const API = "https://api.bouncehouse.cloud";
const headers = {
Authorization: `Bearer ${process.env.BOUNCEHOUSE_API_KEY}`,
"Content-Type": "application/json",
};
test("password reset sends email and link works", async ({ page }) => {
// Create a receiver
const receiver = await fetch(`${API}/api/accounts/me/receivers`, {
method: "POST",
headers,
body: JSON.stringify({ channel: "email", label: "pw-reset", ttl_seconds: 3600 }),
}).then((r) => r.json());
// Trigger password reset
await page.goto("https://your-app.com/forgot-password");
await page.fill('[name="email"]', receiver.address);
await page.click('button[type="submit"]');
// Wait for the reset email
const message = await fetch(`${API}/api/accounts/me/receivers/${receiver.id}/wait?timeout=30`, {
headers,
}).then((r) => r.json());
// Follow the reset link
const resetLink = message.extracted.links[0];
await page.goto(resetLink);
// Complete the reset
await page.fill('[name="password"]', "new-secure-password");
await page.fill('[name="confirmPassword"]', "new-secure-password");
await page.click('button[type="submit"]');
await expect(page.locator("text=Password updated")).toBeVisible();
// Clean up
await fetch(`${API}/api/accounts/me/receivers/${receiver.id}`, {
method: "DELETE",
headers,
});
});
```
## Tips [#tips]
* Set `ttl_seconds: 3600` (or shorter) for CI receivers — they auto-expire even if cleanup fails
* Use `label` to identify receivers in the dashboard during debugging
* The `wait` endpoint's timeout should exceed your app's email sending latency (30s is usually sufficient)
# Getting Started (/docs)
Bounce House is a unified platform for email and auth ops. Monitor domain health, track deliverability across providers, test and automate auth flows, and give AI agents access to email, webhooks, and verification tooling, all through a single API and dashboard.
## Get started with Bounce House [#get-started-with-bounce-house]
Add a [domain](/docs/domains) to find out if your email is landing in inboxes or spam folders, track who's sending as you through DMARC reports, and get specific DNS fixes to improve deliverability. Connect your [email sending providers](/docs/providers) (SendGrid, Postmark, Mailgun, or SES) to see bounces, spam complaints, and deliveries across all of them in one place, correlated with your domain health, DNS configuration, and inbound signals that no single provider can see on its own.
For CI pipelines and AI agents, create [ephemeral receivers](/docs/receivers) that capture verification emails, webhook callbacks, SMS codes, or OAuth redirects during a test run. Each receiver gets a unique address, blocks until a message arrives, and auto-expires when you're done. Combine them with [TOTP generation](/docs/totp) to handle two-factor login challenges and [flow completions](/docs/flow-completions) to click verification links and fill forms automatically.
The [MCP server](/docs/mcp) gives AI agents in Claude, Cursor, and other MCP clients access to all of these capabilities directly.
See the [API reference](/docs/api) for authentication, rate limits, and a quick start walkthrough, or jump directly to what you need:
## Quick references [#quick-references]
These docs are available as [llms.txt](/llms.txt) and [llms-full.txt](/llms-full.txt) for AI
agents and LLM-powered tools.
For direct API access from agents, use the [MCP server](/docs/mcp).
## Monitor your email infrastructure [#monitor-your-email-infrastructure]
Every domain that sends email needs to prove it's authorized to do so, or messages end up in spam. Bounce House continuously monitors your domains for authentication issues, parses the DMARC reports that Gmail, Outlook, and Yahoo send back about your email, scores your overall health, and tells you exactly what DNS records to add or fix. Connect a DNS provider like Cloudflare or Route 53 to apply those fixes directly.
When you connect email sending providers, you get a unified view of what happened to every message: delivered, bounced, opened, complained about. Addresses that bounce or generate complaints are automatically added to a unified suppression list, and you can subscribe to bounce and complaint events to keep your own sending systems in sync in real time.
* [**Domains**](/docs/domains): Email authentication monitoring, deliverability health scoring, and one-click DNS fixes
* [**Providers**](/docs/providers): Unified bounce, delivery, and complaint tracking across SendGrid, Postmark, Mailgun, and SES
* [**Suppression**](/docs/suppression): Stop sending to addresses that will damage your sender reputation
* [**Events & alerts**](/docs/events): Get notified when health scores drop, bounces spike, or configurations change
* [**Tag routes**](/docs/tag-routes): Automatically process inbound operational email like abuse reports and security disclosures
* [**Messages**](/docs/messages): Query everything Bounce House has received across all channels in one place
## Build CI and agent workflows [#build-ci-and-agent-workflows]
Modern test suites and AI agents need to receive messages from the outside world: verification emails, webhook payloads, SMS codes, OAuth callbacks. Bounce House provides ephemeral receivers that give you a unique address or URL, block until a message arrives (no polling necessary), and auto-expire when you're done.
Combine receivers with TOTP generation to handle two-factor login challenges, flow completions to click verification links and fill forms, and the MCP server to let AI agents drive the whole process.
* [**Receivers**](/docs/receivers): Get a unique email address, webhook URL, phone number, or OAuth callback for each test run
* [**TOTP**](/docs/totp): Generate valid two-factor authentication codes without an authenticator app
* [**Flow completions**](/docs/flow-completions): Click verification links, fill out forms, and capture screenshots automatically
* [**MCP server**](/docs/mcp): Let AI agents in Claude, Cursor, and other MCP clients use all of the above
## Explore guides and tutorials [#explore-guides-and-tutorials]
Step-by-step guides for common workflows:
* [**CI email testing**](/docs/guides/ci-email-testing): Verify real email delivery in your test suite without shared inboxes or polling loops
* [**Playwright integration**](/docs/guides/playwright-integration): End-to-end email flow testing with Playwright and ephemeral receivers
# MCP Server (/docs/mcp)
AI agents are increasingly capable of performing multi-step workflows autonomously: signing up for services, testing integrations, monitoring infrastructure, responding to alerts. But they can only interact with systems that expose tools they can call. The Model Context Protocol (MCP) is the emerging standard for this, supported by Claude Code, Cursor, Claude Desktop, and a growing number of AI development tools.
The Bounce House MCP server gives AI agents direct access to the full platform. An agent can create an ephemeral email receiver, sign up for a service using that address, wait for the verification email, extract the link, follow it in a headless browser, fill out any forms, and clean up, all without human intervention. It can also monitor domain health, generate TOTP codes for two-factor challenges, acknowledge alerts, and query suppression lists. If your team is building AI-powered test automation or agentic workflows that interact with the real world, MCP is how the agent gets access to email, webhooks, and the rest of the external message layer.
## Installation [#installation]
```bash
pip install bouncehouse-mcp
# or run directly
uvx bouncehouse-mcp
```
## Configuration [#configuration]
Add to your MCP client config (Claude Desktop, Cursor, Claude Code, etc.):
```json
{
"mcpServers": {
"bouncehouse": {
"command": "uvx",
"args": ["bouncehouse-mcp"],
"env": {
"BOUNCEHOUSE_API_KEY": ""
}
}
}
}
```
Set `BOUNCEHOUSE_API_URL` to override the base URL (defaults to production).
## Available tools [#available-tools]
### Receivers [#receivers]
| Tool | Parameters | What it does |
| ------------------ | --------------------------------- | ------------------------------------------------ |
| `create_receiver` | `label`, `channel`, `ttl_seconds` | Create an email, webhook, SMS, or OAuth receiver |
| `wait_for_message` | `receiver_id`, `timeout`, `since` | Block until a message arrives (no polling) |
| `get_messages` | `receiver_id`, `limit` | List messages captured by a receiver |
| `destroy_receiver` | `receiver_id` | Delete a receiver and stop capturing |
### TOTP [#totp]
| Tool | Parameters | What it does |
| --------------- | ----------------------------------------------- | ------------------------------------------ |
| `register_totp` | `label`, `secret`, `issuer`, `digits`, `period` | Register a TOTP secret for code generation |
| `get_totp_code` | `secret_id` | Generate the current valid 2FA code |
### Domains [#domains]
| Tool | Parameters | What it does |
| -------------------------- | ---------------------------------- | --------------------------------------------------- |
| `get_domain_health` | `domain_name` | Get health score, DNS findings, and recommendations |
| `get_recommendations` | `domain_name` | Get specific DNS fixes for a domain |
| `apply_dns_recommendation` | `domain_name`, `recommendation_id` | Apply a DNS fix through a connected provider |
### Alerts [#alerts]
| Tool | Parameters | What it does |
| ------------------- | ----------------------- | ---------------------------------------------------------- |
| `get_alerts` | `acknowledged`, `limit` | List active alerts (abuse spikes, delivery failures, etc.) |
| `acknowledge_alert` | `event_id`, `note` | Mark an alert as handled with a note |
### Auth providers [#auth-providers]
| Tool | Parameters | What it does |
| ---------------------- | -------------------- | -------------------------------------------------------- |
| `list_auth_providers` | | List configured auth provider integrations |
| `create_auth_provider` | `provider`, `config` | Connect an auth provider webhook |
| `get_auth_events` | `config_id`, `limit` | List recent auth events (signups, verifications, resets) |
### Browser flows [#browser-flows]
| Tool | Parameters | What it does |
| ------------------ | ---------------------------------------- | --------------------------------------------------------------------- |
| `follow_link` | `url`, `instructions` | Navigate to a URL and return page state (links, forms, buttons) |
| `complete_oauth` | `receiver_id`, `instructions` | Get verification link from a receiver's message and follow it |
| `send_flow_action` | `flow_id`, `action`, `selector`, `value` | Click, fill, submit, screenshot, or wait in an active browser session |
| `close_flow` | `flow_id`, `mark_complete` | Close a browser session, optionally marking the flow as complete |
### Platform introspection [#platform-introspection]
Diagnostic tools an agent can call when something looks off, or when planning a workflow that depends on what the account or platform supports.
| Tool | Parameters | What it does |
| -------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `get_status` | | Diagnostic snapshot of platform health (db, redis, active incidents). Call when you suspect a platform issue, not as a pre-flight check. |
| `get_usage` | | Account-level volume over the last 30 days plus subscription state. `limits` is null until enforcement ships — handle null as "retry on 429s," not "unlimited." |
| `get_account_capabilities` | | Live capability snapshot — channels enabled, DNS provider attribution, auth providers connected, custom-MX domains. Use to plan workflows around actually-configured features. |
| `get_changelog` | `limit` | Recent platform changes parsed from CHANGELOG.md. Useful when a previously-working workflow starts failing — check what changed. |
## Example: AI agent signup flow [#example-ai-agent-signup-flow]
```
Agent: create_receiver(label="signup", channel="email", ttl_seconds=3600)
→ Receiver created. Address: a1b2.signup@shard-bo.ing-bo.ing
Agent: [signs up on target site using the receiver address]
Agent: wait_for_message(receiver_id="rx_123", timeout=30)
→ Message received. Subject: "Verify your email"
Extracted: verification_links: ["https://example.com/verify?token=abc"]
Agent: complete_oauth(receiver_id="rx_123")
→ Following verification link: https://example.com/verify?token=abc
Page title: "Confirm your account"
Buttons: "Confirm" id=confirm-btn
Agent: send_flow_action(flow_id="flow_456", action="click", selector="#confirm-btn")
→ Page title: "Account verified!"
Agent: close_flow(flow_id="flow_456", mark_complete=true)
→ Flow completed. Evidence: screenshot, HAR recording
Agent: destroy_receiver(receiver_id="rx_123")
→ Receiver deleted
```
The `complete_oauth` tool is particularly useful: it automatically extracts the verification link from the latest message at a receiver and follows it in a browser, combining three API calls (get message, extract link, start flow) into one.
# Messages (/docs/messages)
Bounce House receives content from many different sources: forwarded operational email, webhook payloads captured by ephemeral receivers, delivery and bounce events from your sending providers, SMS messages, and auth provider notifications. Without a unified view, you'd need to check each source separately to understand what's happening across your email infrastructure.
Messages are the unified view. Every piece of inbound content, regardless of how it arrived, is stored as a message that you can query through a single API. Filter by channel, tag, sender, or time range, and paginate through results with cursors.
## Automatic content extraction [#automatic-content-extraction]
Every email and webhook message is processed through an extraction pipeline that surfaces structured data from unstructured content. The pipeline runs five extractors:
* **OTP codes** — 4-8 digit codes near keywords like "code," "verify," or "confirm"
* **Verification links** — URLs with paths like `/verify`, `/confirm`, `/activate`, or query parameters like `?token=`
* **Auth provider links** — Links from Auth0, Clerk, Supabase, Firebase, Cognito, Neon Auth, WorkOS, and Stytch, classified by provider and purpose (verification, password reset, magic link, invitation)
* **OAuth parameters** — Authorization codes, state parameters, and access tokens from query parameters, JSON bodies, and URLs
* **Unsubscribe links** — RFC 2369 List-Unsubscribe headers, RFC 8058 one-click unsubscribe, and unsubscribe URLs in the body
Results are merged into the `extracted` field:
```json
{
"otp_codes": ["123456"],
"verification_links": ["https://example.com/verify?token=abc"],
"provider_links": [{ "provider": "auth0", "purpose": "verification", "url": "https://..." }],
"oauth": { "code": "auth_code", "state": "csrf_token" },
"unsubscribe": {
"header_urls": ["https://example.com/unsub"],
"one_click": true,
"body_urls": ["https://example.com/opt-out?id=123"]
}
}
```
Extractor failures are isolated: if one fails, the error is logged in `extracted._errors` and the others still run.
## Channels [#channels]
Messages arrive through five channels, each with channel-specific metadata:
| Channel | Source | Metadata |
| ---------------- | ------------------------------------------------ | ------------------------------------------------------------------- |
| `email` | Forwarded operational email or receiver captures | Headers, attachments, body text and HTML |
| `webhook` | HTTP requests at ephemeral receiver URLs | HTTP method, query parameters, content type, source IP |
| `provider_event` | SendGrid, Postmark, Mailgun, SES webhooks | Event type, bounce type/category, subject line, provider message ID |
| `sms` | Inbound SMS at allocated phone numbers | Provider, provider message ID, media URLs (MMS) |
| `auth_event` | Auth0, Clerk, Supabase, etc. webhooks | Auth event type, user email, sending service, source IP |
Email messages preserve the full raw RFC 5322 payload for replay or forensic analysis. Attachment metadata (filename, content type, size) is stored, though full attachment data is not retained.
## Query messages [#query-messages]
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/messages?channel=email&tag=abuse&limit=50" \
-H "Authorization: Bearer bh_your_key"
```
Returns `{items: [...], pagination: {next_cursor, has_more}}`.
**Query parameters:**
* `channel` — `email`, `webhook`, `provider_event`, `sms`, `auth_event`
* `tag` — filter by routing tag (exact match)
* `sender` — filter by sender (substring match)
* `since` — ISO timestamp
* `cursor` — pagination cursor (from `next_cursor`)
* `limit` — results per page (max 200, default 50)
Pagination is cursor-based using `created_at` timestamps. Pass the `next_cursor` value to get the next page.
## Get a message [#get-a-message]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/messages/{message_id} \
-H "Authorization: Bearer bh_your_key"
```
Returns the full message detail including body text, HTML, headers, attachments, extracted content, and channel-specific metadata.
## Delete a message [#delete-a-message]
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/messages/{message_id} \
-H "Authorization: Bearer bh_your_key"
```
## Retention [#retention]
Configure per-channel retention periods:
```bash
curl -X PATCH https://api.bouncehouse.cloud/api/accounts/me/retention \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{
"email_days": 30,
"webhook_days": 7,
"provider_event_days": 90,
"sms_days": 7,
"auth_event_days": 30
}'
```
Messages older than the configured retention period are automatically purged. Messages linked to ephemeral receivers inherit the receiver's expiry time. Linked resources (events, webhook deliveries) are cleaned up in the same purge cycle.
# Providers (/docs/providers)
Your email sending provider's dashboard shows you what happened to messages sent through that provider. But if you send through more than one (production on SendGrid, transactional on Postmark, marketing on Mailgun), you're checking three dashboards to understand your deliverability. None of them can tell you how your overall sender reputation is trending, whether bounces on one provider correlate with DNS issues visible in your DMARC reports, or whether an address that hard-bounced on SendGrid is still being sent to through Postmark.
Bounce House connects all of your providers into a single normalized event stream. Delivery, bounce, complaint, open, click, and unsubscribe events all arrive in one common format regardless of which provider sent them. Bounced and complained addresses are automatically added to a unified suppression list, so a hard bounce on SendGrid is visible before you send to that address through Postmark. And because provider events are correlated with your domain health data, a spike in bounces can trigger a DNS audit and surface the root cause.
## Supported providers [#supported-providers]
* **SendGrid** — HMAC-SHA256 signature verification
* **Postmark** — webhook signature verification
* **Mailgun** — webhook signature verification
* **AWS SES** — SNS signature verification with automatic subscription confirmation
## Connect a provider [#connect-a-provider]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/providers \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"provider": "sendgrid", "signing_secret": ""}'
```
Returns `{id, provider, enabled, config, created_at}`. Signing secrets are encrypted at rest.
Then point your provider's webhook URL to:
```
https://api.bouncehouse.cloud/webhooks/provider/{account_id}/sendgrid
```
Inbound webhooks are verified against the provider's signing secret (not your API key) and rate-limited to 300 requests/minute per account. For AWS SES, SNS subscription confirmations are handled automatically.
## Normalized event types [#normalized-event-types]
Each provider uses its own event names and formats. Bounce House normalizes them into a common schema:
| Normalized type | What happened |
| --------------- | ------------------------------------------------------------------- |
| `delivered` | Message accepted by the recipient's mail server |
| `bounced` | Message rejected (classified as hard or soft, with bounce category) |
| `deferred` | Temporary delivery failure, provider will retry |
| `dropped` | Provider refused to send (suppression, rate limit, etc.) |
| `opened` | Recipient opened the message |
| `clicked` | Recipient clicked a link in the message |
| `complained` | Recipient marked the message as spam or unsubscribed |
Bounces include `bounce_type` (hard or soft) and `bounce_category` (invalid address, mailbox full, etc.) so you can distinguish between permanent failures and transient issues.
## Auto-suppression [#auto-suppression]
When any connected provider reports a hard bounce or spam complaint, the recipient address is automatically added to your [suppression list](/docs/suppression). Soft bounces (transient failures like a full mailbox) are excluded since they may resolve on their own. This works across all providers: a complaint reported by SendGrid suppresses the address globally, not just for SendGrid.
## Fan-out [#fan-out]
Forward normalized events to your own endpoints for triggering downstream workflows, updating CRM records, or feeding analytics pipelines:
```bash
# Add a fan-out URL
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/providers/{config_id}/fanout-urls \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-app.com/webhooks/email-events"}'
# List current fan-out URLs
curl https://api.bouncehouse.cloud/api/accounts/me/providers/{config_id}/fanout-urls \
-H "Authorization: Bearer bh_your_key"
```
Fan-out deliveries use exponential backoff retry (30s, 2m, 10m, 30m, 2h) with configurable max attempts. Server errors and rate limits trigger retries; client errors (4xx except 429) are treated as permanent failures. You can track delivery status and manually retry failed deliveries via the [deliveries API](/docs/events#delivery-tracking).
## List providers [#list-providers]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/providers \
-H "Authorization: Bearer bh_your_key"
```
## Remove a provider [#remove-a-provider]
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/providers/{config_id} \
-H "Authorization: Bearer bh_your_key"
```
## Auth providers [#auth-providers]
Separately from email sending providers, you can connect authentication providers to monitor auth-related email events: signup confirmations, password resets, MFA challenges, and magic link sends.
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/auth-providers \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"provider": "auth0", "config": {...}}'
```
Supported: **Auth0**, **Clerk**, **Supabase**, **Neon Auth**, **Firebase**, **Cognito**.
Auth events are stored as messages with `channel: "auth_event"` and include the auth event type, user email, sending service (which ESP the auth provider uses), and source IP. When a verification email later arrives at a receiver, Bounce House correlates the two events and calculates the actual email delivery latency, giving you data on how long your auth provider's ESP takes to deliver.
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/auth-providers/{config_id}/events \
-H "Authorization: Bearer bh_your_key"
```
# Receivers (/docs/receivers)
Testing real-world flows in CI is hard because your test suite needs to actually receive messages from external systems: a verification email from your auth provider, a webhook callback from a payment processor, an SMS code from a phone verification service, or an OAuth redirect from a third-party login. You can't mock these interactions without losing confidence that the real flow works, but the existing options are painful: shared test inboxes create race conditions between parallel runs, local SMTP catchers can't receive real email from production providers like Auth0 or Clerk, and polling loops with retries are slow and flaky.
Receivers solve this. Each receiver is an ephemeral endpoint that gives your test run its own unique email address, webhook URL, phone number, or OAuth callback URL. You send to it, call the wait endpoint which blocks until a message arrives (no polling, no retries, no sleep loops), assert on the content, and clean up. Receivers auto-expire after a configurable TTL, so forgotten resources don't accumulate and you don't need to worry about cleanup failures in CI.
## Channels [#channels]
| Channel | What you get | Use case |
| ---------------- | ------------------------------------------------ | ----------------------------------------------------------- |
| `email` | HMAC-signed email address on a managed domain | Verification emails, OTPs, password resets |
| `webhook` | HMAC-signed HTTPS URL (accepts all HTTP methods) | Provider callbacks, payment notifications, form submissions |
| `sms` | Real phone number from a managed pool | SMS verification codes |
| `oauth_callback` | Stable OAuth redirect URL (no expiry) | OAuth flow testing with automatic token exchange |
## Create a receiver [#create-a-receiver]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/receivers \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"channel": "email", "label": "signup-test", "ttl_seconds": 3600}'
```
Returns `{id, label, channel, address_or_url, expires_at, created_at}` with status 201.
**Parameters:**
* `channel` — `email`, `webhook`, `sms`, or `oauth_callback`
* `label` — identifier for this receiver (must be unique per account; 409 if duplicate)
* `ttl_seconds` — time to live in seconds. Omit (or pass `null`) for a persistent receiver (the default).
## Automatic content extraction [#automatic-content-extraction]
Every message that arrives at a receiver is automatically processed through an extraction pipeline before it's stored. You don't need to parse HTML or write regex to pull out verification codes and links.
**What gets extracted:**
* **OTP codes** — 4-8 digit codes that appear near keywords like "code," "verify," or "confirm," or standalone codes on their own line
* **Verification links** — URLs containing paths like `/verify`, `/confirm`, `/activate`, `/reset`, or query parameters like `?token=` or `?code=`
* **Auth provider links** — Links from known auth providers (Auth0, Clerk, Supabase, Firebase, Cognito, Neon Auth, WorkOS, Stytch) are identified by provider and classified by purpose: `verification`, `password_reset`, `magic_link`, `invitation`, or `callback`. Your tests can match on provider and purpose instead of brittle URL patterns that break when the provider changes their URLs.
* **OAuth parameters** — Authorization codes, state parameters, access tokens, and error responses extracted from query parameters, JSON bodies, and URLs in email
* **Unsubscribe links** — RFC 2369 List-Unsubscribe headers, RFC 8058 one-click unsubscribe support, and unsubscribe URLs in the email body
All extraction results are available in the message's `extracted` field:
```json
{
"otp_codes": ["123456"],
"verification_links": ["https://example.com/verify?token=abc"],
"provider_links": [{ "provider": "clerk", "purpose": "verification", "url": "https://..." }],
"oauth": { "code": "auth_code_here", "state": "csrf_token" },
"unsubscribe": {
"header_urls": ["https://example.com/unsub"],
"one_click": true
}
}
```
If an individual extractor fails, the error is captured in `extracted._errors` without breaking the rest of the pipeline.
## Wait for a message [#wait-for-a-message]
Long-poll until a message arrives:
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/receivers/{id}/wait?timeout=30" \
-H "Authorization: Bearer bh_your_key"
```
Returns the message immediately when it arrives, or **204 No Content** if the timeout expires.
**Parameters:**
* `timeout` — seconds to wait (1-120, default 30)
* `since` — ISO timestamp; only return messages after this time (useful for waiting for a second message after you've already consumed the first)
The wait endpoint uses async notification internally, with Redis pub/sub for cross-instance delivery. A message arriving on any server instance immediately wakes your waiting client, regardless of which instance it's connected to.
## List messages [#list-messages]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/receivers/{id}/messages \
-H "Authorization: Bearer bh_your_key"
```
Returns `[{id, sender, subject, extracted, created_at, latency_ms}, ...]`
The `latency_ms` field measures the actual time between when an auth provider sent a verification email and when it arrived at the receiver. This is calculated by correlating the email's arrival time with auth provider events within a 10-minute window, giving you real delivery speed data for your email service provider.
## OAuth receivers [#oauth-receivers]
OAuth receivers are designed for testing OAuth login flows. Unlike other receivers, they don't expire (OAuth providers need a stable redirect URL in their configuration).
If you provide client credentials when creating the receiver, Bounce House will automatically exchange the authorization code for tokens when the OAuth redirect arrives:
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/receivers \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{
"channel": "oauth_callback",
"label": "github-login-test",
"oauth_client_id": "your_client_id",
"oauth_client_secret": "",
"oauth_token_url": "https://github.com/login/oauth/access_token"
}'
```
When the provider redirects to your receiver URL with an authorization code, the extraction pipeline captures the code, and the token exchange happens automatically. The resulting access token, refresh token, and expiration are stored in `extracted.oauth.token_exchange`. Client secrets are encrypted at rest.
## SMS receivers [#sms-receivers]
SMS receivers allocate a real phone number from a managed pool. Numbers have account affinity: the system prefers numbers previously used by your account to reduce carrier-side association churn. When a receiver is deleted, the number enters a cooldown period (default 15 minutes) before being released back to the pool, so late-arriving messages still route correctly.
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/receivers \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"channel": "sms", "label": "phone-verify", "ttl_seconds": 3600}'
```
Returns 503 if no numbers are available in the pool.
## Webhook receivers [#webhook-receivers]
Webhook receivers accept all HTTP methods (GET, POST, PUT, PATCH, DELETE) at their unique URL. The full request is captured: method, headers, body, query parameters, content type, and source IP.
The inbound URL is HMAC-signed, so the URL itself is the credential. No authentication header is needed on the inbound side. The URL format is:
```
https://api.bouncehouse.cloud/webhooks/ingest/{receiver_id}/{signature}
```
## Receiver metadata [#receiver-metadata]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/receivers/{id}/metadata \
-H "Authorization: Bearer bh_your_key"
```
Returns `{id, label, channel, address_or_url, expires_at, created_at, message_count}`.
## Update a receiver [#update-a-receiver]
Rename or extend the TTL:
```bash
curl -X PATCH https://api.bouncehouse.cloud/api/accounts/me/receivers/{id} \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"ttl_seconds": 14400}'
```
Cannot set TTL on persistent receivers (OAuth callbacks).
## List receivers [#list-receivers]
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/receivers?channel=email&status=active" \
-H "Authorization: Bearer bh_your_key"
```
Filter by `channel`, `label` (substring match), and `status` (`active` or `expired`).
## Delete a receiver [#delete-a-receiver]
Persistent receivers stay until you delete them. Ephemeral receivers (those created with a `ttl_seconds`) auto-expire when their `expires_at` elapses. To destroy one immediately:
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/receivers/{id} \
-H "Authorization: Bearer bh_your_key"
```
Returns 204. Attempting to use an expired receiver returns **410 Gone**.
# Suppression Lists (/docs/suppression)
Every time you send to an address that has already hard-bounced or filed a spam complaint, Gmail, Outlook, and other mailbox providers notice. They use this as a signal that you're not maintaining clean lists, and they respond by downgrading your sender reputation. Once your reputation drops far enough, your legitimate email starts landing in spam for everyone, not just the problematic addresses.
Your sending providers each maintain their own suppression lists, but they don't share them with each other. If an address hard-bounces on SendGrid, Postmark doesn't know about it, and you'll keep sending to it there. Bounce House maintains a single suppression list across all of your connected providers. When any provider reports a hard bounce or spam complaint, the address is suppressed everywhere. You can also import existing lists in bulk, check individual addresses before sending, and export the full list.
## List suppressed addresses [#list-suppressed-addresses]
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/suppression?reason=hard_bounce&limit=50" \
-H "Authorization: Bearer bh_your_key"
```
Returns `[{id, email_address, reason, bounce_type, provider, created_at}, ...]`
**Parameters:**
* `email` — search for a specific address
* `reason` — filter by reason
* `limit` — max 200, default 50
## Check an address [#check-an-address]
```bash
curl "https://api.bouncehouse.cloud/api/accounts/me/suppression/check?email=user@example.com" \
-H "Authorization: Bearer bh_your_key"
```
Returns `{suppressed: true, reason: "hard_bounce"}` or `{suppressed: false}`.
## Add manually [#add-manually]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/suppression \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"email_address": "bad@example.com", "reason": "manual", "bounce_type": "hard"}'
```
## Bulk import [#bulk-import]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/suppression/bulk \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"entries": [{"email_address": "a@example.com", "reason": "hard_bounce"}, ...]}'
```
Returns `{imported, skipped}`.
## Export [#export]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/suppression/export \
-H "Authorization: Bearer bh_your_key"
```
Returns all suppressed addresses (no limit).
## Remove from suppression [#remove-from-suppression]
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/suppression/{entry_id} \
-H "Authorization: Bearer bh_your_key"
```
## Automatic population [#automatic-population]
When provider webhooks report a hard bounce or spam complaint, the address is automatically added to your suppression list. This works across all connected providers.
# Tag Routes (/docs/tag-routes)
Every domain receives operational email that usually goes unread: abuse reports from other mail servers, postmaster delivery failure notices, security vulnerability disclosures, billing alerts from SaaS providers, and compliance notifications. Most teams either ignore these addresses entirely or forward them to a shared inbox where they disappear. But this email contains valuable signals: an abuse report might indicate a compromised account, a postmaster notice might explain why a customer's password reset never arrived, and a spike in security disclosures might mean a vulnerability was published.
Tag routes let you process this email programmatically. When you forward operational email to Bounce House, the tag in the address (the part after the `+`) determines what happens: store it for querying via the API, extract structured content like OTP codes or links, push the payload to a webhook in real time, or apply conditional rules that alert you on keyword matches or frequency spikes. You can also BCC your outgoing transactional email to a tagged address to build a complete record of what you sent and correlate it with bounce and delivery events.
## Handlers [#handlers]
| Handler | Behavior |
| --------- | -------------------------------------------------------------------------------- |
| `store` | Store the message and make it queryable via the messages API |
| `extract` | Extract structured content (OTPs, links, specific fields) |
| `webhook` | Push the message payload to an external URL on arrival |
| `rules` | Apply conditional rules (keyword matching, sender filtering, frequency alerting) |
Handlers can be combined — a single tag route can store, extract, and push to a webhook.
## Create a tag route [#create-a-tag-route]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/tag-routes \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{
"tag": "security",
"handler": "webhook",
"config": {
"url": "https://your-app.com/webhooks/security-reports",
"extract_links": true
}
}'
```
Returns `{id, tag, handler, config, created_at}`.
## List tag routes [#list-tag-routes]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/tag-routes \
-H "Authorization: Bearer bh_your_key"
```
## Update a tag route [#update-a-tag-route]
```bash
curl -X PUT https://api.bouncehouse.cloud/api/accounts/me/tag-routes/{route_id} \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"handler": "store", "config": {"retention_days": 90}}'
```
## Delete a tag route [#delete-a-tag-route]
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/tag-routes/{route_id} \
-H "Authorization: Bearer bh_your_key"
```
## Examples [#examples]
**Monitor abuse reports:**
```
abuse@example.com → example-com_abuse_{hmac}@{account-name}.{shard}-bo.ing-bo.ing
```
Built-in handler. Stores reports, extracts sender info, alerts on frequency spikes.
**Collect security disclosures:**
```
security@example.com → example-com_security_{hmac}@{account-name}.{shard}-bo.ing-bo.ing
```
Tag route with `webhook` handler pushes to your security team's Slack integration.
**Track outgoing transactional email via BCC:**
```
BCC: {id}+txn@shard-bo.ing-bo.ing
```
Tag route with `store` + `extract` captures what was sent, extracts OTPs and links, and correlates with bounce/delivery events.
# TOTP (/docs/totp)
Two-factor authentication protects your accounts, but it creates a problem for automation. When your staging environment requires a 6-digit code from an authenticator app to log in, your CI tests can't get past the login screen. The common workarounds are bad: disabling 2FA in staging leaves it untested and less secure, and sharing authenticator app access across a team is fragile and a security risk.
TOTP (Time-based One-Time Password) is the standard behind authenticator apps like Google Authenticator and Authy. The app and the server share a secret key, and the app uses it to generate a new code every 30 seconds. Bounce House lets you register that secret key via API and request the current valid code on demand, so your CI tests and AI agents can complete two-factor challenges the same way a human would, without disabling security or sharing devices.
## Register a secret [#register-a-secret]
```bash
curl -X POST https://api.bouncehouse.cloud/api/accounts/me/totp \
-H "Authorization: Bearer bh_your_key" \
-H "Content-Type: application/json" \
-d '{"label": "staging-admin", "secret": "", "issuer": "MyApp", "digits": 6, "period": 30}'
```
Returns `{id, label, issuer, digits, period, created_at}`. The secret value is encrypted at rest and never returned in subsequent API calls.
**Parameters:**
* `label` — unique identifier (409 if duplicate)
* `secret` — base32-encoded TOTP shared key
* `issuer` — optional, the service name
* `digits` — code length (default 6)
* `period` — code rotation interval in seconds (default 30)
## Generate a code [#generate-a-code]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/totp/{secret_id}/code \
-H "Authorization: Bearer bh_your_key"
```
Returns `{code, valid_for_seconds}`.
## List secrets [#list-secrets]
```bash
curl https://api.bouncehouse.cloud/api/accounts/me/totp \
-H "Authorization: Bearer bh_your_key"
```
Secret values are never exposed — only `{id, label, issuer, digits, period, created_at}`.
## Delete a secret [#delete-a-secret]
```bash
curl -X DELETE https://api.bouncehouse.cloud/api/accounts/me/totp/{secret_id} \
-H "Authorization: Bearer bh_your_key"
```