Download OpenAPI specification:
Payment and Subscription Management API for Convilyn.
This API handles all payment-related functionality:
Convilyn uses Paddle as the payment processor.
Most endpoints require Bearer token authentication. Public endpoints: /plans, /validate-email, /leads, /status/{lead_id}, /activate
Returns available subscription plans with pricing, features, and promotion info.
V1.2 Changes:
pricing object with monthly/yearly pricespromotion object for active promotionsbusiness tier supportSecurity: Public endpoint (no auth required).
Pricing Source: Backend billing.py is the single source of truth.
Frontend should use this API to display prices dynamically.
| Accept-Language | string Default: en Locale for plan descriptions (en, zh-TW, ja, ko) |
{- "plans": [
- {
- "id": "free",
- "name": "Free",
- "tier": "free",
- "pricing": null,
- "features": {
- "goalModeRequests": 5,
- "fileSizeLimitMb": 100,
- "batchProcessing": true,
- "priorityQueue": false
}
}, - {
- "id": "pro_monthly",
- "name": "Pro",
- "tier": "pro",
- "billingCycle": "monthly",
- "paddlePriceId": "pri_pro_monthly",
- "pricing": {
- "amount": 1600,
- "currency": "USD",
- "formatted": "$16"
}, - "features": {
- "goalModeRequests": 100,
- "fileSizeLimitMb": 500,
- "batchProcessing": true,
- "priorityQueue": true
}
}, - {
- "id": "pro_yearly",
- "name": "Pro",
- "tier": "pro",
- "billingCycle": "yearly",
- "paddlePriceId": "pri_pro_yearly",
- "pricing": {
- "amount": 14400,
- "currency": "USD",
- "formatted": "$144",
- "monthlyEquivalent": "$12",
- "savingsPercent": 25
}, - "features": {
- "goalModeRequests": 100,
- "fileSizeLimitMb": 500,
- "batchProcessing": true,
- "priorityQueue": true
}
}, - {
- "id": "business_monthly",
- "name": "Business",
- "tier": "business",
- "billingCycle": "monthly",
- "paddlePriceId": "pri_business_monthly",
- "pricing": {
- "amount": 3500,
- "currency": "USD",
- "formatted": "$35"
}, - "features": {
- "goalModeRequests": -1,
- "fileSizeLimitMb": 1000,
- "batchProcessing": true,
- "priorityQueue": true
}
}, - {
- "id": "business_yearly",
- "name": "Business",
- "tier": "business",
- "billingCycle": "yearly",
- "paddlePriceId": "pri_business_yearly",
- "pricing": {
- "amount": 35700,
- "currency": "USD",
- "formatted": "$357",
- "monthlyEquivalent": "$29.75",
- "savingsPercent": 15
}, - "features": {
- "goalModeRequests": -1,
- "fileSizeLimitMb": 1000,
- "batchProcessing": true,
- "priorityQueue": true
}
}
], - "promotion": {
- "active": true,
- "id": "spring_sale_2026",
- "badge": "spring_sale",
- "discountPercent": {
- "monthly": 20,
- "yearly": 25
}, - "endDate": "2026-03-31",
- "discountIds": {
- "proMonthly": "dsc_01kgq0gve8rh2mv314zjy076s2",
- "proYearly": "dsc_01kgxhygh1sk7j23vcd6nk178n",
- "businessMonthly": "dsc_01kgq0hxt6m3wng403ngyx5cpv",
- "businessYearly": "dsc_01kgxhxnx8fz3kvxd7hb3yadh1"
}
}
}Pre-payment email validation to prevent users from paying for a subscription when their email is already registered.
Important: This is a UX hint only - non-authoritative.
The authoritative check happens in /payment/activate.
Use Case: Checkout page validates email as user types, showing "Sign in instead" link if email is already registered.
Security: Public endpoint (no auth required). Rate limiting should be applied at infrastructure level.
| email required | string <email> [ 5 .. 254 ] characters Email to validate before checkout |
{- "email": "user@example.com"
}{- "status": "available"
}Check if email is available for account creation during lead activation.
Security: Public endpoint (no auth required).
Use Case: Frontend checks email availability as user types in the signup form after payment, showing inline error with "Sign in instead" link if email is taken.
Difference from /validate-email:
| leadId required | string [ 1 .. 100 ] characters Lead ID to validate (must be in paid state) |
| email required | string <email> [ 5 .. 254 ] characters Email to check availability for |
{- "leadId": "lead_abc123def456",
- "email": "user@example.com"
}{- "available": true,
- "email": "user@example.com"
}Creates a lead record for the Pay-Before-Signup flow.
Flow:
Security: Public endpoint (no auth required).
| priceId required | string [ 4 .. 100 ] characters Paddle price ID (pri_...) |
string or null <email> Optional email (captured during checkout) | |
| source | string or null <= 50 characters Lead source (pricing_page, upgrade_prompt) |
{- "priceId": "pri_01234567890",
- "email": "user@example.com",
- "source": "pricing_page"
}{- "lead_id": "lead_abc123def456",
- "checkout_url": null
}Poll this endpoint after Paddle checkout to check payment status.
Security: Public endpoint. Lead ID is a UUID, so guessing is impractical.
States:
pending: Waiting for paymentpaid: Payment received, ready for activationcompleted: Account createdexpired: TTL exceeded (24h)Email Conflict Detection (v1.1):
When payment_state is paid and lead has email, the response includes
activation_blocked and activation_block_reason to warn users early
if their email is already registered.
| lead_id required | string Lead ID from /payment/leads response |
{- "payment_state": "pending",
- "is_expired": false,
- "can_activate": false
}Creates a user account from a paid lead. This is the authoritative gate for activation - even if pre-validation passed, this endpoint makes the final decision.
Prerequisites:
Response Variants (all return HTTP 200):
status: success - Account created, includes auth tokensstatus: email_taken - Email conflict, includes recovery actionstatus: already_activated - Lead already used (idempotent)Why not 409 for email conflict?
action: SIGN_IN_TO_CLAIM tells frontend to redirect to loginpaddleCustomerId so support can link subscription if neededEffects (on success):
Security: Public endpoint. Lead validation prevents abuse.
| leadId required | string [ 1 .. 100 ] characters Lead ID to activate |
| email required | string <email> Email for the new account |
| password required | string [ 8 .. 128 ] characters Password for the new account |
| name | string or null <= 100 characters Display name |
{- "leadId": "lead_abc123def456",
- "email": "user@example.com",
- "password": "SecureP@ss123!",
- "name": "John Doe"
}{- "status": "success",
- "user": {
- "id": "user_123",
- "email": "user@example.com",
- "name": "John Doe",
- "emailVerified": false
}, - "accessToken": "eyJhbGciOiJIUzI1NiIs...",
- "refreshToken": "eyJhbGciOiJIUzI1NiIs...",
- "subscription": { }
}Returns the authenticated user's subscription information.
Security: Requires authentication.
{- "has_subscription": true,
- "plan": "string",
- "status": "active",
- "currency": "string",
- "unit_amount": 0,
- "current_period_start": "2019-08-24T14:15:22Z",
- "current_period_end": "2019-08-24T14:15:22Z",
- "cancel_at_period_end": true,
- "is_active": true,
- "is_trial": true,
- "trial_end": "2019-08-24T14:15:22Z",
- "features": {
- "goalLaneRequests": 0,
- "aiToolCalls": 0,
- "fileSizeLimitMb": 0,
- "batchProcessing": true,
- "priorityQueue": true,
- "turboConversions": true,
- "concurrentJobs": 0,
- "batchLimit": 0,
- "aiQualityTier": "haiku"
}, - "paddle_subscription_id": "string",
- "paddle_customer_id": "string"
}Returns feature flags and limits for the user's current plan.
Security: Requires authentication.
{- "goalLaneRequests": 0,
- "aiToolCalls": 0,
- "fileSizeLimitMb": 0,
- "batchProcessing": true,
- "priorityQueue": true,
- "turboConversions": true,
- "concurrentJobs": 0,
- "batchLimit": 0,
- "aiQualityTier": "haiku"
}Creates a Paddle checkout session for subscription purchase.
Security: Requires authentication. User ID is embedded in checkout customData for webhook association.
| priceId required | string [ 4 .. 100 ] characters Paddle price ID (pri_...) |
| successUrl | string or null <uri> <= 500 characters Redirect URL after successful checkout |
| cancelUrl | string or null <uri> <= 500 characters Redirect URL if checkout canceled |
{- "priceId": "pri_01234567890",
}{- "transactionId": "string"
}Cancels the user's subscription at the end of the billing period.
Behavior:
Security: Requires authentication.
{- "status": "success",
- "message": "Subscription will be canceled at the end of the billing period",
- "scheduledCancellation": "2026-02-28T23:59:59Z"
}Reactivates a subscription that's scheduled for cancellation.
Use Case: User cancelled their subscription but changed their mind before the current billing period ends.
Behavior:
Security: Requires authentication.
{- "status": "success",
- "message": "Subscription has been reactivated and will continue to renew"
}Switches between monthly and yearly billing.
Behavior:
Security: Requires authentication.
| targetBillingCycle required | string Enum: "monthly" "yearly" Target billing cycle |
{- "targetBillingCycle": "yearly"
}{- "status": "string",
- "previousPlan": "string",
- "newPlan": "string",
- "message": "string"
}Forces a sync with Paddle API when webhook hasn't arrived yet.
Use Case: After checkout, user returns but webhook delayed. Pass Paddle IDs from checkout.completed event for direct lookup.
Security: Requires authentication. Rate limited.
| paddleCustomerId | string or null <= 100 characters Paddle customer ID (ctm_...) |
| paddleSubscriptionId | string or null <= 100 characters Paddle subscription ID (sub_...) |
{- "paddleCustomerId": "ctm_abc123",
- "paddleSubscriptionId": "sub_def456"
}{- "has_subscription": true,
- "plan": "string",
- "status": "active",
- "currency": "string",
- "unit_amount": 0,
- "current_period_start": "2019-08-24T14:15:22Z",
- "current_period_end": "2019-08-24T14:15:22Z",
- "cancel_at_period_end": true,
- "is_active": true,
- "is_trial": true,
- "trial_end": "2019-08-24T14:15:22Z",
- "features": {
- "goalLaneRequests": 0,
- "aiToolCalls": 0,
- "fileSizeLimitMb": 0,
- "batchProcessing": true,
- "priorityQueue": true,
- "turboConversions": true,
- "concurrentJobs": 0,
- "batchLimit": 0,
- "aiQualityTier": "haiku"
}, - "paddle_subscription_id": "string",
- "paddle_customer_id": "string"
}Returns the user's invoice history from Paddle.
Security: Requires authentication.
| limit | integer [ 1 .. 100 ] Default: 20 Maximum number of invoices to return |
{- "invoices": [
- {
- "id": "string",
- "invoice_number": "string",
- "date": "2019-08-24T14:15:22Z",
- "total": "string",
- "currency": "string",
- "status": "paid",
- "has_pdf": true,
}
], - "has_more": true
}Returns a URL to download the invoice PDF. URL is valid for a limited time.
Security: Requires authentication. Only returns PDF for invoices belonging to the authenticated user.
| transaction_id required | string Paddle transaction ID (txn_...) |
{
}Returns the user's payment method on file.
Security: Requires authentication. Only returns masked card details (last 4 digits, expiry).
{- "hasPaymentMethod": true,
- "type": "card",
- "cardBrand": "string",
- "lastFour": "string",
- "expiryMonth": 0,
- "expiryYear": 0
}Returns a Paddle checkout URL to update payment method.
Behavior:
Security: Requires authentication.
{- "transactionId": "txn_abc123"
}Returns current usage statistics for a single metric in the billing period.
Security: Requires authentication.
Query Parameter: metric selects which usage metric to query
(default: goal_lane_request).
| metric | string Default: "goal_lane_request" Usage metric to query. Available metrics:
|
{- "metric": "string",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "used": 0,
- "limit": 0,
- "remaining": 0,
- "is_unlimited": true,
- "percentage_used": 0
}Returns a comprehensive usage report across all V1 metrics, including plan limits, feature flags, and platform access.
Security: Requires authentication (Bearer token).
{- "plan": "free",
- "metrics": {
- "property1": {
- "metric": "string",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "used": 0,
- "limit": 0,
- "remaining": 0,
- "is_unlimited": true,
- "percentage_used": 0
}, - "property2": {
- "metric": "string",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "used": 0,
- "limit": 0,
- "remaining": 0,
- "is_unlimited": true,
- "percentage_used": 0
}
}, - "plan_limits": {
- "property1": 0,
- "property2": 0
}, - "features": { },
- "platforms": { }
}Returns usage report for an anonymous session (cookie-based). Plan is always "anonymous" with limited quotas. Does not include features or platforms sections.
Security: No Bearer token required. Uses anonymous session cookie.
{- "plan": "free",
- "metrics": {
- "property1": {
- "metric": "string",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "used": 0,
- "limit": 0,
- "remaining": 0,
- "is_unlimited": true,
- "percentage_used": 0
}, - "property2": {
- "metric": "string",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "used": 0,
- "limit": 0,
- "remaining": 0,
- "is_unlimited": true,
- "percentage_used": 0
}
}, - "plan_limits": {
- "property1": 0,
- "property2": 0
}, - "features": { },
- "platforms": { }
}Returns usage history across all metrics for past billing periods.
Security: Requires authentication.
[- {
- "metric": "string",
- "period_start": "2019-08-24T14:15:22Z",
- "period_end": "2019-08-24T14:15:22Z",
- "used": 0,
- "limit": 0
}
]Per-tool-call forge billing. Each successful workflow run debits 1 U
via /forge/commit (Worktree B). /forge/quote is the pre-flight
check the frontend calls before running a workflow.
Returns whether the caller has enough capacity to run a workflow forge and, when they don't, a Paddle checkout URL to top up.
Cost. Every successful forge debits exactly 1 U. Free credits (earned via the rebate program) are consumed first; plan quota is consumed only after free credits are exhausted.
Balance. balanceU = remaining_plan_quota + freeCredits for the
authenticated user.
Security. Requires authentication.
{- "costU": 1,
- "balanceU": 4,
- "freeCredits": 1,
- "sufficient": true,
- "topUp": {
- "kind": "paddle_inline",
- "transactionId": "string"
}
}Receives and processes Paddle webhook events.
Security Requirements:
Supported Events:
| Paddle-Signature required | string Paddle webhook signature for verification |
Paddle webhook event payload
{ }{- "status": "string",
- "eventType": "string"
}