Skip to main content

Billing for Multiple Workspaces

One billing account can own several workspaces (sub-accounts). Agencies and platforms often create one workspace per client, and companies one per brand or department. This page explains how such an account is billed and how to get usage and cost per workspace through the API.

How to create sub-accounts and control them from one token is covered in Multi-Tenancy & Sub-Accounts, Creating Sub-Accounts and Cross-Account API Access. The general billing rules are in How Billing Works.

Who pays​

  • The billing account pays for all of its workspaces. It has one plan, one payment method and gets one invoice per month for every workspace on it. A workspace has no payment relationship of its own.
  • The owner of the billing account manages the plan and the payment method. Only the owner can add workspaces to the billing account.
  • Workspaces share the plan. Every workspace is rated at the billing account's plan and rates, unless it has its own plan.
  • On the invoice, each usage line starts with the name of the workspace it belongs to, so you can see which workspace caused which charge.

One pool for all workspaces​

The included message pool belongs to the billing account, not to a workspace:

  • Usage from every workspace is added up and drawn from the same pool, most expensive usage first.
  • The base fee is charged once for the billing account, not per workspace. Exception: on plans that charge the base fee per connected channel, every channel counts, whatever workspace it is in.
  • A single busy workspace can use up most of the pool for everyone. Usage per workspace shows you which one.
  • SendSeven does not split the pool among workspaces. The API returns each workspace's gross usage (before the pool) and applies the pool once, at account level. Deciding how the free units are shared is up to you. See step 4 of the recipe.

Workspaces on their own plan​

A billing account can also have one or more workspaces on their own negotiated plan, while the others stay on the account plan. The SendSeven team sets this up on request. Such a workspace:

  • has its own base fee and its own included pool. It neither uses nor adds to the pool of the account plan.
  • stays on the same invoice, with its lines labelled with the workspace name.
  • is listed separately in the usage API (pricing_groups, see below), not among the workspaces of the account plan.

An account without such a workspace has a single plan, and pricing_groups is null.

Why read usage per workspace​

  • Re-bill your clients. As an agency or reseller, charge each client for exactly what their workspace used.
  • Allocate costs internally, per brand, country or department.
  • Find the workspace that uses the pool, before it pushes the whole account into overage.
  • Reconcile the monthly invoice. The per-workspace figures add up to the account totals the invoice is built from.

Usage per workspace API​

GET https://api.sendseven.com/api/v1/billing/tenant-usage
ParameterValues
scopeaccount (default): every workspace on the billing account. tenant: only the workspace your credential resolves to.
period0 (default): the current month to date. 1: the last closed month. 2: the month before.

Periods are calendar months in UTC. A closed month is priced at the plan that was active during that month, the same way the invoice priced it.

Access​

This endpoint shows data from every workspace on the billing account, so it needs more than a normal workspace permission:

  1. The credential needs billing:read.
  2. The user must be the billing-account owner, or have account-wide billing access. The owner can give this to a team member in Settings > Members (open the member, turn on Account-wide billing stats) or with PUT /api/v1/users/{user_id}/account-billing-access and the body {"enabled": true}. That call needs team:admin and only works for the owner. A role such as *:* in a workspace does not give account-wide billing access.
  3. An API token must itself list the scope account:billing_read, written out exactly. A wildcard does not count.
  4. OAuth apps can never call this endpoint (403, code: oauth_not_allowed).

Otherwise the API returns 403. A token for this purpose needs these scopes:

{
"name": "Billing export",
"scopes": ["billing:read", "account:billing_read"]
}

Create it with POST /api/v1/api-tokens as the billing-account owner (see Authentication). Add tenants:manage only if you also want to read a single workspace with X-Tenant-ID.

You do not need a token per workspace. With scope=account, one call from any workspace of the billing account returns every workspace on it. The owner sees all of them, including workspaces they are not a member of.

Example response​

GET /billing/tenant-usage?scope=account&period=0 for a billing account with two workspaces. Rates and amounts are example values.

