Saveloops

API Documentation

Use the Saveloops API to fetch offers, record outcomes, apply offers to Stripe, analyze sentiment, and manage dunning and success signals.

Base URL & authentication

All endpoints use the base URL {your-app}/api/v1. Send your organization API key in the X-Knot-Key header. Requests and responses are JSON.

X-Knot-Key: <organization_api_key>

POST /api/v1/intercept

Fetch offers and flags for the current context (e.g. cancel flow). Use this when the user hits a cancel or exit intent so you can show the right offer or prompt.

Request body

{
  "target_id": "cancel-btn",     // required, string, max 255
  "user_email": "user@example.com",  // optional, email
  "trigger": "click",             // optional: click | exit_intent | hover
  "stripe_customer_id": "cus_xxx",   // optional
  "stripe_subscription_id": "sub_xxx",  // optional
  "mrr_value": 29                 // optional, number >= 0
}

Response (200)

{
  "offers": [
    { "id": 1, "type": "discount", "value": 20, "label": "20% off", "config": {}, "experiment": null }
  ],
  "show_update_card": false,
  "show_onboarding_prompt": false
}

POST /api/v1/intercept/outcome

Record whether the user saved (accepted an offer) or abandoned (closed without accepting). Call this after the user makes a choice in the intercept flow.

Request body

{
  "offer_id": 1,                  // required, integer, exists in offers
  "status": "saved",              // required: saved | abandoned
  "user_email": "user@example.com",  // optional
  "mrr_value": 29                 // optional, number >= 0
}

Response (200)

{ "message": "Recorded" }

POST /api/v1/intercept/apply-offer

Apply the selected offer to the Stripe subscription (e.g. apply discount). Records a save in analytics on success.

Request body

{
  "offer_id": 1,                  // required, integer, exists in offers
  "stripe_subscription_id": "sub_xxx",  // required
  "user_email": "user@example.com",  // optional
  "mrr_value": 29                 // optional, number >= 0
}

Response (200)

{ "message": "Offer applied successfully." }

On failure (e.g. Stripe error): 422 with message and optional error (when debug is on).

POST /api/v1/intercept/sentiment

Analyze the user’s cancellation reason and get a suggested action (e.g. discount, setup call, support ticket) plus matching offers. May create a support ticket when suggested action is support.

Request body

{
  "reason": "Too expensive for now",  // required, string, max 2000
  "user_email": "user@example.com",   // optional
  "target_id": "cancel-form"          // optional, string, max 255
}

Response (200)

{
  "suggested_action": "discount",
  "confidence": 0.85,
  "offers": [ { "id": 1, "type": "discount", "value": 20, "label": "20% off", "config": {}, "experiment": null } ],
  "create_support_ticket": false
}

POST /api/v1/dunning

Mark an end user as needing payment update (e.g. after Stripe invoice.payment_failed). Call from your backend when you detect a failed payment. One of user_email or stripe_customer_id is required.

Request body

{
  "user_email": "user@example.com",   // required without stripe_customer_id
  "stripe_customer_id": "cus_xxx"     // required without user_email
}

Response (200)

{ "message": "Recorded" }

POST /api/v1/success-signal

Record a positive event (e.g. payment succeeded, subscription renewed). Upserts the end user and sets last success time. One of user_email or stripe_customer_id is required.

Request body

{
  "event": "payment_succeeded",       // required, string, max 64
  "user_email": "user@example.com",  // required without stripe_customer_id
  "stripe_customer_id": "cus_xxx"     // required without user_email
}

Response (200)

{ "message": "Recorded", "end_user_id": 123 }

Example request

curl -X POST "https://your-app.com/api/v1/intercept" \
  -H "Content-Type: application/json" \
  -H "X-Knot-Key: your_organization_api_key" \
  -d '{"target_id":"cancel-btn","user_email":"user@example.com"}'