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

# Retention offers

> Return retention save-offer views, accepts, declines, acceptance rates, and accepted-versus-declined revenue LTV where supported. Requires a tenant-scoped API key and the retention-offer analytics capability.

Returns retention save-offer views, accepts, declines, acceptance rates, and accepted-versus-declined revenue LTV where supported.

<Note>This endpoint requires a tenant-scoped API key and the retention-offer analytics capability for the tenant.</Note>

<RequestExample>
  ```bash Retention offer uptake theme={null}
  curl "https://app.nautilus.co/api/v1/retention-offers?start=2026-01-01&end=2026-06-30" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash Last 30 days with timestamps theme={null}
  curl "https://app.nautilus.co/api/v1/retention-offers?start=2026-06-01T00:00:00Z&end=2026-06-30T23:59:59Z" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```
</RequestExample>

## Response

```json theme={null}
{
  "organizationId": "org_example_123",
  "dateRange": {
    "start": "2026-01-01T00:00:00.000Z",
    "end": "2026-06-30T23:59:59.999Z"
  },
  "totals": {
    "viewed": 512,
    "accepted": 146,
    "declined": 366,
    "acceptanceRatePct": 28.5
  },
  "offers": [
    {
      "offerId": "offer_example_001",
      "offerName": "Stay and Save",
      "targetPlanName": "Unlimited Plus",
      "targetPlanPrice": 2999,
      "viewed": 310,
      "accepted": 96,
      "declined": 214,
      "acceptanceRatePct": 31.0
    }
  ],
  "ltv": {
    "acceptedMembers": {
      "count": 84,
      "avgRevenuePostOffer": 73.42,
      "avgLifetimeRevenue": 412.18
    },
    "declinedMembers": {
      "count": 190,
      "avgRevenuePostOffer": 18.65,
      "avgLifetimeRevenue": 263.91
    },
    "windowMonths": null,
    "definition": "LTV uses unique members whose accepted or declined offer event carried a customer_id. Lifetime revenue is all transaction total for that customer in sonnys.transactions_shared; post-offer revenue is transaction total where completeDate is after the member's earliest decision event in the requested offer-event window. Amounts are returned in dollars.",
    "coverage": {
      "acceptedDecisionEvents": 146,
      "acceptedJoinableDecisionEvents": 91,
      "acceptedMembersWithRevenueLookup": 84,
      "declinedDecisionEvents": 366,
      "declinedJoinableDecisionEvents": 205,
      "declinedMembersWithRevenueLookup": 190
    }
  }
}
```

## LTV availability

Revenue LTV is currently available only for `sonnys-shared` tenants. Other providers return an explained null LTV block:

```json theme={null}
{
  "ltv": {
    "acceptedMembers": null,
    "declinedMembers": null,
    "windowMonths": null,
    "definition": "LTV uses unique members whose accepted or declined offer event carried a customer_id. Lifetime revenue is all transaction total for that customer in sonnys.transactions_shared; post-offer revenue is transaction total where completeDate is after the member's earliest decision event in the requested offer-event window. Amounts are returned in dollars.",
    "coverage": null,
    "unavailableReason": "Revenue LTV is currently available only for the sonnys-shared provider because it depends on sonnys.transactions_shared."
  }
}
```

Use `ltv.coverage` to compare LTV sample size against `totals.accepted` and `totals.declined`. LTV is computed only for decision events that include a joinable customer id.

## Dates

`start` and `end` accept date-only values or explicit timestamps. Date-only lower bounds start at `00:00:00.000Z`; date-only upper bounds include the full UTC day.

If you omit `start`, Nautilus queries all time. If you omit `end`, Nautilus uses the current time internally. Omitted bounds are returned as `null` in `dateRange`.

## Errors

* **400** - Retention offer analytics are not available for this organization.
* **401** - Missing, invalid, or non-tenant-scoped API key.
* **500** - Internal server error.


## OpenAPI

````yaml GET /api/v1/retention-offers
openapi: 3.1.0
info:
  title: Nautilus Public API
  version: 1.0.0
  description: >-
    Tenant-scoped public API. Except for /api/v1/openapi.json, requests use a
    Clerk tenant API key as a Bearer token. The key's organizationId is the sole
    tenant scope; organization IDs supplied by callers are never accepted.
    Detail endpoints deliberately return 404 for both missing and cross-tenant
    records. Canonical collections use the standard envelope and endpoint-bound
    cursor pagination. Timestamps are UTC ISO 8601 values and monetary amounts
    ending in Cents are integer cents. Responses use narrow serializers and
    exclude credentials, API keys, provider configuration, access tokens,
    webhook secrets, raw provider payloads, and internal workflow or activity
    data.
servers:
  - url: https://app.nautilus.co
security:
  - tenantApiKey: []
tags:
  - name: Contract
  - name: Cancellations
  - name: Sites
  - name: Products
  - name: Checkout links
  - name: Purchases
  - name: Contact lists
  - name: Contacts
  - name: Marketing
  - name: Forms
  - name: Vouchers
  - name: Links
  - name: Automations
  - name: Surveys
  - name: Cases
  - name: Sonny's POS
  - name: Analytics
  - name: Messaging
  - name: Legacy
    description: Deprecated noncanonical endpoints retained for compatibility.
