# Test mode

URL: https://submitator.com/docs/test-mode

> A free sbm_test_ key runs the API on test data. Listings move on their own in about 15 minutes, and nothing is submitted or spent.

A key that starts with `sbm_test_` runs every call in test mode: the same routes, objects and errors as live mode, on test data of their own. Nothing reaches a directory and nothing is spent. Test keys are free, so you can build and check an integration before your first purchase.

Share links are the one exception: a client page needs a live project, so creating a link with a test key answers 422 `validation_failed`, and reading or turning one off answers 404.

## Get a test key

1. Create your agency at [app.submitator.com/agency/start](https://app.submitator.com/agency/start?next=/agency/developers). Test mode needs no payment.
2. In the console, open Developers, choose the TEST tab and create a key. It is shown once.
3. Call `GET /ping` with it. The response shows `livemode` `false`.

```bash
curl https://api.submitator.com/v1/ping \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
```

```js
const res = await fetch("https://api.submitator.com/v1/ping", {
  headers: { Authorization: `Bearer ${process.env.SUBMITATOR_API_KEY}` },
});
console.log(await res.json());
```

```python
import os
import requests

res = requests.get(
    "https://api.submitator.com/v1/ping",
    headers={"Authorization": f"Bearer {os.environ['SUBMITATOR_API_KEY']}"},
)
print(res.json())
```

Response 200:

```json
{
  "object": "ping",
  "ok": true,
  "livemode": false,
  "account": { "name": "Northlight Studio", "state": "setup" },
  "key": { "id": "key_3Rw8Kx2Lq9Vt5Nb1Zm7HcW", "permissions": "full" }
}
```

`state` is `setup` until your first purchase and `active` after it. Your test key keeps working either way.

## What works the same

- Every route takes the same parameters and returns the same objects, with `livemode` `false`.
- Events are recorded as in live mode. Webhooks reach the endpoints you add with a test key, signed the same way.
- Errors use the same codes, plus 422 `quota_exceeded` for the limits below.

Test data and live data never meet. A live key cannot read a test project, and a test key cannot read a live one: the id returns 404 `not_found`.

## What is simulated

| What | In test mode |
| --- | --- |
| Listings | Each listing moves to `in_progress` after 1 to 5 minutes, to `submitted` after 3 to 8 minutes, and to its outcome after 6 to 15 minutes. |
| Outcomes | 80% of listings go `live`, 12% end `not_accepted` and 8% stay `submitted`. The outcome depends only on the listing's id, so a listing always ends the same way. |
| Autofill and Domain Rating | Simulated. We do not read your client's site. |
| Images | Image URLs are not downloaded, and uploaded files are checked but not stored. An image's `url` is a placeholder. |
| Badge feed | Feed URLs have the right shape but return 404. |
| Reports | A sample PDF or XLSX, marked TEST MODE. |
| Launches | 100 test launches to start. They are not the launches you buy. |

A project launched in test mode completes in about 15 minutes. Apart from webhook deliveries, nothing leaves our servers in test mode.

[Logins](https://submitator.com/docs/api/logins) are placeholders: about 45% of test listings get one, on the project's contact email or on an address at `login.test`, with a password made from the project's id. A login at `login.test` comes with sample emails whose links point there, and nothing signs up anywhere. A report with logins is the same sample report plus sample logins on `.test` addresses: a Directory logins section in the PDF, a Logins sheet in the XLSX.

## Inputs that fail on purpose

| Input | What happens |
| --- | --- |
| A site whose host starts with `nobadge.`, such as `https://nobadge.ledgerly.test` | The badge never passes: `POST /projects/{project_id}/badge/verify` returns 422 `badge_not_found`. |
| An image URL that ends in `/fail.png` | The import returns 422 `image_unreachable`, as an image that cannot be downloaded does in live mode. |

Sites on `.test` and `.example` are accepted in test mode. Live mode rejects them with `not_public_url`, so use your client's real site there.

## Move a listing yourself

To skip the wait, `POST /test_helpers/listings/{listing_id}/advance` moves a test listing 1 step along the usual path, or to the status you name in `to`. The move must be one the listing could make in live mode, and its event and webhook fire as they would there.

```bash
curl https://api.submitator.com/v1/test_helpers/listings/lst_7Hk2Qn4Rt9Vx1Bm6Cz3LdK/advance \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "live"}'
```

```js
const res = await fetch(
  "https://api.submitator.com/v1/test_helpers/listings/lst_7Hk2Qn4Rt9Vx1Bm6Cz3LdK/advance",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUBMITATOR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ to: "live" }),
  },
);
console.log(await res.json());
```

```python
import os
import requests

res = requests.post(
    "https://api.submitator.com/v1/test_helpers/listings/lst_7Hk2Qn4Rt9Vx1Bm6Cz3LdK/advance",
    headers={"Authorization": f"Bearer {os.environ['SUBMITATOR_API_KEY']}"},
    json={"to": "live"},
)
print(res.json())
```

Response 200 (some fields left out):

```json
{
  "object": "listing",
  "id": "lst_7Hk2Qn4Rt9Vx1Bm6Cz3LdK",
  "livemode": false,
  "status": "live",
  "live_url": "https://betalist.com/startups/ledgerly",
  "live_at": "2026-10-08T14:00:00Z"
}
```

The example moves a listing from `submitted` to `live`. A listing in `pending` goes there in 3 calls without `to`: `in_progress`, `submitted`, then `live`.

`POST /test_helpers/account/launches` sets your test launches to any number from 0 to 1,000. Set 0 to see how your code handles 402 `no_launches_left`.

Both helpers work with test keys only. A live key gets 403 `test_mode_only`.

## Limits before your first purchase

| What | Before your first purchase | After it |
| --- | --- | --- |
| Directories a launch picks from | The top 20 of the catalog | The full catalog |
| Test projects | 25 at a time, and 100 new ones a day | No test quota |
| Webhook endpoints in test mode | 3 | No test quota |
| Requests per UTC hour | 600 | 3,600 |

Above a quota, the call returns 422 `quota_exceeded` and changes nothing. Delete test data you no longer need, or wait for the next day.

## Test data

Test data is deleted after 30 days without activity. To start over at any time, delete all of it in the console, under Developers.

## What can go wrong

| Code | Status | Fix |
| --- | --- | --- |
| [`quota_exceeded`](https://submitator.com/docs/errors#quota_exceeded) | 422 | A test quota is used up. Delete test data, wait for the next day, or buy launches. |
| [`test_mode_only`](https://submitator.com/docs/errors#test_mode_only) | 403 | A live key called a test helper. Send a `sbm_test_` key. |
| [`not_found`](https://submitator.com/docs/errors#not_found) | 404 | The id belongs to the other mode. Check which key you sent. |
| [`badge_not_found`](https://submitator.com/docs/errors#badge_not_found) | 422 | The site's host starts with `nobadge.`, which never passes in test mode. |

## Next steps

- [Quickstart](https://submitator.com/docs/quickstart): from a test key to a signed webhook, step by step.
- [Signatures](https://submitator.com/docs/webhooks/signatures): check that a delivery came from us.
- [How it works](https://submitator.com/docs/how-it-works): every listing status and when it changes.
- [Go-live checklist](https://submitator.com/docs/guides/go-live-checklist): from test mode to your first live launch.