{
"scope": "account",
"period": 0,
"period_year": 2026,
"period_month": 10,
"pricing_model_id": "pm_plan",
"commitment_pinned": false,
"billing_account_id": "ba_7f3e",
"period_start": "2026-10-01T00:00:00Z",
"period_end": "2026-10-19T09:00:00Z",
"tenants": [
{
"tenant_id": "3f6c1a2e-tenant-retail",
"tenant_name": "Acme Retail",
"is_current": true,
"messages_sent": 1840,
"email_campaign_sends": 1200,
"kb_queries": 420,
"counts": {
"message": 1840, "campaign_message": 900, "email_campaign": 1200,
"kb_query": 420, "alert_sms": 0, "alert_whatsapp": 0
},
"gross_by_type": {
"message": 36.8, "campaign_message": 18.0, "email_campaign": 12.0,
"kb_query": 4.2, "alert_sms": 0.0, "alert_whatsapp": 0.0
},
"gross_total_eur": 71.0,
"channels": [
{
"channel_id": "c-wa-1",
"channel_type": "whatsapp",
"display_name": "WhatsApp",
"identifier": "+4930123456",
"is_archived": false,
"counts": {"message": 1700, "campaign_message": 900, "email_campaign": 0, "kb_query": 0, "alert_sms": 0, "alert_whatsapp": 0},
"gross_by_type": {"message": 34.0, "campaign_message": 18.0, "email_campaign": 0.0, "kb_query": 0.0, "alert_sms": 0.0, "alert_whatsapp": 0.0},
"gross_total_eur": 52.0
},
{
"channel_id": null,
"channel_type": "account",
"display_name": "Account-level / system",
"identifier": null,
"is_archived": false,
"counts": {"message": 140, "campaign_message": 0, "email_campaign": 1200, "kb_query": 420, "alert_sms": 0, "alert_whatsapp": 0},
"gross_by_type": {"message": 2.8, "campaign_message": 0.0, "email_campaign": 12.0, "kb_query": 4.2, "alert_sms": 0.0, "alert_whatsapp": 0.0},
"gross_total_eur": 19.0
}
],
"ai_credits": {
"quantity": 420, "amount_eur": 4.2, "unit_price_eur": 0.01,
"bot_messages": {"quantity": 0, "amount_eur": 0.0, "by_charge": {}},
"tool_usage": {"quantity": 30, "amount_eur": 0.3, "by_charge": {"web_search": {"quantity": 30, "amount_eur": 0.3}}},
"other": {"quantity": 390, "amount_eur": 3.9, "by_charge": {"ai_credits": {"quantity": 390, "amount_eur": 3.9}}}
}
},
{
"tenant_id": "8b2d9e41-tenant-support",
"tenant_name": "Acme Support",
"is_current": false,
"messages_sent": 610,
"email_campaign_sends": 0,
"kb_queries": 180,
"counts": {
"message": 610, "campaign_message": 0, "email_campaign": 0,
"kb_query": 180, "alert_sms": 2, "alert_whatsapp": 0
},
"gross_by_type": {
"message": 12.2, "campaign_message": 0.0, "email_campaign": 0.0,
"kb_query": 1.8, "alert_sms": 0.2, "alert_whatsapp": 0.0
},
"gross_total_eur": 14.2,
"channels": ["..."],
"ai_credits": {"quantity": 180, "amount_eur": 1.8, "...": "..."}
}
],
"totals": {"messages_sent": 2450, "email_campaign_sends": 1200, "kb_queries": 600},
"cost": {
"fee_type": "base_fee",
"workspace_gross_total_eur": 85.2,
"pool_deduction_eur": 50.0,
"pool_deduction_by_type": {"message": 49.0, "campaign_message": 1.0},
"net_usage_eur": 35.2,
"base_fee_eur": 79.0,
"included_allocation": [
{"category": "message", "units": 2450, "unit_rate": 0.02},
{"category": "campaign_message", "units": 50, "unit_rate": 0.02}
],
"invoice_total_eur": null
},
"daily": [
{
"tenant_id": "3f6c1a2e-tenant-retail",
"points": [
{"date": "2026-10-01", "counts": {"message": 96, "campaign_message": 0, "email_campaign": 0, "kb_query": 21}, "gross_eur": null}
],
"reconciles": true
}
],
"pricing_groups": null,
"pricing_groups_total": null,
"ai_credits": {"quantity": 600, "amount_eur": 6.0, "...": "..."}
}

