Docs

Overview

End users of partner applications — tenant-scoped identities with their own registration and space access.

A Separate Identity Population

Consumers are not admins — and one email is not one person.

Consumers are the end users of partner applications built on KeenAgents (and of Keen's own first-party surface). They are a completely separate population from organization accounts: they have their own registration flows, their own sign-in, and their own admin pages. An organization admin account and a consumer account never mix — even when they share an email address.

The load-bearing identity rule is tenant scoping: a consumer's email is unique only per tenant. Each partner site is a tenant, identified by a 19-character consumer-contract key shared with that partner. The same email can therefore hold one account under partner A, another under partner B, and a first-party account — all independent, with independent passwords, profiles and space grants. Every login, password reset, profile read and chat query is scoped to the tenant resolved from the calling partner's credentials, so one tenant can never read or authenticate another tenant's users. Requests use a two-token model — the partner backend's application_token plus the consumer browser's access_token — covered in detail on the Authentication page; the self-service flows (password reset, delete, profile, chat) have their own page too.

Consumers come in two kinds. A consumer is a person — email, password, a profile, optionally a one-time passcode at sign-in. An MCP consumer is a headless identity an AI agent creates for itself through the Keen MCP Server — ID and secret instead of email and password, never a passcode, and a token that only the MCP server can issue. Everything on this page about tenants, contracts and spaces applies to both; the MCP Consumers page covers the rest. An AI agent signed in with an MCP account may view the consumers listed here but can never invite, reset, move, re-grant or delete one — those actions are human-only.

Core Concepts

Consumer
An end-user account: email, a password stored irreversibly hashed, first and last name, gender, date of birth (submitted by the partner as a YYYY-MM-DD string and stored as day / month / year), a consent flag, the list of AI spaces the account may use, a confirmed status (the invite or registration was completed) and an external marker set on accounts that signed up through a partner site (admin-invited accounts carry no marker; the console shows Partner sign-up or nothing). Each account is bound to at most one tenant.
Tenant (consumer contract)
Each partner site is a tenant, represented by a consumer contract with a 19-character key that the partner holds. The contract carries the per-tenant policy: five toggles, a master allowed-spaces set and two default space sets. The partner-registration default is granted automatically when a user signs up on the partner site; an admin-invited consumer starts with no spaces and is granted them from its detail page (the in-app default set is published to your site with the policy but is not applied automatically). Partner notifications travel through the webhook entry bound to the partner's API key. Every consumer belongs to at most one contract; deleting a contract releases its consumers for re-assignment rather than deleting them.
Email uniqueness per tenant
KeenAgents refuses a duplicate email within a tenant, never across tenants: the same address may exist once under each partner, plus a first-party account with no tenant at all. A duplicate partner sign-up is refused at the confirmation step; in the admin console a duplicate invite or re-bind answers "This consumer is already created!".
First-party consumer
A consumer with no partner contract. It has no policy at all — no one-time passcode, no login freeze — and its spaces are not clamped. It can sign in, but it cannot run an agent or use the chat routes: both require a tenant's contract. Treat a first-party consumer as a placeholder until an admin binds it to a contract.
Consumer spaces
The space list controls which AI spaces the consumer may use. At login the effective set is the account's own list intersected with the tenant's allowed spaces, and on every conversation run KeenAgents re-checks the tenant's current allowed spaces — so a space revoked from a tenant stops working immediately, not at the next login.

Registration: System vs Outside

Two ways a consumer comes to exist — the external marker records which.

Outside registration is partner-driven and follows a request → email → confirm handshake — nothing is created until the user proves ownership of the address:

  1. The partner backend submits the registration request with its application token, an X-Callback-Url (the target of the email link) and the user's details. KeenAgents refuses the request when the tenant's outsideRegistration toggle is off — each tenant can be gated independently.
  2. The platform hashes the password irreversibly, encrypts the registration details so that only this tenant can read them, and emails the user a verification link. The plaintext password never rides the link.
  3. The user clicks; the partner site decrypts the link payload with its tenant secret and submits the confirmation step with the single-use confirmation secret the payload contains.
  4. KeenAgents re-checks the tenant's partner sign-up toggle and the API key's bindings (a change between the request and the click takes effect on this very step), then validates the payload, the secret and the expiry — links are valid for 10 minutes from the initial request.
  5. The account is created already confirmed and marked external, with spaces seeded from the contract's default outside-registration set. A duplicate under the same tenant is refused at the confirmation step.

System registration is an admin invite from the org console (Ai → Consumers → Create): the admin enters the email and picks the tenant it belongs to — or none, for a first-party consumer — the account is created unconfirmed, and an activation mail with a one-time link goes out. The user completes name and password on the confirmation page, which marks the account confirmed. An invited consumer starts with no spaces; grant them from its detail page. It can be moved to another tenant later.

OTP: when the tenant's policy requires it, login returns a "Verify" challenge instead of signing the user in — a 6-digit pin (100000–999999) is emailed, valid for 2 minutes and tied to the exact login attempt and site that triggered it. A correct pin works exactly once; a wrong pin is answered "Expired" and does not spend the challenge, so the user may retry with the right pin until it expires. Which OTP toggle applies is chosen by how the account was registered — One-time code — consumers who signed up on a partner site for partner sign-ups, One-time code — consumers created in-app for invited accounts. A consumer with no contract is never challenged.

The challenge is self-contained
Changing the contract's OTP toggle after a pin was sent does not strand a user holding a valid pin — it stays redeemable until it expires.

Rules & Limits

  • Email uniqueness is per tenant: the same email at most once under each partner, plus a first-party account. Duplicates within a tenant are refused.
  • Emails are compared exactly as typed — A@x.com and a@x.com are different accounts. Addresses must be a valid email shape.
  • Outside-registration fields: a valid email plus firstName / lastName / password as required non-empty values, consent (a boolean, or the strings 'true' / 'false'), gender exactly male | female | other, and dob as a YYYY-MM-DD string.
  • Partner registration links are valid for 10 minutes from the request; an expired link is refused and the flow starts over.
  • OTP: a 6-digit pin in [100000, 999999], valid for 2 minutes; a correct pin is accepted once, a wrong pin answers "Expired" and leaves the challenge open for another try.
  • At login the consumer's effective spaces are the account's list intersected with the tenant's allowed list. An empty intersection still signs in — with zero spaces.
  • Registration gating is per tenant — there is no organization-wide switch. The tenant's partner sign-up toggle gates both the start and the completion of a registration (it is re-checked when the link is redeemed, so a toggle flipped off between the two steps rejects the pending registration). The same toggle also gates every partner-driven self-service flow — password reset, logged-in password change, account delete and profile update answer 403 Action forbidden. while it is off.
  • On every conversation run KeenAgents re-checks that the target space is still granted to both the consumer and the tenant, and that the requested agent exists and is active in that space — otherwise the run is refused: a space outside the consumer's grant or outside the tenant's current allowed set answers a space-authorization error, and a missing or inactive agent answers "agent … not found in space".

Admin Console & Partner Webhooks

What operators change, and how partner sites hear about it.

Consumers are managed from the org admin console under Ai → Consumers: viewing requires the consumer scope (Consumers on the permissions board); all changes additionally require its write grant. The consumer list is a paged table with filters — narrow it by name, email, contract (including No contract (orphans & first-party)), confirmed, sign-up origin, ID and creation date before paging through — and each row opens the consumer's detail page. From there admins can invite by email, send a password reset, edit the space list (the Spaces board offers only the tenant's allowed spaces and is locked until the consumer has a tenant), bind / re-bind / unbind the tenant contract (binding clamps spaces to the contract's allowed set; unbinding wipes them; a re-bind that collides with an existing same-email account under the destination tenant is refused), and delete. When a contract is deleted, its consumers are released — they remain, unassigned, and the consumer detail page is where an admin re-assigns each one to a contract. Per-tenant policy lives on the consumer contract page — five toggles plus three space lists, where the defaults must be subsets of the master set and shrinking the master set automatically prunes every bound consumer. There are no organization-wide consumer switches: every registration, passcode and login rule belongs to the tenant's consumer contract. An AI agent signed in through the Keen MCP server with an MCP account may view consumers but can never invite, reset, move, re-grant or delete one — those actions are human-only and answer 403 for an MCP session.

outsideRegistrationotpInsideRegistrationotpOutsideRegistrationblockOutsideRegistrationLoginsblockSystemRegistrationLogins

Partner sites hear about changes through notify-then-fetch webhooks: a lightweight POST says what changed — never the data itself — and the partner re-fetches through the authenticated API. Consumer-targeted pings carry the consumer's id:

targeted consumer ping

POST <your webhook entry's notification URL>
{ "synch-data": ["r_consumer_spaces_update"], "consumer": { "id": "<consumer id>" } }
  • r_consumer_spaces_update — an admin changed that consumer's spaces; sent to the current tenant's notification URL (the webhook entry bound to its API key).
  • r_consumer_contract_changed — an admin re-bound or unbound the tenant; sent to the previous tenant's URL, since that site holds the now-stale session.
  • A contract-level change — policy toggles, space sets, a rename or a key rotation — automatically sends the bound API key's notification URL a r_consumer_policy refresh notice ({ "synch-data": ["r_consumer_policy"] }), per contract rather than per consumer; deleting the contract sends r_consumer_policy_delete. The API key's Wake action re-sends the full set of notices on demand.
  • The expected partner reaction is a force-logout of the named consumer: entitlements are fixed into the session when the user signs in, so a re-login is what picks up the new spaces or tenant. Delivery is best-effort — a webhook never delays or blocks the admin's action, and a tenant whose API key has no webhook entry bound is silently skipped.

Good to Know

  • Two first-party accounts with the same email can coexist (a duplicate admin invite creates a real second account). Not exploitable across tenants — login is tenant-scoped — and an admin recovers by deleting one of the two.
  • A consumer who "can log in but nothing works" usually has an empty space intersection: a valid session with zero spaces, so every run fails space authorization. Check the tenant's allowed spaces against the account's spaces.
  • Flipping a tenant's partner sign-up toggle off rejects that tenant's in-flight registrations (requested but not yet confirmed) at the confirmation step, and refuses its self-service flows until it is on again — intentional, not a bug.
  • Rotating a contract's key from the contract page is a per-tenant kill-switch: every signed-in consumer of that tenant is refused immediately and must log in again. Other tenants are untouched.
Where the rest lives
Tokens, sign-in and OTP confirmation are on the Authentication page; registration confirmation details, password flows, delete, profile and the chat surface are on the Self-Service page; the headless identities AI agents create for themselves are on the MCP Consumers page.

Previous

Connecting a Site

Next

Authentication

Keen Agents 2026

Documentation

Release 15