API reference
7 endpoints, served at https://orbitwrite.com/api/v1. Each one lists the credentials it accepts, its parameters and every response it can give.
Plan details (any member) plus the Stripe customer email and recent invoices (owner/admin only — null/empty for plain members). Stripe read failures degrade to defaults rather than erroring.
A signed-in session only. The Orbitwrite app calls this; an API key is refused.
organizationIdstringRequiredAt least 1 character.
200BillingSummaryThe workspace's billing summary
canManagebooleanRequiredemailstring | nullRequiredStripe customer email — null for plain members
invoicesBillingInvoice[]RequiredRecent invoices — empty for plain members
planDetailsBillingPlanDetailsRequiredplanstringRequiredpricenumber | nullRequiredcurrencystring | nullRequiredintervalstring | nullRequirednextBillingDatestring | nullRequiredcurl -X GET "https://orbitwrite.com/api/v1/billing?organizationId=organizationId" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"canManage": true,
"email": "string",
"invoices": [
{
"id": "string",
"number": "string",
"status": "string",
"amount": 0,
"currency": "string",
"created": "string",
"hostedUrl": "string",
"pdfUrl": "string"
}
],
"planDetails": {
"plan": "string",
"price": 0,
"currency": "string",
"interval": "string",
"nextBillingDate": "string"
}
}Owner/admin only. Updates the Stripe customer's email.
A signed-in session only. The Orbitwrite app calls this; an API key is refused.
organizationIdstringRequiredAt least 1 character.
emailstringRequiredAt least 1 character.
200BillingMutationResultThe mutation was applied
okbooleanRequired501ErrorBilling is not configured (STRIPE_SECRET_KEY unset)
curl -X POST "https://orbitwrite.com/api/v1/billing/email" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "string",
"email": "string"
}'{
"ok": true
}Pulls the subscription's status/cancellation/period fields from Stripe into the local row. Best-effort: always returns ok, even when Stripe is unreachable or there is no subscription. Owner/admin only.
A signed-in session only. The Orbitwrite app calls this; an API key is refused.
organizationIdstringRequiredAt least 1 character.
200BillingMutationResultThe mutation was applied
okbooleanRequiredcurl -X POST "https://orbitwrite.com/api/v1/billing/sync" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "string"
}'{
"ok": true
}What the workspace's plan allows and how much of each allowance it has spent this period.
Use remaining to pace a batch: a null value is unlimited, and xPosts is the monthly X allowance and xLinkPosts the much smaller one for posts containing a link; xPostsPerDay is null on every listed plan. storageBytes is a running total rather than a per-period count.
A quota with a rate in overage doesn't block at zero remaining — going over is allowed and billed at that rate. On plans with overage.channels set, remaining.channels is the remaining included channels before per-channel billing starts, not a cap.
Authenticates with a workspace API key or a session cookie.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
200EntitlementsThe workspace's entitlements
planstringRequiredPlan name. Never null — a workspace with no plan resolves to none, whose limits are all closed.
sourcestringRequiredWhere the plan comes from: a paid or trialing subscription; a grant, a plan held with no one billing it (workspaces that were on the retired free plan were moved onto Standard this way); or none.
"subscription""grant""none"statusstring | nullRequiredStripe subscription status, or null when the plan is a grant or there is none.
trialEndsAtstring | nullRequiredWhen the free trial ends, while the subscription is trialing. Null otherwise.
unrestrictedbooleanRequiredBilling is not enforced for this deployment (dev / self-hosted): every limit is open and every feature on.
featuresstring[]RequiredGated capabilities this plan unlocks.
limitsPlanLimitsRequiredmembersinteger | nullRequiredincludedChannelsinteger | nullRequiredchannelsPerPlatforminteger | nullRequiredpostsPerMonthinteger | nullRequiredxPostsPerDayinteger | nullRequiredxPostsPerMonthinteger | nullRequiredxLinkPostsPerMonthinteger | nullRequiredaiAssistsPerMonthinteger | nullRequiredautoEngagementPerMonthinteger | nullRequiredstorageBytesinteger | nullRequiredanalyticsHistoryDaysinteger | nullRequiredusagePlanUsageRequiredpostsintegerRequiredxPostsintegerRequiredxPostsTodayintegerRequiredxLinkPostsintegerRequiredaiAssistsintegerRequiredautoEngagementintegerRequiredstorageBytesintegerRequiredmembersintegerRequiredchannelsintegerRequiredremainingPlanRemainingRequiredmembersinteger | nullRequiredchannelsinteger | nullRequiredpostsinteger | nullRequiredxPostsPerDayinteger | nullRequiredxPostsinteger | nullRequiredxLinkPostsinteger | nullRequiredaiAssistsinteger | nullRequiredautoEngagementinteger | nullRequiredstorageBytesinteger | nullRequiredissueBillingIssue | nullRequiredSet when the workspace has a subscription that isn't entitling — a failed payment, an unfinished checkout, or a cancellation. Null on a workspace that simply never subscribed. plan is none either way; this is what distinguishes 'never chose a plan' from 'card expired'.
pausedobjectRequiredMembers and channels past the plan's limits. Nothing over a limit is removed: a paused member can read but not create, schedule, publish or reply; a paused channel stays connected and its posts are held until it is active again. Choose which stay active with POST /seats/activate.
memberIdsstring[]RequiredchannelIdsstring[]RequiredoverageobjectRequiredPrice in USD cents for exceeding a quota. Null means the quota blocks instead of billing — which is the case for every quota on Standard.
xPostsinteger | nullRequiredxLinkPostsinteger | nullRequiredchannelsinteger | nullRequiredMonthly (not per-unit) price for each channel above limits.includedChannels. When set, the included count is a billing threshold, not a cap — connecting more is allowed and billed.
curl -X GET "https://orbitwrite.com/api/v1/entitlements" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY"{
"plan": "string",
"source": "subscription",
"status": "string",
"trialEndsAt": "string",
"unrestricted": true,
"period": {
"key": "string",
"start": "string",
"end": "string"
},
"features": [
"string"
],
"limits": {
"members": 0,
"includedChannels": 0,
"channelsPerPlatform": 0,
"postsPerMonth": 0,
"xPostsPerDay": 0,
"xPostsPerMonth": 0,
"xLinkPostsPerMonth": 0,
"aiAssistsPerMonth": 0,
"autoEngagementPerMonth": 0,
"storageBytes": 0,
"analyticsHistoryDays": 0
},
"usage": {
"posts": 0,
"xPosts": 0,
"xPostsToday": 0,
"xLinkPosts": 0,
"aiAssists": 0,
"autoEngagement": 0,
"storageBytes": 0,
"members": 0,
"channels": 0
},
"remaining": {
"members": 0,
"channels": 0,
"posts": 0,
"xPostsPerDay": 0,
"xPosts": 0,
"xLinkPosts": 0,
"aiAssists": 0,
"autoEngagement": 0,
"storageBytes": 0
},
"issue": {
"kind": "payment_failed",
"plan": "string",
"status": "string",
"periodEnd": "string"
},
"paused": {
"memberIds": [
"string"
],
"channelIds": [
"string"
]
},
"overage": {
"xPosts": 0,
"xLinkPosts": 0,
"channels": 0
}
}When the plan covers fewer members or channels than the workspace has, the rest are paused, never removed. This moves one member (memberId, needs member:update) or channel (accountId, needs account:update) to the front of the queue for a seat; the most recently activated wins. It changes nothing while the workspace has room. Read the result from paused on GET /entitlements.
Authenticates with a workspace API key or a session cookie.
An API key or OAuth access token as a bearer token, or a signed-in session.
organizationIdstringSession callers only — an API key already determines the workspace.
At least 1 character.
memberIdstringRequiredAt least 1 character.
204Recorded
curl -X POST "https://orbitwrite.com/api/v1/seats/activate" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"memberId": "string"
}'{
"error": "string",
"issues": []
}What the invite offers and which of the caller's own workspaces it can be put on. Changes nothing. An unknown, expired, revoked or used-up link is a 404, whichever it was.
A signed-in session only. The Orbitwrite app calls this; an API key is refused.
tokenstringRequiredBetween 1 and 200 characters.
200PlanInvitePreviewThe offer, and where it can go
planstringRequiredexpiresAtstringRequiredWhen the link stops working. ISO 8601.
redemptionobject | any | nullRequiredThe caller's own earlier acceptance of this invite. held means it is waiting for them to have a workspace.
organizationIdstring | nullRequiredheldbooleanRequiredworkspacesobject[]RequiredidstringRequirednamestringRequiredslugstringRequiredeligiblebooleanRequiredFalse when the workspace already holds this plan or a higher one.
subscribedbooleanRequiredThe workspace has a subscription, which outranks a granted plan: accepting here takes effect only if that subscription ends.
curl -X POST "https://orbitwrite.com/api/v1/plan-invites/preview" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"token": "string"
}'{
"plan": "string",
"expiresAt": "string",
"redemption": {
"organizationId": "string",
"held": true
},
"workspaces": [
{
"id": "string",
"name": "string",
"slug": "string",
"eligible": true,
"subscribed": true
}
]
}Puts the invite's plan on organizationId, a workspace the caller owns. A caller who owns no workspace omits it: the acceptance is held and applied to the first workspace they create. Accepting twice spends one redemption.
A signed-in session only. The Orbitwrite app calls this; an API key is refused.
tokenstringRequiredBetween 1 and 200 characters.
organizationIdstring | nullAt least 1 character.
200PlanInviteRedemptionThe invite was accepted
planstringRequiredstatusstringRequired"applied""held"organizationIdstring | nullRequiredcurl -X POST "https://orbitwrite.com/api/v1/plan-invites/redeem" \
-H "Authorization: Bearer $ORBITWRITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"token": "string"
}'{
"plan": "string",
"status": "applied",
"organizationId": "string"
}