URL: /revenue/reviews-references-referrals · Id: `page:/revenue/reviews-references-referrals`

# Reviews, references and referrals: one ask at the right moment

> Reviews, references and referrals: one ask at a time, after a delivered outcome, shown only with their OK.

Each [Customer](https://startupbuilder.dev/business/nouns/customer) gets one ask at a time, a review, a reference or a referral, after a delivered outcome.

```text title="Ask"
Ask our happiest customer for a review and a referral. Don’t send.
```

```ts title="SDK"
import { $ } from 'startup-builder'

const { rows: health } = await $.Metric.query({ metric: 'customer-health', shape: 'trend', breakdown: 'customer' })   // Elm Street Rentals: health 92, activated since July, no open ask
await $.Review.publish({ review: 'review_14' }, { mode: 'try' })   // Maple Court’s 5-star review, on your site
// 422 · Maple Court’s review did not publish: they have not said yes to showing it.
// display-consent: Ask once for their OK to show it; it publishes the moment they say yes. · then Message.schedule
await $.Message.schedule({ message: 'message_95', at: '2026-10-21T15:00:00Z' }, { mode: 'try' })   // the referral ask to Elm Street, today: one Message
const { rows } = await $.Metric.query({ metric: 'referral-share', shape: 'trend', window: '28d' })
await $.Referral.invite({ customer: 'customer_31' }, { mode: 'try' })   // Not live yet, gaps #3
return rows
```

```json title="Response · sample"
[
  {
    "window": "28d",
    "value": 0.12,
    "referred": 2,
    "newCustomers": 17
  }
]
```

`2 of 17 new Customers referred in 28 days · 12 % · ■□□□ Too early to tell · n 17`

`Metric.query({"metric":"referral-share","shape":"trend","window":"28d"})` · sample · leaseline.app at 2026-10-19

## What it does
| What it does | Price per unit | Moves | Who does it | Today |
| --- | --- | --- | --- | --- |
| <Op verb="Review.publish" /> shows a Review only with the Customer’s OK; a referral ask is one <Op verb="Message.schedule" /> until `Referral.invite` (Not live yet, gaps #3) lands. | $0.04 per ask (Worker cost) | [Referred Customers](/revenue/referral-share), [Sent by your other Startups](/revenue/sibling-sourced-share) | [Partnerships](/capabilities/how-your-agents-act#partnerships) | 4 of 5 calls live |

## Set it up
| Check (dry run on a 0-Customer Startup, gaps #8) | Fix | Then | Your time | Wait |
| --- | --- | --- | --- | --- |
| [`use-when`](/code/refusals#check-use-when) (Not live yet, gaps #8) · pass: 29 activated Customers, 11 with a delivered outcome this month | Run the Play Not yet when names, or wait until the volume is there. | <Op verb="Metric.query" /> | 1 min | none |
| [`domain-authenticated`](/code/refusals#check-domain-authenticated) · pass: leaseline.app | Add the 3 DNS records shown; it passes once they resolve, often within an hour. | <Op verb="Domain.authenticate" /> | 0 min | none |
| [`role-unstaffed`](/code/refusals#check-role-unstaffed) (Not live yet, gaps #8) · warn: Partnerships has no person: asks come to you to approve | Staff Partnerships, or leave it: each ask comes to you. | <Op verb="Role.staff" /> | 0 min | none |

## The records
```json title="Maple Court’s review, held for their OK"
{
  "$id": "https://api.sb/leaseline.app/reviews/review_14",
  "$type": "Review",
  "rating": 5,
  "body": "Renewal notices go out on time now.",
  "venue": "site",
  "displayConsent": false,
  "status": "withheld",
  "customer": "customer_14"
}
```

## Calls
| Call | Here | Emits | Status |
| --- | --- | --- | --- |
| <Op verb="Metric.query" /> | Elm Street Rentals: health 92, activated since July, no open ask | `Metric.queried` | live |
| <Op verb="Review.publish" /> | Maple Court’s 5-star review, on your site | `Review.published` | live |
| <Op verb="Message.schedule" /> | the referral ask to Elm Street, today: one Message | `Message.scheduled` | live |
| <Op verb="Metric.query" /> |  | `Metric.queried` | live |
| `Referral.invite` (Not live yet, gaps #3) |  | — | Not live yet, gaps #3 |

## Events
| Event | When it fires | What hears it |
| --- | --- | --- |
| `Order.delivered` | an activated Customer, not at risk, gets an outcome | starts one ask, the advocate ladder |
| `Review.published` | the Customer says yes to showing it | the review on your Pages |

## Refuses when

```json title="The 422 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "Maple Court’s review did not publish: they have not said yes to showing it.",
  "checks": [
    {
      "check": "display-consent",
      "status": "fail",
      "doc": "/code/refusals#check-display-consent",
      "fix": "Ask once for their OK to show it; it publishes the moment they say yes.",
      "then": "Message.schedule"
    }
  ]
}
```


```text title="Ask"
Ask Juniper Homes for a referral too. Don’t send.
```

```ts title="SDK"
import { $ } from 'startup-builder'

return await $.Message.schedule({ message: 'message_97', at: '2026-10-21T15:00:00Z' }, { mode: 'try' })   // the referral ask, while the prepay ask is open
// 422 · The referral ask to Juniper Homes did not send: their prepay ask is open until Nov 3.
// customer-ask-cap: Wait for the open ask to close; Decision.list names the Play that holds them. · then Decision.list
```

```json title="Response · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The referral ask to Juniper Homes did not send: their prepay ask is open until Nov 3.",
  "checks": [
    {
      "check": "customer-ask-cap",
      "status": "fail",
      "doc": "/code/refusals#check-customer-ask-cap",
      "fix": "Wait for the open ask to close; Decision.list names the Play that holds them.",
      "then": "Decision.list"
    }
  ]
}
```


```json title="The 422 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The referral ask to Juniper Homes did not send: their prepay ask is open until Nov 3.",
  "checks": [
    {
      "check": "customer-ask-cap",
      "status": "fail",
      "doc": "/code/refusals#check-customer-ask-cap",
      "fix": "Wait for the open ask to close; Decision.list names the Play that holds them.",
      "then": "Decision.list"
    }
  ]
}
```

| Check | Refuses | Fix | Then |
| --- | --- | --- | --- |
| [`display-consent`](/code/refusals#check-display-consent) | a Review shown on your own Pages before its reviewer agreed to display | Ask the reviewer once; the Review shows when they agree. | <Op verb="Message.schedule" /> |
| [`customer-ask-cap`](/code/refusals#check-customer-ask-cap) (Not live yet, gaps #3) | a second open ask to one Customer in a window, from any Play | Wait for the open ask to close; <Op verb="Decision.list" /> names the Play that holds them. | <Op verb="Decision.list" /> |
| [`keep-first`](/code/refusals#check-keep-first) (Not live yet, gaps #8) | an upgrade, referral, review or reference ask to a Customer who is at risk, past due or leaving | Let their CSM reach them first, then ask again once they are healthy. | <Op verb="Task.next" /> |

## Plays that use it
| Play | What it does | Use it when |
| --- | --- | --- |
| [Review requests](/plays/review-requests) | Asks every Customer for an honest Review when their result lands, on the one Venue you need most, and answers every Review within a day. | 10 or more Customers who reached a delivered outcome, and a Venue listing of your own |
| [Written references](/plays/written-references) | Answers a buyer’s reference request within days with a reference an opted-in Customer writes and signs, its credit shown on it. | 2 or more reference requests a month, and 5 or more Customers in the reference pool |
| [Customer referrals](/plays/customer-referrals) | Asks Customers to invite a peer right after they get the result they came for, and rewards both once the peer’s first payment clears refunds. | 10 or more Customers who reached the outcome you declared, with Stripe connected |
| [Champion follow](/plays/champion-follow) | When a champion changes jobs, offers them their own setup, pre-loaded at the new company, 30 days in; the old company’s data never moves. | 30 or more champions on file and 2 or more job moves a quarter |
| `advocate-ladder` (Not live yet, gaps #4) | The Play that asks a happy Customer for a review, then a reference, then a referral, one ask at a time. | today: Order.delivered for an activated Customer starts one ask, a <Op verb="Message.schedule" />. |

## Hub guides it deepens
| On startupbuilder.dev | Id |
| --- | --- |
| [Customer](https://startupbuilder.dev/agents/customer) | `team:customer` |

## Example
```text title="Leaseline, as the fixture ran it"
Elm Street Rentals · health 92 · one referral ask Oct 21, a Message today · Maple Court’s review held until they say yes (display-consent) · Juniper’s referral ask refused while their prepay ask is open (customer-ask-cap) · 2 of 17 new Customers referred · sample
```