> ## Documentation Index
> Fetch the complete documentation index at: https://docs.theguarantors.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox Guide

> How the sandbox differs from live, the properties and units pre-seeded for testing, and how to trigger webhook events yourself.

The sandbox is a dedicated environment for building and testing your integration. It is a fully isolated, near mirror of our production system, so you can send requests, receive responses and handle webhooks without the risk of affecting live customer data or critical production workflows.

This guide covers only what is specific to the sandbox: how it differs from live, the properties, units and coverage levels pre-seeded for you, and how to trigger webhook events yourself. Everything else works the same in both environments. See [Authentication](https://docs.theguarantors.com/authentication.md), [Application Lifecycle](https://docs.theguarantors.com/application-lifecycle.md), [Webhooks](https://docs.theguarantors.com/webhooks.md) and [Errors](https://docs.theguarantors.com/errors.md) for how the API works, and [Amending an Invite](https://docs.theguarantors.com/amending-an-invite.md) and [Canceling an Application](https://docs.theguarantors.com/canceling-an-application.md) for step-by-step guides.

## How the Sandbox Differs from Live

Send sandbox requests to `https://api.sandbox.theguarantors.com` with a sandbox API key. Live requests go to `https://api.theguarantors.com`, and keys only work in the environment they were issued for. See [Environments](https://docs.theguarantors.com/authentication.md#environments).

Beyond that, the sandbox differs from live in three ways:

* **Webhook events are triggered by you.** Within the sandbox, we provide developers with complete control over the timing of webhook event triggers through the `/v1/webhooks/trigger` endpoint. This endpoint allows developers to manually and programmatically simulate the occurrence of various webhook events, thereby facilitating robust and repeatable testing of their event-handling logic. See [Triggering Events in Sandbox](#triggering-events-in-sandbox).
* **Invite links are non-functional.** Invite links returned by the sandbox `POST /v1/invites` endpoint will not send real emails or allow tenants to complete an application. They are intended solely for testing purposes, to allow developers to validate their integration before going live.
* **Bond documents are a sample.** Bond `document_link` URLs returned in the sandbox always return the same sample PDF, whichever bond they belong to.

## Pre-Seeded Sandbox Entities

The Sandbox environment comes preset with Properties, Units and Coverage Levels that developers can use to start testing invites and applications immediately.

| Description | Sandbox Property ID |
| - | - |
| Active Property 1 | `prop_ve2x7i0j6psdizy7` |
| Active Property 2 | `prop_yaj2bwpvzb1mkhvi` |
| Inactive Property (will result in error) | `prop_umzmn6q7b26l3xnu` |

On the pre-seeded properties, each `coverage_level` resolves to the following coverage:

| Coverage Level | Description |
| - | - |
| `low` | 3 months Lease Guarantee + 1 month Security Deposit Replacement |
| `medium` | 6 months Lease Guarantee + 1 month Security Deposit Replacement |
| `high` | 12 months Lease Guarantee + 1 month Security Deposit Replacement |

If an invite includes a `free_rent_amount` or `prepaid_rent_amount`, its Lease Guarantee is reduced by the months of rent they cover, `(free_rent_amount + prepaid_rent_amount) / monthly_rent`, down to a minimum of 0. For example, a `medium` invite with one month of free rent gets 5 months of Lease Guarantee.

| Sandbox Unit ID | Property | Description |
| - | - | - |
| `unit_raejxs6hgymxo7` | Active Property 1 | 4104 Valley Street, Apt 4D, Camden, NJ 08104 |
| `unit_u7yr0vt3r9yk0c` | Active Property 1 | 4104 Valley Street, Apt 2B, Camden, NJ 08104 |
| `unit_o77c9xr084tu53` | Active Property 2 | 4637 Haven St, Apt 5C, Rapid City, SD 57703 |
| `unit_ty9ngmafvxshtz` | Active Property 2 | 4637 Haven St, Apt 1M, Rapid City, SD 57703 |

These IDs can be used as inputs to the sandbox POST `/v1/invites` and PATCH `/v1/invites/:id` endpoints.

## Triggering Events in Sandbox

In the sandbox environment, you have complete control over webhook event timing through the `/v1/webhooks/trigger` endpoint. This allows you to manually trigger webhook events for testing purposes. See [Webhooks](https://docs.theguarantors.com/webhooks.md) for the payloads and how they're delivered.

To trigger a webhook event in sandbox, send a POST request to:

```
POST /v1/webhooks/trigger
```

See [Trigger a sandbox webhook](https://docs.theguarantors.com/application-api/webhooks/trigger-a-sandbox-webhook.md) in the API reference for the full request and response schemas.

### Request Body

The request body takes the `event_type` to trigger and the ID of the entity it applies to. Which ID that is depends on the event type, as listed in [Available Event Types for Sandbox](#available-event-types-for-sandbox) below. For example, `application.status.started` creates the application, so it takes the `invite_id` of the invite to convert:

```json theme={null}
{
  "event_type": "application.status.started",
  "invite_id": "inv_xyz789"
}
```

### Available Event Types for Sandbox

* `applicant.status.started` (requires `invite_recipient_id`)
* `applicant.status.submitted` (requires `invite_recipient_id`)
* `application.status.started` (requires `invite_id`)
* `application.status.submitted` (requires `application_id`)
* `application.status.pending` (requires `application_id`)
* `application.status.approved` (requires `application_id`)
* `application.status.denied` (requires `application_id`)
* `bond.status.issued` (requires `application_id`)
* `bond.status.canceled` (requires `bond_id`)

For applicant events, the trigger endpoint enforces the applicant state machine (`not_started` → `started` → `submitted`) and cascades to the application where appropriate:

* `applicant.status.started` for a recipient whose invite has no application yet creates the application and emits `application.status.started` in addition to `applicant.status.started` for that recipient. The other recipients on the invite remain `not_started`.
* `applicant.status.started` for a recipient whose application already exists moves only that applicant to `started`.
* `applicant.status.submitted` moves that applicant to `submitted`. If it is the last applicant on the application still outstanding, the application also moves to `submitted` and an `application.status.submitted` event is emitted.
* The application-level events emit applicant events too: `application.status.started` (with an `invite_id`) emits an `applicant.status.started` event for every recipient on the invite, and `application.status.submitted` (with an `application_id`) emits an `applicant.status.submitted` event for every applicant that had not already submitted. You can use them as shortcuts instead of triggering an event for each applicant, but the two approaches can't be mixed freely: `application.status.started` returns a 409 once an application exists for the invite, and `application.status.submitted` requires every applicant to have at least started, so it returns a 400 if any recipient is still `not_started`.

`application.status.canceled` cannot be sent through the trigger endpoint. To test it, cancel the application with `POST /v1/applications/:id/cancel`, which sends the webhook to your subscribed endpoint. See [Canceling an Application](https://docs.theguarantors.com/canceling-an-application.md).

To cancel an issued bond, trigger `bond.status.canceled` through `POST /v1/webhooks/trigger` with the bond's `bond_id`, which is returned when you trigger `bond.status.issued` and by `GET /v1/applications/:application_id/bonds`. This also cancels the bond's application and sends `application.status.canceled` along with `bond.status.canceled`:

```json theme={null}
{
  "event_type": "bond.status.canceled",
  "bond_id": "bond_def456"
}
```

Invite and invite recipient events cannot be sent through the trigger endpoint either. To test them, cancel an invite with `POST /v1/invites/:id/cancel` or amend it with the endpoints in [Amending an Invite](https://docs.theguarantors.com/amending-an-invite.md), which send the webhooks to your subscribed endpoint.

### Response

The endpoint returns the webhook payload that would be sent to subscribers:

```json theme={null}
{
  "event_id": "1761580894123456",
  "event_type": "application.status.submitted",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    "application": {
      // ... application data
    }
  }
}
```

## Testing Workflow

1. **Create an invite** using the `POST /v1/invites` endpoint
2. **Trigger application started** event using the `POST /v1/webhooks/trigger` endpoint with the invite ID (or **trigger applicant started** for each invite recipient, the first one creates the application)
3. **Trigger application submitted** event using the `POST /v1/webhooks/trigger` endpoint with the application ID (or **trigger applicant submitted** for each invite recipient, the last one moves the application to `submitted`)
4. **Trigger application approved** event using the `POST /v1/webhooks/trigger` endpoint with the application ID
5. **Trigger bond issued** event using the `POST /v1/webhooks/trigger` endpoint with the application ID

This allows you to simulate the complete application lifecycle and test your webhook handling.

To test cancelation, create an invite and trigger application started as above, then cancel the application with `POST /v1/applications/:id/cancel` at any step before triggering bond issued.

To test a denial, follow steps 1 through 3 above, then trigger `application.status.denied` with the application ID instead of application approved. A denied application can't be issued a bond, so triggering bond issued for it returns a 400.
