Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/command-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ rc paywalls delete <id> [--force] # attached/published pa

# Flows (paywalls, web funnels, and multi-step experiences)
rc flows list [--type paywall|web-funnel|general] [--exclude-type ...] # experimental (hidden from --help until Checkpoint rules roll out); repeatable; shows type and publish state
rc flows show [id] # editable draft, including steps and screen content (GET /flows/{id}?expand=steps)
rc flows export [id] --output <path> # save an update request containing the current revision and all steps
rc flows update [id] --config <path|-> # save the exported JSON or a partial update; only deleted_step_ids removes steps (PATCH /flows/{id})
rc flows create --config <path|-> # create an unpublished web-funnel or general flow (type paywall is rejected)
rc flows duplicate [id] [--name <name>] [--source-version draft|published] # create an unpublished copy
rc flows validate [id] [--revision <revision>] # return saved-draft validation issues
# Experiments — development v2 endpoints, normal discoverable CLI surface
rc experiments list [--status <status>]
rc experiments show <id> # human detail view shows variants, targeting, placements, and chronological timeline
Expand Down
12 changes: 12 additions & 0 deletions docs/specs/cli-coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,15 @@ endpoints:
path: /projects/{project_id}/store_state/plans
- method: GET
path: /projects/{project_id}/store_state/plans/{plan_id}