In this example the plan includes 2,500 messages. The pool covers the 2,450 conversation messages and 50 of the 900 campaign sends, because those have the highest rate. The rest is charged: 85.20 gross − 50.00 covered by the pool = 35.20 net usage, on top of the base fee.

Fields​

Per workspace (tenants[])

FieldMeaning
tenant_id, tenant_nameThe workspace. Use tenant_id as X-Tenant-ID.
is_currenttrue for the workspace your credential resolves to.
countsBillable units per category. Outgoing only: inbound is never counted.
gross_by_type, gross_total_eurCost in EUR per category, units × your rate, before the pool.
channels[]The same counts and cost per channel. See Per channel.
ai_creditsThe workspace's AI credits split into groups. See AI Credits. null if the split could not be computed.
messages_sent, email_campaign_sends, kb_queriesOlder count fields, kept for compatibility. messages_sent excludes campaign sends. Prefer counts.

Category keys in counts and gross_by_type:

KeyUsageIn the pool
messageConversation messages (team, bots, flows, API), including transactional emailYes
campaign_messageCampaign sends on messaging channelsYes
email_campaignEmail campaign sendsYes
kb_queryAI credits (the key name is kept for compatibility)Yes
alert_sms, alert_whatsappMonitoring alerts by SMS or WhatsAppNo, always charged

Account level (cost)

FieldMeaning
fee_typebase_fee for current plans, minimum_commit for legacy plans.
workspace_gross_total_eurSum of all workspaces' gross_total_eur.
pool_deduction_eur, pool_deduction_by_typeWhat the included pool covered, in EUR, applied once for the whole account.
included_allocation[]The pool in units: per category the units covered and the rate.
net_usage_eurUsage that is charged: workspace_gross_total_eur − pool_deduction_eur.
base_fee_eurThe monthly base fee. Charged on top of net_usage_eur, never part of a workspace's gross.
invoice_total_eurClosed months only: the amount of SendSeven's stored invoice for that month, as a cross-check, or null. The invoice also carries the base fee and VAT, so it is not equal to net_usage_eur.

Other top-level fields: period_year, period_month, period_start, period_end describe the period. pricing_model_id is the plan used to price it. commitment_pinned is false when a closed month had no record of its plan and was priced at the current plan, so treat its euro figures as an estimate. totals is the sum of the older count fields. ai_credits is the AI-credit split for all listed workspaces.

Own-plan workspaces (pricing_groups)

null unless a workspace is on its own plan. Then:

  • tenants[], totals and cost cover only the workspaces on the account plan.
  • pricing_groups[] lists one entry for the account plan (is_default: true, key: "account") and one per own-plan workspace (key = its tenant_id, label = its name). Each entry has its tenant_ids, plan (plan_display_name, pricing_model_id), included_messages_total / included_messages_used, usage_total_euros (gross), included_credit_euros, billable_usage_euros and group_total_euros.
  • pricing_groups_total sums all groups. estimated_total_euros is the account's estimated next charge across all plans.

Per channel​

Each workspace's channels[] lists every channel that had usage, archived channels included (is_archived: true), sorted by cost. identifier is the phone number, handle or page the channel belongs to.

  • Only message and campaign_message belong to a real channel.
  • One last entry with channel_id: null and channel_type: "account" takes everything without a channel: email campaigns, AI credits, monitoring alerts, and messages without a channel such as transactional email.
  • The channels of a workspace always add up to that workspace's counts and gross_total_eur.

Per day​

daily[] holds one series per workspace with one point per day (UTC) for message, campaign_message, email_campaign and kb_query. Monitoring alerts are not in the daily series. reconciles is true when the days add up to the workspace's period counts. daily can be empty if the daily figures are not available, and the rest of the response is still complete.

Rules you can rely on​

  • Sum of tenants[*].counts[c] = the account total for category c.
  • Sum of tenants[*].gross_total_eur = cost.workspace_gross_total_eur.
  • cost.workspace_gross_total_eur − cost.pool_deduction_eur = cost.net_usage_eur.
  • For the current month, cost.net_usage_eur is the usage part of GET /billing/upcoming-charges.
  • Workspaces that were deactivated or are being deleted are still listed with scope=account, because their usage is still invoiced.

A single workspace​

