Billing
Billing is managed via Stripe. The API provides endpoints to initiate checkout sessions, open the customer portal, change subscription tiers, and retrieve billing status and invoice history.
All billing endpoints are under /api/admin/billing and /api/admin/invoices and require a GitHub OAuth Bearer token.
Subscription Tiers
| Tier | Max projects | Monthly API requests |
|---|---|---|
free | 3 | 200 |
hobby | 7 | 2,000 |
pro | 15 | 20,000 |
Object: BillingStatus
{
"tier": "hobby",
"projects": {
"count": 4,
"limit": 7
},
"currentPeriodEnd": "2025-02-01T00:00:00.000Z",
"overLimit": [],
"apiRequests": {
"count": 847,
"limit": 2000,
"resetDate": "2025-02-01T00:00:00.000Z"
},
"lastStripeError": null,
"scheduledTier": null,
"scheduledChangeDate": null
}| Field | Type | Description |
|---|---|---|
tier | string | Current subscription tier: free, hobby, or pro |
projects | ResourceUsage | Project count and limit |
currentPeriodEnd | ISO 8601 or null | End of the current Stripe billing period |
overLimit | string[] | Names of resources currently over their limit |
apiRequests | ApiRequestUsage | Monthly API request count, limit, and reset date |
lastStripeError | string or null | Last Stripe error message, if any |
scheduledTier | string or null | Tier that takes effect at scheduledChangeDate |
scheduledChangeDate | ISO 8601 or null | When the scheduled tier change takes effect |
ResourceUsage:
| Field | Type | Description |
|---|---|---|
count | integer | Current number of resources |
limit | integer or null | Maximum allowed; null means unlimited |
ApiRequestUsage:
| Field | Type | Description |
|---|---|---|
count | integer | Requests used this calendar month |
limit | integer or null | Monthly limit; null means unlimited |
resetDate | ISO 8601 datetime | First second of next calendar month (UTC) |
Object: Invoice
{
"invoiceId": "550e8400-e29b-41d4-a716-446655440000",
"accountId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"tier": "hobby",
"amountPaidCents": 900,
"status": "paid",
"billingPeriodStart": "2025-01-01T00:00:00.000Z",
"billingPeriodEnd": "2025-02-01T00:00:00.000Z",
"createdAt": "2025-01-15T10:30:00.000Z"
}| Field | Type | Description |
|---|---|---|
invoiceId | UUID | Unique invoice ID |
accountId | UUID | Owning account |
tier | string | Subscription tier at time of invoice |
amountPaidCents | integer | Amount charged in cents (e.g. 900 = $9.00) |
status | string | Invoice status (e.g. paid, open) |
billingPeriodStart | ISO 8601 datetime | Start of billing period |
billingPeriodEnd | ISO 8601 datetime | End of billing period |
createdAt | ISO 8601 datetime | When the invoice was created |
Get Billing Status
Returns current usage, tier, and upcoming changes.
GET /api/admin/billing/statusAuth: Authorization: Bearer <token>
Responses:
| Status | Description |
|---|---|
200 | Returns BillingStatus object |
404 | Account not found |
Example:
curl https://app.skystate.io/api/admin/billing/status \
-H "Authorization: Bearer <token>"Create Checkout Session
Initiates a Stripe Checkout session for upgrading to a paid tier. Returns a Stripe-hosted URL to redirect the user to.
POST /api/admin/billing/checkoutAuth: Authorization: Bearer <token>
Request body:
{
"tier": "hobby",
"successUrl": "https://app.skystate.io/settings?upgrade=success",
"cancelUrl": "https://app.skystate.io/settings"
}| Field | Type | Required | Description |
|---|---|---|---|
tier | string | Yes | Target tier: hobby or pro |
successUrl | string | Yes | URL to redirect to after successful payment |
cancelUrl | string | Yes | URL to redirect to if the user cancels |
Responses:
| Status | Body | Description |
|---|---|---|
200 | { "url": "https://checkout.stripe.com/..." } | Checkout URL |
400 | Error | Validation error or account not found |
500 | Error | Unexpected error |
Example:
curl -X POST https://app.skystate.io/api/admin/billing/checkout \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"tier":"hobby","successUrl":"https://app.skystate.io?success=1","cancelUrl":"https://app.skystate.io"}'Open Customer Portal
Creates a Stripe Customer Portal session for managing payment methods, viewing past invoices, and cancelling subscriptions.
POST /api/admin/billing/portalAuth: Authorization: Bearer <token>
Request body:
{
"returnUrl": "https://app.skystate.io/settings"
}| Field | Type | Required | Description |
|---|---|---|---|
returnUrl | string | Yes | URL to return to after leaving the portal |
Responses:
| Status | Body | Description |
|---|---|---|
200 | { "url": "https://billing.stripe.com/..." } | Portal URL |
400 | Error | Account not found or Stripe error |
500 | Error | Unexpected error |
Example:
curl -X POST https://app.skystate.io/api/admin/billing/portal \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"returnUrl":"https://app.skystate.io/settings"}'Change Tier
Changes the active subscription tier immediately or schedules a downgrade. Upgrading takes effect immediately; downgrading is scheduled for the end of the current billing period.
POST /api/admin/billing/change-tierAuth: Authorization: Bearer <token>
Request body:
{
"tier": "pro"
}| Field | Type | Required | Description |
|---|---|---|---|
tier | string | Yes | Target tier: free, hobby, or pro |
Responses:
| Status | Body | Description |
|---|---|---|
200 | { "message": "..." } | Tier change processed |
400 | Error | Validation error or account not found |
500 | Error | Unexpected error |
Example:
curl -X POST https://app.skystate.io/api/admin/billing/change-tier \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"tier":"pro"}'Cancel Scheduled Downgrade
Cancels a pending tier downgrade that was scheduled for the end of the billing period.
POST /api/admin/billing/cancel-downgradeAuth: Authorization: Bearer <token>
Request body: None
Responses:
| Status | Body | Description |
|---|---|---|
200 | { "message": "..." } | Downgrade cancelled |
400 | Error | Account not found or no downgrade scheduled |
500 | Error | Unexpected error |
Example:
curl -X POST https://app.skystate.io/api/admin/billing/cancel-downgrade \
-H "Authorization: Bearer <token>"List Invoices
Returns all invoices for the authenticated account, ordered by creation date descending.
GET /api/admin/invoicesAuth: Authorization: Bearer <token>
Response: 200 OK — array of Invoice objects.
Example:
curl https://app.skystate.io/api/admin/invoices \
-H "Authorization: Bearer <token>"Get Invoice by ID
GET /api/admin/invoices/{invoiceId}Auth: Authorization: Bearer <token>
Path parameters:
| Parameter | Type | Description |
|---|---|---|
invoiceId | UUID | Invoice ID |
Responses:
| Status | Description |
|---|---|
200 | Returns Invoice object |
404 | Invoice not found |
Example:
curl https://app.skystate.io/api/admin/invoices/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer <token>"