# Flow authoring (development API)
- method: POST
path: /projects/{project_id}/flows
- method: POST
path: /projects/{project_id}/flows/{flow_id}/actions/duplicate
- method: POST
path: /projects/{project_id}/flows/{flow_id}/actions/validate
- method: GET
path: /projects/{project_id}/flows/{flow_id}
- method: PATCH
path: /projects/{project_id}/flows/{flow_id}
233 changes: 233 additions & 0 deletions docs/specs/v2-beta-overlay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,119 @@ paths:
responses:
'200':
description: OK
post:
summary: Create a Flow draft
operationId: create-flow
x-release-status: development
x-revenuecat-release-gatekeeping: false
x-scopes:
- project_configuration:workflows:read_write
responses:
'200':
description: OK
/projects/{project_id}/flows/{flow_id}/actions/duplicate:
parameters:
- name: project_id
in: path
required: true
schema:
type: string
- name: flow_id
in: path
required: true
schema:
type: string
post:
summary: Duplicate a Flow
operationId: duplicate-flow
x-release-status: development
x-revenuecat-release-gatekeeping: false
x-scopes:
- project_configuration:workflows:read_write
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DuplicateFlow'
responses:
'200':
description: OK
/projects/{project_id}/flows/{flow_id}/actions/validate:
parameters:
- name: project_id
in: path
required: true
schema:
type: string
- name: flow_id
in: path
required: true
schema:
type: string
post:
summary: Validate a Flow draft
operationId: validate-flow
x-release-status: development
x-revenuecat-release-gatekeeping: false
x-scopes:
- project_configuration:workflows:read
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ValidateFlow'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/FlowValidation'
/projects/{project_id}/flows/{flow_id}:
parameters:
- name: project_id
in: path
required: true
schema:
type: string
- name: flow_id
in: path
required: true
schema:
type: string
get:
summary: Get a Flow
operationId: get-flow
x-release-status: development
x-revenuecat-release-gatekeeping: false
x-scopes:
- project_configuration:workflows:read
parameters:
- name: expand
in: query
description: Accepted values are `steps` (adds the draft steps, settings and revision).
required: false
schema:
type: array
items:
type: string
enum:
- steps
responses:
'200':
description: OK
patch:
summary: Update a Flow
operationId: update-flow
x-release-status: development
x-revenuecat-release-gatekeeping: false
x-scopes:
- project_configuration:workflows:read_write
responses:
'200':
description: OK
/projects/{project_id}/checkpoints:
parameters:
- name: project_id
Expand Down Expand Up @@ -1416,3 +1529,123 @@ components:
- ZAR
- ZMW
- ZWL
DuplicateFlow:
type: object
additionalProperties: false
properties:
display_name:
type: string
description: Name of the duplicate.
minLength: 1
maxLength: 1500
example: Onboarding Copy
source_version:
type: string
enum:
- draft
- published
default: draft
description: Which source version to copy. The copy is always unpublished.
example: draft
ValidateFlow:
type: object
additionalProperties: false
properties:
revision:
type: integer
description: Current draft revision. Saves must supply this value to reject
stale writes.
example: 1
format: int64
minimum: 1
FlowValidation:
type: object
additionalProperties: true
required:
- object
- id
- valid
- revision
- step_issues
- workflow_errors
properties:
object:
type: string
enum:
- flow_validation
description: Type of this object.
example: flow_validation
id:
type: string
description: Flow ID.
example: wf_123456789a
valid:
type: boolean
description: Whether the draft can be published. Warnings do not prevent publication.
example: false
revision:
type: integer
description: Current draft revision. Saves must supply this value to reject
stale writes.
example: 1
format: int64
minimum: 1
nullable: true
step_issues:
type: object
description: Issues keyed by step ID.
additionalProperties:
type: array
items:
$ref: '#/components/schemas/FlowValidationIssue'
example: {}
workflow_errors:
type: array
description: Issues affecting the entire flow.
items:
$ref: '#/components/schemas/FlowValidationError'
example: []
FlowValidationIssue:
type: object
FlowValidationError:
type: object
additionalProperties: true
required:
- key
- message
- severity
properties:
key:
type: string
description: Workflow validation error key.
enum:
- Workflow_ExperimentRunningCombosOverLimit
- Workflow_ExperimentPotentialCombosOverLimit
- Workflow_NoPaywallScreen
- Workflow_RestoreButtonNotOnFinalScreen
- Workflow_NoInitialStep
- Workflow_InitialStepNotFound
- Workflow_NoCheckoutStep
- Workflow_CheckoutNotReachable
- Workflow_CheckoutNoSuccessStep
- Workflow_NoWebConfig
- Workflow_NoWebProducts
- Workflow_ExclusiveRuleMixedOutcome
- Workflow_CookieConsentNoPrivacyPolicy
example: Workflow_ExperimentRunningCombosOverLimit
message:
description: A human-readable description of the issue.
type: string
example: Add a name to your paywall.
severity:
description: The severity of the validation issue.
type: string
enum:
- error
- warning
example: error
action:
type: string
description: Suggested action to resolve the issue.
nullable: true
example: add_screen
85 changes: 85 additions & 0 deletions internal/api/flows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
Expand All @@ -10,7 +11,9 @@ import (
// FlowsService wraps /flows. Feedback surveys are not returned by the API.
type FlowsService struct{ c *Client }

// Flow carries the settings and Steps only when fetched with FlowExpandSteps.
type Flow struct {
FlowSettings
ID string `json:"id"`
Object string `json:"object,omitempty"`
Name string `json:"name"`
Expand All @@ -19,6 +22,44 @@ type Flow struct {
CreatedAt Millis `json:"created_at,omitempty"`
PublishedAt *Millis `json:"published_at"`
HasUnpublishedChanges bool `json:"has_unpublished_changes"`
Revision int `json:"revision"`

Steps map[string]json.RawMessage `json:"steps,omitempty"`
}

type FlowSettings struct {
DisplayName *string `json:"display_name,omitempty"`
InitialStepID json.RawMessage `json:"initial_step_id,omitempty"`
SingleStepFallbackID json.RawMessage `json:"single_step_fallback_id,omitempty"`
AppID json.RawMessage `json:"app_id,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
CustomContextParametersConfig json.RawMessage `json:"custom_context_parameters_config,omitempty"`
ContentMaxWidth json.RawMessage `json:"content_max_width,omitempty"`
}

type UpdateFlow struct {
FlowSettings
Revision int `json:"revision"`
UpdatedSteps map[string]json.RawMessage `json:"updated_steps,omitempty"`
DeletedStepIDs []string `json:"deleted_step_ids,omitempty"`
}

type CreateFlow struct {
FlowSettings
Type string `json:"type,omitempty"`
Steps map[string]json.RawMessage `json:"steps,omitempty"`
}

type FlowValidationIssue struct {
Key string `json:"key"`
Message string `json:"message"`
Severity string `json:"severity"`
ComponentID *string `json:"component_id,omitempty"`
Property *string `json:"property,omitempty"`
Locale *string `json:"locale,omitempty"`
LocalizationID *string `json:"localization_id,omitempty"`
Path *string `json:"path,omitempty"`
Action *string `json:"action,omitempty"`
}

// FlowListOptions filters by Flow type. Type and ExcludeType can't be combined.
Expand Down Expand Up @@ -55,3 +96,47 @@ func (s *FlowsService) List(ctx context.Context, projectID string, opts FlowList
}
return &out, nil
}

// FlowExpand names the optional sections get-flow can embed.
type FlowExpand string

// FlowExpandSteps adds the draft steps, settings and revision.
const FlowExpandSteps FlowExpand = "steps"

func (s *FlowsService) Get(ctx context.Context, projectID, id string, expand ...FlowExpand) (*Flow, error) {
path := pathFlow(projectID, id)
if len(expand) > 0 {
query := url.Values{}
for _, e := range expand {
query.Add("expand", string(e))
}
path += "?" + query.Encode()
}
var out Flow
err := s.c.do(ctx, http.MethodGet, path, nil, &out)
return &out, err
}

func (s *FlowsService) Update(ctx context.Context, projectID, id string, body UpdateFlow) (*Flow, error) {
var out Flow
err := s.c.do(ctx, http.MethodPatch, pathFlow(projectID, id), body, &out)
return &out, err
}

func (s *FlowsService) Create(ctx context.Context, projectID string, body CreateFlow) (*Flow, error) {
var out Flow
err := s.c.do(ctx, http.MethodPost, pathFlows(projectID), body, &out)
return &out, err
}

func (s *FlowsService) Duplicate(ctx context.Context, projectID, id string, body DuplicateFlow) (*Flow, error) {
var out Flow
err := s.c.do(ctx, http.MethodPost, pathFlowActionsDuplicate(projectID, id), body, &out)
return &out, err
}

func (s *FlowsService) Validate(ctx context.Context, projectID, id string, body ValidateFlow) (*FlowValidation, error) {
var out FlowValidation
err := s.c.do(ctx, http.MethodPost, pathFlowActionsValidate(projectID, id), body, &out)
return &out, err
}
Loading
Loading