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

# Capture and qualify leads

> Capture an opportunity, create or associate a deal, and manage the opportunity's lifecycle.

Use an opportunity to capture interest before you have a qualified sales deal. When the lead is ready for your sales process, create a deal from the opportunity or associate it with an existing deal.

An opportunity does not reserve a unit or create an order. Use the [booking flow](https://docs.stora.co/2025-09/guides/booking-flow.md) when the customer is ready to rent.

## Before you start

Use a token with `public.opportunity:read` and `public.opportunity:write`. To create a deal, [associate an opportunity with a deal](https://docs.stora.co/2025-09/api-reference/opportunities/associate-an-opportunity-with-a-deal.md), or [disassociate it](https://docs.stora.co/2025-09/api-reference/opportunities/disassociate-an-opportunity-from-its-deal.md), also grant `public.deal:write`. Listing deal stages requires `public.deal_stage:read`.

You need an existing contact ID for the example below. To include a storage preference, also choose a site and a unit type from that site. All records must belong to the connected operator. Add the appropriate read scopes if you look up or expand those resources.

To run this guide's `curl` examples, set `STORA_ACCESS_TOKEN` in your shell to your access token. `STORA_ACCESS_TOKEN` is an example variable name, not an API requirement. Replace the placeholder IDs with your records' IDs.

## Capture the opportunity

Generate an idempotency key for the lead:

```bash theme={null}
OPPORTUNITY_KEY="$(uuidgen)"
```

[Create an opportunity](https://docs.stora.co/2025-09/api-reference/opportunities/create-an-opportunity.md) with the contact and the storage preference:

```bash theme={null}
curl --request POST \
	'https://public-api.stora.co/2025-09/opportunities' \
	--header "Authorization: Bearer $STORA_ACCESS_TOKEN" \
	--header 'Content-Type: application/json' \
	--header "Idempotency-Key: $OPPORTUNITY_KEY" \
	--data '{
		"contact": {"id": "con_REPLACE"},
		"site": {"id": "site_REPLACE"},
		"line_items": [
			{"type": "unit_type", "item": {"id": "utype_REPLACE"}}
		],
		"metadata": {"external_lead_id": "lead_123"}
	}'
```

The response contains `opportunity.id`, an `open` status, and a `public_api` source. Save the ID with the lead in your system and set `OPPORTUNITY_ID` to that returned ID for later requests.

If the site or storage preference is not known yet, omit `site` and `line_items`. To create a new contact, omit the contact ID and supply `contact.email`. This creates a new contact rather than looking up an existing contact by email. See the [create request](https://docs.stora.co/2025-09/api-reference/opportunities/create-an-opportunity.md) for the supported contact fields. When you supply a contact ID alongside other contact fields, those fields update that contact.

## Update the customer's preference

While the opportunity is not associated with a deal, [update it](https://docs.stora.co/2025-09/api-reference/opportunities/update-an-opportunity.md) as you qualify the lead.

* Keep the original contact. You cannot change the contact on an existing opportunity.
* Include at most one unit-type line item and one protection line item. A site is required for either type.
* Choose a unit type and [site-scoped protection level](https://docs.stora.co/2025-09/guides/site-scoped-protection-levels.md) from the opportunity's site.
* To remove all line items, send `"line_items": []`. Omitting `line_items` leaves them unchanged.
* If you change or clear the site, remove or replace any line items that no longer belong to it in the same request.

Use metadata for your own identifiers. Source, creator, and lifecycle fields are read-only.

## Create a deal for a qualified lead

[List deal stages](https://docs.stora.co/2025-09/api-reference/deals-stages/list-all-stages.md) and choose an active stage for the new deal:

```bash theme={null}
curl --request GET \
	'https://public-api.stora.co/2025-09/deals/stages?limit=100' \
	--header "Authorization: Bearer $STORA_ACCESS_TOKEN"
```

Use that stage's ID to [create a deal from the opportunity](https://docs.stora.co/2025-09/api-reference/opportunities/create-a-deal-from-an-opportunity.md). Generate a different idempotency key for this action:

```bash theme={null}
DEAL_KEY="$(uuidgen)"

curl --request POST \
	"https://public-api.stora.co/2025-09/opportunities/$OPPORTUNITY_ID/deal" \
	--header "Authorization: Bearer $STORA_ACCESS_TOKEN" \
	--header 'Content-Type: application/json' \
	--header "Idempotency-Key: $DEAL_KEY" \
	--data '{
		"stage": {"id": "dstg_REPLACE"},
		"name": "Storage enquiry from our CRM"
	}'
```

The response contains the new `deal`. Stora copies the opportunity's contact, site, and line items, then associates the opportunity with that deal. The opportunity's status becomes `associated`. An archived opportunity is unarchived when it is converted.

> [!WARNING]
>
> Conversion fails while the opportunity is associated or still has a previous conversion recorded. It also fails if its unit type or protection no longer belongs to its site. Fix invalid preferences before retrying. To change an existing association, follow the disassociation procedure below.

## Use an existing deal instead

If your CRM already has the deal in Stora, [associate the opportunity](https://docs.stora.co/2025-09/api-reference/opportunities/associate-an-opportunity-with-a-deal.md) instead of creating another deal.

Send `POST /opportunities/{opportunity_id}/associate_deal` with a `deal` object containing the existing deal's `id`. Use an idempotency key for this action too.

The deal and opportunity must belong to the same contact. Several opportunities can refer to one deal, but each opportunity can refer to only one deal. Association does not change the deal or copy the opportunity's preferences into it.

After either creation or association, you can update only the opportunity's metadata. Update the deal itself for subsequent sales work.

## Archive or remove the association

For a lead you no longer need to work, [archive the opportunity](https://docs.stora.co/2025-09/api-reference/opportunities/archive-an-opportunity.md). You can archive only an open, unassociated opportunity. [Unarchive it](https://docs.stora.co/2025-09/api-reference/opportunities/unarchive-an-opportunity.md) if interest resumes.

To remove a deal association, use [Disassociate an Opportunity from its Deal](https://docs.stora.co/2025-09/api-reference/opportunities/disassociate-an-opportunity-from-its-deal.md). The deal is unchanged. Association unarchives an archived opportunity. Disassociation returns the opportunity to `open`, or to `archived` if it was archived before association.

Disassociation also clears the previous conversion record, so you can associate the opportunity with another deal or convert it again. The original deal remains. Create another deal only if you intend a separate sales record.

You cannot delete an associated opportunity. Remove its association first if you need to [delete it](https://docs.stora.co/2025-09/api-reference/opportunities/delete-an-opportunity.md).

Handle `409` responses as lifecycle conflicts. Retrieve the current opportunity before choosing another action. For request timeouts, retry the original action with the same idempotency key rather than creating a duplicate lead or deal.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.