On this page
Billing
Self-service checkout and the billing portal are not currently available. Email hello@robota.us to arrange Pro access. GET /v1/orgs/:org/billing reports your current plan.
The checkout and portal examples below describe the API when billing is enabled. They do not establish a subscription or authorize a charge today.
Checkout and the portal use payment-provider pages. Docsary does not store card numbers; it records billing references and subscription status.
All three need an organization API key. The two POST calls need a
write-capable one; GET /billing works with a read-only key.
Pro is $9 a month or $90 a year, per organization. See Pricing and access for what that buys.
Start a subscription
POST /v1/orgs/:org/billing/checkout
curl -s -X POST "$DOCSARY_API/v1/orgs/acme/billing/checkout" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{ "interval": "month" }'
{
"url": "https://checkout.stripe.com/c/pay/cs_example_SESSION",
"session_id": "cs_example_SESSION",
"interval": "month"
}
When enabled, 201 returns a payment URL. After payment, check GET /billing to confirm the plan update. No content push is required.
| Field | Type | Required | Notes |
|---|---|---|---|
interval |
string | no | month or year. Defaults to month. |
success_url |
string | no | Where the browser lands after paying. Defaults to https://docsary.com/access?checkout=success. |
cancel_url |
string | no | Where it lands if you back out. Defaults to https://docsary.com/access?checkout=cancel. |
Omitting the body selects monthly billing and the default return URLs.
Return URLs
Return URLs must meet both rules:
httpsonly;- the host must be
docsary.comorwww.docsary.com, or a hostname your organization has attached and verified — which includes anydocsary.comaddress allocated to one of your sites at creation.
Other hosts return 400. Example using a verified custom domain:
curl -s -X POST "$DOCSARY_API/v1/orgs/acme/billing/checkout" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{
"interval": "year",
"success_url": "https://docs.acme.com/thanks",
"cancel_url": "https://docs.acme.com/pricing"
}'
Discount codes
The checkout page accepts promotion codes. If we have given you one, enter it there rather than sending it through this call.
Errors
| Response | Cause |
|---|---|
400 {"error":"interval must be 'month' or 'year'"} |
Anything else in interval |
400 containing must be an https URL |
A return URL that is not https |
400 containing attached and verified |
A return URL on a host that is not yours |
401 {"error":"missing bearer token"} |
No key |
403 containing read-only |
A read-only key. Starting a subscription is a mutation |
404 {"error":"org not found"} |
Unknown organization — or somebody else's. The two answer identically on purpose |
409 containing already has an active subscription |
You are already subscribed; use the portal below |
409 containing already open |
You have a checkout session open. Finish it, or wait for it to expire |
502 containing could not create a checkout session |
The payment provider refused the request or could not be reached. Retry once; if it repeats, email us rather than looping — a repeat means something needs fixing on our side, not on yours |
503 {"error":"billing is not configured"} |
Self-service checkout is not switched on. Email us and we will upgrade you by hand |
One checkout at a time
Send checkout requests sequentially and reuse the returned link. Concurrent requests can create duplicate subscriptions. An already-recorded open checkout returns 409 with its session ID and expiry:
{
"error": "a checkout session is already open for this organization",
"detail": "Finish or abandon the one you started. It expires by itself, and after that a new one can be created.",
"session_id": "cs_example_SESSION",
"expires_at": 1790001800000
}
expires_at uses Unix milliseconds; current_period_end uses Unix seconds. Use the returned expiry before requesting another session.
Errors, continued
An existing subscription returns the portal endpoint:
{
"error": "this organization already has an active subscription",
"detail": "Use POST /v1/orgs/acme/billing/portal to change the plan, switch between monthly and yearly, update the card, or cancel.",
"billing_status": "active",
"billing_interval": "month"
}
Change or cancel
POST /v1/orgs/:org/billing/portal
curl -s -X POST "$DOCSARY_API/v1/orgs/acme/billing/portal" \
-H "authorization: Bearer $DOCSARY_KEY"
{
"url": "https://billing.stripe.com/p/session/bps_example_SESSION",
"session_id": "bps_example_SESSION"
}
When enabled, 201 returns a portal URL for changing billing interval, updating payment details, downloading invoices, or cancelling.
503 billing is not configured means the portal is unavailable. Contact us about your paid arrangement.
For a subscription managed through this portal, cancellation keeps Pro through the paid period, then applies Free limits. Downgrading does not delete content. See downgrade behavior.
| Response | Cause |
|---|---|
401 / 403 / 404 |
As above |
404 containing no billing customer |
This organization has never bought anything. Start with checkout |
502 containing could not create a portal session |
The payment provider was unreachable. Retry |
503 {"error":"billing is not configured"} |
As above |
What am I on
GET /v1/orgs/:org/billing
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/orgs/acme/billing"
{
"org": "acme",
"plan": "pro",
"plan_source": "stripe",
"billing_status": "active",
"billing_interval": "year",
"current_period_end": 1790000000,
"current_period_end_unit": "unix_seconds",
"cancel_at_period_end": false,
"customer": "…7f21",
"subscription": "…c904"
}
This endpoint reads the recorded plan without contacting the payment provider.
| Field | Meaning |
|---|---|
plan |
The effective plan used for quotas: free or pro for customers. owner is reserved for the service operator and is not a customer subscription |
plan_source |
stripe if a subscription is paying for it, manual if a human set it, none once a subscription has ended and nothing is paying for the plan |
billing_status |
The subscription's status, verbatim: active, trialing, past_due, canceled, unpaid, incomplete, incomplete_expired, paused, or null if you have never subscribed |
billing_interval |
month or year |
current_period_end |
When the period you have paid for ends, in unix seconds — not milliseconds, unlike every other timestamp in this API. current_period_end_unit says so in the response. It moves when a renewal is actually paid, so for the hour or so between a renewal being raised and its invoice clearing, it still shows the period before |
cancel_at_period_end |
true once you have cancelled and are running out the period you paid for |
customer, subscription |
The last four characters of the provider's ids, enough to match against an invoice in a support email. The full ids are not returned |
GET /v1/whoami also reports the same effective plan, without the billing detail.
The statuses that keep you on Pro
For a subscription-backed plan, active and trialing qualify for Pro unless payment collection is paused. past_due qualifies only within its grace period and while the subscription has not ended.
The grace cutoff uses current_period_end plus the configured period, normally 14 days. When applicable, past_due_grace_ends reports that cutoff in milliseconds. Earlier subscription termination can end Pro before that date. Successful payment restores Pro without another content push.
Paused subscriptions do not qualify for Pro. Check the returned effective plan; status alone is not sufficient.
What happens after a payment
When billing is enabled, successful payment updates the plan. A paid renewal advances current_period_end; failed payment can start the grace period described above. Portal cancellation takes effect at the paid period’s end, while nonpayment can end the subscription earlier.
Confirm changes with GET /billing rather than assuming that opening or returning from a payment page completed them.
What a downgrade does not do
A downgrade does not delete pages, versions, redirects, or assets. Existing custom hostnames keep serving. Free limits can prevent new sites, new custom domains, and additional storage.
GET /billing lists limits your organization exceeds:
{ "plan": "free", "over_plan": ["sites", "custom_domains"] }
This list does not schedule content deletion. A manually assigned plan is not revoked by subscription events; contact us about changes to a separate paid arrangement.
If checkout is not available
503 {"error":"billing is not configured"} is the current availability response for self-service billing. Retrying will not enable it. Email hello@robota.us with the organization you want upgraded.