# Opportunities

Create, maintain, close, and review customer and supplier opportunities.

Canonical HTML: https://developer.shelfcycle.com/guides/opportunities

Create, maintain, close, and review customer- and supplier-side opportunities from the workflows where selling work happens.

## Endpoints

| Endpoint | Use it for |
| --- | --- |
| `GET /opportunities` | Find visible opportunities by text, status, relationship, owner, product, or created date. |
| `POST /opportunities` | Create an opportunity for one existing customer or supplier. |
| `GET /opportunities/{id}` | Read exact opportunity details, structured metadata, products, and recent activity. |
| `PATCH /opportunities/{id}` | Update an open opportunity and move it between lead and in-progress stages. |
| `POST /opportunities/{id}/close` | Close an in-progress opportunity as won or lost. |
| `PUT /opportunities/{id}/meta` | Create or replace one structured detail by key. |

Reads require `opportunities:read`. Writes require `opportunities:write`. `GET /me` reports `effectiveCapabilities.opportunities` for view, create, update, close, and metadata access, plus the matching `writeReadiness.opportunities` checks.

Opportunity access follows the acting user's current customer, supplier, and product visibility. This keeps pipeline work aligned with the relationships each person can already use in ShelfCycle.

## Find opportunities

Use `GET /opportunities` for focused pipeline review or to select an exact opportunity before reading or updating it.

```bash
curl "$SHELFCYCLE_API_BASE_URL/opportunities?status=LEAD,IN_PROGRESS&assignedToId=user-id&sortDirection=desc&limit=50" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Opportunity follow-up"
```

Supported filters include:

- `query` for matching text;
- `status` for one or more of `LEAD`, `IN_PROGRESS`, or `CLOSED`;
- exact `customerId`, `supplierId`, or `productId`;
- `assignedToId` for one or more owners;
- `createdFrom` and `createdTo` ISO timestamps; and
- `sortDirection` as `asc` or `desc` for `updatedAt` ordering.

Responses include the complete visible `meta.total`, the page `meta.limit`, and a signed `meta.nextCursor` when more results are available. Continue with the cursor by itself so the original filters and ordering stay together.

## Read exact context

After selecting an opportunity, fetch its exact detail before deciding on an update.

```bash
curl "$SHELFCYCLE_API_BASE_URL/opportunities/opportunity-id" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

Selected fields from one response:

```json
{
  "data": {
    "type": "opportunity",
    "id": "opportunity-id",
    "title": "Northstar solvent program",
    "description": "Qualify annual demand and delivery requirements.",
    "status": "IN_PROGRESS",
    "successStatus": null,
    "estimatedValue": {
      "value": "125000.00",
      "kind": "estimate",
      "currencyCode": "USD"
    },
    "assignedTo": { "id": "user-id", "name": "Jordan Buyer" },
    "relationships": [
      {
        "customer": {
          "id": "customer-id",
          "name": "Northstar Chemical",
          "pipelineStatus": "PROSPECT"
        }
      }
    ],
    "products": [{ "id": "product-id", "code": "ACETONE-PAIL" }],
    "meta": [
      {
        "key": "next_step",
        "value": "Confirm annual volume",
        "updatedAt": "2026-08-08T14:00:00.000Z"
      }
    ],
    "timeline": [
      { "eventType": "STATUS_CHANGED", "createdAt": "2026-08-08T13:30:00.000Z" }
    ],
    "timelineCoverage": { "status": "complete", "limit": 100 },
    "createdAt": "2026-08-08T13:00:00.000Z",
    "updatedAt": "2026-08-08T14:00:00.000Z"
  }
}
```

Estimated values are exact decimal strings with an explicit three-letter currency. An opportunity without an entered estimate returns `estimatedValue: null`.

## Create an opportunity

Choose exactly one existing customer or supplier. Add an owner, products, description, and estimated value when they are known.

Call the same route with `dryRun=true` first when your workflow wants a readiness check:

```bash
curl "$SHELFCYCLE_API_BASE_URL/opportunities?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-intake-4182:create-opportunity" \
  -d '{
    "title": "Northstar solvent program",
    "description": "Qualify annual demand and delivery requirements.",
    "customerId": "customer-id",
    "estimatedValue": 125000,
    "assignedToId": "user-id",
    "productIds": ["product-id"]
  }'
```

Review `data.status`, `checks`, and `verification`. When the response is ready, remove `dryRun=true` and execute with the same request body and stable `Idempotency-Key`.

The saved response includes the complete opportunity detail and `idempotencyStatus`. Reusing the same key with the same body returns the original opportunity; reusing it for a different body returns `409 idempotency_key_reused`.

## Update an open opportunity

Fetch the latest detail and send its `updatedAt` value in `If-Match`. PATCH accepts title, description, estimated value, owner, and a status of `LEAD` or `IN_PROGRESS`.

```bash
curl "$SHELFCYCLE_API_BASE_URL/opportunities/opportunity-id?dryRun=true" \
  -X PATCH \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "If-Match: 2026-08-08T14:00:00.000Z" \
  -d '{
    "status": "IN_PROGRESS",
    "estimatedValue": 140000,
    "assignedToId": "user-id"
  }'
```

After a ready response, execute the same request without `dryRun=true`. If another update landed first, `409 stale_record` means fetch the detail again, rebuild from the latest values, and retry once with the new `updatedAt`.

## Close as won or lost

Move an opportunity to `IN_PROGRESS` before closing it. Then record the outcome and optional closing notes.

```bash
curl "$SHELFCYCLE_API_BASE_URL/opportunities/opportunity-id/close?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "successStatus": "WON",
    "closingNotes": "Approved for the initial annual program."
  }'
```

Execute without `dryRun=true` after the readiness response is ready. The returned opportunity has `status: "CLOSED"`, `successStatus: "WON"` or `"LOST"`, and a populated `closedAt`.

## Maintain structured details

Use the metadata route to create or replace one organization-defined opportunity detail. The same key updates in place.

```bash
curl "$SHELFCYCLE_API_BASE_URL/opportunities/opportunity-id/meta" \
  -X PUT \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "next_step",
    "value": "Confirm annual volume"
  }'
```

Add `dryRun=true` when the workflow wants to confirm target visibility and write readiness before saving.

## Review the pipeline

Use `GET /reports/pipeline-summary` for complete visible counts and entered estimate totals grouped by status, owner, or customer and supplier activity.

```bash
curl "$SHELFCYCLE_API_BASE_URL/reports/pipeline-summary?groupBy=status&status=LEAD,IN_PROGRESS" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

The report includes opportunity counts, estimate totals and currency, missing-value counts, won and lost counts, oldest open dates, and filtered-population totals. See the [Reports guide](/guides/reports) for grouping and filter details.

> **Guardrail**: Create against one selected customer or supplier, fetch the latest detail before changes, and use the dedicated close route to record won or lost outcomes.
