# Test mode

A test key lets you, or an AI agent writing your integration, call the real API and get real-shaped answers from sample data. Get a call wrong, write the same entry twice, send garbage: nothing reaches a real Space, nobody gets an email, and you are not charged.

1. **Make a test key** For your Space's information, open [/dash/integrations/api-tokens](https://bynefit.com/dash/integrations/api-tokens), choose *Create key*, then *Test*. For the public directory, do the same at [/dash/developers/keys](https://bynefit.com/dash/developers/keys). A test key is ready straight away, with any permission you tick, including changing your directory. No review.
2. **Keep it in an environment variable** Put it in `BYNEFIT_API_KEY`, the same place your live key will go later, so switching is one line.
3. **Call the API as normal** Same addresses, same headers, same answers. Only the data is pretend.

**cURL**

```bash
curl -i "https://bynefit.com/dash/api/v1/events?limit=2" \
  -H "Authorization: Bearer bapi_tk_test_YOUR_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://bynefit.com/dash/api/v1/events?limit=2", {
  headers: { Authorization: "Bearer " + process.env.BYNEFIT_API_KEY },
});
console.log(res.headers.get("X-Bynefit-Mode"));
const { data, livemode } = await res.json();
console.log(livemode, data[0].id);
```

**Python**

```python
import os
import requests

res = requests.get(
    "https://bynefit.com/dash/api/v1/events?limit=2",
    headers={"Authorization": f"Bearer {os.environ['BYNEFIT_API_KEY']}"},
)
print(res.headers["X-Bynefit-Mode"], res.json()["livemode"])
```

**200 OK (application/json)**

```json
HTTP/1.1 200 OK
X-Bynefit-Mode: test
X-RateLimit-Limit: 500

{
    "data": [
        {
            "id": "evt_test_0001",
            "type": "event",
            "event_type": "member.joined",
            "actor": {
                "id": "usr_test_0001"
            },
            "subject": {
                "id": "mem_test_0001",
                "type": "membership"
            },
            "created_at": "2026-10-01T06:00:00+00:00"
        },
        {
            "id": "evt_test_0002",
            "type": "event",
            "event_type": "form.submitted",
            "actor": {
                "id": "usr_test_0002"
            },
            "subject": {
                "id": "sub_test_0102",
                "type": "form_submission"
            },
            "created_at": "2026-10-01T00:00:00+00:00"
        }
    ],
    "next_cursor": "MjAyNi0xMC0wMSAwMDowMDowMHwy",
    "has_more": true,
    "livemode": false
}
```

## How to tell a test key from a live one

|  | Test | Live |
| --- | --- | --- |
| Space key starts with | `bapi_tk_test_` | `bapi_tk_` |
| Developer key starts with | `bapi_pk_test_` | `bapi_pk_` |
| Every response carries the header | `X-Bynefit-Mode: test` | `X-Bynefit-Mode: live` |
| Every JSON body carries | `"livemode": false` | No extra field |
| Sample ids look like | `usr_test_0001`, `evt_test_0007` | Long random ids |

Have your code check the header before it does anything it cannot undo, and stop if it ever sees `test` where it expected `live`, or the other way round.

## What the sample data holds

The same sample data comes back every time, so a test you write today still passes next month. Filters, paging and errors work exactly as they do live.

| Permission | Address | What you get |
| --- | --- | --- |
| Your Space | `GET /dash/api/v1/teams/current` | One sample Space, with 11 active members. |
| Activity | `GET /dash/api/v1/events` | 30 events: people joining, forms sent in, donations and tasks. |
| Tasks | `GET /dash/api/v1/tasks` | 15 tasks across every status and priority. |
| Member profiles | `GET /dash/api/v1/users` | 12 members named Sample Member 1, 2, 3, with addresses at example.test. |
| Forms and answers | `GET /dash/api/v1/forms` | 3 forms, each with 4 answers. |
| Payments | `GET /dash/api/v1/payments` | 10 donations and membership payments in USD. |
| Public directory | `GET /dash/api/v1/resources` | 12 made-up organisations, one per category. |

