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_url is not a redirect URI. It is where a person starts, not where an OAuth flow returns. Your application's own sign-in still supplies its redirect_uri as usual. The two are unrelated and need not match. home_url must be an absolute http(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/login with 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:

SettingWhat it does
Display labelThe plain-English name people see, instead of the name the application was registered under. Up to 60 characters.
Short descriptionOne line under the label saying what the application is for. Up to 200 characters.
Icon addressThe 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/appsThe original. Being retired — see below.

/t/<your-slug>/hosted/apps is 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.json rather 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:

AddressWhere it sits
https://<hap>/t/<your-slug>/menuInside 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:

ReasonMeaning
RegisteredThey hold a grant for it — from an administrator, an invitation, or a previous sign-in.
GroupA group they belong to grants it. Active members only.
Self-enrolmentIt 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": true if you want it shown anyway.
  • A partial update never disturbs it. Send a body naming only redirect_uris and 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:

  1. The application is listed for people who have never used it.
  2. 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 fromRecorded?Why not
The application admits anyone (open)YesInert today, load-bearing if you tighten it
An existing grantNoThe record already exists
Group membershipNoGroups are revocable. A direct grant would survive removal from the group and silently keep the person in.
An external access-decision endpointNoThe 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/exit when 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 operatorthe selector
has at most one application and no administrative reachthe signed-out page — their session ends
has no valid sessionthe 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.granted with a reason of sign_in or selector, 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.