The application selector: giving people somewhere to land
Audience: tenant administrators and application developers.
Someone opens your tenant's sign-in page from a bookmark. Nobody sent them; no application asked for them. They sign in — and then what?
Until now, nothing. That request was refused outright with a 400, because the sign-in page had no
valid destination to return to. The application selector is that destination: the applications
this person may use, from which choosing one takes them there, signed in.
It also closes a quieter gap. The platform did not previously record that a person uses a given application unless an administrator explicitly granted it. It does now — and that record is what makes it safe to tighten an application later. See Registration, and why it is recorded before it is needed.
What you have to do
One thing: give every application a home page. Without it the selector has nowhere to send people, and it will say so rather than guess.
curl -sX PATCH "$HAP/v1/applications/$APP" \
-H "X-Admin-Key: $KEY" -H "X-Tenant-Id: $TENANT" \
-H 'content-type: application/json' \
-d '{"home_url": "https://app.example.com/"}'Or in the console: Admin → Applications → your app → Edit settings, where the home page sits with the redirect URIs.
To find the ones still missing it:
curl -s "$HAP/v1/applications" -H "X-Admin-Key: $KEY" -H "X-Tenant-Id: $TENANT" \
| jq -r '.[] | select(.home_url == null) | .name'The console flags them too — the Applications table has a Home page column that reads "No home page — cannot be opened" for any application that needs one.
home_urlis not a redirect URI. It is where a person starts, not where an OAuth flow returns. Your application's own sign-in still supplies itsredirect_urias usual. The two are unrelated and need not match.home_urlmust be an absolutehttp(s)URL, and it is administrator configuration only — it is never accepted as a request parameter, so it cannot be used to bounce anyone anywhere.
Where people see it
https://<hap>/t/<your-slug>
No parameters. No per-user or per-application component. Hard-code it in your applications as a "choose another application" link and it works for everyone, forever.
- Signed in → the selector.
- Signed out → sign in, then the selector.
- Signing in at
/t/<your-slug>/hosted/loginwith no application behind it lands here too, as does the Sign in again link on the signed-out page.
What each entry shows
Each application on the menu can carry three things an administrator sets, on Tenant administration → Applications → (the application) → Edit settings, or through the API:
| Setting | What it does |
|---|---|
| Display label | The plain-English name people see, instead of the name the application was registered under. Up to 60 characters. |
| Short description | One line under the label saying what the application is for. Up to 200 characters. |
| Icon address | The web address of a small image shown beside it. |
All three are optional, and all three are appearance only — none of them changes who may use an application or how signing in works.
Leave the label unset and the menu shows the registered name, exactly as it always did. That is
usually the thing to fix first: a name like hr-payroll-2 is chosen by whoever registered the
application, often a deployment tool, and it means nothing to the person choosing from the list.
Setting a label does not rename the application — the registered name stays, and stays what
every other system calls it.
Use an https address for the icon. A plain http image is blocked by every browser on a secure
page, and the person then sees the label with a plain initial beside it. HAP never fetches the
image itself; it hands the address to the browser.
Two addresses, one page — and one of them is going away
The selector answers at your tenant's root and at the original /t/<your-slug>/hosted/apps.
They are the same page: the same handler, the same session, the same everything.
| Address | |
|---|---|
https://<hap>/t/<your-slug> | Your tenant's root — the address this is moving to. A trailing slash works too. |
https://<hap>/t/<your-slug>/hosted/apps | The original. Being retired — see below. |
/t/<your-slug>/hosted/appsis going away. Point new links at your tenant's root. Nothing breaks today — both addresses work, and the old one will keep working until it is withdrawn — but anything you write down now should use the root.
Signing in at either address returns you to that address, so the two never bounce you between them.
To settle whether an address exists, read the service's own API document at
https://<hap>/openapi.jsonrather than any published copy of these pages. It is generated by the running service, so it lists exactly what that instance answers — which also tells you whether something described here as coming or going has actually shipped yet. Copies of this documentation reach the wiki by a manual step and can be older than the service.
Two shorter addresses
For a link someone has to read out, type from memory, or print, even /t/<your-slug> may be more
than you want to say. Two shorter addresses redirect to it:
| Address | Where it sits |
|---|---|
https://<hap>/t/<your-slug>/menu | Inside your tenant's space, and says what you will get. |
https://<hap>/menu/<your-slug> | Shorter still, and the only one outside your tenant's space. |
Both redirect to your tenant's root — so after following one, that is what the address bar
reads, and neither is affected when /hosted/apps is retired. They redirect rather than rendering
because, unlike the root, they are not going anywhere: they are conveniences, and a convenience
that renders is one more page to keep in step.
An alias grants nothing on its own. Opening one while signed out takes you to sign-in exactly as the canonical address does, and an unknown tenant is refused the same way. They are addresses, not shortcuts past anything.
One address you may have seen is not served: https://<hap>/t/<your-slug>/selector. It was
never a HAP address — a version of the WBSP platform's client guidelines named it for about a day —
and although it was briefly served here as a redirect, it was withdrawn on 2026-09-04 without ever
reaching a running server. It answers 404. If an application of yours links there, point it at
the tenant root.
Getting somewhere that is not an application
The page carries these links beneath the list:
- Sign out — ends the session and shows the signed-out page. Present for everyone, including when no applications qualify; the empty page is exactly where someone most needs a way out.
- Tenant administration — shown only to a tenant administrator, linking to
/t/<your-slug>/hosted/admin. Everyone else does not see it, because the console would refuse them anyway. - Platform view — shown only to a designated platform operator, whether or not they also administer this tenant. The hand-off has always admitted them; until recently the only link to it sat inside the console, which refuses a non-administrator, so they had a working route and nothing pointing at it.
Everyone who reaches the page sees these, including a person for whom only one application qualifies. That is why the page is always rendered — see below.
Two behaviours worth knowing before people ask
One application still gets a page. Early builds forwarded a person with exactly one qualifying application straight to it, on the reasoning that a menu of one is not a choice. That was withdrawn: the page is also the only place the platform offers sign-out and the administration console, so skipping it left single-application people — including tenant administrators — with no route to either. They now see a one-item list and click it. One extra click; nobody stranded.
An application with no home page is shown, not hidden. It appears in the list with an error saying it cannot be opened, and it cannot be clicked. Hiding it would leave the person wondering where their application went; naming it tells them exactly what to ask you for.
Who sees which applications
An application appears in someone's selector for one of three reasons:
| Reason | Meaning |
|---|---|
| Registered | They hold a grant for it — from an administrator, an invitation, or a previous sign-in. |
| Group | A group they belong to grants it. Active members only. |
| Self-enrolment | It advertises itself to the whole tenant, and their email address passes the sign-up domain rules. |
Applications that are disabled, deleted, or belong to another tenant never appear. Neither does an application a person simply has no route to — the selector never advertises the existence of something they cannot use.
Nor does an application whose show_in_menu setting is off. See
Taking an application off the menu.
Taking an application off the menu
Some applications are signed in through rather than opened from a menu — a command-line tool, a deploy client, a machine integration. The menu is the list of applications a person has used, so those accumulate on it as rows that can never be opened.
show_in_menu takes one off:
curl -sX PATCH "$HAP/v1/applications/$APP" … -d '{"show_in_menu": false}'Console: the Show in the application menu checkbox on the create and edit forms.
Three things worth knowing:
- It hides; it never denies. Everyone who could sign in still can, tokens are unchanged, and the usual registration is still recorded. It is not an access control.
- New command-line clients arrive off. Register an application whose redirect URIs are all
loopback addresses without naming the setting, and it is created off — nobody has to remember.
Say
"show_in_menu": trueif you want it shown anyway. - A partial update never disturbs it. Send a body naming only
redirect_urisand the setting keeps its stored value, so an automated redeploy cannot quietly put a row back.
The platform's own HAP CLI client is off in every tenant and needs no action from you.
Advertising an application to everyone (self-enrolment)
Off by default on every application, including every one that existed before this feature. Turning it on is a deliberate act, because it makes the application visible to your whole tenant:
curl -sX PATCH "$HAP/v1/applications/$APP" … -d '{"self_enrollment_enabled": true}'Console: the Let anyone in this tenant start using it checkbox on the create and edit forms.
Two things follow from switching it on:
- The application is listed for people who have never used it.
- Those people are enrolled automatically — on first sign-in through the application, or when they choose it from the selector.
Who qualifies is decided by your tenant's existing sign-up domain rules (the allow/deny lists on
/t/<your-slug>/hosted/admin/registration) evaluated against the person's primary email address.
There is no per-application domain list: one place to configure, one place a refusal can come from.
The account-creation gates do not apply here. An invitation-only tenant still shows the selector normally — the people looking at it already have accounts, so "who may become a new account" is not the question being asked. The domain rules do apply, because those describe which addresses your tenant will deal with at all.
If you want a narrower audience than your whole tenant, leave self-enrolment off and grant access explicitly — see Managing application access.
Registration, and why it is recorded before it is needed
Signing in to an application that admits any authenticated person now records a (person, application) grant.
While that application stays open, the record does nothing. Open applications admit everyone;
the access decision never consults grants. So why write it?
Because of the day you decide the application should be restricted. Before this, that single change
locked out every user at once — the application admitted everyone precisely because nobody had a
grant, and the moment grants started mattering, nobody had one. The record is the difference between
tightening an application and taking it down.
# Safe, because everyone who has signed in since this shipped already holds a record:
curl -sX PATCH "$HAP/v1/applications/$APP" … -d '{"access_mode": "restricted"}'Check who holds one before you flip it (admin key with application_access:read):
curl -s "$HAP/v1/applications/$APP/access" -H "X-Admin-Key: $KEY" -H "X-Tenant-Id: $TENANT"The guarantee has a start date. Only sign-ins since this feature shipped left a record. Someone who last used the application before that holds nothing and will be locked out. There is no backfill. If an application has dormant long-tail users, grant them explicitly before tightening it, or expect them to ask you for access.
What is deliberately not recorded
A registration is written only where it would be inert — an application that admits anyone. It is never written when access came from something revocable:
| Access came from | Recorded? | Why not |
|---|---|---|
The application admits anyone (open) | Yes | Inert today, load-bearing if you tighten it |
| An existing grant | No | The record already exists |
| Group membership | No | Groups are revocable. A direct grant would survive removal from the group and silently keep the person in. |
| An external access-decision endpoint | No | The endpoint is re-asked every time and may refuse tomorrow; a stored grant would outlive that refusal |
The same rule governs the selector: choosing an application you reach through a group forwards you but records nothing. Clicking an item in a list is navigation, not a decision to entitle someone.
Headless equivalent
The selector is a convenience, never the only way. The same list is available to the signed-in person over the API:
curl -s "$HAP/v1/me/applications" \
-H "X-Tenant-Id: $TENANT" -H "Authorization: Bearer $SESSION_TOKEN"{"applications": [
{"application_id": "0d9f…", "client_id": "hap_x0Kd…", "name": "Podcast Platform",
"home_url": "https://podcasts.example.com/", "launchable": true, "source": "registered"},
{"application_id": "77ab…", "client_id": "hap_9WqL…", "name": "Expenses",
"home_url": null, "launchable": false, "source": "self_enrollment"}
],
"selector_relevant": true,
"destinations": ["tenant_admin"]}launchable is false exactly when no home page is recorded. Do not forward to a non-launchable
application — show the same "ask an administrator" message the hosted page shows. An empty list is
a 200 with {"applications": []}, never a 404.
selector_relevant says whether this person has a choice to make: two or more applications, or a
destination that is not an application (destinations, an ordered subset of ["tenant_admin", "platform"]). Sign-out is never listed, because everyone has it.
Build your own launcher on this if you would rather not use the hosted page.
Sending someone back: the exit page
Give your application a "leave" or "switch application" control and point it at one fixed address:
<a href="https://auth.example.com/t/acme/exit">Leave</a>It takes no parameters, so you hard-code it once and never compute anything per person. HAP decides where they go:
Moved. This address was
/t/<your-slug>/hosted/exitwhen it first shipped. It is now/t/<your-slug>/exit, beside your tenant's root. The old address still works — it redirects to the new one — so an application already linking to it is not broken. Point new links at/t/<your-slug>/exit; the old spelling has no removal date yet, and will not be withdrawn without notice.
| The person… | Lands on |
|---|---|
| has two or more applications, or administers the tenant, or is a platform operator | the selector |
| has at most one application and no administrative reach | the signed-out page — their session ends |
| has no valid session | the signed-out page, with no error and no demand to sign in |
The second row is deliberate: their only application is the one they just left, so a selector would show a single button back into it and returning them to it would be a loop. Exit means exit.
A destination you append (?return_to=…) is ignored — the address accepts none, so it can never be
turned into an open redirect. If you need the browser returned to an address of your choosing after
signing out, keep using the end-session endpoint
(/t/{slug}/hosted/logout?client_id=…&post_logout_redirect_uri=…), which is unchanged.
Good to know
- Nothing changes for existing applications on upgrade. Both settings default to off/empty: no application becomes visible to your tenant's population, and no sign-in outcome changes.
- Forwarding is an ordinary redirect. The person lands on your home page and your application starts its own sign-in, which completes silently against the session they already have. No new integration, no new token, no change to what your application receives.
- The hosted pages are opt-in. They exist only for tenants with hosted login enabled; the headless API is unaffected either way.
- Branding applies. The selector carries the same name, logo, and colours as your other platform-rendered screens — see appearance customization.
- Audit. Every registration writes
application_access.grantedwith areasonofsign_inorselector, so you can tell an automatic record from a deliberate grant. - Choosing is a
POST, not a link. It writes an entitlement, and the target is re-checked server-side against the person's real list — a forged or stale application id is refused, not honoured.
Related
- Managing application access — grants, groups, application administrators.
- Managing your applications — disable, delete, rotate a secret.
- Hosted Login guidelines — the wider hosted surface.
- API reference §6.8 and §6.22 — the application fields and
/v1/me/applications.