From 7a6e56cb655329c83378d919ddc3b99e31317f5f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 21:04:56 -0700 Subject: [PATCH 1/4] fix(revenuecat): align tools and block with REST v1 API spec --- .../docs/content/docs/en/tools/revenuecat.mdx | 18 +- .../integrations/data/integrations.json | 2 +- apps/sim/blocks/blocks/revenuecat.ts | 158 ++++++++++++++---- apps/sim/tools/revenuecat/create_purchase.ts | 27 ++- .../revenuecat/defer_google_subscription.ts | 26 ++- apps/sim/tools/revenuecat/delete_customer.ts | 23 ++- apps/sim/tools/revenuecat/get_customer.ts | 18 +- .../sim/tools/revenuecat/grant_entitlement.ts | 20 ++- apps/sim/tools/revenuecat/list_offerings.ts | 4 +- .../revenuecat/refund_google_subscription.ts | 13 +- .../tools/revenuecat/revoke_entitlement.ts | 5 +- .../revenuecat/revoke_google_subscription.ts | 5 +- apps/sim/tools/revenuecat/types.ts | 34 +++- .../update_subscriber_attributes.ts | 10 +- 14 files changed, 283 insertions(+), 80 deletions(-) diff --git a/apps/docs/content/docs/en/tools/revenuecat.mdx b/apps/docs/content/docs/en/tools/revenuecat.mdx index ad4b42ee4a8..f96a02d0e13 100644 --- a/apps/docs/content/docs/en/tools/revenuecat.mdx +++ b/apps/docs/content/docs/en/tools/revenuecat.mdx @@ -119,8 +119,10 @@ Record a purchase (receipt) for a subscriber via the REST API | `productId` | string | Yes | The product identifier for the purchase | | `price` | number | No | The price of the product in the currency specified | | `currency` | string | No | ISO 4217 currency code \(e.g., USD, EUR\) | -| `isRestore` | boolean | No | Whether this is a restore of a previous purchase | -| `platform` | string | No | Platform of the purchase \(ios, android, amazon, macos, stripe\). Required for Stripe and Paddle purchases. | +| `isRestore` | boolean | No | Whether this is a restore of a previous purchase \(deprecated by RevenueCat\) | +| `presentedOfferingIdentifier` | string | No | Identifier of the offering that was presented to the user when they made this purchase. Used by RevenueCat for offering-level analytics. | +| `paymentMode` | string | No | Payment mode for the purchase. One of: pay_as_you_go, pay_up_front, free_trial. Only applies to introductory pricing periods. | +| `platform` | string | Yes | Platform of the purchase. One of: ios, android, amazon, macos, uikitformac, stripe, roku, paddle. Sent as the X-Platform header \(required by RevenueCat\). | #### Output @@ -170,7 +172,8 @@ Grant a promotional entitlement to a subscriber | `apiKey` | string | Yes | RevenueCat secret API key \(sk_...\) | | `appUserId` | string | Yes | The app user ID of the subscriber | | `entitlementIdentifier` | string | Yes | The entitlement identifier to grant | -| `duration` | string | Yes | Duration of the entitlement \(daily, three_day, weekly, monthly, two_month, three_month, six_month, yearly, lifetime\) | +| `duration` | string | No | Duration of the entitlement. Provide either duration or endTimeMs. One of: daily, three_day, weekly, two_week, monthly, two_month, three_month, six_month, yearly, lifetime | +| `endTimeMs` | number | No | Absolute end time in milliseconds since Unix epoch. Use instead of duration to grant the entitlement until a specific timestamp. | | `startTimeMs` | number | No | Optional start time in milliseconds since Unix epoch. Set to a past time to achieve custom durations shorter than daily. | #### Output @@ -296,7 +299,7 @@ Update custom subscriber attributes (e.g., $email, $displayName, or custom key-v | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | RevenueCat secret API key \(sk_...\) | | `appUserId` | string | Yes | The app user ID of the subscriber | -| `attributes` | json | Yes | JSON object of attributes to set. Each key maps to an object with a "value" field. Example: \{"$email": \{"value": "user@example.com"\}, "$displayName": \{"value": "John"\}\} | +| `attributes` | json | Yes | JSON object of attributes to set. Each key maps to an object with "value" \(string; null or empty deletes the attribute\) and "updated_at_ms" \(Unix epoch ms used for conflict resolution — required\). Example: \{"$email": \{"value": "user@example.com", "updated_at_ms": 1709195668093\}\} | #### Output @@ -316,7 +319,8 @@ Defer a Google Play subscription by extending its billing date by a number of da | `apiKey` | string | Yes | RevenueCat secret API key \(sk_...\) | | `appUserId` | string | Yes | The app user ID of the subscriber | | `productId` | string | Yes | The Google Play product identifier of the subscription to defer \(use the part before the colon for products set up after Feb 2023\) | -| `extendByDays` | number | Yes | Number of days to extend the subscription by \(1-365\) | +| `extendByDays` | number | No | Number of days to extend the subscription by \(1-365\). Provide either extendByDays or expiryTimeMs. | +| `expiryTimeMs` | number | No | Absolute new expiry time in milliseconds since Unix epoch. Use instead of extendByDays to set an exact expiry. | #### Output @@ -357,7 +361,7 @@ Defer a Google Play subscription by extending its billing date by a number of da ### `revenuecat_refund_google_subscription` -Refund and optionally revoke a Google Play subscription (Google Play only) +Refund a specific store transaction by its store transaction identifier and revoke access (subscription or non-subscription, last 365 days) #### Input @@ -365,7 +369,7 @@ Refund and optionally revoke a Google Play subscription (Google Play only) | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | RevenueCat secret API key \(sk_...\) | | `appUserId` | string | Yes | The app user ID of the subscriber | -| `productId` | string | Yes | The Google Play product identifier of the subscription to refund | +| `storeTransactionId` | string | Yes | The store transaction identifier of the purchase to refund \(e.g., GPA.3309-9122-6177-45730 for Google Play\) | #### Output diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 9a1513dc6f8..4fceb728a8b 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -10748,7 +10748,7 @@ }, { "name": "Refund Google Subscription", - "description": "Refund and optionally revoke a Google Play subscription (Google Play only)" + "description": "Refund a specific store transaction by its store transaction identifier and revoke access (subscription or non-subscription, last 365 days)" }, { "name": "Revoke Google Subscription", diff --git a/apps/sim/blocks/blocks/revenuecat.ts b/apps/sim/blocks/blocks/revenuecat.ts index c99b5cf6917..ad4eaedd11c 100644 --- a/apps/sim/blocks/blocks/revenuecat.ts +++ b/apps/sim/blocks/blocks/revenuecat.ts @@ -72,6 +72,7 @@ export const RevenueCatBlock: BlockConfig = { { label: 'Daily', id: 'daily' }, { label: '3 Days', id: 'three_day' }, { label: 'Weekly', id: 'weekly' }, + { label: '2 Weeks', id: 'two_week' }, { label: 'Monthly', id: 'monthly' }, { label: '2 Months', id: 'two_month' }, { label: '3 Months', id: 'three_month' }, @@ -85,6 +86,28 @@ export const RevenueCatBlock: BlockConfig = { value: 'grant_entitlement', }, }, + { + id: 'endTimeMs', + title: 'End Time (ms)', + type: 'short-input', + placeholder: 'Optional absolute end time in ms since epoch', + condition: { + field: 'operation', + value: 'grant_entitlement', + }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a Unix epoch timestamp in milliseconds based on the user's description. +The timestamp should represent the absolute end time for the entitlement. +Examples: +- "in 7 days" -> current time plus 604800000 milliseconds +- "next month" -> current time plus 2592000000 milliseconds +- "end of 2026" -> 1798761600000 + +Return ONLY the numeric timestamp, no text.`, + }, + }, { id: 'startTimeMs', title: 'Start Time (ms)', @@ -129,21 +152,25 @@ Return ONLY the numeric timestamp, no text.`, placeholder: 'Product identifier', condition: { field: 'operation', - value: [ - 'create_purchase', - 'defer_google_subscription', - 'refund_google_subscription', - 'revoke_google_subscription', - ], + value: ['create_purchase', 'defer_google_subscription', 'revoke_google_subscription'], + }, + required: { + field: 'operation', + value: ['create_purchase', 'defer_google_subscription', 'revoke_google_subscription'], + }, + }, + { + id: 'storeTransactionId', + title: 'Store Transaction ID', + type: 'short-input', + placeholder: 'e.g., GPA.3309-9122-6177-45730', + condition: { + field: 'operation', + value: 'refund_google_subscription', }, required: { field: 'operation', - value: [ - 'create_purchase', - 'defer_google_subscription', - 'refund_google_subscription', - 'revoke_google_subscription', - ], + value: 'refund_google_subscription', }, }, { @@ -168,6 +195,32 @@ Return ONLY the numeric timestamp, no text.`, }, mode: 'advanced', }, + { + id: 'presentedOfferingIdentifier', + title: 'Presented Offering ID', + type: 'short-input', + placeholder: 'Offering identifier shown to the user', + condition: { + field: 'operation', + value: 'create_purchase', + }, + mode: 'advanced', + }, + { + id: 'paymentMode', + title: 'Payment Mode', + type: 'dropdown', + options: [ + { label: 'Pay As You Go', id: 'pay_as_you_go' }, + { label: 'Pay Up Front', id: 'pay_up_front' }, + { label: 'Free Trial', id: 'free_trial' }, + ], + condition: { + field: 'operation', + value: 'create_purchase', + }, + mode: 'advanced', + }, { id: 'isRestore', title: 'Is Restore', @@ -192,13 +245,19 @@ Return ONLY the numeric timestamp, no text.`, { label: 'Android', id: 'android' }, { label: 'Amazon', id: 'amazon' }, { label: 'macOS', id: 'macos' }, + { label: 'UIKit for Mac', id: 'uikitformac' }, { label: 'Stripe', id: 'stripe' }, + { label: 'Roku', id: 'roku' }, + { label: 'Paddle', id: 'paddle' }, ], condition: { field: 'operation', value: 'create_purchase', }, - mode: 'advanced', + required: { + field: 'operation', + value: 'create_purchase', + }, }, { id: 'attributes', @@ -238,10 +297,24 @@ Return ONLY valid JSON.`, field: 'operation', value: 'defer_google_subscription', }, - required: { + }, + { + id: 'expiryTimeMs', + title: 'Expiry Time (ms)', + type: 'short-input', + placeholder: 'Absolute new expiry time in ms since epoch', + condition: { field: 'operation', value: 'defer_google_subscription', }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a Unix epoch timestamp in milliseconds based on the user's description. +The timestamp should represent the new absolute expiry time of the subscription. + +Return ONLY the numeric timestamp, no text.`, + }, }, { id: 'platform', @@ -251,13 +324,15 @@ Return ONLY valid JSON.`, { label: 'iOS', id: 'ios' }, { label: 'Android', id: 'android' }, { label: 'Amazon', id: 'amazon' }, - { label: 'macOS', id: 'macos' }, { label: 'Stripe', id: 'stripe' }, + { label: 'Roku', id: 'roku' }, + { label: 'Paddle', id: 'paddle' }, ], condition: { field: 'operation', value: 'list_offerings', }, + mode: 'advanced', }, ], tools: { @@ -274,23 +349,32 @@ Return ONLY valid JSON.`, 'revenuecat_revoke_google_subscription', ], config: { - tool: (params) => { + tool: (params) => `revenuecat_${params.operation}`, + params: (params) => { + const next: Record = { ...params } if (params.purchasePlatform && params.operation === 'create_purchase') { - params.platform = params.purchasePlatform + next.platform = params.purchasePlatform } - if (params.isRestore !== undefined) { - params.isRestore = params.isRestore === 'true' + next.purchasePlatform = undefined + if (params.isRestore !== undefined && params.isRestore !== '') { + next.isRestore = params.isRestore === true || params.isRestore === 'true' } if (params.price !== undefined && params.price !== '') { - params.price = Number(params.price) + next.price = Number(params.price) } if (params.extendByDays !== undefined && params.extendByDays !== '') { - params.extendByDays = Number(params.extendByDays) + next.extendByDays = Number(params.extendByDays) } if (params.startTimeMs !== undefined && params.startTimeMs !== '') { - params.startTimeMs = Number(params.startTimeMs) + next.startTimeMs = Number(params.startTimeMs) + } + if (params.endTimeMs !== undefined && params.endTimeMs !== '') { + next.endTimeMs = Number(params.endTimeMs) } - return `revenuecat_${params.operation}` + if (params.expiryTimeMs !== undefined && params.expiryTimeMs !== '') { + next.expiryTimeMs = Number(params.expiryTimeMs) + } + return next }, }, }, @@ -303,25 +387,43 @@ Return ONLY valid JSON.`, startTimeMs: { type: 'number', description: 'Custom start time in ms since epoch' }, fetchToken: { type: 'string', description: 'Store receipt or purchase token' }, productId: { type: 'string', description: 'Product identifier' }, + storeTransactionId: { type: 'string', description: 'Store transaction identifier' }, price: { type: 'number', description: 'Product price' }, currency: { type: 'string', description: 'ISO 4217 currency code' }, isRestore: { type: 'boolean', description: 'Whether this is a restore purchase' }, - purchasePlatform: { type: 'string', description: 'Platform for the purchase' }, + presentedOfferingIdentifier: { + type: 'string', + description: 'Identifier of the offering presented to the user', + }, + paymentMode: { + type: 'string', + description: 'Payment mode (pay_as_you_go, pay_up_front, free_trial)', + }, attributes: { type: 'string', description: 'JSON object of subscriber attributes' }, extendByDays: { type: 'number', description: 'Number of days to extend (1-365)' }, - platform: { type: 'string', description: 'Platform filter for offerings' }, + expiryTimeMs: { type: 'number', description: 'Absolute new expiry time in ms since epoch' }, + endTimeMs: { + type: 'number', + description: 'Absolute end time for entitlement in ms since epoch', + }, + platform: { type: 'string', description: 'Platform (X-Platform header)' }, }, outputs: { subscriber: { type: 'json', - description: 'Subscriber object with subscriptions and entitlements', + description: + 'Subscriber object (first_seen, original_app_user_id, original_purchase_date, management_url, subscriptions, entitlements, non_subscriptions)', }, offerings: { type: 'json', - description: 'Array of offerings with packages', + description: 'Array of offerings, each with identifier, description, and packages[]', }, current_offering_id: { type: 'string', description: 'Current offering identifier' }, - metadata: { type: 'json', description: 'Operation metadata' }, + metadata: { + type: 'json', + description: + 'Operation metadata. For get_customer: app_user_id, first_seen, active_entitlements, active_subscriptions. For list_offerings: count, current_offering_id.', + }, deleted: { type: 'boolean', description: 'Whether the subscriber was deleted' }, app_user_id: { type: 'string', description: 'The app user ID' }, updated: { type: 'boolean', description: 'Whether the attributes were updated' }, diff --git a/apps/sim/tools/revenuecat/create_purchase.ts b/apps/sim/tools/revenuecat/create_purchase.ts index ac259f49833..bea3662c8ad 100644 --- a/apps/sim/tools/revenuecat/create_purchase.ts +++ b/apps/sim/tools/revenuecat/create_purchase.ts @@ -1,5 +1,5 @@ import type { CreatePurchaseParams, CreatePurchaseResponse } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatCreatePurchaseTool: ToolConfig< @@ -53,14 +53,28 @@ export const revenuecatCreatePurchaseTool: ToolConfig< type: 'boolean', required: false, visibility: 'user-or-llm', - description: 'Whether this is a restore of a previous purchase', + description: 'Whether this is a restore of a previous purchase (deprecated by RevenueCat)', }, - platform: { + presentedOfferingIdentifier: { type: 'string', required: false, visibility: 'user-or-llm', description: - 'Platform of the purchase (ios, android, amazon, macos, stripe). Required for Stripe and Paddle purchases.', + 'Identifier of the offering that was presented to the user when they made this purchase. Used by RevenueCat for offering-level analytics.', + }, + paymentMode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Payment mode for the purchase. One of: pay_as_you_go, pay_up_front, free_trial. Only applies to introductory pricing periods.', + }, + platform: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Platform of the purchase. One of: ios, android, amazon, macos, uikitformac, stripe, roku, paddle. Sent as the X-Platform header (required by RevenueCat).', }, }, @@ -86,11 +100,16 @@ export const revenuecatCreatePurchaseTool: ToolConfig< if (params.price !== undefined) body.price = params.price if (params.currency) body.currency = params.currency if (params.isRestore !== undefined) body.is_restore = params.isRestore + if (params.presentedOfferingIdentifier) { + body.presented_offering_identifier = params.presentedOfferingIdentifier + } + if (params.paymentMode) body.payment_mode = params.paymentMode return body }, }, transformResponse: async (response) => { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} diff --git a/apps/sim/tools/revenuecat/defer_google_subscription.ts b/apps/sim/tools/revenuecat/defer_google_subscription.ts index 8a2df5aae13..171d0e4e6f0 100644 --- a/apps/sim/tools/revenuecat/defer_google_subscription.ts +++ b/apps/sim/tools/revenuecat/defer_google_subscription.ts @@ -2,7 +2,7 @@ import type { DeferGoogleSubscriptionParams, DeferGoogleSubscriptionResponse, } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatDeferGoogleSubscriptionTool: ToolConfig< @@ -37,26 +37,38 @@ export const revenuecatDeferGoogleSubscriptionTool: ToolConfig< }, extendByDays: { type: 'number', - required: true, + required: false, + visibility: 'user-or-llm', + description: + 'Number of days to extend the subscription by (1-365). Provide either extendByDays or expiryTimeMs.', + }, + expiryTimeMs: { + type: 'number', + required: false, visibility: 'user-or-llm', - description: 'Number of days to extend the subscription by (1-365)', + description: + 'Absolute new expiry time in milliseconds since Unix epoch. Use instead of extendByDays to set an exact expiry.', }, }, request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/subscriptions/${encodeURIComponent(params.productId)}/defer`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/subscriptions/${encodeURIComponent(params.productId.trim())}/defer`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, 'Content-Type': 'application/json', }), - body: (params) => ({ - extend_by_days: params.extendByDays, - }), + body: (params) => { + const body: Record = {} + if (params.extendByDays !== undefined) body.extend_by_days = params.extendByDays + if (params.expiryTimeMs !== undefined) body.expiry_time_ms = params.expiryTimeMs + return body + }, }, transformResponse: async (response) => { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} diff --git a/apps/sim/tools/revenuecat/delete_customer.ts b/apps/sim/tools/revenuecat/delete_customer.ts index 017de1ce514..2ff4e1468f7 100644 --- a/apps/sim/tools/revenuecat/delete_customer.ts +++ b/apps/sim/tools/revenuecat/delete_customer.ts @@ -1,5 +1,5 @@ import type { DeleteCustomerParams, DeleteCustomerResponse } from '@/tools/revenuecat/types' -import { DELETE_OUTPUT_PROPERTIES } from '@/tools/revenuecat/types' +import { DELETE_OUTPUT_PROPERTIES, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatDeleteCustomerTool: ToolConfig< @@ -28,7 +28,7 @@ export const revenuecatDeleteCustomerTool: ToolConfig< request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -37,11 +37,24 @@ export const revenuecatDeleteCustomerTool: ToolConfig< }, transformResponse: async (response, params) => { + await throwIfRevenueCatError(response) + let body: Record = {} + try { + body = await response.json() + } catch { + // Some delete responses have empty bodies — treat as success + } return { - success: response.ok, + success: true, output: { - deleted: response.ok, - app_user_id: params?.appUserId ?? '', + deleted: + typeof body.deleted === 'boolean' + ? body.deleted + : typeof body.was_deleted === 'boolean' + ? body.was_deleted + : true, + app_user_id: + typeof body.app_user_id === 'string' ? body.app_user_id : (params?.appUserId ?? ''), }, } }, diff --git a/apps/sim/tools/revenuecat/get_customer.ts b/apps/sim/tools/revenuecat/get_customer.ts index 884a47c254b..1fead522810 100644 --- a/apps/sim/tools/revenuecat/get_customer.ts +++ b/apps/sim/tools/revenuecat/get_customer.ts @@ -1,5 +1,9 @@ import type { CustomerResponse, GetCustomerParams } from '@/tools/revenuecat/types' -import { METADATA_OUTPUT_PROPERTIES, SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { + METADATA_OUTPUT_PROPERTIES, + SUBSCRIBER_OUTPUT, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatGetCustomerTool: ToolConfig = { @@ -25,7 +29,7 @@ export const revenuecatGetCustomerTool: ToolConfig - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -34,14 +38,18 @@ export const revenuecatGetCustomerTool: ToolConfig { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} const entitlements = subscriber.entitlements ?? {} const subscriptions = subscriber.subscriptions ?? {} + const requestDate: string | undefined = data.request_date - const activeEntitlements = Object.values(entitlements).filter( - (e: unknown) => (e as Record).is_active - ).length + const now = requestDate ? new Date(requestDate).getTime() : Date.now() + const activeEntitlements = Object.values(entitlements).filter((e: unknown) => { + const expires = (e as Record).expires_date as string | null | undefined + return !expires || new Date(expires).getTime() > now + }).length const activeSubscriptions = Object.keys(subscriptions).length return { diff --git a/apps/sim/tools/revenuecat/grant_entitlement.ts b/apps/sim/tools/revenuecat/grant_entitlement.ts index ad1cb1237a9..c0f38594f8a 100644 --- a/apps/sim/tools/revenuecat/grant_entitlement.ts +++ b/apps/sim/tools/revenuecat/grant_entitlement.ts @@ -1,5 +1,5 @@ import type { GrantEntitlementParams, GrantEntitlementResponse } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatGrantEntitlementTool: ToolConfig< @@ -32,10 +32,17 @@ export const revenuecatGrantEntitlementTool: ToolConfig< }, duration: { type: 'string', - required: true, + required: false, + visibility: 'user-or-llm', + description: + 'Duration of the entitlement. Provide either duration or endTimeMs. One of: daily, three_day, weekly, two_week, monthly, two_month, three_month, six_month, yearly, lifetime', + }, + endTimeMs: { + type: 'number', + required: false, visibility: 'user-or-llm', description: - 'Duration of the entitlement (daily, three_day, weekly, monthly, two_month, three_month, six_month, yearly, lifetime)', + 'Absolute end time in milliseconds since Unix epoch. Use instead of duration to grant the entitlement until a specific timestamp.', }, startTimeMs: { type: 'number', @@ -48,20 +55,23 @@ export const revenuecatGrantEntitlementTool: ToolConfig< request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/entitlements/${encodeURIComponent(params.entitlementIdentifier)}/promotional`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/entitlements/${encodeURIComponent(params.entitlementIdentifier.trim())}/promotional`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, 'Content-Type': 'application/json', }), body: (params) => { - const body: Record = { duration: params.duration } + const body: Record = {} + if (params.duration) body.duration = params.duration + if (params.endTimeMs !== undefined) body.end_time_ms = params.endTimeMs if (params.startTimeMs !== undefined) body.start_time_ms = params.startTimeMs return body }, }, transformResponse: async (response) => { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} diff --git a/apps/sim/tools/revenuecat/list_offerings.ts b/apps/sim/tools/revenuecat/list_offerings.ts index 529c75b8272..f2efd8fdbba 100644 --- a/apps/sim/tools/revenuecat/list_offerings.ts +++ b/apps/sim/tools/revenuecat/list_offerings.ts @@ -2,6 +2,7 @@ import type { ListOfferingsParams, ListOfferingsResponse } from '@/tools/revenue import { OFFERING_OUTPUT_PROPERTIES, OFFERINGS_METADATA_OUTPUT_PROPERTIES, + throwIfRevenueCatError, } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' @@ -34,7 +35,7 @@ export const revenuecatListOfferingsTool: ToolConfig - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/offerings`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/offerings`, method: 'GET', headers: (params) => { const headers: Record = { @@ -49,6 +50,7 @@ export const revenuecatListOfferingsTool: ToolConfig { + await throwIfRevenueCatError(response) const data = await response.json() const offerings = data.offerings ?? [] const currentOfferingId = data.current_offering_id ?? null diff --git a/apps/sim/tools/revenuecat/refund_google_subscription.ts b/apps/sim/tools/revenuecat/refund_google_subscription.ts index 603f92efd66..e315e903ce0 100644 --- a/apps/sim/tools/revenuecat/refund_google_subscription.ts +++ b/apps/sim/tools/revenuecat/refund_google_subscription.ts @@ -2,7 +2,7 @@ import type { RefundGoogleSubscriptionParams, RefundGoogleSubscriptionResponse, } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatRefundGoogleSubscriptionTool: ToolConfig< @@ -11,7 +11,8 @@ export const revenuecatRefundGoogleSubscriptionTool: ToolConfig< > = { id: 'revenuecat_refund_google_subscription', name: 'RevenueCat Refund Google Subscription', - description: 'Refund and optionally revoke a Google Play subscription (Google Play only)', + description: + 'Refund a specific store transaction by its store transaction identifier and revoke access (subscription or non-subscription, last 365 days)', version: '1.0.0', params: { @@ -27,17 +28,18 @@ export const revenuecatRefundGoogleSubscriptionTool: ToolConfig< visibility: 'user-or-llm', description: 'The app user ID of the subscriber', }, - productId: { + storeTransactionId: { type: 'string', required: true, visibility: 'user-or-llm', - description: 'The Google Play product identifier of the subscription to refund', + description: + 'The store transaction identifier of the purchase to refund (e.g., GPA.3309-9122-6177-45730 for Google Play)', }, }, request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/subscriptions/${encodeURIComponent(params.productId)}/refund`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/transactions/${encodeURIComponent(params.storeTransactionId.trim())}/refund`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -46,6 +48,7 @@ export const revenuecatRefundGoogleSubscriptionTool: ToolConfig< }, transformResponse: async (response) => { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} diff --git a/apps/sim/tools/revenuecat/revoke_entitlement.ts b/apps/sim/tools/revenuecat/revoke_entitlement.ts index 5a3c99d0e20..cd24e4a2f2e 100644 --- a/apps/sim/tools/revenuecat/revoke_entitlement.ts +++ b/apps/sim/tools/revenuecat/revoke_entitlement.ts @@ -1,5 +1,5 @@ import type { RevokeEntitlementParams, RevokeEntitlementResponse } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatRevokeEntitlementTool: ToolConfig< @@ -34,7 +34,7 @@ export const revenuecatRevokeEntitlementTool: ToolConfig< request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/entitlements/${encodeURIComponent(params.entitlementIdentifier)}/revoke_promotionals`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/entitlements/${encodeURIComponent(params.entitlementIdentifier.trim())}/revoke_promotionals`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -43,6 +43,7 @@ export const revenuecatRevokeEntitlementTool: ToolConfig< }, transformResponse: async (response) => { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} diff --git a/apps/sim/tools/revenuecat/revoke_google_subscription.ts b/apps/sim/tools/revenuecat/revoke_google_subscription.ts index 68ec3f8e1a8..dc7d450665e 100644 --- a/apps/sim/tools/revenuecat/revoke_google_subscription.ts +++ b/apps/sim/tools/revenuecat/revoke_google_subscription.ts @@ -2,7 +2,7 @@ import type { RevokeGoogleSubscriptionParams, RevokeGoogleSubscriptionResponse, } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT } from '@/tools/revenuecat/types' +import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatRevokeGoogleSubscriptionTool: ToolConfig< @@ -38,7 +38,7 @@ export const revenuecatRevokeGoogleSubscriptionTool: ToolConfig< request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/subscriptions/${encodeURIComponent(params.productId)}/revoke`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/subscriptions/${encodeURIComponent(params.productId.trim())}/revoke`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -47,6 +47,7 @@ export const revenuecatRevokeGoogleSubscriptionTool: ToolConfig< }, transformResponse: async (response) => { + await throwIfRevenueCatError(response) const data = await response.json() const subscriber = data.subscriber ?? {} diff --git a/apps/sim/tools/revenuecat/types.ts b/apps/sim/tools/revenuecat/types.ts index 1d9f0415e8f..48d2eb3527b 100644 --- a/apps/sim/tools/revenuecat/types.ts +++ b/apps/sim/tools/revenuecat/types.ts @@ -186,6 +186,28 @@ export const OFFERINGS_METADATA_OUTPUT_PROPERTIES = { }, } as const satisfies Record +/** + * Parse a RevenueCat REST API error response into a meaningful Error. + * RevenueCat returns `{ code, message }` on 4xx/5xx. + */ +export async function throwIfRevenueCatError(response: Response): Promise { + if (response.ok) return + let message = `RevenueCat API error (${response.status})` + try { + const body = await response.clone().json() + if (body && typeof body === 'object') { + const m = (body as Record).message + const c = (body as Record).code + if (typeof m === 'string' && m.length > 0) { + message = c ? `${m} (code ${c})` : m + } + } + } catch { + // Body not JSON — fall back to status-only message + } + throw new Error(message) +} + /** * Base params interface for RevenueCat API calls */ @@ -204,7 +226,8 @@ export interface DeleteCustomerParams extends RevenueCatBaseParams { export interface GrantEntitlementParams extends RevenueCatBaseParams { appUserId: string entitlementIdentifier: string - duration: string + duration?: string + endTimeMs?: number startTimeMs?: number } @@ -225,7 +248,9 @@ export interface CreatePurchaseParams extends RevenueCatBaseParams { price?: number currency?: string isRestore?: boolean - platform?: string + presentedOfferingIdentifier?: string + paymentMode?: string + platform: string } export interface UpdateSubscriberAttributesParams extends RevenueCatBaseParams { @@ -236,12 +261,13 @@ export interface UpdateSubscriberAttributesParams extends RevenueCatBaseParams { export interface DeferGoogleSubscriptionParams extends RevenueCatBaseParams { appUserId: string productId: string - extendByDays: number + extendByDays?: number + expiryTimeMs?: number } export interface RefundGoogleSubscriptionParams extends RevenueCatBaseParams { appUserId: string - productId: string + storeTransactionId: string } export interface RevokeGoogleSubscriptionParams extends RevenueCatBaseParams { diff --git a/apps/sim/tools/revenuecat/update_subscriber_attributes.ts b/apps/sim/tools/revenuecat/update_subscriber_attributes.ts index 5fed414a43e..bf817ae308c 100644 --- a/apps/sim/tools/revenuecat/update_subscriber_attributes.ts +++ b/apps/sim/tools/revenuecat/update_subscriber_attributes.ts @@ -2,6 +2,7 @@ import type { UpdateSubscriberAttributesParams, UpdateSubscriberAttributesResponse, } from '@/tools/revenuecat/types' +import { throwIfRevenueCatError } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatUpdateSubscriberAttributesTool: ToolConfig< @@ -32,13 +33,13 @@ export const revenuecatUpdateSubscriberAttributesTool: ToolConfig< required: true, visibility: 'user-or-llm', description: - 'JSON object of attributes to set. Each key maps to an object with a "value" field. Example: {"$email": {"value": "user@example.com"}, "$displayName": {"value": "John"}}', + 'JSON object of attributes to set. Each key maps to an object with "value" (string; null or empty deletes the attribute) and "updated_at_ms" (Unix epoch ms used for conflict resolution — required). Example: {"$email": {"value": "user@example.com", "updated_at_ms": 1709195668093}}', }, }, request: { url: (params) => - `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId)}/attributes`, + `https://api.revenuecat.com/v1/subscribers/${encodeURIComponent(params.appUserId.trim())}/attributes`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -52,10 +53,11 @@ export const revenuecatUpdateSubscriberAttributesTool: ToolConfig< }, transformResponse: async (response, params) => { + await throwIfRevenueCatError(response) return { - success: response.ok, + success: true, output: { - updated: response.ok, + updated: true, app_user_id: params?.appUserId ?? '', }, } From f7d9c4fd94e19004f17865318746e4c4ae39b95c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 21:14:46 -0700 Subject: [PATCH 2/4] fix(revenuecat): keep productId subblock for refund op to preserve backwards compat --- .../integrations/data/integrations.json | 2 +- apps/sim/blocks/blocks/revenuecat.ts | 43 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 4fceb728a8b..08fa8bb80d7 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -14173,7 +14173,7 @@ "description": "Hire a pre-hire into an employee position. Converts an applicant into an active employee record with position, start date, and manager assignment." }, { - "name": "Update Worker", + "name": "Update Personal Information", "description": "Update fields on an existing worker record in Workday." }, { diff --git a/apps/sim/blocks/blocks/revenuecat.ts b/apps/sim/blocks/blocks/revenuecat.ts index ad4eaedd11c..0ff4790d0fd 100644 --- a/apps/sim/blocks/blocks/revenuecat.ts +++ b/apps/sim/blocks/blocks/revenuecat.ts @@ -147,30 +147,27 @@ Return ONLY the numeric timestamp, no text.`, }, { id: 'productId', - title: 'Product ID', + title: 'Product ID / Store Transaction ID', type: 'short-input', - placeholder: 'Product identifier', + placeholder: + 'Product ID, or store transaction ID for refunds (e.g., GPA.3309-9122-6177-45730)', condition: { field: 'operation', - value: ['create_purchase', 'defer_google_subscription', 'revoke_google_subscription'], + value: [ + 'create_purchase', + 'defer_google_subscription', + 'refund_google_subscription', + 'revoke_google_subscription', + ], }, required: { field: 'operation', - value: ['create_purchase', 'defer_google_subscription', 'revoke_google_subscription'], - }, - }, - { - id: 'storeTransactionId', - title: 'Store Transaction ID', - type: 'short-input', - placeholder: 'e.g., GPA.3309-9122-6177-45730', - condition: { - field: 'operation', - value: 'refund_google_subscription', - }, - required: { - field: 'operation', - value: 'refund_google_subscription', + value: [ + 'create_purchase', + 'defer_google_subscription', + 'refund_google_subscription', + 'revoke_google_subscription', + ], }, }, { @@ -356,6 +353,10 @@ Return ONLY the numeric timestamp, no text.`, next.platform = params.purchasePlatform } next.purchasePlatform = undefined + if (params.productId && params.operation === 'refund_google_subscription') { + next.storeTransactionId = params.productId + next.productId = undefined + } if (params.isRestore !== undefined && params.isRestore !== '') { next.isRestore = params.isRestore === true || params.isRestore === 'true' } @@ -386,8 +387,10 @@ Return ONLY the numeric timestamp, no text.`, duration: { type: 'string', description: 'Promotional entitlement duration' }, startTimeMs: { type: 'number', description: 'Custom start time in ms since epoch' }, fetchToken: { type: 'string', description: 'Store receipt or purchase token' }, - productId: { type: 'string', description: 'Product identifier' }, - storeTransactionId: { type: 'string', description: 'Store transaction identifier' }, + productId: { + type: 'string', + description: 'Product identifier (or store transaction ID for refunds)', + }, price: { type: 'number', description: 'Product price' }, currency: { type: 'string', description: 'ISO 4217 currency code' }, isRestore: { type: 'boolean', description: 'Whether this is a restore purchase' }, From 873f4cabb5b32d44058e9e651d9f2febc46c7ef2 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 21:26:59 -0700 Subject: [PATCH 3/4] fix(revenuecat): handle entitlement grace periods, guard one-of body params --- apps/sim/tools/revenuecat/defer_google_subscription.ts | 5 ++++- apps/sim/tools/revenuecat/get_customer.ts | 10 ++++++++-- apps/sim/tools/revenuecat/grant_entitlement.ts | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/sim/tools/revenuecat/defer_google_subscription.ts b/apps/sim/tools/revenuecat/defer_google_subscription.ts index 171d0e4e6f0..11ca40eb00d 100644 --- a/apps/sim/tools/revenuecat/defer_google_subscription.ts +++ b/apps/sim/tools/revenuecat/defer_google_subscription.ts @@ -60,9 +60,12 @@ export const revenuecatDeferGoogleSubscriptionTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { + if (params.extendByDays === undefined && params.expiryTimeMs === undefined) { + throw new Error('Provide either extendByDays or expiryTimeMs to defer a subscription') + } const body: Record = {} - if (params.extendByDays !== undefined) body.extend_by_days = params.extendByDays if (params.expiryTimeMs !== undefined) body.expiry_time_ms = params.expiryTimeMs + else if (params.extendByDays !== undefined) body.extend_by_days = params.extendByDays return body }, }, diff --git a/apps/sim/tools/revenuecat/get_customer.ts b/apps/sim/tools/revenuecat/get_customer.ts index 1fead522810..84f1676d975 100644 --- a/apps/sim/tools/revenuecat/get_customer.ts +++ b/apps/sim/tools/revenuecat/get_customer.ts @@ -47,8 +47,14 @@ export const revenuecatGetCustomerTool: ToolConfig { - const expires = (e as Record).expires_date as string | null | undefined - return !expires || new Date(expires).getTime() > now + const ent = e as Record + if (typeof ent.is_active === 'boolean') return ent.is_active + const expires = ent.expires_date as string | null | undefined + const grace = ent.grace_period_expires_date as string | null | undefined + if (!expires) return true + if (new Date(expires).getTime() > now) return true + if (grace && new Date(grace).getTime() > now) return true + return false }).length const activeSubscriptions = Object.keys(subscriptions).length diff --git a/apps/sim/tools/revenuecat/grant_entitlement.ts b/apps/sim/tools/revenuecat/grant_entitlement.ts index c0f38594f8a..08bdec49ecf 100644 --- a/apps/sim/tools/revenuecat/grant_entitlement.ts +++ b/apps/sim/tools/revenuecat/grant_entitlement.ts @@ -62,9 +62,12 @@ export const revenuecatGrantEntitlementTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { + if (!params.duration && params.endTimeMs === undefined) { + throw new Error('Provide either duration or endTimeMs to grant a promotional entitlement') + } const body: Record = {} - if (params.duration) body.duration = params.duration if (params.endTimeMs !== undefined) body.end_time_ms = params.endTimeMs + else if (params.duration) body.duration = params.duration if (params.startTimeMs !== undefined) body.start_time_ms = params.startTimeMs return body }, From e999b64afa487d7f414b679d59cde9275fc6d955 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 6 May 2026 22:00:57 -0700 Subject: [PATCH 4/4] fix(revenuecat): align tools with v1 docs - Unwrap {value:{subscriber}} envelope across post-receipts, attributes, entitlements, and Google sub endpoints - Trim entitlement output to documented fields (expires_date, grace_period_expires_date, product_identifier, purchase_date) - Add subscriber output fields: last_seen, original_application_version, other_purchases, subscriber_attributes - create_purchase: productId optional (Google-only required), add introductoryPrice, attributes, updated_at_ms; surface customer + subscriber - update_subscriber_attributes: read response and surface subscriber - defer_google_subscription: enforce XOR(extendByDays, expiryTimeMs) and 1-365 range - get_customer: count active subs by expiry/refund instead of object-key length --- .../docs/content/docs/en/tools/revenuecat.mdx | 154 ++++++++++------- apps/sim/blocks/blocks/revenuecat.ts | 53 +++++- apps/sim/tools/revenuecat/create_purchase.ts | 72 +++++--- .../revenuecat/defer_google_subscription.ts | 35 ++-- apps/sim/tools/revenuecat/get_customer.ts | 54 +++--- .../sim/tools/revenuecat/grant_entitlement.ts | 16 +- .../revenuecat/refund_google_subscription.ts | 16 +- .../tools/revenuecat/revoke_entitlement.ts | 16 +- .../revenuecat/revoke_google_subscription.ts | 16 +- apps/sim/tools/revenuecat/types.ts | 159 ++++++++++-------- .../update_subscriber_attributes.ts | 16 +- 11 files changed, 384 insertions(+), 223 deletions(-) diff --git a/apps/docs/content/docs/en/tools/revenuecat.mdx b/apps/docs/content/docs/en/tools/revenuecat.mdx index f96a02d0e13..ebd9814dd88 100644 --- a/apps/docs/content/docs/en/tools/revenuecat.mdx +++ b/apps/docs/content/docs/en/tools/revenuecat.mdx @@ -51,8 +51,10 @@ Retrieve subscriber information by app user ID | --------- | ---- | ----------- | | `subscriber` | object | The subscriber object with subscriptions and entitlements | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -71,16 +73,13 @@ Retrieve subscriber information by app user ID | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | | `metadata` | object | Subscriber summary metadata | | ↳ `app_user_id` | string | The app user ID | | ↳ `first_seen` | string | ISO 8601 date when the subscriber was first seen | @@ -115,23 +114,29 @@ Record a purchase (receipt) for a subscriber via the REST API | --------- | ---- | -------- | ----------- | | `apiKey` | string | Yes | RevenueCat API key \(public or secret\) | | `appUserId` | string | Yes | The app user ID of the subscriber | -| `fetchToken` | string | Yes | The receipt token or purchase token from the store \(App Store receipt, Google Play purchase token, or Stripe subscription ID\) | -| `productId` | string | Yes | The product identifier for the purchase | -| `price` | number | No | The price of the product in the currency specified | -| `currency` | string | No | ISO 4217 currency code \(e.g., USD, EUR\) | -| `isRestore` | boolean | No | Whether this is a restore of a previous purchase \(deprecated by RevenueCat\) | -| `presentedOfferingIdentifier` | string | No | Identifier of the offering that was presented to the user when they made this purchase. Used by RevenueCat for offering-level analytics. | -| `paymentMode` | string | No | Payment mode for the purchase. One of: pay_as_you_go, pay_up_front, free_trial. Only applies to introductory pricing periods. | +| `fetchToken` | string | Yes | For iOS, the base64-encoded receipt \(or JWSTransaction for StoreKit2\); for Android the purchase token; for Amazon the receipt; for Stripe the subscription ID or Checkout Session ID; for Roku the transaction ID; for Paddle the subscription ID or transaction ID | +| `productId` | string | No | Apple, Google, Amazon, Roku, or Paddle product identifier or SKU. Required for Google. | +| `price` | number | No | Price of the product. Required if you provide a currency. | +| `currency` | string | No | ISO 4217 currency code \(e.g., USD, EUR\). Required if you provide a price. | +| `isRestore` | boolean | No | Deprecated. Triggers configured restore behavior for shared fetch tokens. | +| `presentedOfferingIdentifier` | string | No | Identifier of the offering presented to the customer at the time of purchase. Attached to new transactions in this fetch token and exposed in ETL exports and webhooks. | +| `paymentMode` | string | No | Payment mode for the introductory period. One of: pay_as_you_go, pay_up_front, free_trial. Defaults to free_trial when an introductory period is detected and no value is provided. | +| `introductoryPrice` | number | No | Introductory price paid \(if any\). | +| `attributes` | json | No | JSON object of subscriber attributes to set alongside the purchase. Each key maps to \{"value": string, "updated_at_ms": number\}. | +| `updatedAtMs` | number | No | UNIX epoch in milliseconds used to resolve attribute conflicts at the request level. | | `platform` | string | Yes | Platform of the purchase. One of: ios, android, amazon, macos, uikitformac, stripe, roku, paddle. Sent as the X-Platform header \(required by RevenueCat\). | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | +| `customer` | object | Customer object returned at the top level of POST /v1/receipts \(first_seen, last_seen, original_app_user_id, original_application_version, original_sdk_version, management_url, entitlements, original_purchase_date, request_date\). Null when the response uses the `value`-wrapped envelope. | | `subscriber` | object | The updated subscriber object after recording the purchase | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -150,16 +155,13 @@ Record a purchase (receipt) for a subscriber via the REST API | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | ### `revenuecat_grant_entitlement` @@ -182,8 +184,10 @@ Grant a promotional entitlement to a subscriber | --------- | ---- | ----------- | | `subscriber` | object | The updated subscriber object after granting the entitlement | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -202,16 +206,13 @@ Grant a promotional entitlement to a subscriber | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | ### `revenuecat_revoke_entitlement` @@ -231,8 +232,10 @@ Revoke all promotional entitlements for a specific entitlement identifier | --------- | ---- | ----------- | | `subscriber` | object | The updated subscriber object after revoking the entitlement | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -251,16 +254,13 @@ Revoke all promotional entitlements for a specific entitlement identifier | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | ### `revenuecat_list_offerings` @@ -307,6 +307,37 @@ Update custom subscriber attributes (e.g., $email, $displayName, or custom key-v | --------- | ---- | ----------- | | `updated` | boolean | Whether the subscriber attributes were successfully updated | | `app_user_id` | string | The app user ID of the updated subscriber | +| `subscriber` | object | The updated subscriber object after applying the attribute changes | +| ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | +| ↳ `original_app_user_id` | string | Original app user ID | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | +| ↳ `management_url` | string | URL for managing the subscriber subscriptions | +| ↳ `subscriptions` | object | Map of product identifiers to subscription objects | +| ↳ `store_transaction_id` | string | Store transaction identifier | +| ↳ `original_transaction_id` | string | Original transaction identifier | +| ↳ `purchase_date` | string | ISO 8601 purchase date | +| ↳ `original_purchase_date` | string | ISO 8601 date of the original purchase | +| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `is_sandbox` | boolean | Whether this is a sandbox purchase | +| ↳ `unsubscribe_detected_at` | string | ISO 8601 date when unsubscribe was detected | +| ↳ `billing_issues_detected_at` | string | ISO 8601 date when billing issues were detected | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | +| ↳ `ownership_type` | string | Ownership type \(purchased, family_shared\) | +| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional, prepaid\) | +| ↳ `store` | string | Store the subscription was purchased from \(app_store, play_store, stripe, etc.\) | +| ↳ `refunded_at` | string | ISO 8601 date when subscription was refunded | +| ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | +| ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | +| ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | +| ↳ `product_identifier` | string | Product identifier | +| ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | +| ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | ### `revenuecat_defer_google_subscription` @@ -328,8 +359,10 @@ Defer a Google Play subscription by extending its billing date by a number of da | --------- | ---- | ----------- | | `subscriber` | object | The updated subscriber object after deferring the Google subscription | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -348,16 +381,13 @@ Defer a Google Play subscription by extending its billing date by a number of da | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | ### `revenuecat_refund_google_subscription` @@ -377,8 +407,10 @@ Refund a specific store transaction by its store transaction identifier and revo | --------- | ---- | ----------- | | `subscriber` | object | The updated subscriber object after refunding the Google subscription | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -397,16 +429,13 @@ Refund a specific store transaction by its store transaction identifier and revo | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | ### `revenuecat_revoke_google_subscription` @@ -426,8 +455,10 @@ Immediately revoke access to a Google Play subscription and issue a refund (Goog | --------- | ---- | ----------- | | `subscriber` | object | The updated subscriber object after revoking the Google subscription | | ↳ `first_seen` | string | ISO 8601 date when subscriber was first seen | +| ↳ `last_seen` | string | ISO 8601 date when subscriber was last seen | | ↳ `original_app_user_id` | string | Original app user ID | -| ↳ `original_purchase_date` | string | ISO 8601 date of original purchase | +| ↳ `original_application_version` | string | iOS only. First App Store version of your app the customer installed | +| ↳ `original_purchase_date` | string | iOS only. Date the app was first purchased/downloaded | | ↳ `management_url` | string | URL for managing the subscriber subscriptions | | ↳ `subscriptions` | object | Map of product identifiers to subscription objects | | ↳ `store_transaction_id` | string | Store transaction identifier | @@ -446,15 +477,12 @@ Immediately revoke access to a Google Play subscription and issue a refund (Goog | ↳ `auto_resume_date` | string | ISO 8601 date when a paused subscription will auto-resume | | ↳ `product_plan_identifier` | string | Google Play base plan identifier \(for products set up after Feb 2023\) | | ↳ `entitlements` | object | Map of entitlement identifiers to entitlement objects | -| ↳ `grant_date` | string | ISO 8601 grant date | -| ↳ `expires_date` | string | ISO 8601 expiration date | +| ↳ `expires_date` | string | ISO 8601 expiration date \(null for non-expiring entitlements\) | +| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `product_identifier` | string | Product identifier | -| ↳ `is_active` | boolean | Whether the entitlement is active | -| ↳ `will_renew` | boolean | Whether the entitlement will renew | -| ↳ `period_type` | string | Period type \(normal, trial, intro, promotional\) | | ↳ `purchase_date` | string | ISO 8601 date of the latest purchase or renewal | -| ↳ `store` | string | Store the entitlement was granted from | -| ↳ `grace_period_expires_date` | string | ISO 8601 grace period expiration date | | ↳ `non_subscriptions` | object | Map of non-subscription product identifiers to arrays of purchase objects | +| ↳ `other_purchases` | object | Other purchases attached to the subscriber | +| ↳ `subscriber_attributes` | object | Custom attributes set on the subscriber. Only returned when using a secret API key | diff --git a/apps/sim/blocks/blocks/revenuecat.ts b/apps/sim/blocks/blocks/revenuecat.ts index 0ff4790d0fd..316a4fe8425 100644 --- a/apps/sim/blocks/blocks/revenuecat.ts +++ b/apps/sim/blocks/blocks/revenuecat.ts @@ -163,7 +163,6 @@ Return ONLY the numeric timestamp, no text.`, required: { field: 'operation', value: [ - 'create_purchase', 'defer_google_subscription', 'refund_google_subscription', 'revoke_google_subscription', @@ -218,6 +217,35 @@ Return ONLY the numeric timestamp, no text.`, }, mode: 'advanced', }, + { + id: 'introductoryPrice', + title: 'Introductory Price', + type: 'short-input', + placeholder: 'e.g., 0.99', + condition: { + field: 'operation', + value: 'create_purchase', + }, + mode: 'advanced', + }, + { + id: 'updatedAtMs', + title: 'Updated At (ms)', + type: 'short-input', + placeholder: 'Unix epoch ms used to resolve attribute conflicts', + condition: { + field: 'operation', + value: 'create_purchase', + }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a Unix epoch timestamp in milliseconds based on the user's description. +Used by RevenueCat to resolve attribute conflicts on a posted purchase. + +Return ONLY the numeric timestamp, no text.`, + }, + }, { id: 'isRestore', title: 'Is Restore', @@ -263,7 +291,7 @@ Return ONLY the numeric timestamp, no text.`, placeholder: '{"$email": {"value": "user@example.com"}}', condition: { field: 'operation', - value: 'update_subscriber_attributes', + value: ['update_subscriber_attributes', 'create_purchase'], }, required: { field: 'operation', @@ -375,6 +403,12 @@ Return ONLY the numeric timestamp, no text.`, if (params.expiryTimeMs !== undefined && params.expiryTimeMs !== '') { next.expiryTimeMs = Number(params.expiryTimeMs) } + if (params.introductoryPrice !== undefined && params.introductoryPrice !== '') { + next.introductoryPrice = Number(params.introductoryPrice) + } + if (params.updatedAtMs !== undefined && params.updatedAtMs !== '') { + next.updatedAtMs = Number(params.updatedAtMs) + } return next }, }, @@ -402,7 +436,16 @@ Return ONLY the numeric timestamp, no text.`, type: 'string', description: 'Payment mode (pay_as_you_go, pay_up_front, free_trial)', }, - attributes: { type: 'string', description: 'JSON object of subscriber attributes' }, + attributes: { + type: 'string', + description: + 'JSON object of subscriber attributes (used by update_subscriber_attributes and create_purchase)', + }, + introductoryPrice: { type: 'number', description: 'Introductory price for the purchase' }, + updatedAtMs: { + type: 'number', + description: 'Unix epoch ms used by RevenueCat to resolve attribute conflicts', + }, extendByDays: { type: 'number', description: 'Number of days to extend (1-365)' }, expiryTimeMs: { type: 'number', description: 'Absolute new expiry time in ms since epoch' }, endTimeMs: { @@ -430,5 +473,9 @@ Return ONLY the numeric timestamp, no text.`, deleted: { type: 'boolean', description: 'Whether the subscriber was deleted' }, app_user_id: { type: 'string', description: 'The app user ID' }, updated: { type: 'boolean', description: 'Whether the attributes were updated' }, + customer: { + type: 'json', + description: 'Customer object returned by create_purchase (when present in the response)', + }, }, } diff --git a/apps/sim/tools/revenuecat/create_purchase.ts b/apps/sim/tools/revenuecat/create_purchase.ts index bea3662c8ad..75b58c1d2a4 100644 --- a/apps/sim/tools/revenuecat/create_purchase.ts +++ b/apps/sim/tools/revenuecat/create_purchase.ts @@ -1,5 +1,11 @@ import type { CreatePurchaseParams, CreatePurchaseResponse } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractCustomer, + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatCreatePurchaseTool: ToolConfig< @@ -29,45 +35,66 @@ export const revenuecatCreatePurchaseTool: ToolConfig< required: true, visibility: 'user-or-llm', description: - 'The receipt token or purchase token from the store (App Store receipt, Google Play purchase token, or Stripe subscription ID)', + 'For iOS, the base64-encoded receipt (or JWSTransaction for StoreKit2); for Android the purchase token; for Amazon the receipt; for Stripe the subscription ID or Checkout Session ID; for Roku the transaction ID; for Paddle the subscription ID or transaction ID', }, productId: { type: 'string', - required: true, + required: false, visibility: 'user-or-llm', - description: 'The product identifier for the purchase', + description: + 'Apple, Google, Amazon, Roku, or Paddle product identifier or SKU. Required for Google.', }, price: { type: 'number', required: false, visibility: 'user-or-llm', - description: 'The price of the product in the currency specified', + description: 'Price of the product. Required if you provide a currency.', }, currency: { type: 'string', required: false, visibility: 'user-or-llm', - description: 'ISO 4217 currency code (e.g., USD, EUR)', + description: 'ISO 4217 currency code (e.g., USD, EUR). Required if you provide a price.', }, isRestore: { type: 'boolean', required: false, visibility: 'user-or-llm', - description: 'Whether this is a restore of a previous purchase (deprecated by RevenueCat)', + description: 'Deprecated. Triggers configured restore behavior for shared fetch tokens.', }, presentedOfferingIdentifier: { type: 'string', required: false, visibility: 'user-or-llm', description: - 'Identifier of the offering that was presented to the user when they made this purchase. Used by RevenueCat for offering-level analytics.', + 'Identifier of the offering presented to the customer at the time of purchase. Attached to new transactions in this fetch token and exposed in ETL exports and webhooks.', }, paymentMode: { type: 'string', required: false, visibility: 'user-or-llm', description: - 'Payment mode for the purchase. One of: pay_as_you_go, pay_up_front, free_trial. Only applies to introductory pricing periods.', + 'Payment mode for the introductory period. One of: pay_as_you_go, pay_up_front, free_trial. Defaults to free_trial when an introductory period is detected and no value is provided.', + }, + introductoryPrice: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Introductory price paid (if any).', + }, + attributes: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'JSON object of subscriber attributes to set alongside the purchase. Each key maps to {"value": string, "updated_at_ms": number}.', + }, + updatedAtMs: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'UNIX epoch in milliseconds used to resolve attribute conflicts at the request level.', }, platform: { type: 'string', @@ -95,8 +122,8 @@ export const revenuecatCreatePurchaseTool: ToolConfig< const body: Record = { app_user_id: params.appUserId, fetch_token: params.fetchToken, - product_id: params.productId, } + if (params.productId) body.product_id = params.productId if (params.price !== undefined) body.price = params.price if (params.currency) body.currency = params.currency if (params.isRestore !== undefined) body.is_restore = params.isRestore @@ -104,6 +131,14 @@ export const revenuecatCreatePurchaseTool: ToolConfig< body.presented_offering_identifier = params.presentedOfferingIdentifier } if (params.paymentMode) body.payment_mode = params.paymentMode + if (params.introductoryPrice !== undefined) { + body.introductory_price = params.introductoryPrice + } + if (params.attributes !== undefined && params.attributes !== '') { + body.attributes = + typeof params.attributes === 'string' ? JSON.parse(params.attributes) : params.attributes + } + if (params.updatedAtMs !== undefined) body.updated_at_ms = params.updatedAtMs return body }, }, @@ -111,23 +146,22 @@ export const revenuecatCreatePurchaseTool: ToolConfig< transformResponse: async (response) => { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - subscriptions: subscriber.subscriptions ?? {}, - entitlements: subscriber.entitlements ?? {}, - non_subscriptions: subscriber.non_subscriptions ?? {}, - }, + customer: extractCustomer(data), + subscriber: shapeSubscriber(extractSubscriber(data)), }, } }, outputs: { + customer: { + type: 'object', + description: + 'Customer object returned at the top level of POST /v1/receipts (first_seen, last_seen, original_app_user_id, original_application_version, original_sdk_version, management_url, entitlements, original_purchase_date, request_date). Null when the response uses the `value`-wrapped envelope.', + optional: true, + }, subscriber: { ...SUBSCRIBER_OUTPUT, description: 'The updated subscriber object after recording the purchase', diff --git a/apps/sim/tools/revenuecat/defer_google_subscription.ts b/apps/sim/tools/revenuecat/defer_google_subscription.ts index 11ca40eb00d..cc744735c2c 100644 --- a/apps/sim/tools/revenuecat/defer_google_subscription.ts +++ b/apps/sim/tools/revenuecat/defer_google_subscription.ts @@ -2,7 +2,12 @@ import type { DeferGoogleSubscriptionParams, DeferGoogleSubscriptionResponse, } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatDeferGoogleSubscriptionTool: ToolConfig< @@ -60,12 +65,25 @@ export const revenuecatDeferGoogleSubscriptionTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - if (params.extendByDays === undefined && params.expiryTimeMs === undefined) { + const hasExtend = params.extendByDays !== undefined + const hasExpiry = params.expiryTimeMs !== undefined + if (!hasExtend && !hasExpiry) { throw new Error('Provide either extendByDays or expiryTimeMs to defer a subscription') } + if (hasExtend && hasExpiry) { + throw new Error( + 'Provide only one of extendByDays or expiryTimeMs — they cannot be used together' + ) + } const body: Record = {} - if (params.expiryTimeMs !== undefined) body.expiry_time_ms = params.expiryTimeMs - else if (params.extendByDays !== undefined) body.extend_by_days = params.extendByDays + if (hasExpiry) body.expiry_time_ms = params.expiryTimeMs + else if (hasExtend) { + const days = params.extendByDays as number + if (!Number.isFinite(days) || days < 1 || days > 365) { + throw new Error('extendByDays must be an integer between 1 and 365') + } + body.extend_by_days = days + } return body }, }, @@ -73,17 +91,10 @@ export const revenuecatDeferGoogleSubscriptionTool: ToolConfig< transformResponse: async (response) => { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - subscriptions: subscriber.subscriptions ?? {}, - entitlements: subscriber.entitlements ?? {}, - }, + subscriber: shapeSubscriber(extractSubscriber(data)), }, } }, diff --git a/apps/sim/tools/revenuecat/get_customer.ts b/apps/sim/tools/revenuecat/get_customer.ts index 84f1676d975..6989078be1c 100644 --- a/apps/sim/tools/revenuecat/get_customer.ts +++ b/apps/sim/tools/revenuecat/get_customer.ts @@ -1,7 +1,9 @@ import type { CustomerResponse, GetCustomerParams } from '@/tools/revenuecat/types' import { + extractSubscriber, METADATA_OUTPUT_PROPERTIES, SUBSCRIBER_OUTPUT, + shapeSubscriber, throwIfRevenueCatError, } from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' @@ -40,39 +42,47 @@ export const revenuecatGetCustomerTool: ToolConfig { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - const entitlements = subscriber.entitlements ?? {} - const subscriptions = subscriber.subscriptions ?? {} - const requestDate: string | undefined = data.request_date - + const subscriberRaw = extractSubscriber(data) + const subscriber = shapeSubscriber(subscriberRaw) + const requestDate = (data?.value?.request_date ?? data?.request_date) as string | undefined const now = requestDate ? new Date(requestDate).getTime() : Date.now() - const activeEntitlements = Object.values(entitlements).filter((e: unknown) => { - const ent = e as Record - if (typeof ent.is_active === 'boolean') return ent.is_active - const expires = ent.expires_date as string | null | undefined - const grace = ent.grace_period_expires_date as string | null | undefined + + const isActiveByDates = ( + expires: string | null | undefined, + grace: string | null | undefined, + refundedAt?: string | null | undefined + ) => { + if (refundedAt) return false if (!expires) return true if (new Date(expires).getTime() > now) return true if (grace && new Date(grace).getTime() > now) return true return false + } + + const activeEntitlements = Object.values(subscriber.entitlements).filter((e) => { + const ent = e as Record + return isActiveByDates( + ent.expires_date as string | null | undefined, + ent.grace_period_expires_date as string | null | undefined + ) + }).length + + const activeSubscriptions = Object.values(subscriber.subscriptions).filter((s) => { + const sub = s as Record + return isActiveByDates( + sub.expires_date as string | null | undefined, + sub.grace_period_expires_date as string | null | undefined, + sub.refunded_at as string | null | undefined + ) }).length - const activeSubscriptions = Object.keys(subscriptions).length return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - original_purchase_date: subscriber.original_purchase_date ?? null, - management_url: subscriber.management_url ?? null, - subscriptions: subscriptions, - entitlements: entitlements, - non_subscriptions: subscriber.non_subscriptions ?? {}, - }, + subscriber, metadata: { - app_user_id: subscriber.original_app_user_id ?? '', - first_seen: subscriber.first_seen ?? '', + app_user_id: subscriber.original_app_user_id, + first_seen: subscriber.first_seen, active_entitlements: activeEntitlements, active_subscriptions: activeSubscriptions, }, diff --git a/apps/sim/tools/revenuecat/grant_entitlement.ts b/apps/sim/tools/revenuecat/grant_entitlement.ts index 08bdec49ecf..d17946b7d8e 100644 --- a/apps/sim/tools/revenuecat/grant_entitlement.ts +++ b/apps/sim/tools/revenuecat/grant_entitlement.ts @@ -1,5 +1,10 @@ import type { GrantEntitlementParams, GrantEntitlementResponse } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatGrantEntitlementTool: ToolConfig< @@ -76,17 +81,10 @@ export const revenuecatGrantEntitlementTool: ToolConfig< transformResponse: async (response) => { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - subscriptions: subscriber.subscriptions ?? {}, - entitlements: subscriber.entitlements ?? {}, - }, + subscriber: shapeSubscriber(extractSubscriber(data)), }, } }, diff --git a/apps/sim/tools/revenuecat/refund_google_subscription.ts b/apps/sim/tools/revenuecat/refund_google_subscription.ts index e315e903ce0..8fa2a03f043 100644 --- a/apps/sim/tools/revenuecat/refund_google_subscription.ts +++ b/apps/sim/tools/revenuecat/refund_google_subscription.ts @@ -2,7 +2,12 @@ import type { RefundGoogleSubscriptionParams, RefundGoogleSubscriptionResponse, } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatRefundGoogleSubscriptionTool: ToolConfig< @@ -50,17 +55,10 @@ export const revenuecatRefundGoogleSubscriptionTool: ToolConfig< transformResponse: async (response) => { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - subscriptions: subscriber.subscriptions ?? {}, - entitlements: subscriber.entitlements ?? {}, - }, + subscriber: shapeSubscriber(extractSubscriber(data)), }, } }, diff --git a/apps/sim/tools/revenuecat/revoke_entitlement.ts b/apps/sim/tools/revenuecat/revoke_entitlement.ts index cd24e4a2f2e..015d4bc7936 100644 --- a/apps/sim/tools/revenuecat/revoke_entitlement.ts +++ b/apps/sim/tools/revenuecat/revoke_entitlement.ts @@ -1,5 +1,10 @@ import type { RevokeEntitlementParams, RevokeEntitlementResponse } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatRevokeEntitlementTool: ToolConfig< @@ -45,17 +50,10 @@ export const revenuecatRevokeEntitlementTool: ToolConfig< transformResponse: async (response) => { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - subscriptions: subscriber.subscriptions ?? {}, - entitlements: subscriber.entitlements ?? {}, - }, + subscriber: shapeSubscriber(extractSubscriber(data)), }, } }, diff --git a/apps/sim/tools/revenuecat/revoke_google_subscription.ts b/apps/sim/tools/revenuecat/revoke_google_subscription.ts index dc7d450665e..208ac42e697 100644 --- a/apps/sim/tools/revenuecat/revoke_google_subscription.ts +++ b/apps/sim/tools/revenuecat/revoke_google_subscription.ts @@ -2,7 +2,12 @@ import type { RevokeGoogleSubscriptionParams, RevokeGoogleSubscriptionResponse, } from '@/tools/revenuecat/types' -import { SUBSCRIBER_OUTPUT, throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatRevokeGoogleSubscriptionTool: ToolConfig< @@ -49,17 +54,10 @@ export const revenuecatRevokeGoogleSubscriptionTool: ToolConfig< transformResponse: async (response) => { await throwIfRevenueCatError(response) const data = await response.json() - const subscriber = data.subscriber ?? {} - return { success: true, output: { - subscriber: { - first_seen: subscriber.first_seen ?? '', - original_app_user_id: subscriber.original_app_user_id ?? '', - subscriptions: subscriber.subscriptions ?? {}, - entitlements: subscriber.entitlements ?? {}, - }, + subscriber: shapeSubscriber(extractSubscriber(data)), }, } }, diff --git a/apps/sim/tools/revenuecat/types.ts b/apps/sim/tools/revenuecat/types.ts index 48d2eb3527b..4376dacaf2b 100644 --- a/apps/sim/tools/revenuecat/types.ts +++ b/apps/sim/tools/revenuecat/types.ts @@ -76,43 +76,40 @@ export const SUBSCRIPTION_OUTPUT_PROPERTIES = { } as const satisfies Record export const ENTITLEMENT_OUTPUT_PROPERTIES = { - grant_date: { type: 'string', description: 'ISO 8601 grant date', optional: true }, - expires_date: { type: 'string', description: 'ISO 8601 expiration date', optional: true }, - product_identifier: { type: 'string', description: 'Product identifier', optional: true }, - is_active: { type: 'boolean', description: 'Whether the entitlement is active' }, - will_renew: { - type: 'boolean', - description: 'Whether the entitlement will renew', + expires_date: { + type: 'string', + description: 'ISO 8601 expiration date (null for non-expiring entitlements)', optional: true, }, - period_type: { + grace_period_expires_date: { type: 'string', - description: 'Period type (normal, trial, intro, promotional)', + description: 'ISO 8601 grace period expiration date', optional: true, }, + product_identifier: { type: 'string', description: 'Product identifier', optional: true }, purchase_date: { type: 'string', description: 'ISO 8601 date of the latest purchase or renewal', optional: true, }, - store: { +} as const satisfies Record + +export const SUBSCRIBER_OUTPUT_PROPERTIES = { + first_seen: { type: 'string', description: 'ISO 8601 date when subscriber was first seen' }, + last_seen: { type: 'string', - description: 'Store the entitlement was granted from', + description: 'ISO 8601 date when subscriber was last seen', optional: true, }, - grace_period_expires_date: { + original_app_user_id: { type: 'string', description: 'Original app user ID' }, + original_application_version: { type: 'string', - description: 'ISO 8601 grace period expiration date', + description: 'iOS only. First App Store version of your app the customer installed', optional: true, }, -} as const satisfies Record - -export const SUBSCRIBER_OUTPUT_PROPERTIES = { - first_seen: { type: 'string', description: 'ISO 8601 date when subscriber was first seen' }, - original_app_user_id: { type: 'string', description: 'Original app user ID' }, original_purchase_date: { type: 'string', - description: 'ISO 8601 date of original purchase', + description: 'iOS only. Date the app was first purchased/downloaded', optional: true, }, management_url: { @@ -135,6 +132,17 @@ export const SUBSCRIBER_OUTPUT_PROPERTIES = { description: 'Map of non-subscription product identifiers to arrays of purchase objects', optional: true, }, + other_purchases: { + type: 'object', + description: 'Other purchases attached to the subscriber', + optional: true, + }, + subscriber_attributes: { + type: 'object', + description: + 'Custom attributes set on the subscriber. Only returned when using a secret API key', + optional: true, + }, } as const satisfies Record export const SUBSCRIBER_OUTPUT: OutputProperty = { @@ -186,6 +194,29 @@ export const OFFERINGS_METADATA_OUTPUT_PROPERTIES = { }, } as const satisfies Record +/** + * Several RevenueCat v1 endpoints (post receipts, update attributes, revoke promotionals, + * defer/refund/revoke Google subscriptions) wrap responses in `{ value: { request_date, subscriber } }`. + * GET customer info returns the same payload unwrapped. This helper handles both shapes. + */ +export function extractSubscriber(data: unknown): Record { + if (!data || typeof data !== 'object') return {} + const root = data as Record + const wrapped = root.value as Record | undefined + const subscriber = (wrapped?.subscriber ?? root.subscriber) as Record | undefined + return subscriber ?? {} +} + +/** + * POST /v1/receipts may return a top-level `customer` object alongside `subscriber`. + * Returns null when not present (e.g., wrapped envelope responses). + */ +export function extractCustomer(data: unknown): Record | null { + if (!data || typeof data !== 'object') return null + const customer = (data as Record).customer + return customer && typeof customer === 'object' ? (customer as Record) : null +} + /** * Parse a RevenueCat REST API error response into a meaningful Error. * RevenueCat returns `{ code, message }` on 4xx/5xx. @@ -244,12 +275,15 @@ export interface ListOfferingsParams extends RevenueCatBaseParams { export interface CreatePurchaseParams extends RevenueCatBaseParams { appUserId: string fetchToken: string - productId: string + productId?: string price?: number currency?: string isRestore?: boolean presentedOfferingIdentifier?: string paymentMode?: string + introductoryPrice?: number + attributes?: string + updatedAtMs?: number platform: string } @@ -275,17 +309,39 @@ export interface RevokeGoogleSubscriptionParams extends RevenueCatBaseParams { productId: string } +export interface RevenueCatSubscriber { + first_seen: string + last_seen: string | null + original_app_user_id: string + original_application_version: string | null + original_purchase_date: string | null + management_url: string | null + subscriptions: Record + entitlements: Record + non_subscriptions: Record + other_purchases: Record + subscriber_attributes: Record | null +} + +export function shapeSubscriber(raw: Record): RevenueCatSubscriber { + return { + first_seen: (raw.first_seen as string) ?? '', + last_seen: (raw.last_seen as string | null) ?? null, + original_app_user_id: (raw.original_app_user_id as string) ?? '', + original_application_version: (raw.original_application_version as string | null) ?? null, + original_purchase_date: (raw.original_purchase_date as string | null) ?? null, + management_url: (raw.management_url as string | null) ?? null, + subscriptions: (raw.subscriptions as Record) ?? {}, + entitlements: (raw.entitlements as Record) ?? {}, + non_subscriptions: (raw.non_subscriptions as Record) ?? {}, + other_purchases: (raw.other_purchases as Record) ?? {}, + subscriber_attributes: (raw.subscriber_attributes as Record | null) ?? null, + } +} + export interface CustomerResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - original_purchase_date: string | null - management_url: string | null - subscriptions: Record - entitlements: Record - non_subscriptions: Record - } + subscriber: RevenueCatSubscriber metadata: { app_user_id: string first_seen: string @@ -304,23 +360,13 @@ export interface DeleteCustomerResponse extends ToolResponse { export interface GrantEntitlementResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - subscriptions: Record - entitlements: Record - } + subscriber: RevenueCatSubscriber } } export interface RevokeEntitlementResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - subscriptions: Record - entitlements: Record - } + subscriber: RevenueCatSubscriber } } @@ -344,13 +390,8 @@ export interface ListOfferingsResponse extends ToolResponse { export interface CreatePurchaseResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - subscriptions: Record - entitlements: Record - non_subscriptions: Record - } + customer: Record | null + subscriber: RevenueCatSubscriber } } @@ -358,39 +399,25 @@ export interface UpdateSubscriberAttributesResponse extends ToolResponse { output: { updated: boolean app_user_id: string + subscriber: RevenueCatSubscriber } } export interface DeferGoogleSubscriptionResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - subscriptions: Record - entitlements: Record - } + subscriber: RevenueCatSubscriber } } export interface RefundGoogleSubscriptionResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - subscriptions: Record - entitlements: Record - } + subscriber: RevenueCatSubscriber } } export interface RevokeGoogleSubscriptionResponse extends ToolResponse { output: { - subscriber: { - first_seen: string - original_app_user_id: string - subscriptions: Record - entitlements: Record - } + subscriber: RevenueCatSubscriber } } diff --git a/apps/sim/tools/revenuecat/update_subscriber_attributes.ts b/apps/sim/tools/revenuecat/update_subscriber_attributes.ts index bf817ae308c..0603d2b77f4 100644 --- a/apps/sim/tools/revenuecat/update_subscriber_attributes.ts +++ b/apps/sim/tools/revenuecat/update_subscriber_attributes.ts @@ -2,7 +2,12 @@ import type { UpdateSubscriberAttributesParams, UpdateSubscriberAttributesResponse, } from '@/tools/revenuecat/types' -import { throwIfRevenueCatError } from '@/tools/revenuecat/types' +import { + extractSubscriber, + SUBSCRIBER_OUTPUT, + shapeSubscriber, + throwIfRevenueCatError, +} from '@/tools/revenuecat/types' import type { ToolConfig } from '@/tools/types' export const revenuecatUpdateSubscriberAttributesTool: ToolConfig< @@ -54,11 +59,14 @@ export const revenuecatUpdateSubscriberAttributesTool: ToolConfig< transformResponse: async (response, params) => { await throwIfRevenueCatError(response) + const data = await response.json().catch(() => ({})) + const subscriber = shapeSubscriber(extractSubscriber(data)) return { success: true, output: { updated: true, - app_user_id: params?.appUserId ?? '', + app_user_id: subscriber.original_app_user_id || (params?.appUserId ?? ''), + subscriber, }, } }, @@ -72,5 +80,9 @@ export const revenuecatUpdateSubscriberAttributesTool: ToolConfig< type: 'string', description: 'The app user ID of the updated subscriber', }, + subscriber: { + ...SUBSCRIBER_OUTPUT, + description: 'The updated subscriber object after applying the attribute changes', + }, }, }