Used by thousands of students worldwide - help us reach 150 members
Billing & Subscriptions
/stripeStripe-backed subscription management (checkout, cancel, reactivate, plan changes), payment methods, invoices, and OCR page-credit usage tracking. Every call acts on the Stripe customer attached to the authenticated user. Plans: $5/month (1,000 OCR pages per billing period), $10/month (2,000 pages) and $50/month (10,000 pages); pages beyond the plan allowance are billed as overage at $0.02 per page. Most errors in this group are returned as plain text, not JSON.
/stripe/create-checkout-sessionCreate a subscription checkout sessionGET/stripe/subscriptionGet the current subscriptionPOST/stripe/subscription/cancelCancel a subscription at period endPOST/stripe/subscription/reactivateReactivate a subscription pending cancellationPOST/stripe/subscription/updateChange the subscription planGET/stripe/payment-methodsList saved cardsPOST/stripe/payment-methods/create-setup-intentCreate a SetupIntent for adding a cardGET/stripe/subscriptions/countCount active subscriptions platform-widePOST/stripe/payment-methods/set-defaultSet the default payment methodDELETE/stripe/payment-methods/:idRemove a saved cardGET/stripe/billing-historyList recent invoicesGET/stripe/usageGet OCR page-credit usage for the current billing periodPOST/stripe/usage/estimateEstimate the cost of processing a number of pagesGET/stripe/usage/historyList OCR usage historyGET/stripe/usage/failed-paymentsList unresolved failed overage paymentsPOST/stripe/usage/retry-payment/:logIdRetry a failed overage paymentGET/stripe/usage/can-uploadCheck whether the user may start an OCR uploadCreate a subscription checkout session
Creates a Stripe Checkout session in `subscription` mode (card payments only, quantity 1) for the authenticated user's Stripe customer and returns the hosted checkout URL to redirect the user to. After checkout Stripe redirects to `<Origin>/profile` on success and `<Origin>/support` on cancel, where `<Origin>` is the value of the request's `Origin` header. The handler does not validate `priceId` against the known plans; it is passed straight to Stripe. Page credits are derived from the price ID: the $5/month plan grants 1,000 pages, $10/month grants 2,000 pages and $50/month grants 10,000 pages per billing period. A price ID that is not one of the configured plan prices grants 0 page credits.
Body parameters
priceIdrequiredstring | Stripe Price ID of the plan to subscribe to. Live plan prices: `price_1PpFguId9CfEnWZpxaleTN3u` ($5/month, 1,000 pages), `price_1PpFh2Id9CfEnWZpVxMTmMJv` ($10/month, 2,000 pages), `price_1PpFh7Id9CfEnWZpB4p2Il7o` ($50/month, 10,000 pages). |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Stripe rejected the request, e.g. missing or unknown `priceId` (plain text: "Internal Server Error. Unable to complete request.")
curl -X POST "https://app.ummahspot.com/stripe/create-checkout-session" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"priceId":"string"}'{
"id": "cs_test_a1B2c3D4e5F6g7H8i9J0",
"url": "https://checkout.stripe.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0"
}Get the current subscription
Lists the Stripe subscriptions of the authenticated user's customer across all statuses (including canceled ones) and returns details of the first one Stripe returns. If the customer has never had a subscription the response is just `{ "active": false }`. `active` is true only when the subscription status is exactly `active`. `plan.amount` is in major currency units (Stripe's `unit_amount` divided by 100) and `plan.name` falls back to "Standard Plan" when the Stripe price has no nickname. Use `subscription_id` with the cancel, reactivate and update endpoints.
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Stripe lookup failed (plain text: "Failed to retrieve subscription information")
curl -X GET "https://app.ummahspot.com/stripe/subscription" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"active": true,
"status": "active",
"current_period_end": "2026-10-18T14:03:22.000Z",
"cancel_at_period_end": false,
"subscription_id": "sub_1234567890abcdef",
"plan": {
"id": "price_1PpFguId9CfEnWZpxaleTN3u",
"name": "Standard Plan",
"amount": 5,
"currency": "usd",
"interval": "month"
}
}Cancel a subscription at period end
Sets `cancel_at_period_end: true` on the subscription. The subscription is not terminated immediately: it stays usable, and the user keeps their remaining OCR page credits, until `current_period_end`. The handler verifies that the subscription belongs to the authenticated user's Stripe customer before changing it. Can be undone with the reactivate endpoint while the period is still running.
Body parameters
subscriptionIdrequiredstring | Stripe subscription ID, as returned in `subscription_id` by `GET /stripe/subscription`. |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Invalid token, or the subscription belongs to a different Stripe customer (plain text: "Unauthorized to cancel this subscription")500Missing or unknown `subscriptionId`, or another Stripe error (plain text: "Failed to cancel subscription")
curl -X POST "https://app.ummahspot.com/stripe/subscription/cancel" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subscriptionId":"string"}'{
"success": true,
"canceled": true,
"current_period_end": "2026-10-18T14:03:22.000Z"
}Reactivate a subscription pending cancellation
Sets `cancel_at_period_end: false` on a subscription that was previously scheduled to cancel, so it renews normally. The handler verifies that the subscription belongs to the authenticated user's Stripe customer before changing it.
Body parameters
subscriptionIdrequiredstring | Stripe subscription ID, as returned in `subscription_id` by `GET /stripe/subscription`. |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Invalid token, or the subscription belongs to a different Stripe customer (plain text: "Unauthorized to reactivate this subscription")500Missing or unknown `subscriptionId`, or another Stripe error (plain text: "Failed to reactivate subscription")
curl -X POST "https://app.ummahspot.com/stripe/subscription/reactivate" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subscriptionId":"string"}'{
"success": true,
"active": true,
"current_period_end": "2026-10-18T14:03:22.000Z"
}Change the subscription plan
Swaps the price on the subscription's first item to `newPriceId` (upgrade or downgrade). The handler verifies that the subscription belongs to the authenticated user's Stripe customer first; `newPriceId` is not validated against the known plans. The page-credit allowance is not changed by this call itself: when Stripe notifies the backend of the subscription update, the allowance of the current billing period is set to the new plan's credits (1,000 / 2,000 / 10,000 pages) while pages already used in the period are kept.
Body parameters
subscriptionIdrequiredstring | Stripe subscription ID, as returned in `subscription_id` by `GET /stripe/subscription`. |
newPriceIdrequiredstring | Stripe Price ID of the plan to switch to (see `POST /stripe/create-checkout-session` for the plan price IDs). |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Invalid token, or the subscription belongs to a different Stripe customer (plain text: "Unauthorized to update this subscription")500Missing or unknown `subscriptionId` / `newPriceId`, or another Stripe error (plain text: "Failed to update subscription")
curl -X POST "https://app.ummahspot.com/stripe/subscription/update" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subscriptionId":"string","newPriceId":"string"}'{
"success": true,
"updated": true,
"subscription_id": "sub_1234567890abcdef"
}List saved cards
Lists the card payment methods attached to the authenticated user's Stripe customer. `is_default` is derived from the payment method's Stripe metadata (`metadata.is_default === "true"`), not from the customer's default payment method setting, and `POST /stripe/payment-methods/set-default` does not write that metadata.
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Stripe lookup failed (plain text: "Failed to retrieve payment methods")
curl -X GET "https://app.ummahspot.com/stripe/payment-methods" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"payment_methods": [
{
"id": "pm_1234567890abcdef",
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2028,
"is_default": false
}
]
}Create a SetupIntent for adding a card
Creates a Stripe SetupIntent (card only) for the authenticated user's Stripe customer and returns its client secret. Confirm it client-side with Stripe.js / the Stripe mobile SDK to attach a new card to the customer. Takes no request body.
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Stripe error (plain text: "Failed to create setup intent")
curl -X POST "https://app.ummahspot.com/stripe/payment-methods/create-setup-intent" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"clientSecret": "seti_1234567890abcdef_secret_AbCdEfGhIjKlMnOp"
}Count active subscriptions platform-wide
Returns the total number of subscriptions with status `active` in the Stripe account. This is a platform-wide figure, not specific to any user, and the route is intentionally public because the site-wide campaign banner shows it to every visitor. The count is cached on the server for 5 minutes, so the value can be up to 5 minutes old; requests arriving while the cache is being refreshed share a single refresh. If the refresh from Stripe fails and a previously cached value exists, that cached value is returned instead of an error.
Errors
500Stripe lookup failed and no cached count is available yet (plain text: "Failed to retrieve subscription count")
curl -X GET "https://app.ummahspot.com/stripe/subscriptions/count"{
"success": true,
"active_subscriptions": 128
}Set the default payment method
Sets the customer's `invoice_settings.default_payment_method` in Stripe, which is the card used for subscription renewals and overage invoices. The handler verifies that the payment method is attached to the authenticated user's Stripe customer first.
Body parameters
paymentMethodIdrequiredstring | Stripe payment method ID (`pm_...`), as returned by `GET /stripe/payment-methods`. |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Invalid token, or the payment method belongs to a different Stripe customer (plain text: "Unauthorized to update this payment method")500Missing or unknown `paymentMethodId`, or another Stripe error (plain text: "Failed to set default payment method")
curl -X POST "https://app.ummahspot.com/stripe/payment-methods/set-default" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"paymentMethodId":"string"}'{
"success": true
}Remove a saved card
Detaches the payment method from the authenticated user's Stripe customer. The handler verifies that the payment method is attached to that customer first.
Path parameters
idstring | Stripe payment method ID (`pm_...`) |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Invalid token, or the payment method belongs to a different Stripe customer (plain text: "Unauthorized to delete this payment method")500Unknown payment method ID or another Stripe error (plain text: "Failed to delete payment method")
curl -X DELETE "https://app.ummahspot.com/stripe/payment-methods/:id" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"success": true
}List recent invoices
Returns the 10 most recent Stripe invoices of the authenticated user's customer (fixed limit, no pagination). This includes subscription invoices as well as the separate invoices created for OCR overage charges. `amount_paid` is in major currency units (Stripe's amount divided by 100).
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Stripe lookup failed (plain text: "Failed to retrieve billing history")
curl -X GET "https://app.ummahspot.com/stripe/billing-history" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"invoices": [
{
"id": "in_1234567890abcdef",
"amount_paid": 5,
"currency": "usd",
"status": "paid",
"created": "2026-09-18T14:03:22.000Z",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_123/test_abc/pdf",
"period_start": "2026-08-18T14:03:22.000Z",
"period_end": "2026-09-18T14:03:22.000Z"
}
]
}Get OCR page-credit usage for the current billing period
Returns the page-credit status of the current billing period plus a summary of unresolved failed overage payments. `tierCredits` is the plan allowance (1,000 pages for $5/month, 2,000 for $10/month, 10,000 for $50/month; 0 for an unrecognised price), `remainingCredits` is `max(0, tierCredits - pagesUsed)` and `isInOverage` is true once `pagesUsed` exceeds `tierCredits`. Pages beyond the allowance are charged immediately at OCR time at 2 cents ($0.02) per page with a 2 cent minimum, on a separate Stripe invoice; `overageChargedCents` is the amount successfully collected so far in this period. Credits reset when a subscription renewal invoice is paid. A subscription that was canceled but is still inside its paid period still counts as a subscription. If there is no usage record for the period yet, one is created on the fly from the Stripe subscription. Users without a subscription get `hasSubscription: false` with zeroed counters and null dates. Users with the `editor` or `admin` role bypass billing entirely: they get `hasSubscription: true`, `isAdmin: true`, and unlimited credits (`tierCredits` and `remainingCredits` are serialised as `null` in JSON). As a side effect, previously failed overage payments whose Stripe invoice has since been paid are marked as successful.
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Usage lookup failed, including when the user has no Stripe customer ID (plain text: "Failed to retrieve usage information")
curl -X GET "https://app.ummahspot.com/stripe/usage" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"hasSubscription": true,
"tierCredits": 1000,
"pagesUsed": 1040,
"remainingCredits": 0,
"isInOverage": true,
"overagePages": 40,
"overageChargedCents": 80,
"billingPeriodStart": "2026-09-18T14:03:22.000Z",
"billingPeriodEnd": "2026-10-18T14:03:22.000Z",
"priceId": "price_1PpFguId9CfEnWZpxaleTN3u",
"hasFailedPayments": false,
"failedPaymentCount": 0,
"totalUnpaidCents": 0
}Estimate the cost of processing a number of pages
Dry-run calculation of how an OCR job of `pageCount` pages would be billed; nothing is charged or recorded. Pages are first covered by the remaining credits of the current period (`freePages`); the rest are `overagePages`, costed at 2 cents ($0.02) per page with a 2 cent minimum (`estimatedCostCents`). Without an active subscription the response is `{ "canProcess": false, "reason": "No active subscription", "estimatedCostCents": 0 }`. For users with the `editor` or `admin` role the response has `isAdmin: true`, all pages free, a cost of 0 and `remainingCreditsAfter` serialised as `null` (unlimited).
Body parameters
pageCountrequirednumber | Number of pages to be processed. Must be at least 1. |
Errors
400`pageCount` missing, zero or less than 1 (JSON: `{ "error": "Invalid page count" }`)401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Estimate failed (plain text: "Failed to estimate cost")
curl -X POST "https://app.ummahspot.com/stripe/usage/estimate" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pageCount":0}'{
"canProcess": true,
"pageCount": 150,
"freePages": 100,
"overagePages": 50,
"estimatedCostCents": 100,
"remainingCreditsAfter": 0
}List OCR usage history
Paginated log of the authenticated user's OCR jobs, newest first, across all billing periods. Each entry shows how many pages of the job were covered by plan credits (`freePages`) versus billed as overage (`overagePages`, `overageChargeCents`). `total` is the total number of log entries for the user. No maximum is enforced on `limit`.
Query parameters
limitnumber | Number of log entries to return.Default: 10 |
offsetnumber | Number of log entries to skip.Default: 0 |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Lookup failed (plain text: "Failed to retrieve usage history")
curl -X GET "https://app.ummahspot.com/stripe/usage/history" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"success": true,
"total": 23,
"logs": [
{
"jobId": "b7e1c9d2-4f3a-4e8b-9a6c-2d1f0e5a7c34",
"pageCount": 150,
"freePages": 100,
"overagePages": 50,
"overageChargeCents": 100,
"createdAt": "2026-09-18T14:03:22.000Z"
}
]
}List unresolved failed overage payments
Returns the overage charges of the current billing period whose payment failed and is still unpaid, newest first. Before responding, each failed entry with a Stripe invoice is re-checked against Stripe; entries whose invoice has since been paid are marked successful and dropped from the list. If the user has no active usage record (no subscription) the response is empty with zero totals. Use the `_id` of an entry with `POST /stripe/usage/retry-payment/:logId`.
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Lookup failed (plain text: "Failed to retrieve failed payments")
curl -X GET "https://app.ummahspot.com/stripe/usage/failed-payments" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"success": true,
"hasFailedPayments": true,
"failedPaymentCount": 1,
"totalUnpaidCents": 100,
"failedLogs": [
{
"_id": "66eae1b2c3d4e5f6a7b8c9d0",
"jobId": "b7e1c9d2-4f3a-4e8b-9a6c-2d1f0e5a7c34",
"overagePages": 50,
"overageChargeCents": 100,
"stripeInvoiceId": "in_1234567890abcdef",
"createdAt": "2026-09-18T14:03:22.000Z"
}
]
}Retry a failed overage payment
Attempts to collect a failed OCR overage charge again. The handler verifies that the usage log belongs to the authenticated user. If the original Stripe invoice is still open it is paid; if it turns out to be paid already the log is simply marked successful; otherwise a new invoice for the same amount is created, finalised and charged to the customer's default payment method. Note that an unsuccessful retry is also returned with HTTP 200: check `success`. Possible `message` values with `success: false` are "Payment is not in failed status", "Payment failed", or the Stripe error message; with `success: true` they are "Payment successful" or "Payment was already completed". Takes no request body.
Path parameters
logIdstring | Usage log ObjectId: the `_id` of an entry from `GET /stripe/usage/failed-payments`. |
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Invalid token, or the usage log belongs to another user (JSON: `{ "error": "Unauthorized" }`)404No usage log with this ID (JSON: `{ "error": "Payment record not found" }`)500Malformed `logId` or unexpected failure (JSON: `{ "success": false, "message": "Failed to retry payment" }`)
curl -X POST "https://app.ummahspot.com/stripe/usage/retry-payment/:logId" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"success": true,
"message": "Payment successful"
}Check whether the user may start an OCR upload
Combined eligibility check for OCR uploads. Returns `{ "canUpload": true }` when the user has an active subscription (or a canceled one still inside its paid period) and their unresolved failed overage payments for the current period total no more than 500 cents ($5.00). Otherwise returns `canUpload: false` with a `reason`: `"No active subscription"`, or `"UNPAID_BALANCE"` together with `unpaidBalance` (cents) and `failedPaymentCount`. Users with the `editor` or `admin` role always get `{ "canUpload": true, "isAdmin": true }`.
Errors
401No bearer token provided (plain text: "Unauthorized: No token provided")403Token is invalid or expired (plain text: "Forbidden: Invalid token")500Check failed (plain text: "Failed to check upload eligibility")
curl -X GET "https://app.ummahspot.com/stripe/usage/can-upload" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"canUpload": false,
"reason": "UNPAID_BALANCE",
"unpaidBalance": 640,
"failedPaymentCount": 2
}