--- title: CLI authentication description: Static credentials, endpoint callers, account sessions, actor grants, and credential diagnosis. kind: Reference audience: operators --- import { Callout } from 'fumadocs-ui/components/callout'; Static minting requires local signing authority. Account sign-in and actor grants are separate, and diagnostic reports state which local claims they checked. ## auth mint [#auth-mint] ```bash cotal auth mint [--profile agent|observer|admin] [--out ] [--json|--plain] cotal auth mint --provision [--role ] [--space ] [--server ] cotal auth mint [--expires-in | --expires-at | --no-expiry] cotal auth mint --identity --out cotal auth mint --replace cotal auth mint --no-persona [--allow-subscribe ] [--allow-publish ] [--role ] cotal auth mint --endpoint --out [--channels ] [--expires-in ] ``` The fixed-profile forms use the existing native static credential profiles. No generic host profile is exposed. A space registered as open refuses `UNSUPPORTED`, since it authenticates no one and issues no credentials. Per-user-auth spaces refuse too; a cached login does not replace signing authority. The resolved root supplies the persona ACL, signer and default storage together. A root that cannot be resolved refuses with its next step, such as `cotal up`. Conflicting current-folder and selected-root accounts refuse before issuance. A label takes letters, digits and `_`, since `-` separates the parts of a principal's name. `--role` takes letters, digits, `_` and `-`. A label or role with any other character, or an empty one, refuses `USAGE_INVALID` without the command's help. The refusal names the character, the value and the characters allowed, and its Next names the value with each such character replaced by `_`. `agent` is the default profile. Its channel ACLs and role come from the selected root's `.md` persona file, when present. It does not fall back to `default.md` for a different name. Explicit `--allow-subscribe`, `--allow-publish` and `--role` override the file's grants. Use `--no-persona` to ignore the label's persona, including its boot subscriptions and role. With that flag, only explicit flags or a matching `--identity` supply grants; an omitted role grants no task queue, and empty ACL flags grant no channels. Generated replacement advice includes `--no-persona` so a preserved same-label persona cannot add grants the old credential did not hold. A persona file of that name that cannot be read as a persona refuses the mint `SCHEMA_INVALID`, exit 1, even when the flags name every grant. The mint refusal does not print file values or parser text. It names the file and `cotal persona show ` to inspect it. A role from the file that is not one word of letters, digits, `_` and `-` refuses the same way, naming `cotal persona edit `. With `--identity` naming an agent credential this space issued, the new credential keeps that credential's read channels, post channels and role in place of the persona's, and each flag still replaces its own grant. The receipt lists the grants as May read, May post and Role, marking those carried from `--identity`; JSON has them under `result.grants`, with `fromIdentity` naming the carried ones. An empty ACL flag means no explicit entries. The native read-ACL resolver still enforces boot membership. Without a matching persona, `--identity` or grant flags, the agent credential has no channel grants or role. Its validity does not establish that it can perform a particular task. Observer and admin have fixed native scope and reject agent ACL, role, `--no-persona` and provisioning flags rather than implying those inputs narrowed anything. ### Private credential output After `reset space` and a new `up`, credentials the previous local identity issued no longer work on that space. A rejected `--creds` file for the registered static-auth space names the identity mismatch and an `auth mint` replacement with the file's channel grants and role, or its profile. The broker still decides whether to accept the credential. Without matching local trust, the refusal keeps the broker's generic credential guidance. The default credential is created through its canonical secret-store kind. An existing label is not overwritten. Use another label or an explicit private output. `--replace` replaces the label's standing credential when it has expired, has no expiry claim or has a key that `auth revoke-credential` revoked. It mints under a new identity unless `--identity` names one, which a revoked key never is, and removes a no-expiry record beside it unless `--no-expiry` writes a new one. The receipt names what it replaced and its public key under `result.replaced`. A replacement does not revoke the credential it replaced, so a copy of one with no expiry claim stays valid. The receipt then warns `REPLACED_CREDENTIAL_VALID`, naming that key and the [`auth revoke-credential`](#auth-revoke-credential) route that stops it. A standing credential that is still valid refuses `CONFLICT`, naming its expiry, and so does one that is not a native credential. `--profile` still defaults to `agent`, so a replacement of an observer or admin credential needs `--profile observer` or `--profile admin`; a replacement with another profile than the one it replaced warns `REPLACED_PROFILE_CHANGED`. A replacement holds only the grants its persona file, `--identity` or grant flags name, not those of the agent credential it replaced. When both are agent-profile credentials, the old one names this space's account as issuer, and their grants differ, the receipt warns `REPLACED_GRANTS_CHANGED`, naming both and the `--allow-subscribe`, `--allow-publish` and `--role` flags that name the replaced ones. With no standing credential under the label, `--replace` mints a new one and warns `NOTHING_REPLACED`. Exported files stay unchanged; the warning names `doctor auth --space ` to inspect their state and the guidance for each file, and the space's root to run it in, since `doctor auth` reads the root it runs in. `--replace` takes no `--out`. An exported file stays outside managed `.cotal` and workstation state. Its path, label, profile and public key, without the credential, are recorded under `credentials/` in the Cotal home so `doctor auth` lists the file. A pre-existing explicit output must be an owned private native credential matching the nkey deliberately supplied through `--identity`; unrelated files and identities refuse. New outputs use create-only publication. The output's directories follow the [`space create`](cli-selection.mdx#selecting-a-space) path rule, which also accepts a root-owned sticky directory such as `/tmp`, and the file is owner-only. Unsupported platforms refuse rather than weakening it. A credential is never written through a symbolic link: a link among the output's folders refuses `PERMISSION_DENIED`, naming the link, the folder it points to and the same file named without the link. A path through a file refuses `USAGE_INVALID`, and a folder this account cannot create refuses `PERMISSION_DENIED`, naming the folder. None of them writes anything. `--identity` reuses the native nkey. The out-of-band agent mint reports its newly allocated lifecycle UID, with a warning that it did not reuse the old inbox/delivery footprint. Use that reported UID for the resulting client; do not infer it from the label or assume the old UID was preserved. No private nkey or JWT appears in a receipt. A key revoked with `auth revoke-credential` refuses `CONFLICT`, because a credential signed now would postdate the revocation and the broker would accept the key again; mint a fresh key without `--identity`. ### Provisioning Without `--provision`, fixed-profile minting writes credentials only. `--provision` uses one native provisioner connection to pre-create that lifecycle's bind-only DM/delivery and role footprint. It performs the authenticated preflight against the same recorded broker and TLS requirement, without pruning a registration. An acknowledgement proves neither a joined agent nor task completion. A partial or unknown result retains the allocated public identity and lifecycle for diagnosis and does not retry the mint. A fixed-profile credential expires 24 hours after it is minted unless `--expires-in` or `--expires-at` sets another time, in native seconds. `--no-expiry` issues one with no expiry claim and records that request beside the file, in `.no-expiry.json`, so `doctor auth` reports it as a warning rather than a finding. The receipt reports the resulting JWT expiry. The three flags are mutually exclusive, and an `--expires-at` that is not in the future refuses before anything is written. So does an expiry after the last date Cotal can represent, in the year 275760. JSON is one finite document; plain output uses the same public receipt. Signing material is exported by [auth signer](cli-endpoints.mdx#account-signer-export), not a signer flag on mint. The old top-level `mint` spelling is refused with the literal namespace replacement. A minted credential stays valid until it expires, and one minted with `--no-expiry` until it is revoked or the space identity rotates. [auth revoke-credential](#auth-revoke-credential) stops one credential and keeps the store. Rotating the space identity stops every credential the space issued: `cotal down`, then `cotal reset space ` (or `cotal clean all --force` from the root when `down` left no registration), then `cotal up`. That reset also removes the space's store, with its workflow runs and their journals, channel history and stored messages, so the space starts again with no history. `cotal reset space --dry-run` lists what the store holds first. It also stops every earlier full backup of the space from restoring into this root: only its registry restores, and chat and DM history start empty (see [Backups](cli-backups.mdx#backups)). ## Endpoint caller credentials [#endpoint-caller-credentials] `--endpoint` is a separate closed mode, not an ACL override on an observer or admin profile. It requires a private `--out` in an existing protected physical parent outside managed state. Only `--channels`, `--expires-in`, space/server and presentation flags combine with it. It has no default lifetime: without `--expires-in` the caller carries no expiry claim. An empty or omitted channel list grants no explicit read/post channels. Supplied channels deliberately enter both ACLs, as in the native standalone Linear caller form. No persona ACL or role is inferred. The same static-root/account checks run first. The existing native control instrument then resolves a live endpoint and digest-verifies its visible command surface. This issuer accepts only nonempty, bounded untargeted synchronous ephemeral surfaces. A targeted, journaled or action command refuses the whole issuance before footprint work; unsupported rows are not silently dropped. The result records the observed responder and contract digests, not a promise that every future incarnation has the same implementation. The caller is scoped to the class's observed command names, not that single answering instance. The native agent provisioner creates the new lifecycle's bind-only footprint and additional endpoint request rows. No spawn, run or admin capability is added. The ordinary native agent baseline remains; this is not a new hand-written permission profile or a managed-seat launch adapter. User-auth/open spaces still refuse. The selected broker's required TLS is retained for discovery and provisioning. ### Issuance records An immutable owner-private `.issuance.json` intent precedes the first footprint operation. It contains public identity/lifecycle, requested scope and contract coordinates, never credentials. Its presence is not a grant or successful submission receipt. An existing output or retained intent blocks another issuance, even if no output was published. Partial/unknown provisioning retains that original intent and does not replay the native call. Success reports native footprint acknowledgement and the exact privately published file; provisioning is not presence, task completion or cleanup. Temporary provisioner close is reported separately and can leave a partial result. Rich/plain receipts show the actual caller/lifecycle, described endpoint owner and responder, explicit channel scope and native expiry. Command tables show at most 40 rows with an omission count; JSON retains every command and contract digest. The local issuance request is not a broker request acknowledgement. No credential bytes or environment-export recipe are printed. Endpoint describe/invoke consume this paired file under the same registered static root: ```bash cotal ep describe ep: --space --creds /absolute/private/caller.creds cotal ep invoke ep: --space --creds /absolute/private/caller.creds --args '' ``` Keep the matching `.issuance.json` beside the credential. The consumer checks both protected snapshots and validates the selected root's full static trust chain. The native JWT signature, matching user seed, current validity window, account and current delegated signing key are checked before connecting. A retained older signing key is not guessed from a different root. The caller coordinates and recorded commands must reproduce the signed publish/subscribe/response permissions and principal tags through the native builder. The unsigned local intent selects that comparison; it grants nothing and proves no submission, lifecycle currency or completion. Original responder/contract metadata remains an issuance observation, not an instance or future-code pin. The connection retains registered TLS and uses only that file. Its broker admission is separate from local checks. Invocation accepts only an included untargeted synchronous ephemeral method from the fresh digest-verified surface. A changed targeted, journaled or action shape refuses before publication. Instance pins, scatter, admin, agent targeting and goal-follow flags are unsupported for this caller mode. Calls do not repair a binding, switch authority or resubmit an unknown effect. `--server`, when supplied, must repeat the registered broker. Target/reference selection is read-only, including when a paired file is selected. Receipts retain the public caller binding; describe shows methods outside the file scope without granting them. Issuance next steps keep explicit file selection instead of switching back to the normal operator instrument. Arbitrary raw files, issued-generation files and different native profiles are not interpreted as this standalone caller. User-auth issuance and managed static launch are not supported. ## Account login [#login] ```bash cotal login --idp [--client-id ] [--plain] cotal logout --idp [--local] ``` Signs you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it once per machine. `--plain` presents the approval prompt and signed-in result without color. Device approval is still interactive and has no native noninteractive or JSON result in this client; `--no-input` refuses before starting a provider request. The IdP URL must use `https://`. Plain `http://` is accepted only on a loopback IP literal such as `127.0.0.1` or `::1`, for an IdP on this machine. `localhost` is refused because it is a name: a hosts entry would choose the IdP. It prints your IdP subject, the id the operator grants against. When the trusted `/token` response advertises a same-origin space catalog, login validates and records that account's spaces immediately, lists them under `Your spaces`, and names `cotal use ` to select one. A space registered from elsewhere that has not granted your account refuses with those discovered spaces as its remedy, because only that space's operator can grant you. On the root that holds the space's grant ledger, the refusal names the operator's `cotal actor grant` command instead. After a login, every command on that mesh works under your identity: each connect takes a fresh IdP proof, exchanges it locally for a short-lived bearer, and is authorized against the actor ledger at connect time. A rate-limited `/token` read retries once when the IdP supplies a valid `Retry-After` or `X-Retry-After` hint that fits within `COTAL_IDP_TIMEOUT_MS` (30 seconds by default). When both hints are valid, the later minimum wins. Both reads and the wait share that deadline; cancellation stops the wait. Missing hints, a second refusal and other HTTP errors are not retried. The exchange or Manager operation that consumes the proof is never replayed by this retry. A provider that cannot be reached, or that stops answering, refuses with `DEPENDENCY_UNAVAILABLE` (exit 2) and caches no session. `logout` revokes the IdP session, clears its cache, and removes only that account's discovered registry entries. See [identity & auth](identity-and-auth.mdx). ## actor [#actor] ```bash # an upsert of the WHOLE row: name all three ACL flags, or pass --full for the wide defaults below cotal actor grant --sub --scope a,b --allow-subscribe a,b --allow-publish a,b [--role ] [--label ] [--json|--plain] cotal actor grant --sub --full [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role ] [--label ] [--json|--plain] cotal actor revoke (--sub | --owner ) [--json|--plain] cotal actor label (--sub | --owner )