Skip to main content
These walkthroughs show how to use the Snowglobe Billing Management API (/management/*) to manage organizations, members, and quotas. Every example is plain Python 3 with requests. The tutorials cover the following operator flows:
  1. Create an organization
  2. Add users to an organization
  3. Remove users from an organization
  4. Set quotas for an organization
  5. Set up a billing hierarchy (parent and child orgs)
The mutating endpoints are: create org, create invitations, and set/delete quota. Reads (get org, list orgs, list invitations, billing reports) are available to any member.

Prerequisites

  • A Snowglobe account with billing_admin access. Sign up here.
  • A Snowglobe API key (see Setup below).
  • Python 3 with requests installed (pip install requests).

Setup: shared client

Every request is authenticated with an API key sent in the x-api-key header. Generate a key from the Snowglobe web app (under your account/organization settings) and export it as an environment variable. All examples below reuse this one client.

Roles

Most flows are role-gated. The four roles are owner, admin, billing_admin, and member. A 403 almost always means your API key’s roles don’t satisfy the row above, not that the org is missing (that returns 404).

Quick start: onboard a customer end-to-end

This routine chains the common onboarding path into one function: create the org, invite the initial admins and members, set the quota, and verify the result. It calls helpers defined in the flows below (create_organization_full in Flow 1, create_invitations in Flow 2, and set_quota / get_organization in Flow 4), so copy those in alongside the shared client. Each flow below explains one piece in depth.
Notes:
  • The caller is assigned billing_admin on the new org automatically, and is the only account added directly. Everyone else, whether passed in members at create-time or through the invitations endpoint, and regardless of role, must accept an invitation before they join. So the admins and members here all show up under pending_invitations until they accept.
  • The caller must be a billing_admin for step 1 (create) and step 3 (quota). The same API key is fine for the invitations in step 2, since billing_admin satisfies that gate too.
  • To onboard a customer under a billing parent, pass billing_org=parent_id (see Flow 5).

Flow 1: Create an organization

Endpoint: POST /management/organizations Who: the authenticated user must be a billing_admin.

Minimal create

Only display_name is required.
The 201 response is the full Organization object. The fields you’ll most often key off of afterwards:

Create with members, a billing parent, and an initial quota

You can seed people in the same call that creates the org. members here is a list of OrganizationMemberInput, the same shape used to invite people in Flow 2 (each needs roles, plus id or email). The caller (the billing_admin making this request) is the only account added to the org directly. Everyone listed in members receives an invitation and must accept it before they appear in org["members"], regardless of the role they’re assigned. In other words, passing someone in members is a shortcut for inviting them at create-time, not a way to add them without acceptance.
billingOrg rules:
  • The referenced org must already exist.
  • It must not itself have a billingOrg. Nested billing hierarchies are rejected. A parent can have many children; a child cannot be a parent.
  • Violating either rule returns 400.

Confirm it exists

GET on an org you have no role in returns 403; a missing id returns 404.

Flow 2: Add users to an organization

Endpoint: POST /management/organizations/{orgId}/invitations Who: the caller needs owner, admin, or billing_admin in that org.

How adding a user works

There is no add-member-directly endpoint. You invite users, and they become members once they accept. The lifecycle:

Batch-invite

The request body is an array of OrganizationMemberInput. Each entry needs roles and at least one of email or id. When only id is given, the server resolves the email via the auth provider at send-time.
The response is a list of full OrganizationInvitation objects, including the invitation_url each invitee must follow to accept. Common 400 causes: an entry missing roles, or an entry with neither id nor email.

List invitations to track who hasn’t accepted

This endpoint returns only unaccepted invitations, so everything it returns is still pending; no client-side filtering is needed. An invitation drops out of the list once the invitee accepts.

Verify the user landed as a member

After the invitee accepts, they move from pendingInvitations into members:

Flow 3: Remove users from an organization

Membership changes run through the invitation lifecycle, so the operator task here is to audit who currently has access, both active members and outstanding invitations, and confirm the resulting state.
You can filter that report to find a specific person before acting on them:

Flow 4: Set quotas for an organization

Endpoints: PUT /management/organizations/{orgId}/quota (set/replace), DELETE /management/organizations/{orgId}/quota (clear). Who: the caller must be the billing_admin of that specific org.

What a quota is

A quota is a single DimensionedValue:
  • amount: an integer cap that applies per billing period. Billing periods are monthly in practice, so the quota is effectively a monthly cap; usage resets each period.
  • unit: a string. In practice it is always "conversations", and the server defaults it to "conversations" when omitted, so you don’t have to send it. Sending it explicitly makes the code more readable, and the field is left open for forward compatibility.
Quota is a replace, not an increment: each PUT overwrites the previous value wholesale. There is no separate “add 10k” call at this layer. The API models three distinct numeric concepts. Only the first is settable here; the other two are read-only. Keep them separate so you don’t confuse them with the quota. When someone says “set the quota,” they mean the first row: a conversations cap via PUT. Usage and adjustments are surfaced for visibility (see reading them back below) but are not writable through this API.

Set or replace a quota

The 200 response echoes back the stored DimensionedValue. Raising or lowering the cap later is the same call with a new amount.

Remove a quota (back to unlimited/unconfigured)

DELETE returns 204 No Content, so there is no body to parse.

Read quota, usage, and billing back

Quota and usage ride directly on the Organization object. Historical billing (usage, adjustments, and final billed amount per period) comes from the billing-reports endpoint.
If the caller is a billing_admin, the billing-reports endpoint also returns reports for any child orgs billed under this one (those whose billingOrg points here). A non-billing-admin sees only the org’s own reports.

Flow 5: Set up a billing hierarchy (parent and child orgs)

Snowglobe supports a one-level billing hierarchy: a parent org that carries the billing relationship, and any number of child orgs billed under it. This is the reseller / multi-tenant pattern. Each customer team gets its own org, with its own members and quota, but the bills roll up to a single parent.

The nesting rule

billingOrg may point only at an org that does not itself have a billingOrg:
A parent can have many children; a child can never be a parent. Attempting to nest returns 400.

Build the hierarchy

create_organization_full is defined in Flow 1.

Guard against the nesting error

If you’re building the hierarchy from data you don’t fully control, check the prospective parent before you point at it. An org that already has a billingOrg cannot serve as a parent:

Read the whole hierarchy’s billing at once

When the caller is a billing_admin, the parent’s billing-reports endpoint returns reports for the parent and every child billed under it, so a single call gives you the consolidated view:
billing_reports is defined in Flow 4. Setting or clearing a quota is always per-org (there is no inherited quota), so use Flow 4 against each child id to adjust its cap.

Appendix: status codes

The request() helper at the top raises RuntimeError with the server’s body on any 4xx/5xx code, so failures surface the actual reason rather than a bare status number.