Skip to content
On this page and tools

Hosted sites

Client portal kit

Put a working client portal and a staff pipeline on a hosted site with two lines of HTML and no JavaScript of your own. Clients see their requests, messages and documents and can start a new request. Staff move work through its stages and answer clients. Everything on screen comes from the Space's engagement types and forms, so a new kind of work needs no new code.

Available
What drives it. The kit reads GET /portal/schema from the client portal API. That is the Space's active engagement types, their stages, and the linked form's fields. Change a type or a form on the Engagements page and the portal changes with it.

Set up

  1. Turn on sign-inCreate a sign-in client for the Space at /dash/sites/oauth and add its auth block to config.json. See site config.
  2. Turn on the add-onThe Space needs the engagements add-on. Without it the portal says it is not switched on.
  3. Add a mount pointPut one element on a page. The kit loads itself, signs the person in and fills the element.

Publishing pages that use the kit

A page carries the runtime version from the moment it was published. After the kit changes, re-publish each page that mounts it (an edit to the page or a Rebuild is enough) so it loads the current version. Pages you have not re-published keep working on the version they were published with.

Mount points

A client's pageHTML
<div data-bynli="portal" data-portal-heading="Your requests"></div>
The staff pageHTML
<div data-bynli="portal-admin" data-portal-heading="Engagements"></div>
AttributeWhereWhat it does
data-portal-headingBothThe page heading. Plain text only. Defaults to "Your requests" or "Engagements".
data-portal-introClientA line of plain text above the list of requests.
data-portal-siteBothThe key of the site this page belongs to, for example doggie-o. The server already works the site out from where the page is served, so this only confirms it: a key that names a different site is ignored. On the staff page it picks the site shown first. A site sees its own types, booking types and engagements plus the ones the whole Space shares. The staff page has a Site selector with All sites.
data-portal-typesBothComma-separated engagement type keys, for example grooming,boarding. The page lists, offers and shows only those types and their engagements. Keys the Space does not have are ignored, and the list can only narrow what the Space already offers.
data-portal-booking-typesBothComma-separated booking type ids (bkt_ and 32 hex characters). The booking list, the "Book a time" chooser and the staff bookings screen show only those types.

From a script, await Bynli.use('portal') loads the kit, and Bynli.Portal.mount(el) and Bynli.Portal.mountAdmin(el) fill an element you made yourself.

Keep the staff page off public navigation. It works only for a signed-in manager or admin of the Space, but anything on the page can use that person's token. Put it on a page only your staff reach.

What a client gets

What staff get

The schema call

CallWhoReturns
GET /portal/schemaA signed-in clientThe Space's active types. Stages the client may not see are left out.
GET /portal/admin/schemaA manager or admin of the SpaceEvery type, inactive ones too, with every stage, default items, ids, and the allowed status moves.
200JSON
{
  "schema": {
    "types": [
      {
        "key": "walks",
        "label": "Dog walk",
        "kind": "service",
        "stages": [
          {"key": "requested", "label": "Requested", "client_visible": true, "status_bucket": "submitted"},
          {"key": "walked", "label": "Walked", "client_visible": true, "status_bucket": "closed"}
        ],
        "bookingTypeId": "bkt_0123456789abcdef0123456789abcdef",
        "form": {
          "id": "frm_0123456789abcdef0123456789abcdef",
          "name": "Walk request",
          "message": "Thanks, we will be in touch.",
          "fields": [
            {"key": "field_12", "label": "Dog name", "type": "text", "required": true, "placeholder": null, "help": null, "options": []},
            {"key": "field_13", "label": "Size", "type": "dropdown", "required": true, "placeholder": null, "help": null, "options": ["Small", "Medium", "Large"]}
          ]
        }
      }
    ],
    "bookingTypes": [],
    "statuses": ["draft", "submitted", "awaiting_payment", "in_review", "info_needed", "filing", "filed", "issued", "closed", "cancelled"],
    "messageClosed": ["draft", "closed", "cancelled"],
    "kinds": ["formation", "service"],
    "limits": {"title": 160, "message": 4000, "answer": 2000}
  }
}

Starting a request from a type

POST /portal/engagements also takes a type. Send type (the type's key) and answers, an object keyed by the form's field_ keys. The request is created as a draft at the type's first stage with the type's default items, the answers become the first message (Label: value, one per line), and the client then sends it with POST /portal/engagements/{id}/submit.

Start a requestJSON
{"type": "walks", "answers": {"field_12": "Rex", "field_13": "Large"}}
StatusWhen
422The type is unknown, switched off or belongs to another Space; a required answer is missing; an answer is over 2000 characters; or a dropdown or radio answer is not one of the listed options. Unknown answer keys are ignored.
201Created. The body is {engagement} with its type and stage.

Styling and accessibility

Safety