None of it is about real people. Names are numbered, email addresses end in `example.test`, phone numbers use the `555` range, and websites end in `example.org`. A test key never returns your Space's records, not even its name. The one thing it looks up is a partner's list of registered event names, so a partner-events dry run can tell you which events it would accept.

## What gets saved, and for how long

Writes are checked exactly like a live call: the same required fields, the same errors, the same status codes and the same response. What happens next is different.

| Call | In test mode |
| --- | --- |
| Add, import, edit or delete directory entries  
`/dash/api/resources` | Saved in a private sandbox that only this key can see. Read them back with `GET /dash/api/resources`; new entries also show up in that key's `GET /dash/api/v1/resources`. |
| Suggest a directory entry  
`/dash/api/resources/submit` | Saved in the sandbox. No moderator sees it and it is never published. |
| Send partner events  
`POST /dash/api/v1/beacons` | Checked against your registered events and answered with `"test": true`. Nothing is stored. |

- Each test key keeps up to 200 saved records. Past that, the oldest goes first.
- A saved record lasts 24 hours, and can hold up to 8 KB. A bigger one is refused with `413`.
- Revoking or rotating a test key deletes everything it saved.
- Nothing a test key does sends an email or a push notification, fires one of your webhooks, takes a payment, or counts toward your plan.

**cURL**

```bash
curl -X POST https://bynefit.com/dash/api/resources \
  -H "Authorization: Bearer bapi_tk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Sample Food Pantry","category":"food","website":"https://pantry.example.org"}'

curl "https://bynefit.com/dash/api/resources" \
  -H "Authorization: Bearer bapi_tk_test_YOUR_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://bynefit.com/dash/api/resources", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.BYNEFIT_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "Sample Food Pantry", category: "food" }),
});
const { id, livemode } = await res.json();
console.log(res.status, id, livemode);
```

## What a test key cannot call

Some things have no sample data. A test key gets a clear refusal there, never real data:

**403 Forbidden (application/json)**

```json
{
    "error": "test_mode_unsupported",
    "message": "Test keys only work with sample data, and this endpoint has none. Use a live key here.",
    "livemode": false,
    "docs_url": "https://bynefit.com/docs/test-mode"
}
```

| Refused | Why |
| --- | --- |
| `GET /dash/api/v1/beacons/summary` | Partner event totals are real numbers about your product. |
| The embed widget | The widget shows your real Space to visitors. |
| Checkout links | A checkout takes real money. |

## Limits

- Each test key has its own hourly allowance, separate from your live keys. The `X-RateLimit-*` headers show what is left, and a `429` carries `Retry-After`, just like live.
- A Space can have up to 5 test keys at once, and each person up to 5 developer test keys. They do not count toward your live keys.
- Test calls are free and are not shown in your usage.

## Testing webhooks

