ReferenceThe ask object
The ask object#
What is the shape a harness draws a card from?
On this page
Whenever a human must open a link, mehen prints one block, and --json and MCP carry one ask object with the same fields. Its field names are a contract with the apps that draw cards from mehen output.
The block#
Human needed: approve a request
open: https://app.mehen.dev/w/acme/a/apr_7Hq2
what: Send an email to sam@example.com, from gmail-personal (you@example.com)
check: the page shows code K7Q2MX
expires: in 58 min (17:12)
then: mehen wait apr_7Hq2open: is always the second line. The check: sentence always starts "the page", so the human knows where to look. expires: is relative then local clock time, expired, or unknown (about an hour) when the vault did not say. On a terminal the check value is bold and a QR code of the link is drawn between open: and what:; --no-qr or MEHEN_QR=0 turns it off, and it never appears off a terminal or in --json.
The block is the result of mehen enrol, mehen request, mehen connect, mehen policy propose and mehen pending, so there it goes to stdout. When it comes with a refusal (approval_required, needs_reauth, frozen, device_pending) it goes to stderr with the error. mehen wait prints it on stderr before its countdown when stderr is a terminal, and hands it back with a timeout or mismatch error.
The object#
{
"kind": "approval",
"id": "apr_7Hq2",
"url": "https://app.mehen.dev/w/acme/a/apr_7Hq2",
"title": "Approve a request",
"what": "Send an email to sam@example.com, from gmail-personal (you@example.com)",
"check": { "label": "code", "value": "K7Q2MX" },
"expiresAt": "2026-10-24T16:12:00.000Z",
"account": "you@example.com",
"requestedBy": { "device": "build-box", "session": "pi-4f2a" },
"context": { "origin": "https://app.mehen.dev", "workspace": { "id": "tn_aeAL...", "slug": "acme", "name": "Acme" }, "device": "dev_FOKtq..." },
"wait": "mehen wait apr_7Hq2"
}| Field | Type | What to do with it |
|---|---|---|
kind | enrol, intake, connect, reconnect, approval, policy, unfreeze | Which kind of page the human is opening. |
id | enr_..., int_..., con_..., apr_..., pol_...; empty for unfreeze | What status updates refer to; the argument to mehen wait. |
url | string | The one link the human opens. Always on the vault's origin, under the workspace's canonical slug: https://app.mehen.dev/w/<slug>/.... |
title | string | "Approve this machine", "Add a credential", "Connect an account", "Reconnect an account", "Approve a request", "Review a policy change", "Unfreeze the vault". |
what | string | One line with the names: slug, provider, account, recipient. |
check | { label, value } or null | What the human compares with the page. label is fingerprint (enrolment, four groups of four), code (approvals, 6 characters), account (connect and reconnect, the account to sign in as) or asked by (intakes, this machine and session). Relay the value in full; never shorten it. |
expiresAt | ISO 8601 or null | When the link stops working. Null means the vault did not say. |
account | string or null | The account that acts or should sign in. |
requestedBy | { device, session } | This machine's short host name and MEHEN_SESSION, so the human can tell your ask from a stranger's. |
context | { origin, workspace: { id, slug, name } | null, device } | The enrolment that raised the ask: the vault's origin, the workspace as the vault names it (null while enrolling) and the device id. Say which workspace an ask is for when the human is in more than one. |
wait | string or null | The command to run next, mehen wait <id>. |
intent | { workspace, team }, enrolment asks only | The workspace and team the enrolment asked for. Describe intent.workspace as the requested workspace, not an authenticated enrolment: context.workspace and context.device stay null until activation. |
claimed | boolean, enrolment asks only | Whether a connect code claimed the attempt for one account. |
next ({ url, command, say }) is still on every result and error that carries a link, generated from the ask, for anything that only knows next. ask is present on the pending result of mehen enrol --json, on mehen request --json and mehen connect --json (one object per step with step when --request-client chains), on mehen pending --json ({ pending: Ask[], notices }), on mehen wait --json --events waiting lines, on every --json error that carries a link, and in MCP structuredContent.
How to relay it#
Write one block in chat, the same words every time, on every channel:
**Needs you in the vault:** approve my-laptop
Open: https://app.mehen.dev/w/acme/e/ABCD1234
The page must show the fingerprint **5NZV DP67 PXT0 DB6E**. If it differs, do not approve.
Valid until 18:14. I will wait.The first line is title and the name from what; Open: is url; the third line is check (for an approval, "Tick the binding code K7Q2MX, then read the whole request before you approve."; for a connection, "Sign in as you@example.com"; for an intake, "The page must say it was asked by <device>, session <session>"); the last line is expiresAt. Then run wait. Where a harness draws a card from your mehen call with the link, the check and a countdown, do not send the link a second time.
The link check#
mehen checks every link the vault returns against the enrolment it acts under (scheme, host and port; no username, query or fragment; the /w/<slug>/ of the workspace this machine is enrolled in; one of the vault's own ask routes) before printing it, putting it in --json or drawing the QR code. A link under another slug, an old alias of the workspace or another origin fails. A link that fails is never shown: human output prints mehen: warning: The vault returned a link that is not on <vault>, so mehen did not show it, --json carries that sentence as next.say with no next.url, and where an id is known the ask is rebuilt from the id under the canonical slug with a warning field. If you see the warning, check MEHEN_VAULT_URL and tell the human; do not go looking for the link.
For a harness that draws cards#
Build the card from ask: show check.value in full, count down from expiresAt, name the workspace from context when the human is in more than one, and offer one action, opening the link. Nothing a harness sends can approve anything; the fingerprint, code or account is for the human to compare on the page. Draw a trusted card only for an ask your own authenticated call produced, after checking the exact origin, workspace and device context; never for a link that arrived as text.