paths:
  /api/v1/retention-offers:
    get:
      tags:
        - Analytics
        - Legacy
      summary: Retention offers
      description: >-
        Return retention save-offer views, accepts, declines, acceptance rates,
        and accepted-versus-declined revenue LTV where supported. Requires a
        tenant-scoped API key and the retention-offer analytics capability.
      operationId: listRetentionOffers
      parameters:
        - name: start
          in: query
          required: false
          schema:
            type: string
          description: >-
            Inclusive lower bound on offer events. Date-only values start at
            `00:00:00.000Z`.
        - name: end
          in: query
          required: false
          schema:
            type: string
          description: >-
            Inclusive upper bound on offer events. Date-only values include the
            full UTC day.
      responses:
        '200':
          description: Retention offer analytics response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetentionOffersResponse'
        '400':
          description: Retention offer analytics are not available for this organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: >-
                  Retention offer analytics are not available for this
                  organization
        '401':
          description: Missing, invalid, or non-tenant-scoped API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      deprecated: true
components:
  schemas:
    RetentionOffersResponse:
      type: object
      required:
        - organizationId
        - dateRange
        - totals
        - offers
        - ltv
      properties:
        organizationId:
          type: string
        dateRange:
          $ref: '#/components/schemas/DateRange'
        totals:
          type: object
          required:
            - viewed
            - accepted
            - declined
            - acceptanceRatePct
          properties:
            viewed:
              type: integer
            accepted:
              type: integer
            declined:
              type: integer
            acceptanceRatePct:
              type: number
        offers:
          type: array
          items:
            $ref: '#/components/schemas/RetentionOffer'
        ltv:
          oneOf:
            - $ref: '#/components/schemas/RetentionLtvAvailable'
            - $ref: '#/components/schemas/RetentionLtvUnavailable'
    Error:
      type: object
      additionalProperties: false
      properties:
        error:
          type: string
      required:
        - error
    DateRange:
      type: object
      title: Date range
      required:
        - start
        - end
      properties:
        start:
          type:
            - string
            - 'null'
          format: date-time
          description: Parsed `start` filter, or null when omitted.
        end:
          type:
            - string
            - 'null'
          format: date-time
          description: Parsed `end` filter, or null when omitted.
    RetentionOffer:
      type: object
      required:
        - offerId
        - offerName
        - targetPlanName
        - targetPlanPrice
        - viewed
        - accepted
        - declined
        - acceptanceRatePct
      properties:
        offerId:
          type: string
        offerName:
          type: string
        targetPlanName:
          type:
            - string
            - 'null'
        targetPlanPrice:
          type:
            - integer
            - 'null'
          description: Stored integer minor-unit format used by offer metadata.
        viewed:
          type: integer
        accepted:
          type: integer
        declined:
          type: integer
        acceptanceRatePct:
          type: number
    RetentionLtvAvailable:
      type: object
      required:
        - acceptedMembers
        - declinedMembers
        - windowMonths
        - definition
        - coverage
      properties:
        acceptedMembers:
          $ref: '#/components/schemas/RetentionLtvSegment'
        declinedMembers:
          $ref: '#/components/schemas/RetentionLtvSegment'
        windowMonths:
          type: 'null'
        definition:
          type: string
        coverage:
          $ref: '#/components/schemas/RetentionLtvCoverage'
    RetentionLtvUnavailable:
      type: object
      required:
        - acceptedMembers
        - declinedMembers
        - windowMonths
        - definition
        - coverage
        - unavailableReason
      properties:
        acceptedMembers:
          type: 'null'
        declinedMembers:
          type: 'null'
        windowMonths:
          type: 'null'
        definition:
          type: string
        coverage:
          type: 'null'
        unavailableReason:
          type: string
    RetentionLtvSegment:
      type: object
      required:
        - count
        - avgRevenuePostOffer
        - avgLifetimeRevenue
      properties:
        count:
          type: integer
        avgRevenuePostOffer:
          type: number
        avgLifetimeRevenue:
          type: number
    RetentionLtvCoverage:
      type: object
      required:
        - acceptedDecisionEvents
        - acceptedJoinableDecisionEvents
        - acceptedMembersWithRevenueLookup
        - declinedDecisionEvents
        - declinedJoinableDecisionEvents
        - declinedMembersWithRevenueLookup
      properties:
        acceptedDecisionEvents:
          type: integer
        acceptedJoinableDecisionEvents:
          type: integer
        acceptedMembersWithRevenueLookup:
          type: integer
        declinedDecisionEvents:
          type: integer
        declinedJoinableDecisionEvents:
          type: integer
        declinedMembersWithRevenueLookup:
          type: integer
  securitySchemes:
    tenantApiKey:
      type: http
      scheme: bearer
      bearerFormat: Clerk tenant API key
      description: >-
        Clerk tenant API key. The organizationId encoded by the key is the exact
        and only tenant scope.

````