---
title: "Change the settings"
method: PUT
path: "/v1/projects/{project}/retention"
tags: ["Revenue Recovery"]
---

# Change the settings

`PUT /v1/projects/{project}/retention`

## Request

- Base URL: `https://api.subscriby.net`
- URL: `https://api.subscriby.net/v1/projects/{project}/retention`
- Auth: HTTP bearer

```bash
curl -X PUT https://api.subscriby.net/v1/projects/7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13/retention \
  -H "Authorization: Bearer sbt_..." \
  -H "Idempotency-Key: 6b1f0d2e-9c3a-4e8b-b2d1-7a9c5e3f1d04" \
  -H "Content-Type: application/json" \
  -d '{"grace_days": 10, "offer_pause_enabled": true, "offer_return_coupon_id": "3b7c9d1e-5f24-4a86-9c0b-2d4e6f8a1b3c"}'
```

Every field is optional and the row keeps its stored value for each one left out, so a client can flip the digest without restating the window. A coupon must be one of the project's own; send `null` to clear an offer. The two nurture windows are whole numbers within their bounds (`checkout_stall_hours` 1 to 24, `trial_notice_days` 1 to 14); a checkout already open is judged by the window in force when the hourly sweep runs. The answer is the settings as they now read.

> A grace window of `0` pauses access at the first failure. The never-paid trial keeps today's immediate revoke whatever the window says, and a one-time purchase never enters grace.

## Path parameters

- `project` string, uuid, required

## Headers

- `Idempotency-Key` string, uuid, required

## Request body

- UpdateRetentionSettingsRequest: The Revenue Recovery settings of a project: how long a failed renewal keeps access, whether the daily digest is sent, what the cancellation screen asks and which offers it makes, how long a checkout may stay open before it counts as stalled, and how many days before a trial without a card ends its sequence starts. Every field is optional; the row keeps its stored value for each one left out.
  - `grace_days` integer: How many days a failed renewal keeps access while the gateway retries, 0 to 30. `0` pauses access at the failure.
  - `digest_enabled` boolean: Whether the owner gets one message a day with the failures, reminders, pauses and recoveries, only on a day something happened.
  - `cancel_feedback_enabled` boolean: Whether the cancellation screen asks the member why before the offer. Off skips straight to the offer.
  - `offer_pause_enabled` boolean: Whether a cancelling member on Stripe or PayPal is offered one billing cycle on hold. Needs the Growth plan or the Revenue Recovery Addon; ignored by the screen without it.
  - `offer_discount_coupon_id` string, nullable: One of this project's coupons, applied to the member's renewals where Stripe can carry it, or `null` for no discount offer. Needs the Growth plan or the Revenue Recovery Addon.
  - `offer_return_coupon_id` string, nullable: One of this project's coupons, handed to the member on the way out on every payment method, or `null` for no return coupon. Needs the Growth plan or the Revenue Recovery Addon.
  - `checkout_stall_hours` integer: How many hours a checkout may stay open with no settled charge before the hourly sweep counts it as stalled and the stalled-checkout sequences start, 1 to 24; such as `4`.
  - `trial_notice_days` integer: How many days before a trial without a card ends its trial-ending sequences start, 1 to 14; such as `3`. A trial shorter than that enrols at its start.

## Response `200`

The settings as they now read.

- object
  - `data` RetentionSettingsResource, required: A project's Revenue Recovery settings: how long a failed renewal keeps access, the notice ladder that follows in words, whether the daily digest is sent, what the cancellation screen asks, which of the three offers it makes, how long a checkout may stay open before it counts as stalled, and how many days before a trial without a card ends its sequence starts.
    - `project_id` string, required: The project the settings belong to.
    - `grace_days` integer, required: How many days a failed renewal keeps access while the gateway retries, 0 to 30. `0` pauses access at the failure.
    - `ladder` string, required: The grace window and its notices in words, as the dashboard and the bot say them.
    - `digest_enabled` boolean, required: Whether the owner gets one message a day with the failures, reminders, pauses and recoveries, only on a day something happened.
    - `cancel_feedback_enabled` boolean, required: Whether the cancellation screen asks the member why before the offer.
    - `checkout_stall_hours` integer, required: How many hours a checkout may stay open with no settled charge before the hourly sweep counts it as stalled and the stalled-checkout sequences start, 1 to 24.
    - `trial_notice_days` integer, required: How many days before a trial without a card ends its trial-ending sequences start, 1 to 14. A trial shorter than that enrols at its start.
    - `offers_available` boolean, required: Whether the project owner's plan includes the offers (Growth or the Revenue Recovery Addon). The three offer settings are stored either way and ignored by the screen without it.
    - `offer_pause_enabled` boolean, required: Whether a cancelling member on Stripe or PayPal is offered one billing cycle on hold.
    - `offer_discount_coupon` object, nullable: The coupon applied to a cancelling member's renewals where Stripe can carry it, or `null` for no discount offer.
      - `id` string, required: The coupon's id.
      - `code` string, required: The coupon's code.
    - `offer_return_coupon` object, nullable: The coupon handed to a cancelling member on every payment method, to come back with, or `null` for no return coupon.
      - `id` string, required: The coupon's id.
      - `code` string, required: The coupon's code.
    - `updated_at` string, nullable, required: When the settings last changed, ISO 8601.

## Other responses

- `400`: `IDEMPOTENCY_KEY_MISSING`: every write needs an `Idempotency-Key` header. Send a fresh UUID per distinct operation.
- `401`: `AUTHENTICATION_REQUIRED`: the request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an `sbt_test_` token on production).
- `403`: `TOKEN_MISSING_ABILITY`: the token is valid but does not carry the ability this endpoint requires; `error.context.required_ability` names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers `FORBIDDEN`, `TEAM_TIER_REQUIRED` or `CONNECTOR_TIER_REQUIRED` with the same status, and says so in its own description.
- `404`: `RESOURCE_NOT_FOUND`: an id in the path names nothing the token can see. `TENANT_MISMATCH`: the project sits outside the token's `scope:project:` allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.
- `409`: `IDEMPOTENCY_KEY_REUSED`: the key was already used in the last 24 hours with a different request body.
- `422`: `VALIDATION_FAILED`: the payload broke a rule, and `error.fields` maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with `error.message` saying why and no `fields`.
- `425`: `IDEMPOTENCY_REPLAY_IN_PROGRESS`: the first request with this key is still running; retry in a few seconds and the original response is replayed.
- `429`: `RATE_LIMITED`: the token has spent its 300 requests a minute or 10,000 an hour; `Retry-After` says when the next one is accepted.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
