Skip to content

Public API

Use the public API from mobile apps, web apps, and browser code. Authenticate with the project SDK publishable key from Project Settings.

http
X-SDK-Publishable-Key: <project-sdk-publishable-key>

Base URL:

text
https://sdk-api.funnelsgrove.com

Runtime Methods

funnelSdkService exposes these public methods:

MethodPurpose
bootstrapUserStart or restore a funnel user session.
captureEmailSave the user's email during the funnel.
getCurrentUserLoad the current funnel user.
getUserSubscriptionStatusCompatibility method for the legacy GET status endpoint.
claimUserSubscriptionCompatibility method for explicitly claiming a paid user.
updateFunnelUserSave user profile, progress, attribution, or subscription data.
uploadPhotoFileUpload a user photo directly to R2 through a short-lived signed URL.
uploadPhotoDeprecated base64 compatibility method for already-published bundles.
createHostedCheckoutSessionCreate a hosted checkout session.
createSubscriptionCheckoutStart embedded subscription checkout.
createOneTimeCheckoutStart embedded one-time checkout.
updateSubscriptionCheckoutPlanChange the plan in an active subscription checkout.
verifySubscriptionCheckoutVerify subscription checkout completion.
createOneTimePaymentIntentDeprecated; use createOneTimeCheckout.
chargeOneClickPaymentCharge a saved customer payment method.
updateSubscriptionPerform a supported subscription action.
listSubscriptionsList subscriptions for a user or Stripe customer.

Direct Photo Upload

uploadPhotoFile first sends camelCase metadata to POST /sdk/public/uploads/photos/prepare, then uploads the Blob directly to the returned R2 URL. Image bytes never pass through the API process. The legacy POST /sdk/public/uploads/photos base64 route remains temporarily available for older published funnel bundles.

Retryable Responses

Public user bootstrap and upsert can return HTTP 409 with code: "SDK_CONCURRENT_UPDATE" after their bounded internal conflict retries are exhausted. The response includes Retry-After: 1 and retryable: true; retry the same logical request after that delay.

Checkout ownership persistence replays the same idempotent ownership write once only when the first failure is classified as transient, without creating another Stripe Checkout Session. If the retry also has a classified transient failure, the single session is expired and the route returns HTTP 503 with code: "SDK_DEPENDENCY_UNAVAILABLE", Retry-After: 1, and retryable: true. Permanent or unclassified failures are not retried and return a redacted, non-retryable HTTP 500 after the session is expired. Provider and database error details are never returned.

Subscription verification bounds its project, user, and subscription reads to one request deadline. A classified transient read failure is retried once; if it still fails, the route returns the same retryable HTTP 503 contract instead of reporting inactive access or an opaque 500.

Verify Subscription Status

Use the project SDK publishable key with either a known FunnelsGrove userId or a verified email. funnelId is optional.

Verify with a known user ID:

http
POST /sdk/public/users/verify_subscription
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "userId": "u_123"
}

Or verify with an email only after the app has confirmed ownership through OTP or equivalent authentication:

http
POST /sdk/public/users/verify_subscription
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "email": "verified-user@example.com"
}

The endpoint is read-only. It returns the access flag, a test flag, and the stable public user ID; it never returns the user's email, internal database ID, or subscription records. test is true when the selected active subscription belongs to Stripe test mode.

json
{
  "active": true,
  "test": false,
  "user": {
    "user_id": "u_123"
  }
}

If no matching user has active access, active is false. When the identity does not resolve to a project user, user is null.

json
{
  "active": false,
  "test": false,
  "user": null
}

Track App Login

Track app login only after the user has verified ownership of the funnel email through OTP or your backend has already proven the app-account ↔ FunnelsGrove-user association. Read status first, call this endpoint when active is true, and then persist the returned user.user_id on the authenticated account.

Pass the FunnelsGrove user ID in the request body:

http
POST /sdk/public/users/track_login
Content-Type: application/json
X-SDK-Publishable-Key: <project-sdk-publishable-key>

{
  "userId": "u_123",
  "runtimeEnvironment": "test"
}

The endpoint verifies active access and records the login, but it does not receive or store your app account ID.

runtimeEnvironment is optional. When it is live or test, only that environment is checked. When it is omitted, the endpoint checks live first and checks test only if live has no matching active subscription.

Save the returned user.user_id. It is the stable identifier accepted by POST /sdk/public/users/verify_subscription.

An active subscription is required. The first success returns HTTP 200 with active: true, login.tracked: true, and login.newlyTracked: true. A repeat also returns 200, with login.newlyTracked: false. HTTP 409 is reserved for an ownership conflict or a concurrent incompatible state. Immediately after checkout, 404 No active subscription found can mean the Stripe webhook has not arrived yet, so retry the same POST with bounded backoff.

FunnelsGrove records registration_completed after the first successful login-tracking request. Replays reuse the same event identity.

Deprecated: GET Subscription Status

The legacy read-only routes remain available for existing integrations:

http
GET /sdk/public/users/:user_id/subscription
GET /sdk/public/users/subscription?email=<encoded-email>

New integrations should use POST /sdk/public/users/verify_subscription. Legacy responses retain their larger payload and include a Deprecation: true response header.

Deprecated: Claim Subscription

The legacy endpoint remains available for existing integrations:

http
POST /sdk/public/users/:user_id/claim_subscription
X-SDK-Publishable-Key: <project-sdk-publishable-key>

New integrations should use POST /sdk/public/users/track_login. The legacy response keeps the original claim.claimed and claim.newlyClaimed fields and includes a Deprecation: true response header.