Sub-account settings API: recipes
The OpenAPI pages — Subaccounts API and the endpoint reference — document each operation in isolation. This cookbook chains the settings operations on/subaccounts/:id into the multi-step loops a reseller actually runs: read the effective sender, write a custom one, verify it, clear it, seed recipients, scope channels, and audit the inheritance chain. Everything here is a tenant-owned control: your parent organization owns the sub-accounts, the sender identities, and the allowlists; Orbit resolves and enforces them.
Base URL is https://api.orbit.devotel.io/api/v1 throughout. Every response arrives in the shared envelope: a data object plus meta.request_id / meta.timestamp.
cURL
fetch in TypeScript, requests in Python — and runs from any client. If you already use the Node or Python SDK, the same paths work through its escape hatch (client.request(...)), since these routes take no SDK-specific shaping.
0. Auth, roles, and the common path shape
Authenticate with a bearer token from an owner or admin of the parent organization. The settings plane (notification-email, recipients, allowlist, branding, pricing) is parent-admin territory: a token whose roles only cover a child organization gets 403 when it targets a sibling or its own parent. One exception mirrors the branding surface: a child org’s own admin/owner may read and write its own settings by passing its own organization id as :id.
Unknown or invisible sub-account ids return 404, not 403 — the controller does not leak which child orgs exist to a caller without visibility. Treat a 404 on a id you just created as “wrong token”, not “wrong id”.
These routes manage configuration, not message traffic, so there is no pagination to handle; each GET returns the full settings object for one sub-account.
1. Read the effective notification-email config
GET /subaccounts/:id/notification-email returns two layers: what this org configured, and what actually sends after inheritance resolution. The effective.source enum tells you where the resolver landed: subaccount (this org’s verified override), ancestor (the nearest parent with a verified override), or platform (the Orbit default sender).
cURL
200
configured (the top-level from_name / from_address / verified) is what a PUT wrote on this org; it may be all null on a fresh child. effective is what the notification emitter will actually use, resolved sub-account → parent → platform. When the effective sender comes from somewhere else, effective.source says where and effective.resolved_from_organization_id is the chain pointer — the organization id whose config won, or null when the org itself (or the platform default) won. A child inheriting from its parent therefore shows configured.from_address: null next to effective.source: "ancestor" and resolved_from_organization_id: "org_parent_1".
TypeScript
Python
2. Set a custom sender identity on a child
PUT /subaccounts/:id/notification-email writes the override. The write persists immediately, but the configured address only becomes the effective sender once its sending domain passes DKIM/SPF — the same verification evidence the email-senders surface tracks. Until then the resolver keeps using the ancestor or platform sender, so a successful PUT can still show effective.source: "ancestor". That is the designed activation gate, not a failed write.
cURL
200
verified flips and the resolver promotes the override:
cURL
TypeScript
Python
422 with the offending field named:
422
422, so fixing the body and retrying is always safe. The top validation entries are: from_address not a syntactically valid email, and from_name / from_address sent as non-string, non-null types. Send null, not "", when you mean “clear” (next recipe).
3. Clear the custom sender to restore the parent chain
PUT with from_address: null clears the whole override — the resolver then falls back to the nearest ancestor with a verified sender, or the platform default when no ancestor has one. Send from_name: null alongside; a name without an address is not a sender.
cURL
200
effective in the PUT response already shows the fallback the resolver will use from now on — no follow-up GET needed to confirm a clear.
TypeScript
Python
4. Per-subaccount notification recipients
By default a child’s operational notifications (rate-sheet digests, low-balance alerts) go to the inherited recipients.settings.notification_recipients overrides them per sub-account with two lists: rate_sheet_emails and balance_alert_emails.
cURL
200
PUT replaces the object. A non-empty list means “route these emails to exactly these addresses”; an empty or missing list means “inherit the previous hop’s recipients”, so you can override one category and leave the other inheriting:
cURL
null and the emitter falls back to inherited recipients instead of failing the send path. So a GET right after a legacy write can show notification_recipients: null — rewrite the full object to normalise it.
TypeScript
Python
5. Read and scope the channel-family allowlist
GET /subaccounts/:id/allowed-channels returns the channel families this sub-account may use. Absent any scoping, the child inherits every family the parent has enabled.
cURL
200
cURL
DELETE (or a PUT with an empty list — both clear the override):
cURL
TypeScript
Python
6. Chain-walk audit trail
When the panel badge sayssource: platform but ops expected an ancestor’s sender to win, walk the tree yourself: start at the child, print each hop’s configured state, and climb parent_organization_id until the chain resolves. The resolved_from_organization_id from recipe 1 is the fast answer; this walk is the audit trail that explains why — typically an ancestor’s override is present but unverified, so the resolver skipped it.
TypeScript
configured.from_address set but verified: false. The resolver only promotes verified senders, so the chain keeps climbing past it. Re-verify the ancestor’s domain (the email-senders surface) or clear the stale override with recipe 3.
7. White-label reseller checklist
One sequence an API-first reseller runs when a child org goes live: create, set the commercial knobs, brand it, claim and verify the custom domain, install the sender identity, seed recipients, scope channels, and smoke-test with one send.cURL
Troubleshooting
- 403 on every settings call. The token’s roles cover a child org, not the parent. Settings writes need owner or admin on the parent; a child admin can only target its own organization id.
- 404 on a id that exists. The resource is hidden to this caller — the controller answers
404instead of403for sub-accounts outside the token’s visibility. Re-issue the token from the parent org. - 422 with
VALIDATION_ERROR. The top entries:from_addressnot a valid email;from_name/from_addresssent as wrong types (sendnullto clear, not""); an allowlist entry outside the known channel families; a recipients entry that is not an email address. Nothing persists on a422. - The silent fallback. Your
PUTreturned200buteffective.sourceis stillplatformorancestor. The write succeeded — the activation gate did not pass. Check, in order: the new address’s domain is not DKIM/SPF-verified yet (verified: false), you cleared with""instead ofnullon an earlier call, or you wrote a different:idthan the one you are reading. Recipe 6 prints the whole chain and finds which hop is blocking.
See also
- Reseller subaccounts guide — provisioning, credits, and the commercial lifecycle.
- Sub-account lifecycle: suspend and reactivate — how suspension composes with the allowlist.
- Staging/production split and AI configuration — the other per-subaccount settings surfaces.
- API error handling by example — the shared error envelope used above.