Sub-Resellers
A sub-reseller is a reseller whose parent_id points to another reseller. Functionally identical to a top-level reseller — they have their own wallet, their own subscribers, their own login URL — except their parent can fund them, withdraw from them, see their numbers, and (since v1.0.546) move subscribers between themselves and the sub in one click.
This page explains how the hierarchy works, what a parent can and cannot do to a sub, and the tradeoffs behind the one-level depth limit.
How to get here
Section titled “How to get here”From either an admin or a parent reseller’s panel:
- Resellers → Add Reseller
- Direct URL:
/resellers/new
When the parent of the new reseller is non-empty, the new row is a sub-reseller. When parent is empty, it’s a top-level reseller — see Onboarding.
Why one level
Section titled “Why one level”Reseller hierarchy depth is capped at 1: a sub-reseller cannot themselves have sub-resellers. The cap is enforced in the API — a reseller calling POST /resellers with their own ID as parent_id is allowed; a sub-reseller calling the same endpoint gets a 400 “max depth reached”.
Three reasons:
- Balance flow stays linear. Parent → sub is one transfer. Parent → sub → sub-sub would be two transfers with double-entry bookkeeping at every level — easy to get wrong, hard to debug.
- Permission scope stays flat. “Show me my sub-resellers’ subscribers” is one
WHEREclause. “My sub-resellers’ sub-resellers’ subscribers” is recursive — expensive at scale. - No real customer asked. In four years of production deployments, no operator has needed deeper than one level. Resellers who want a deeper tree usually want a separate panel — which is what SaaS-mode does.
Layout — sub-resellers list
Section titled “Layout — sub-resellers list”When you open the Resellers page as a reseller, you see only your own sub-resellers (you never see other top-level resellers). The list shows:
| Column | What it shows |
|---|---|
| Username | Sub-reseller’s login username. |
| Name | Display name (often the company name). |
| Balance | Current wallet balance. |
| Subscribers | Count of subscribers owned by this sub-reseller. |
| Last login | When the sub-reseller last signed in. |
| Actions | Edit, Transfer, Withdraw, Login as, Delete. |
Admins see the full Resellers table (all top-level + all sub-resellers across all parents), with a “Parent” column that links to the parent’s detail page.
What a parent can do to a sub-reseller
Section titled “What a parent can do to a sub-reseller”| Action | Allowed for parent | Allowed for admin | Notes |
|---|---|---|---|
| View sub-reseller’s profile | Yes | Yes | Includes balance, subscriber count, transactions. |
| Edit sub-reseller’s details | Yes | Yes | Name, contact, permission group (within parent’s set). |
| Top up sub’s balance | Yes | Yes | Via Transfer — see below. |
| Withdraw from sub’s balance | Yes | Yes | Returns funds to parent’s own wallet. |
| Change sub’s parent | No | Yes | Re-parenting is an admin-only privilege. |
| Change sub’s credit limit | No | Yes | Credit is a trust setting between admin and reseller. |
Change sub’s allowed_ips | No | Yes | Security setting — admin only. |
| Delete the sub | Yes (if empty) | Yes | Sub must have zero subscribers and zero balance. |
| Impersonate / Login as | No | Yes | Impersonation is admin-only — audit-logged. |
The “no” rows are enforced server-side in the reseller update handler: the field-whitelist in Update silently drops parent_id, credit, allowed_ips, permission_group, etc. for non-admin callers.
Balance flow — Transfer and Withdraw
Section titled “Balance flow — Transfer and Withdraw”Transfer moves money from the parent’s wallet to the sub-reseller’s wallet:
| Step | Effect |
|---|---|
Parent calls POST /resellers/{sub_id}/transfer with amount=100 | Parent balance −100, sub balance +100. |
| Two transaction rows created | One on parent (type=transfer, amount=−100, target=sub), one on sub (type=transfer, amount=+100). |
| Audit log entry | Transferred $100.00 to <sub_name>. |
Withdraw is the reverse — parent pulls funds back from the sub:
| Step | Effect |
|---|---|
Parent calls POST /resellers/{sub_id}/withdraw with amount=50 | Sub balance −50, parent balance +50. |
| Two transaction rows created | Sub (type=withdraw, amount=−50), parent (type=withdraw, amount=+50, target=sub). |
| Audit log entry | Withdrew $50.00 from <sub_name>. |
Both operations enforce direct-child ownership. A parent reseller calling Transfer or Withdraw against a sub that is not their direct child gets a 403 “You can only transfer to your own sub-resellers”. This guard was added as a security audit fix (M6) — without it, any reseller could move money between any two accounts.
See Balance & Transactions for the full list of transaction types.
Common workflows
Section titled “Common workflows”Creating a sub-reseller and funding them
Section titled “Creating a sub-reseller and funding them”- Resellers → Add Reseller.
- Fill in username, password, name, contact. Parent is auto-set to you.
- Pick a permission group. Common choice: a constrained group like
SALES_LIMITEDwith no permission to delete subscribers. - Leave starting balance at
0. Click Create. - From the Resellers list, click the new row → Transfer.
- Enter
200(or whatever you’ve agreed) and a note ("May seed funding"). Click Transfer. - Hand the sub-reseller their credentials. They can now sign in and start adding subscribers.
Reclaiming balance from a sub-reseller who under-performed
Section titled “Reclaiming balance from a sub-reseller who under-performed”- Resellers → click the sub → Withdraw.
- Enter the amount and a note (
"End-of-month settlement"). - Confirm. Balance moves from sub to you in one transaction.
- If you intend to delete the sub afterwards, first withdraw their full balance, then move their subscribers to yourself via the bulk Transfer Reseller action (see Subscribers), then delete.
Deleting a sub-reseller
Section titled “Deleting a sub-reseller”- Ensure the sub has zero subscribers — the API rejects the delete otherwise. Move them to yourself or a different sub first.
- Withdraw any remaining balance back to your own wallet.
- Resellers → click sub → Delete.
- Soft-delete by default. The username is still reserved (you can’t immediately create another sub with the same name). Use the admin Permanent Delete if you need to free the username — only admins have that button.
Ownership boundaries — what a sub-reseller sees of their parent
Section titled “Ownership boundaries — what a sub-reseller sees of their parent”A sub-reseller’s view of their parent:
- They cannot see the parent in any list — the Resellers page filters by
parent_id = caller.reseller_id, which excludes the parent itself. - They cannot call any endpoint with the parent’s ID — the ownership checks on Update, Transfer, Withdraw all reject “caller is not the owner and not a child”.
- They see their own balance and transactions. Transactions where the parent is the counter-party show as
transferorwithdrawwith the parent’s name in the description.
This is the “blind upward” pattern: subs know money comes from somewhere, but they don’t get to introspect the parent.
Permissions
Section titled “Permissions”| Permission | Effect |
|---|---|
resellers.view | See your list of sub-resellers. |
resellers.create | Add a new sub-reseller. |
resellers.edit | Edit your sub-resellers’ details. |
resellers.delete | Delete a sub-reseller (only if empty). |
transactions.transfer | Use Transfer to fund a sub-reseller. |
transactions.withdraw | Pull balance back from a sub-reseller. |
Admins have all of these on every reseller in the system.
Data model snapshot
Section titled “Data model snapshot”resellers├── id├── user_id → users.id (1:1 — every reseller is also a user row)├── parent_id → resellers.id (NULL for top-level)├── balance (NUMERIC — the wallet)├── credit (NUMERIC — credit limit, default 0)├── permission_group → permission_groups.id (NULL = full perms)├── allowed_ips (comma-separated, CIDR or single IP)└── is_active (BOOL — toggling false blocks login)Hierarchy queries are written as WHERE parent_id = ? — depth-1 means there’s never a recursive CTE.
Related pages
Section titled “Related pages”- Reseller Onboarding — the full new-account walk-through.
- Balance & Transactions — Transfer, Withdraw, and the 14 transaction types.
- Managing Subscribers (Reseller) — the one-step parent-to-sub transfer.
- IP Restriction — per-reseller IP allowlist.
- Custom Branding — per-sub branding.