← Docs

Organizations & Applications

Creating an Organization

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.

Registering an Application

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.

Self-registration vs. Access policy — two different questions

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)
QuestionCan 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?
Valuesinvite_only / open / approval_requiredopen / 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, access policy, and grant types — three different knobs

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.

Public application launcher

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.

Using it from your own app

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.

Deactivating or deleting your Organization

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.

What's not built yet

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.