There are two real ways to create a brand-new Organization at /signup: Google, or the direct multi-step
form (email/username/name, then date of birth/gender/country/phone — POST /api/v1/users/signup/, one all-or-nothing
submission, nothing created until the final step succeeds). The direct form creates the
account passwordless — no password is ever set —
and finishes by pointing you at magic link, email OTP, or Google as real, working
sign-in methods for that new account. Password, magic link, and email OTP on their own still cannot create an Organization —
they all require an existing account already in one; only Google and the direct form
above actually provision a new Organization. Either path starts the new Organization on
the free plan (2 Applications, 10,000 monthly
active users — see Billing & plans for the full real catalog), with its creator as Owner,
holding every global Permission on that Organization. See Sign-in methods for how each method itself works once an account exists.
An Application is a client that your Organization's users sign into — a real product, not this dashboard itself (the dashboard has its own Application row, but it's auto-provisioned and never appears in this list). Register one from the Applications page, or directly:
POST /api/v1/applications/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "Forge",
"client_type": "confidential",
"redirect_uris": ["https://forge.example.com/callback"],
"allowed_grant_types": ["authorization_code"]
} The response includes client_secret — shown
exactly once, for a confidential client.
There is no way to retrieve it again afterward; a public client (a browser-only app that can't
hold a secret) never gets one, matching PKCE's own security model.
Every Application also gets a slug, generated
from its name — it names the Application's own <slug>:app.access Permission, covered on
the Roles & permissions page.
Organization.allow_self_registration and Application.access_policy get confused for each
other often enough to be worth stating plainly: they answer two different questions, at
two different scopes, and a real setup almost always needs both.
| allow_self_registration (Organization) | access_policy (per Application) | |
|---|---|---|
| Question | Can a brand-new email, never seen before, become a real member of this Organization via a federated sign-in (Google) with nobody having invited them? | Once someone is already a member of this Organization, can they sign into this one Application? |
| Values | invite_only / open / approval_required | open / restricted |
allow_self_registration is fundamentally an
identity/tenant-boundary decision — whether the Organization acquires new members at
all — never an Application-level one: an Application has no members independent of its
Organization, so there is nothing for a per-Application self-registration setting to
mean. "Some Applications for admins only, some for anyone" is a real, common need, but
it's answered by access_policy below, not by
moving this setting anywhere.
open means any Google account that reaches your
login page becomes a real member with zero human review — the right choice for a
self-serve product, the wrong one for an internal team roster. approval_required is reserved: the backend
(federated_identities.services) raises an
explicit error if it's selected, rather than silently behaving like a pending-request
flow that doesn't actually exist yet. invite_only (the default) matches an admin deciding who joins and the invitee then authenticating
however they like, Google included — the right default for most real Organizations.
There is no self-service endpoint for this field yet; it's set directly in the backend
admin by OneHux staff.
access_policy, covered in full on Roles & permissions,
is the real per-Application knob: open for
anything any member should reach, restricted for
admin-only tools — enforced by checking a real <slug>:app.access Permission at sign-in
time, not just a hidden dashboard toggle. A user can also be blocked from one specific
Application regardless of its access_policy — see that Application's own detail page's
"Blocked users" section.
client_type is about whether the Application can
keep a secret. A server-side app (another backend you control) is confidential — it receives a client_secret and must present it at every token
exchange. A browser SPA or a mobile app is public — there is nowhere safe to hold a secret, so it relies on PKCE alone. Rule of thumb: if
the code exchanging the authorization code for a token runs on a server you control,
confidential; if it runs in the user's own browser or device, public. Set once, at
creation — read-only afterward (see "Registering an Application" above).
access_policy is about who may sign into this one
Application, checked on every sign-in — see the section above. The two are orthogonal: a
public client can be restricted, a confidential client can be open.
allowed_grant_types: authorization_code is the ordinary "a human signs
in" flow — every user-facing Application selects this (note the "What's not built yet"
section below: this particular grant type is stored but not yet independently enforced
against a request, since every sign-in already goes through this flow regardless). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693) is a different mechanism entirely: it lets an Application that already
holds a signed-in user's access token mint itself a narrower, short-lived delegated grant
to act on that user's behalf (the agent_grants app
— e.g. an automation product exchanging a user's session for a scoped grant, without
holding their full session). This one is actively enforced: POST /api/v1/oauth/token/ with that grant type
rejects the request outright if the calling Application doesn't have it checked. Leave it
unchecked unless the Application specifically performs that kind of server-side,
on-behalf-of-user automation — a plain dashboard or admin tool never needs it.
Every Application is private by default — is_public is false until you set it. A public Application also has a home_url (where it actually lives — distinct
from redirect_uris, which are OAuth callback
targets, not a page anyone should be linked to directly):
PATCH /api/v1/applications/{id}/
{ "is_public": true, "home_url": "https://forge.example.com" } Once set, the Application appears — name, logo_url, home_url, nothing else — on a real, public, unauthenticated endpoint, scoped to this
Organization's own slug:
GET /api/v1/organizations/{org_slug}/public-applications/
[{ "name": "Forge", "logo_url": "https://...", "home_url": "https://forge.example.com" }] No client_id, no slug,
no OAuth-relevant identifier is exposed here — this is a pure "what can I launch"
list, not a way to start a sign-in flow. It's a general platform capability, not
specific to any one Organization: any Organization's own slug works the same way,
rate-limited the same as every other public discovery endpoint in this API.
Every one of the four official SDKs (Django, Node.js, PHP/Laravel, Go — see Integrate) ships a typed client method for this endpoint. No OAuth flow, no PKCE, no session — it's a plain, unauthenticated call any of them can make at any time, for any Organization's slug:
# Python (Django SDK)
apps = client.get_public_applications(org_slug="onehux")
// Node.js/TypeScript SDK
const apps = await client.getPublicApplications({ orgSlug: 'onehux' });
// PHP/Laravel SDK
$apps = $client->getPublicApplications('onehux');
// Go SDK
apps, err := client.GetPublicApplications("onehux") pip install onehux-sso · npm install @onehux/sso · composer require onehux/sso · go get github.com/Onehux/onehux-sso-go
Each returns the same three fields (name, logo URL, home URL) in that SDK's own naming convention. Rendering is deliberately left entirely to you — none of the four SDKs ship a launcher UI component. Every integrator has their own design system, and a pre-built component would either not match it or need replacing immediately, which is a worse outcome than no component at all. Each SDK's own README has a plain, unstyled example loop showing how to render the result — explicitly meant to be adapted, not used as-is.
From your Organization's own Settings page — "Danger zone," covered in full on Settings & branding. Deactivating is a reversible status change; deleting is real and requires typing the Organization's exact name to confirm. Both are hard-blocked, at the API level, for the two reserved platform-internal Organizations — never reachable through this feature no matter who's asking.
If the Organization you delete happens to be your own home Organization (the one every personal signup creates for its own creator), you are not permanently locked out: the next time you sign in through the default OneHux Accounts login, a fresh personal Organization is silently minted for you and your account is repointed at it — the same shape a brand-new signup gets, with a one-time notice explaining what happened. This only ever applies to your own account-anchoring Organization, never a business workspace you merely have a Membership in.
SAML 2.0 and SCIM 2.0 relying parties are not supported — every Application here is an
OIDC-shaped Authorization Code + PKCE client. allowed_grant_types is stored but not yet enforced by the OAuth flow itself.