Skip to content
SSalesShiftDocs
API reference

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.

Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request body (optional) — Body_activate_free_api_v1_salesshift_billing_activate_post

FieldTypeRequiredNotes
plan_codestringoptionalDefault "self_hosted".

The body in the request below is the exact one sent during verification.

Request

curl
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

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
404Unknown plan '{...}'
400{...} is a paid plan — use checkout.
409computed 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-activateplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:557Called against http://127.0.0.1:8741 on 2026-08-12; it returned the 409 that refuses to downgrade a granted paid plan. The activation itself was not run against this workspace — it is a comped Organization and must stay that way — but it is not undemonstrated: the /billing/events response captured in the same run carries the plan.activated row this handler wrote earlier in the session, 'Self-Hosted activated — self-serve, no charge'.
POST/api/v1/salesshift/billing/cancelPartialCancel the Stripe subscription, at period end or immediately.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request body (optional) — Body_cancel_subscription_api_v1_salesshift_billing_cancel_post

FieldTypeRequiredNotes
at_period_endbooleanoptionalDefault true.

The body in the request below is the exact one sent during verification.

Request

curl
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

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
409No subscription to cancel
409This workspace is on a granted plan. Contact support to change it.
409Stripe subscription does not match a known plan
502Payment provider error: {...}
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#post-api-v1-salesshift-billing-cancelplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:1088Called against http://127.0.0.1:8741 on 2026-08-12 and returned the 409 that refuses to cancel a granted plan. No Stripe subscription has been cancelled here.
POST/api/v1/salesshift/billing/changePartialMove to another paid plan, change the seat count, or both.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request body (required) — ChangeIn

FieldTypeRequiredNotes
plan_codestring?optional
seatsinteger?optional

The body in the request below is the exact one sent during verification.

Request

curl
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

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
409No subscription to change — choose a plan first.
400To move to {...}, cancel this subscription — the workspace falls to it at the end of the period.
404Unknown plan '{...}'
502Payment provider error: {...}
409Stripe subscription does not match a known plan
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#post-api-v1-salesshift-billing-changeplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:1037Called against http://127.0.0.1:8741 on 2026-08-12 and returned the 400 that refuses a free target. Neither branch that follows was run: the Stripe modify needs a paid subscription, and the comp branch would have moved this workspace off Organization.
POST/api/v1/salesshift/billing/checkoutPartialStart a Stripe Checkout session for a paid plan at a given seat count.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request body (required) — CheckoutIn

FieldTypeRequiredNotes
plan_codestringrequired
seatsintegeroptionalDefault 1. min 1, max 500.

The body in the request below is the exact one sent during verification.

Request

curl
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

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
404Unknown plan '{...}'
400{...} is free — activate it instead of checking out.
502Payment provider error: {...}
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#post-api-v1-salesshift-billing-checkoutplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:873Called against http://127.0.0.1:8741 on 2026-08-12 and returned the 400 that refuses to check out a free plan. No Checkout Session has been created here and no card has ever been charged on this deployment, so the Stripe path is code-only.
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.

Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request body (required) — Body_confirm_checkout_api_v1_salesshift_billing_checkout_confirm_post

FieldTypeRequiredNotes
session_idstringrequired

Values in the request below are typed placeholders, not sample data.

Request

curl
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

Top-level keys
{
  "applied": "…",
  "reason": "…",
  "subscription": "…"
}
Top-level keys read from the handler's return statements.

Errors

Statusdetail
403Checkout session belongs to another workspace
502Payment provider error: {...}
409Stripe subscription does not match a known plan
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#post-api-v1-salesshift-billing-checkout-confirmplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:973Implemented and read in source for this inventory, but not exercised against a running server here — it needs a completed Stripe Checkout Session, and none exists on this deployment. Treat as undemonstrated until someone calls it.
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.

Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/entitlements' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "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"
  }
}
Observed on 2026-08-12 — keys are real, values are the types that came back.
#get-api-v1-salesshift-billing-entitlementsplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:485Called against http://127.0.0.1:8741 on 2026-08-12 with a real JWT; the response matched the shape recorded below.
GET/api/v1/salesshift/billing/eventsCalled liveThe workspace's platform-billing event log, newest first.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Query parameters

NameTypeRequiredNotes
limitintegeroptionalDefault 25.

Request

curl
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/events' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "events": [
    {
      "id": "...",
      "kind": "...",
      "summary": "...",
      "amount_cents": "...",
      "created_at": "..."
    },
    "...x5"
  ]
}
Observed on 2026-08-12 — keys are real, values are the types that came back.
#get-api-v1-salesshift-billing-eventsplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:500Called against http://127.0.0.1:8741 on 2026-08-12 with a real JWT; the response matched the shape recorded below.
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.

Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/invoices' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "invoices": [],
  "reason": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