scope=tenant returns only the workspace your credential resolves to. To read another workspace from one token, use a parent token with tenants:manage and the X-Tenant-ID header (Cross-Account API Access):

curl "https://api.sendseven.com/api/v1/billing/tenant-usage?scope=tenant&period=1" \
-H "Authorization: Bearer s7_api_PARENT_TOKEN" \
-H "X-Tenant-ID: 8b2d9e41-tenant-support"

The token needs billing:read, account:billing_read and tenants:manage.

The cost block with scope=tenant

With scope=tenant, the cost block applies the account's whole included pool to this one workspace. That figure is only meaningful for a workspace on its own plan, which has its own pool. For workspaces that share the account plan, use the per-workspace gross from scope=account, and allocate the pool yourself if you need to.

Recipe: last month's usage per workspace​

This script reads last month (period=1), lists each workspace's usage and cost per category, and checks the totals against the account footer.

import os
import requests

API = "https://api.sendseven.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SENDSEVEN_TOKEN']}"} # billing:read + account:billing_read
CATEGORIES = ["message", "campaign_message", "email_campaign", "kb_query", "alert_sms", "alert_whatsapp"]

def tenant_usage(scope="account", period=1, tenant_id=None):
headers = dict(HEADERS)
if tenant_id:
headers["X-Tenant-ID"] = tenant_id # needs tenants:manage on the token
r = requests.get(f"{API}/billing/tenant-usage",
params={"scope": scope, "period": period}, headers=headers, timeout=60)
r.raise_for_status()
return r.json()

# 1. One call returns every workspace on the account plan.
data = tenant_usage()
print(f"Period {data['period_year']}-{data['period_month']:02d}")

rows = []
for ws in data["tenants"]:
rows.append({
"workspace": ws["tenant_name"],
"tenant_id": ws["tenant_id"],
"counts": {c: ws["counts"].get(c, 0) for c in CATEGORIES},
"gross_eur": {c: ws["gross_by_type"].get(c, 0.0) for c in CATEGORIES},
"gross_total_eur": ws["gross_total_eur"],
})

# 2. Sum per category across workspaces and check against the footer.
account_counts = {c: sum(r["counts"][c] for r in rows) for c in CATEGORIES}
account_gross = round(sum(r["gross_total_eur"] for r in rows), 2)
cost = data["cost"]
assert abs(account_gross - cost["workspace_gross_total_eur"]) < 0.01

# 3. Workspaces on their own plan are listed in pricing_groups. Read each one separately.
for group in data.get("pricing_groups") or []:
if group["is_default"]:
continue
for tid in group["tenant_ids"]:
own = tenant_usage(scope="tenant", tenant_id=tid)
for ws in own["tenants"]:
print(f"{ws['tenant_name']} (own plan): gross {ws['gross_total_eur']:.2f} EUR, "
f"net {own['cost']['net_usage_eur']:.2f} EUR + base fee {own['cost']['base_fee_eur']:.2f} EUR")

# 4. Optional: share the account's net usage in proportion to each workspace's gross.
# SendSeven does not split the pool; this is one possible rule of your own.
for r in rows:
share = r["gross_total_eur"] / account_gross if account_gross else 0
r["net_share_eur"] = round(share * cost["net_usage_eur"], 2)
print(f"{r['workspace']}: counts {r['counts']}, gross {r['gross_total_eur']:.2f} EUR, "
f"share of net usage {r['net_share_eur']:.2f} EUR")

print(f"Account: {account_counts}")
print(f"Gross {cost['workspace_gross_total_eur']:.2f} - pool {cost['pool_deduction_eur']:.2f} "
f"= net {cost['net_usage_eur']:.2f} EUR, plus base fee {cost['base_fee_eur']:.2f} EUR")

Tips:

  • Read a closed month once its invoice has been issued (see GET /invoices). Until then, its figures can still change slightly.
  • period only goes back two closed months. Store each month's result if you need a longer history.
  • If you pass the base fee on to clients, decide how to share it, for example evenly or in proportion to usage. The API reports it once, at account level.

In the app​

The same data is in Settings > Usage. It is visible to the billing-account owner and to members with account-wide billing access. The Settings > Billing page also links to it with View usage by workspace. Choose Whole billing account or This workspace only, and switch the view between By workspace and By channel.

Next steps​