Plans and your subscription
What the workspace pays SalesShift: the published plans, this workspace's own subscription and entitlements, Stripe checkout and the billing portal, and the node handshake behind the free self-hosted plan. Not the customer's own invoicing — that is Invoicing and payments.
16 endpoints· 15 called live· 1 not exercised here· verified against http://127.0.0.1:8741
16 endpoints
POST/api/v1/salesshift/billing/activateCalled livePut this workspace on a free plan.
Put this workspace on a free plan. No card, no Stripe, no email. Deliberately narrow: it will only ever activate a plan flagged `is_free`, so it cannot become a way to grant a paid tier by posting a different code. It also refuses to *downgrade* anything. Any live plan above free — bought or granted — has to be given up through `/cancel`, which tells Stripe when there is a Stripe to tell. The first version of this guard tested only for `source == 'stripe'` and a single POST silently dropped a comped workspace from Organization to the free tier's allowance. Whether somebody paid cash or was given the plan makes no difference to how much it hurts to lose it by accident, so the test is on the plan, not on how it was acquired.
Request body (optional) — Body_activate_free_api_v1_salesshift_billing_activate_post
| Field | Type | Required | Notes |
|---|---|---|---|
| plan_code | string | optional | Default "self_hosted". |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/activate' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "plan_code": "self_hosted" }'Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 404 | Unknown plan '{...}' |
| 400 | {...} is a paid plan — use checkout. |
| 409 | computed at runtime: detail = (
"This workspace has an active paid subscription. Cancel it first "
"and it will fall to Self-Hosted at the end of the period."
if sub.source == "stripe" else
f"This workspace is on {current.name}, granted by VxCloud. "
f"Contact support to move it to {plan.name}."
) |
POST/api/v1/salesshift/billing/cancelPartialCancel the Stripe subscription, at period end or immediately.
Request body (optional) — Body_cancel_subscription_api_v1_salesshift_billing_cancel_post
| Field | Type | Required | Notes |
|---|---|---|---|
| at_period_end | boolean | optional | Default true. |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/cancel' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "at_period_end": true }'Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 409 | No subscription to cancel |
| 409 | This workspace is on a granted plan. Contact support to change it. |
| 409 | Stripe subscription does not match a known plan |
| 502 | Payment provider error: {...} |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
POST/api/v1/salesshift/billing/changePartialMove to another paid plan, change the seat count, or both.
Request body (required) — ChangeIn
| Field | Type | Required | Notes |
|---|---|---|---|
| plan_code | string? | optional | |
| seats | integer? | optional |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/change' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "plan_code": "self_hosted" }'Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 409 | No subscription to change — choose a plan first. |
| 400 | To move to {...}, cancel this subscription — the workspace falls to it at the end of the period. |
| 404 | Unknown plan '{...}' |
| 502 | Payment provider error: {...} |
| 409 | Stripe subscription does not match a known plan |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
POST/api/v1/salesshift/billing/checkoutPartialStart a Stripe Checkout session for a paid plan at a given seat count.
Request body (required) — CheckoutIn
| Field | Type | Required | Notes |
|---|---|---|---|
| plan_code | string | required | |
| seats | integer | optional | Default 1. min 1, max 500. |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/checkout' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "plan_code": "self_hosted", "seats": 1 }'Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 404 | Unknown plan '{...}' |
| 400 | {...} is free — activate it instead of checking out. |
| 502 | Payment provider error: {...} |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
POST/api/v1/salesshift/billing/checkout/confirmNot exercised hereCalled by the browser when Stripe redirects back.
Called by the browser when Stripe redirects back. Without a reachable webhook this is what actually activates the plan, so it re-reads the session from Stripe rather than trusting anything in the URL — a `?checkout=success` query string is attacker-controlled.
Request body (required) — Body_confirm_checkout_api_v1_salesshift_billing_checkout_confirm_post
| Field | Type | Required | Notes |
|---|---|---|---|
| session_id | string | required |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/checkout/confirm' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "session_id": "<string>" }'Response · 200
{
"applied": "…",
"reason": "…",
"subscription": "…"
}Errors
| Status | detail |
|---|---|
| 403 | Checkout session belongs to another workspace |
| 502 | Payment provider error: {...} |
| 409 | Stripe subscription does not match a known plan |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
GET/api/v1/salesshift/billing/entitlementsCalled liveWhat this workspace may do, without the billing detail.
What this workspace may do, without the billing detail. Separate from `/subscription` because the app asks this question far more often than it asks about money — every screen that hides a managed-sending control needs it — and this answer costs two indexed reads with no Stripe call anywhere near it.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/entitlements' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"plan_code": "str",
"plan_name": "str",
"status": "str",
"source": "str",
"seats": "int",
"is_free": "bool",
"managed": {
"compute": "bool",
"sending": "bool",
"ai": "bool"
},
"allowance": {
"emails": "int",
"reveals": "int",
"ai": "int",
"mailboxes": "null",
"contacts": "null",
"users": "null"
},
"self_hosted": {
"required": "bool",
"node_host": "null",
"verified_at": "null",
"ready": "bool"
}
}GET/api/v1/salesshift/billing/eventsCalled liveThe workspace's platform-billing event log, newest first.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | optional | Default 25. |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/events' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"events": [
{
"id": "...",
"kind": "...",
"summary": "...",
"amount_cents": "...",
"created_at": "..."
},
"...x5"
]
}GET/api/v1/salesshift/billing/invoicesPartialStripe is the ledger; we do not keep a shadow copy to drift out of date.
Stripe is the ledger; we do not keep a shadow copy to drift out of date. A comped workspace has no Stripe customer and therefore no invoices — that is an empty list with a reason, not an error.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/invoices' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"invoices": [],
"reason": "str"
}Errors
| Status | detail |
|---|---|
| 502 | Payment provider error: {...} |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
GET/api/v1/salesshift/billing/plansCalled liveThe published plans, what each includes, and whether it is bought or activated.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/plans' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"plans": [
{
"id": "...",
"code": "...",
"name": "...",
"tagline": "...",
"unit_amount_cents": "...",
"price_display": "...",
"currency": "...",
"interval": "...",
"features": "...",
"is_free": "...",
"managed": "...",
"quotas": "...",
"is_purchasable": "...",
"is_activatable": "..."
},
"...x3"
],
"currency": "str",
"interval": "str",
"payments_enabled": "bool"
}POST/api/v1/salesshift/billing/portalPartialCard changes, cancellation and receipts are Stripe's hosted portal.
Card changes, cancellation and receipts are Stripe's hosted portal. Rebuilding that surface would mean handling PCI-scope card entry for no gain.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/portal' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 409 | This workspace has no payment account yet — choose a plan first. |
| 502 | Payment provider error: {...} |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
POST/api/v1/salesshift/billing/resumePartialUndo a cancellation that has not taken effect yet.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/resume' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 409 | Nothing to resume |
| 502 | Payment provider error: {...} |
| 409 | Stripe subscription does not match a known plan |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
GET/api/v1/salesshift/billing/self-hostedCalled liveWhether this workspace has a registered node, and the identity a node must report to become one.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/self-hosted' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"required": "bool",
"host": "str",
"verified_at": "str",
"fingerprint": "str",
"live": {
"reachable": "bool",
"version": "str",
"tenant_name": "str",
"time": "str"
},
"install": {
"tenant_id": "str",
"accepts": [
"str"
],
"image": "str",
"health_path": "str"
}
}Errors
| Status | detail |
|---|---|
| 404 | Workspace not found |
POST/api/v1/salesshift/billing/self-hosted/nodeCalled liveRegister the tenant's own node, after the node proves whose it is.
Request body (required) — SelfHostedNodeIn
| Field | Type | Required | Notes |
|---|---|---|---|
| host | string | required | ≤ 255 chars. |
The body in the request below is the exact one sent during verification.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/self-hosted/node' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "host": "http://localhost:19811" }'Response · 200
{
"host": "str",
"verified": "bool",
"version": "str",
"tenant_name": "str",
"entitlements": {
"plan_code": "str",
"plan_name": "str",
"status": "str",
"source": "str",
"seats": "int",
"is_free": "bool",
"managed": {
"compute": "...",
"sending": "...",
"ai": "..."
},
"allowance": {
"emails": "...",
"reveals": "...",
"ai": "...",
"mailboxes": "...",
"contacts": "...",
"users": "..."
},
"self_hosted": {
"required": "...",
"node_host": "...",
"verified_at": "...",
"ready": "..."
}
}
}Errors
| Status | detail |
|---|---|
| 400 | {...} answered but did not identify a tenant. Set TENANT_ID={...} in the node's environment and restart it. |
| 403 | computed at runtime: detail = (
f"That node identifies as another workspace. Set TENANT_ID={org_id} "
f"in its environment and restart it."
)
if ambiguous:
detail += (
f" (Your workspace name '{ambiguous}' is shared with another "
f"workspace, so only the id above is accepted here.)"
) |
| 404 | Workspace not found |
| 400 | A node address is required |
| 400 | The node must be reachable over HTTPS. |
| 502 | Could not reach {...}/health — {...} |
DELETE/api/v1/salesshift/billing/self-hosted/nodeCalled liveDetach the node.
Detach the node. Sending and agents stop until another is registered.
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/salesshift/billing/self-hosted/node' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"host": "null",
"verified": "bool"
}Errors
| Status | detail |
|---|---|
| 404 | Workspace not found |
GET/api/v1/salesshift/billing/subscriptionCalled liveThis workspace's own subscription: plan, seats, period, members and the entitlements it resolves to.
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/subscription' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"id": "str",
"status": "str",
"entitled": "bool",
"source": "str",
"seats": "int",
"plan": {
"id": "str",
"code": "str",
"name": "str",
"tagline": "str",
"unit_amount_cents": "int",
"price_display": "str",
"currency": "str",
"interval": "str",
"features": [
"str",
"...x5"
],
"is_free": "bool",
"managed": {
"compute": "...",
"sending": "...",
"ai": "..."
},
"quotas": {
"emails": "...",
"reveals": "...",
"ai": "...",
"mailboxes": "...",
"contacts": "...",
"users": "..."
},
"is_purchasable": "bool",
"is_activatable": "bool"
},
"monthly_total_cents": "int",
"monthly_total_display": "str",
"current_period_start": "str",
"current_period_end": "str",
"cancel_at_period_end": "bool",
"canceled_at": "null",
"note": "str",
"has_stripe_customer": "bool",
"allowance": {
"emails": "int",
"reveals": "int",
"ai": "int",
"mailboxes": "null",
"contacts": "null",
"users": "null"
},
"members": "int",
"seats_shortfall": "int",
"entitlements": {
"plan_code": "str",
"plan_name": "str",
"status": "str",
"source": "str",
"seats": "int",
"is_free": "bool",
"managed": {
"compute": "...",
"sending": "...",
"ai": "..."
},
"allowance": {
"emails": "...",
"reveals": "...",
"ai": "...",
"mailboxes": "...",
"contacts": "...",
"users": "..."
},
"self_hosted": {
"required": "...",
"node_host": "...",
"verified_at": "...",
"ready": "..."
}
}
}POST/api/v1/salesshift/billing/webhooksignature-verifiedPartialUnauthenticated by design — the signature IS the authentication.
Unauthenticated by design — the signature IS the authentication. If no real signing secret is configured the endpoint refuses rather than trusting the body. An unsigned webhook that flips subscriptions to `active` is a free-subscription endpoint for anyone who can guess the URL.
NOT public in the sense of unprotected: the Stripe-Signature header is verified against STRIPE_WEBHOOK_SECRET. With no real secret configured the route refuses with 503 rather than trusting the body — an unsigned webhook that flips subscriptions to active would be a free-subscription endpoint for anyone who guesses the URL.
Headers
| Name | Type | Required | Notes |
|---|---|---|---|
| Stripe-Signature | string? | optional |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/webhook'Response · 200
{
"detail": "str"
}Errors
| Status | detail |
|---|---|
| 503 | STRIPE_WEBHOOK_SECRET is not configured on this deployment |
| 400 | Missing Stripe-Signature header |
| 400 | Invalid signature |
| 503 | Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan. |
| 409 | Stripe subscription does not match a known plan |
| 502 | Payment provider error: {...} |