502Payment provider error: {...}
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#get-api-v1-salesshift-billing-invoicesplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:522Called against http://127.0.0.1:8741 on 2026-08-12 and returned 200 {invoices: [], reason: 'no_stripe_customer'}. That is the early return: the workspace is comped, has no Stripe customer, and so the Stripe Invoice.list call this route exists for was never made here.
GET/api/v1/salesshift/billing/plansCalled liveThe published plans, what each includes, and whether it is bought or activated.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/plans' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "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"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.
#get-api-v1-salesshift-billing-plansplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:441Called against http://127.0.0.1:8741 on 2026-08-12 with a real JWT; the response matched the shape recorded below.
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.

Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/portal' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
409This workspace has no payment account yet — choose a plan first.
502Payment provider error: {...}
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#post-api-v1-salesshift-billing-portalplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:1004Called against http://127.0.0.1:8741 on 2026-08-12 and returned the 409 for a workspace with no Stripe customer. Creating a portal session needs a customer, and the only workspace available here is comped, so the Stripe path is undemonstrated.
POST/api/v1/salesshift/billing/resumePartialUndo a cancellation that has not taken effect yet.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/resume' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
409Nothing to resume
502Payment provider error: {...}
409Stripe subscription does not match a known plan
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
#post-api-v1-salesshift-billing-resumeplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:1115Called against http://127.0.0.1:8741 on 2026-08-12 and returned the 409 for a row that is not a Stripe subscription. The resume path needs a Stripe subscription with a pending cancellation, which this deployment has never had.
GET/api/v1/salesshift/billing/self-hostedCalled liveWhether this workspace has a registered node, and the identity a node must report to become one.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/self-hosted' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "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"
  }
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
404Workspace not found
#get-api-v1-salesshift-billing-self-hostedplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:731Called against http://127.0.0.1:8741 on 2026-08-12 in both states: with no node (host null, live null) and with one registered, where it reported the fingerprint and probed the node live.
POST/api/v1/salesshift/billing/self-hosted/nodeCalled liveRegister the tenant's own node, after the node proves whose it is.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request body (required) — SelfHostedNodeIn

FieldTypeRequiredNotes
hoststringrequired≤ 255 chars.

The body in the request below is the exact one sent during verification.

Request

curl
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

Observed shape
{
  "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": "..."
    }
  }
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
400{...} answered but did not identify a tenant. Set TENANT_ID={...} in the node's environment and restart it.
403computed 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.)" )
404Workspace not found
400A node address is required
400The node must be reachable over HTTPS.
502Could not reach {...}/health — {...}
#post-api-v1-salesshift-billing-self-hosted-nodeplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:778All three outcomes were exercised against http://127.0.0.1:8741 on 2026-08-12: 400 for a plaintext http:// host, 403 for a stub node reporting another workspace's id, and 200 for a stub node reporting this one's. The 200 wrote users_organization.tenant_primary_node_host and was undone with the DELETE below.
DELETE/api/v1/salesshift/billing/self-hosted/nodeCalled liveDetach the node.

Detach the node. Sending and agents stop until another is registered.

Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl -X DELETE 'https://api.vxcloud.io/api/v1/salesshift/billing/self-hosted/node' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "host": "null",
  "verified": "bool"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
404Workspace not found
#delete-api-v1-salesshift-billing-self-hosted-nodeplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:838Called against http://127.0.0.1:8741 on 2026-08-12 to detach the node registered a moment earlier; the following GET /billing/self-hosted read host null again.
GET/api/v1/salesshift/billing/subscriptionCalled liveThis workspace's own subscription: plan, seats, period, members and the entitlements it resolves to.
Auth
get_org_user
Scope
organization
Success
200 · application/json
Envelope
bare-object

Request

curl
curl 'https://api.vxcloud.io/api/v1/salesshift/billing/subscription' \
  -H "Authorization: Bearer $SS_TOKEN"

Response · 200

Observed shape
{
  "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": "..."
    }
  }
}
Observed on 2026-08-12 — keys are real, values are the types that came back.
#get-api-v1-salesshift-billing-subscriptionplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:455Called against http://127.0.0.1:8741 on 2026-08-12 with a real JWT; the response matched the shape recorded below.
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.

Auth
no dependency
Scope
signature-verified
Success
200 · application/json
Envelope
bare-object

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

NameTypeRequiredNotes
Stripe-Signaturestring?optional

Request

curl
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/billing/webhook'

Response · 200

Observed shape
{
  "detail": "str"
}
Observed on 2026-08-12 — keys are real, values are the types that came back.

Errors

Statusdetail
503STRIPE_WEBHOOK_SECRET is not configured on this deployment
400Missing Stripe-Signature header
400Invalid signature
503Card payment is not configured on this deployment. Contact support at https://vxcloud.io/support/ to activate a plan.
409Stripe subscription does not match a known plan
502Payment provider error: {...}
#post-api-v1-salesshift-billing-webhookplatform_billing · vxcloud_fastapiclient/app/services/salesshift/platform_billing.py:1135Posted to http://127.0.0.1:8741 on 2026-08-12 with no signature and returned 503: STRIPE_WEBHOOK_SECRET is a placeholder on this deployment, so the endpoint refuses rather than trusting the body. Signature verification and the subscription sync behind it have never run here — /checkout/confirm is what activates a plan today.