Webhooks have their own test. On [/dash/integrations/webhooks](https://bynefit.com/dash/integrations/webhooks), choose *Send test event* on an endpoint. Bynefit posts a sample event to that address, signed with its secret exactly like a real delivery, and shows you the status code your server answered and how long it took. The details are on the [Webhooks](https://bynefit.com/docs/webhooks#test-events) page.

## Moving to a live key

1. Make a live key on the same page with the permissions your code used. Changing your directory needs a review for live keys; the [Keys and review](https://bynefit.com/docs/keys) page explains it.
2. Swap the value of `BYNEFIT_API_KEY`. Nothing else in your code should change.
3. Throw away any sample ids you saved. They do not exist outside test mode.
4. Make one read call and check the response says `X-Bynefit-Mode: live` before you let anything write.
5. Revoke the test key when you no longer need it.

## What exists and what does not

- [Read community resources](https://bynefit.com/docs/resources-read) - List and filter community resources with a developer key. JSON, cursor paging. (Available)
- [Submit a resource for review](https://bynefit.com/docs/resources-submit) - Propose a new entry. A moderator reviews it before it goes live. (Available)
- [Manage your directory](https://bynefit.com/docs/resources-write) - Add, edit and remove your own entries. Try it straight away with a test key; a live key needs a review first. (By approval)
- [Signed webhooks](https://bynefit.com/docs/webhooks) - Get an HTTPS call when something happens in your Space. Each delivery is signed. (Available)
- [Sign in with Bynefit](https://bynefit.com/docs/oauth) - OpenID Connect with PKCE for your own site or app. (Available)
- [Custom app links for sign-in](https://bynefit.com/docs/oauth) - A native app can return through its own reverse-domain scheme once Bynefit approves that scheme for your partner account. (By approval)
- [Embed widget](https://bynefit.com/docs/widget) - Show your directory on any website with one script tag. (Available)
- [Partner events](https://bynefit.com/docs/partner-events) - Partners send their own product events from a page, a server or an app, and read the totals back. Bynefit turns it on per partner. (By approval)
- [Hosted sites runtime](https://bynefit.com/docs/sites-runtime-api) - Events, resources, media, forms and page views for a hosted site, with no key. (Available)
- [Read and edit hosted site files](https://bynefit.com/docs/manage-site-files) - An agent key lists a Space's sites and reads, replaces and deletes their files, with versions, secret scanning and a daily write ceiling. The key owner approves the permission. (Available)
- [Create and manage forms](https://bynefit.com/docs/manage-forms) - An agent key creates forms for a Space, changes their settings and fields, pauses them and deletes unanswered ones, with a daily write ceiling and a test mode. Registration forms and submissions stay out of reach. The key owner approves the permission. (Available)
- [Engagement types and stages](https://bynefit.com/docs/manage-engagement-types) - A Space defines its own kinds of client work with ordered stages inside the existing statuses. Managers edit them on the Engagements page, and an agent key reads and manages them with a daily write budget and a test mode. The key owner approves the permissions. (Available)
- [Secrets and secure requests for hosted sites](https://bynefit.com/docs/site-secrets) - Store a site's keys encrypted, use them in secure requests and form webhooks, and stop them being published in a page. (Available)
- [Client portal API](https://bynefit.com/docs/client-portal-api) - A signed-in client reads their own engagements, documents and messages; a practice manager runs the pipeline. (Available)
- [Client portal kit for hosted sites](https://bynefit.com/docs/client-portal-kit) - Two HTML elements give a hosted site a client portal and a staff pipeline, drawn from the Space's engagement types, stages and forms, with no code of your own. (Available)
- [Site host API](https://bynefit.com/docs/site-host-api) - A WordPress site reports a connection test, visit counts and daily health with a signed site key. (Available)
- Engagements through an API key or a webhook - Read engagements from your own server, or hear about a change, without a person signed in. (Planned)
- Shop, products and orders API for hosted sites - A documented, key-based way to read and update a hosted site's shop. (Planned)
- [OpenAPI spec](https://bynefit.com/docs/ai-agents) - An OpenAPI 3.1 description of the v1 API, with the error envelope and security schemes, at /docs?format=openapi. (Available)
- [Agent keys](https://bynefit.com/docs/agent-keys) - A key made for an AI agent: read-only to start, short-lived, test mode first, every call logged, and the owner approves anything more. (Available)
- [Webhook test event](https://bynefit.com/docs/webhooks) - One button sends a signed sample event to your URL, so your tool can learn the fields before anything real happens. (Available)
- [Replay a past webhook delivery](https://bynefit.com/docs/webhooks) - A Space admin sends a logged delivery to the endpoint again, freshly signed and marked as a replay. (Available)
- Request id and docs link on every error - Every error body carries an id to quote to support and a link to the page that explains it. (Planned)
- [Test keys and a sandbox](https://bynefit.com/docs/test-mode) - Keys that only touch sample data, ready at once with no review, so a first attempt cannot break anything. (Available)
- [Idempotency-Key on writes](https://bynefit.com/docs/authentication) - Retry a v1 write without doing it twice: the first answer is replayed for 24 hours, per key. (Available)
- [Status page](https://bynefit.com/docs/status) - Live state, 90-day uptime and incident history for the API, sign-in, webhook delivery, the widget, hosted sites and email. Atom and JSON feeds. (Available)

---

Source: https://bynefit.com/docs/test-mode
Checked against the live API on October 10, 2026.
