# Testing against Connect safely

No sandbox environment is published: there is no test tenant, no simulated carrier and no separate host to point a client at. Most of what a sandbox usually protects against does not exist here either, because the public developer surface is read-only. What is left is worth doing locally, against fixtures of your own.

- **Status:** Not yet
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/developers/sandbox/

## The honest position

A sandbox exists so that a developer can trigger side effects without a real person receiving them. The public surface documented in this section has no side effects to trigger: `/api/public/*` is read-only, the MCP endpoint exposes documentation tools whose annotations declare `readOnlyHint: true` and `destructiveHint: false`, and there is no public write endpoint of any kind.

So the question splits in two. Against the public surface, you are already safe and need nothing extra. Against a workspace — which is not a public developer surface at all — safety comes from the product's own controls rather than from a parallel environment, and this page says which.

> **Careful** Do not construct a 'sandbox' by pointing a client at a real workspace with test data in it. Suppression lists, follow-ups and relationship records are per workspace; a fictional company that lives beside real ones will eventually appear in a real report.

## What can be exercised safely today

| Surface | Why it is safe | The only thing to respect |
|---|---|---|
| `GET /api/public/docs/*` | Reads generated files anonymous visitors already receive; no session, no workspace, no write | 240 requests a minute per address |
| `POST /mcp` — `initialize`, `ping`, `tools/list`, `resources/list`, `prompts/list` | Metadata only | 120 calls a minute; send a valid `Origin` or none |
| `POST /mcp` — `tools/call` on any public tool | Every public tool reads the generated corpus | The same rate limit |
| `/docs-manifest.json`, `/docs-data/*.json`, `/llms.txt`, any `index.md` | Static files | Cache them; they change on a deploy |

There is nothing to clean up after any of these, and nothing that can be left in a bad state. That is the useful consequence of a documentation API that touches no workspace: the usual reason to want a sandbox does not arise.

## Testing a webhook receiver without deliveries

1. Write the `Event` envelope from the AsyncAPI document into a fixture file, with fictional values throughout — `ws_example`, `ev_…`, "Acme Traders", `someone@example.net`.
   - Result: Your tests own their data, so they run offline and on a laptop.
2. Sign the fixture yourself: `HMAC-SHA256` over `<timestamp>.<raw body>` with a secret your test owns.
   - Result: You exercise the real verification path rather than a bypass, which is where receivers usually break.
3. Generate the failures deliberately — a stale timestamp, a flipped byte, the wrong secret, a duplicate `id`, an unknown `type`.
   - Result: You learn that the rejections happen, which no volume of successful deliveries would ever tell you.
4. Replay one fixture twice in the same test.
   - Result: De-duplication on `id` is proved, and at-least-once delivery stops being a paragraph you read and becomes a case your code handles.

This is a better test than a live delivery would be, because a live delivery only ever shows you the path that works.

## If you do have workspace access

The controls that make a real workspace safe to experiment in are the product's own, and they are stronger than a sandbox because they are the same rules that run in earnest. Set the channel's autonomy to `draft_only` and Connect writes replies without sending any of them; set `ask_before_send` and every outbound action waits for a person in Needs You. Both are described on [what Connect may do](/docs/autonomy/).

Beyond that: keep fictional contacts obviously fictional, remember that a suppression entry is a real record with real consequences, and know that the daily allowance applies to a test send exactly as it applies to a real one. Nothing about an experiment exempts it from metering or from the audit trail.

A separate workspace, if you can have one, is closer to a sandbox than anything this page can offer — the isolation between workspaces is enforced at three independent layers, not by convention.

## Questions

### Is there a base URL I can point a test client at?

No separate host is published. The public documentation surface is safe to call directly because it cannot change anything; there is no second environment documented in this corpus and this page will not invent one.

### Can I generate load against the public API to test my client's retries?

Test your retry logic against a local stub instead. The rate limit exists to stop a loop, and deliberately tripping it teaches you what a 429 looks like at the cost of being the loop it was written for.

### How do I test that my code handles a `foundation` capability correctly?

Query the real corpus: `GET /api/public/docs/list?status=foundation` returns pages whose subject is groundwork rather than a usable feature. Those are real records, so your handling is tested against the vocabulary as published.

## Related

- [Verifying a Connect webhook signature](https://connectbyjbrh.com/developers/webhook-signatures/)
- [Building an agent on Connect safely](https://connectbyjbrh.com/developers/agent-safety/)
- [What Connect may do](https://connectbyjbrh.com/docs/autonomy/)
- [Designing a resilient client](https://connectbyjbrh.com/developers/errors-and-retries/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)

## What this page is based on

- `backend/app/public_developer_api.py` — the read-only surface and its limits
- `backend/app/mcp_server.py` — tool annotations and the rate limit
- `docs-source/facts.py` — CAPABILITY_STATUS, FORBIDDEN_CLAIMS
