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
| Parameter | Values |
|---|---|
scope | account (default): every workspace on the billing account. tenant: only the workspace your credential resolves to. |
period | 0 (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:
- The credential needs
billing:read. - 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-accessand the body{"enabled": true}. That call needsteam:adminand only works for the owner. A role such as*:*in a workspace does not give account-wide billing access. - An API token must itself list the scope
account:billing_read, written out exactly. A wildcard does not count. - 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[])
| Field | Meaning |
|---|---|
tenant_id, tenant_name | The workspace. Use tenant_id as X-Tenant-ID. |
is_current | true for the workspace your credential resolves to. |
counts | Billable units per category. Outgoing only: inbound is never counted. |
gross_by_type, gross_total_eur | Cost in EUR per category, units × your rate, before the pool. |
channels[] | The same counts and cost per channel. See Per channel. |
ai_credits | The workspace's AI credits split into groups. See AI Credits. null if the split could not be computed. |
messages_sent, email_campaign_sends, kb_queries | Older count fields, kept for compatibility. messages_sent excludes campaign sends. Prefer counts. |
Category keys in counts and gross_by_type:
| Key | Usage | In the pool |
|---|---|---|
message | Conversation messages (team, bots, flows, API), including transactional email | Yes |
campaign_message | Campaign sends on messaging channels | Yes |
email_campaign | Email campaign sends | Yes |
kb_query | AI credits (the key name is kept for compatibility) | Yes |
alert_sms, alert_whatsapp | Monitoring alerts by SMS or WhatsApp | No, always charged |
Account level (cost)
| Field | Meaning |
|---|---|
fee_type | base_fee for current plans, minimum_commit for legacy plans. |
workspace_gross_total_eur | Sum of all workspaces' gross_total_eur. |
pool_deduction_eur, pool_deduction_by_type | What 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_eur | Usage that is charged: workspace_gross_total_eur − pool_deduction_eur. |
base_fee_eur | The monthly base fee. Charged on top of net_usage_eur, never part of a workspace's gross. |
invoice_total_eur | Closed 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[],totalsandcostcover 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= itstenant_id,label= its name). Each entry has itstenant_ids, plan (plan_display_name,pricing_model_id),included_messages_total/included_messages_used,usage_total_euros(gross),included_credit_euros,billable_usage_eurosandgroup_total_euros.pricing_groups_totalsums all groups.estimated_total_eurosis 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
messageandcampaign_messagebelong to a real channel. - One last entry with
channel_id: nullandchannel_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
countsandgross_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 categoryc. - 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_euris the usage part ofGET /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.
cost block with scope=tenantWith 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. periodonly 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
- How Billing Works: base fee, the pool, what is billable, the invoice
- AI Credits: the AI-credit split in this response
- Cross-Account API Access: parent tokens and
X-Tenant-ID - Creating Sub-Accounts: add workspaces to your billing account