Cancel Flows
Update Cancel Flow
Update the configuration of an existing cancel flow.
PUT
/
v1
/
cancel-flows
/
{id}
Update a cancel flow
curl --request PUT \
--url https://billing.example.com/v1/cancel-flows/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"is_active": true,
"is_default": true,
"cooldown_days": 123
}
'import requests
url = "https://billing.example.com/v1/cancel-flows/{id}"
payload = {
"name": "<string>",
"is_active": True,
"is_default": True,
"cooldown_days": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', is_active: true, is_default: true, cooldown_days: 123})
};
fetch('https://billing.example.com/v1/cancel-flows/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://billing.example.com/v1/cancel-flows/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'is_active' => true,
'is_default' => true,
'cooldown_days' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://billing.example.com/v1/cancel-flows/{id}"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"is_active\": true,\n \"is_default\": true,\n \"cooldown_days\": 123\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://billing.example.com/v1/cancel-flows/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"is_active\": true,\n \"is_default\": true,\n \"cooldown_days\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://billing.example.com/v1/cancel-flows/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"is_active\": true,\n \"is_default\": true,\n \"cooldown_days\": 123\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"tenant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"is_active": true,
"is_default": true,
"cooldown_days": 123,
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"steps": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"flow_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"step_order": 123,
"step_type": "survey",
"config": "<unknown>",
"created_at": "2023-11-07T05:31:56Z"
}
]
}{
"error": {
"code": "BAD_REQUEST",
"message": "<string>"
}
}{
"error": {
"code": "BAD_REQUEST",
"message": "<string>"
}
}{
"error": {
"code": "BAD_REQUEST",
"message": "<string>"
}
}Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The unique identifier of the cancel flow to update (e.g. cflow_01h8x3kv7m2p). |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Display name for the cancel flow (max 255 characters). |
is_active | boolean | No | Whether the flow is active and can be triggered. |
is_default | boolean | No | Whether this is the default cancel flow for subscriptions without a specific flow assigned. |
cooldown_days | integer | No | Minimum number of days before a customer can be shown this flow again. |
Example Request
curl -X PUT "https://api.recurso.dev/v1/cancel-flows/cflow_01h8x3kv7m2p" \
-H "Authorization: Bearer rsk_live_9f8a7b6c5d4e3f2a1b0c" \
-H "Content-Type: application/json" \
-d '{
"name": "Pro Plan Retention Flow",
"is_active": true,
"is_default": false,
"cooldown_days": 14
}'
Response
Returns the updated cancel flow object.{
"id": "cflow_01h8x3kv7m2p",
"object": "cancel_flow",
"name": "Pro Plan Retention Flow",
"description": "Standard 3-step cancel flow with discount offer",
"is_active": true,
"is_default": false,
"cooldown_days": 14,
"steps": [
{ "id": "cstep_01h8x3mw9n4q", "type": "survey", "title": "We're sorry to see you go", "order": 0 },
{ "id": "cstep_01h8x3pw2r6s", "type": "offer", "title": "How about a discount?", "order": 1 },
{ "id": "cstep_01h8x3rz5t8u", "type": "confirmation", "title": "Confirm cancellation", "order": 2 }
],
"created_at": "2026-03-10T14:22:00Z",
"updated_at": "2026-06-23T17:45:00Z"
}
Notes
- Only fields included in the request body are updated. Omitted fields remain unchanged.
- Setting
is_defaulttotrueautomatically unsets any other flow that was previously the default.
Updating an active flow that is currently being served to customers will take effect immediately. Changes apply to new cancellation sessions only; in-progress sessions continue using the previous configuration.
Authorizations
Tenant API key obtained from POST /auth/register or POST /v1/developer/keys.
Path Parameters
⌘I
Update a cancel flow
curl --request PUT \
--url https://billing.example.com/v1/cancel-flows/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"is_active": true,
"is_default": true,
"cooldown_days": 123
}
'import requests
url = "https://billing.example.com/v1/cancel-flows/{id}"
payload = {
"name": "<string>",
"is_active": True,
"is_default": True,
"cooldown_days": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', is_active: true, is_default: true, cooldown_days: 123})
};
fetch('https://billing.example.com/v1/cancel-flows/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://billing.example.com/v1/cancel-flows/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'name' => '<string>',
'is_active' => true,
'is_default' => true,
'cooldown_days' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://billing.example.com/v1/cancel-flows/{id}"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"is_active\": true,\n \"is_default\": true,\n \"cooldown_days\": 123\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://billing.example.com/v1/cancel-flows/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"is_active\": true,\n \"is_default\": true,\n \"cooldown_days\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://billing.example.com/v1/cancel-flows/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"is_active\": true,\n \"is_default\": true,\n \"cooldown_days\": 123\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"tenant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"is_active": true,
"is_default": true,
"cooldown_days": 123,
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"steps": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"flow_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"step_order": 123,
"step_type": "survey",
"config": "<unknown>",
"created_at": "2023-11-07T05:31:56Z"
}
]
}{
"error": {
"code": "BAD_REQUEST",
"message": "<string>"
}
}{
"error": {
"code": "BAD_REQUEST",
"message": "<string>"
}
}{
"error": {
"code": "BAD_REQUEST",
"message": "<string>"
}
}