Per-tenant rules for a partner's consumers: registration, OTP, login blocking and AI space assignment.
One contract per partner: identity, policy and space assignment in a single record.
A consumer contract defines how one partner's consumers may use KeenAgents. It answers three questions: who they are (a name plus a 19-character tenant key, unique across the whole platform), what they may do (five policy toggles covering registration, OTP and login blocking), and which AI spaces they may use (a master set plus two default lists). A contract is bound 1:1 to at most one API key — the binding is set from the API key side, via the "Pick a consumer contract" dropdown on the API key form.
Consumers belong to their contract. Every consumer registered under a contract stays attached to it for its whole life: the contract's policy governs how the consumer signs in and its allowed-spaces set caps what the consumer may use. Deleting a contract does not delete its consumers — they are released into an unassigned state and an admin re-assigns each one to a contract from the consumer's own page. After changing a contract, use the bound API key's Wake action to send the partner a refresh notice so it re-fetches the contract.
^[A-Za-z0-9]{19}$), unique across the platform. Either admin-supplied or auto-generated when omitted at create. The key is the tenant identity the partner holds — consumer sessions are minted under it, which is why re-keying acts as a per-tenant kill-switch (see below).spaces set is what the contract may offer. Two default lists — spaces auto-assigned to system-registered and to outside-registered consumers — are enforced subsets of the master set. Defaults shape future registrations; the master set governs everyone on the contract.Two partner-facing reads — the contract's identity and spaces, and its live policy — both scoped to your own key.
A partner never changes a contract over the API — that is the admin's job — but it does read two resources from it, both authenticated with your application token and served scoped to the api key that token was minted for. You only ever see your own tenant's contract. Both are the notify-then-fetch targets a Wake nudges you to re-read.
GET /v1/resources/consumer-contract — your identity + the spaces you may use
GET /v1/resources/consumer-contract Authorization: Bearer <application_token> Origin: https://your-site.example → 200 { "success": true, "status": 200, "message": "Success", "result": { "id": "…", "apiKeyId": "…", "name": "Acme Store", "key": "aB3dE7gH1jK4mN6pQ9r", // your 19-char tenant key "createdAt": "2026-01-14T09:22:03.114Z", "spaces": [ { "id": "…", "title": "Support" } ] // the five policy booleans also appear here but read the mapped // DEFAULTS, not the live values — use consumer-policy below for those } } // no contract bound to your key yet: → 200 { "success": true, "status": 200, "message": "Success", "result": null }
GET /v1/resources/consumer-policy — the LIVE registration / login policy
GET /v1/resources/consumer-policy Authorization: Bearer <application_token> Origin: https://your-site.example → 200 { "success": true, "status": 200, "message": "Success", "result": { "id": "…", // your consumer contract id "key": "aB3dE7gH1jK4mN6pQ9r", "outsideRegistration": true, "otpInsideRegistration": true, "otpOutsideRegistration": true, "blockOutsideRegistrationLogins": false, "blockSystemRegistrationLogins": false, "defaultSystemSpaces": [ "…" ], "defaultOutsideSpaces": [ "…" ] } } // no policy bound to your key yet: → 200 { "success": true, "status": 200, "message": "Success", "result": null }
/v1/resources/consumer-policy.Failure modes — both reads
{ "message": "Invalid token", "error": "Unauthorized", "statusCode": 401 } — the application token is missing, malformed, or no longer matches its api key / client binding. Mint a fresh application token and retry.Origin header is not on the allow-list, so the connection is dropped silently (through a proxy it surfaces as a generic gateway error). Check the Origin your site is served from matches the one registered on your webhook entry.Submitted as a full replace-set — always all five, never a patch.
| Flag | Default | What it governs |
|---|---|---|
outsideRegistration | true | Allow partner-side consumer registration. |
otpInsideRegistration | true | Require OTP for system-registered consumers. |
otpOutsideRegistration | true | Require OTP for outside-registered consumers. |
blockOutsideRegistrationLogins | false | Block logins for outside-registered consumers. |
blockSystemRegistrationLogins | false | Block logins for system-registered consumers. |
1–100 characters, unique.19 characters matching ^[A-Za-z0-9]{19}$, unique across the platform; auto-generated when omitted. A bad shape is refused with Key must be exactly 19 characters from A-Z, a-z and 0-9.20 contracts, checked on create only — one past the cap returns 400 with a Reached the maximum number of … message; delete one first. The cap keeps each organization's partner surface small enough to audit at a glance.spaceIds array, max 500 entries, with replace-all semantics (the full desired set, not a delta). An unknown space id → 400; a default list that is not a subset of the master set → 406.Replacing the master space set, step by step.
spaces set (replace-all, max 500 ids; every id must be a known space).Where contracts live and what the forms enforce.
cache read, cache:w write), separate from the edit permission, so a cache manager can refresh it while an editor without cache rights never sees the button. You rarely need it — normal saves refresh the copy automatically. See Resource Cache.
A consumer contract's detail screen — the five policy toggles, the assigned-spaces lists, and the 19-character tenant key.
api_key has no consumer contract bound (401) until a contract is re-bound. The contract's consumers are not deleted: they are released into an unassigned state, cannot sign in through any partner site, and wait for an admin to re-assign each one to a contract from the consumer's own page.Keen Agents 2026
Documentation
Release 15