1. playbooks
temper
  • Temper documentation
  • cli
    • README
    • admin
    • auth
    • check
    • cogmap
    • config
    • context
    • edge
    • init
    • invitations
    • invocation
    • memory
    • pull
    • query
    • resource
    • search
    • skill
    • slack
    • status
    • steward
    • team
    • trail
    • update
    • version
    • warmup
  • config
    • README
  • auth
  • guides
  • doors
    • Building against Temper
    • Running Temper
    • Using Temper
  • concepts
    • Auth Identity
    • Authoring Authorization
    • Contexts and Refs
    • Machine Tokens
    • Operational Memory
    • Release Verification
    • The SAML Reconcile Channel
    • Slack Identity and Revocation
    • Teams and Roles
    • Telemetry
    • Token Verification
    • The Trust Boundary
  • playbooks
    • Adopt operational memory
    • Authenticate
    • Bootstrap an Org
    • Bootstrap a Team's Self-Cognition
    • Build a cognitive map
    • Connect Claude Code
    • Connect Temper to Claude Desktop
    • Deliver L0 Kernel Content
    • Deploy a Steward Agent
    • Deploy the Web UI
    • Enterprise Install
    • Provision a Read-Only GitHub Connection
    • Ingest a corpus
    • Install Temper
    • Run a Team
    • Self-Host Temper
    • Self-host Temper with Okta
    • Self-host Temper with a SAML IdP
    • Send traces to an OTLP backend
    • Slack Mentions
    • Standing Up a Machine Credential
  • sdks
    • Integrating Ruby with temper-rb
  • Temper Cloud API
    • Access
      • POST /api/access/requests — submit a join request for the gating team.
      • GET /api/access/requests/me — check own join request status.
      • DELETE /api/access/requests/me — withdraw a pending join request.
      • POST /api/access/reviews — a revoked principal asks an admin to reconsider (spec D15).
      • GET /api/access/settings — read public system settings.
    • Slack Link
      • Disconnect any principal. Operator path — offboarding and stuck users.
      • Disconnect EVERY Slack principal bound to the caller's own profile.
    • Auditor
      • auditor_dispatch
      • auditor_sweep
      • complete_auditor_job
    • Graph
      • POST /api/cogmaps/{id}/graph/slice — R4 cogmap-scoped neighborhood slice.
      • GET /api/graph/cogmaps/{id}/panorama — enter-a-cogmap Tier-0 interior.
      • GET /api/graph/contexts/composition — Beat E Tier-1: the force-graph composition of a container's (or a residual bucket's) members.
      • GET /api/graph/contexts/panorama — Beat E Tier-0: goal-container territories + residual tray.
      • GET /api/graph/home — the you→teams→cogmaps membership home.
      • GET /api/graph/regions/composition — Beat D region→resources composition drill.
    • Cognitive Maps
      • list_cognitive_maps
      • genesis
      • get_cognitive_map
      • reconcile
      • analytics
      • grant_cogmap_access
      • revoke_cogmap_access
      • materialize
      • materialize_delta
      • region_metrics
      • shape
      • bind_team
      • unbind_team
    • Contexts
      • list_contexts
      • create_context
      • get_context
      • context_materialize
      • reassign
      • context_region_metrics
      • rename
      • context_shape
      • share_team
      • unshare_team
    • Events
      • cursor
      • GET /api/graph/elements/{kind}/{id}/trail — R5 element event-trail. kind ∈ {node, edge}.
    • Facets
      • set_facet
      • Read the live facets of one edge.
      • Set a facet whose owner is an **edge** rather than a resource.
      • Read the live facets of one resource — the confirming read for a write that steers region formation and Atlas grouping.
    • Health
      • health_check
    • Ingest
      • create_ingest
      • update_ingest
      • list_blocks
      • append_block
      • finalize_resource
    • Invitations
      • accept
      • decline
      • list_mine
      • list_team_invitations
      • revoke_team_invitation
      • create_team_invitation
    • Invocations
      • list_invocations
      • open
      • show
      • close
    • Profile
      • get_profile
      • update_profile
      • list_auth_links
    • Query
      • `POST /api/query`.
    • Relationships
      • assert
      • fold
      • retype
      • reweight
    • Resources
      • `GET /api/resources` — **one response type, unconditionally.**
      • create_resource
      • get_resource
      • delete_resource
      • update_resource
      • List the finding at `{id}`'s citation-audit trail — one row per audit, each naming its auditor.
      • Record an auditor's signed defensibility verdict on one `(block, source)` citation of the finding at `{id}`. CONFORM to `handlers::edges::assert` (the sibling authored-write handler): thin — build the command, dispatch it, map the error. No persistence he
      • get_content
      • list_resource_edges
      • resource_evidence
      • grant_resource_access
      • revoke_resource_access
      • resource_lineage
      • provenance
      • annotate_resource
    • Meta
      • get_meta
      • update_meta
    • Reassign
      • reassign_resource
      • reassign_team
    • Search
      • `POST /api/search`.
    • Steward
      • candidates
      • dispatch
      • sweep
      • delta
      • advance
    • Teams
      • list_teams
      • create_team
      • detail
      • delete_team
      • update_team
      • add_member
      • remove_member
      • change_role
    • Schemas
      • AcceptInvitationResponse
      • ActInput
      • ActInvocation
      • ActName
      • ActQuantity
      • ActRefusal
      • ActorAuthority
      • AddMemberRequest
      • AdvanceWatermarkAck
      • AdvanceWatermarkRequest
      • AgentAuthorship
      • AppendBlockPayload
      • AssertRelationshipRequest
      • AtlasEdge
      • AtlasHome
      • AtlasNode
      • AtlasSubgraph
      • AuditCitation
      • AuditSweepRow
      • AuditorDispatchTickRequest
      • AuditorDispatchTickResponse
      • AuditorJobCompleteAck
      • BindTeamOutcome
      • BindTeamRequest
      • BlockId
      • BlockProvenanceRow
      • BlocksResponse
      • BodyStorage
      • Bridge
      • BulkReassignAck
      • BulkReassignRequest
      • ChangeRoleRequest
      • CharterBlock
      • CharterDisposition
      • CitationAuditRequest
      • CitationAuditRow
      • ClaimedAuditJob
      • ClaimedJob
      • CloseInvocationRequest
      • CogmapAnalyticsRow
      • CogmapDetail
      • CogmapFoundationRow
      • CogmapGrantBody
      • CogmapId
      • CogmapRegionMetricsRow
      • CogmapRegionRow
      • CogmapRegulationRow
      • CogmapRevokeBody
      • CogmapRow
      • CogmapStaleness
      • CombineNode
      • CombineOp
      • Composition
      • CompositionTrace
      • ConfidenceBand
      • ContentResponse
      • ContextCreateRequest
      • ContextId
      • ContextOwnerRef
      • ContextPanorama
      • ContextRow
      • ContextRowWithCounts
      • CorrelationId
      • CreateCogmapOutcome
      • CreateCogmapRequest
      • CreateInvitationRequest
      • CreateRequestBody
      • CreateReviewBody
      • DeleteResponse
      • DeviceOverrides
      • DispatchTickRequest
      • DispatchTickResponse
      • Disposition
      • DriftSweepRow
      • EdgeFacetRow
      • EdgeFacetSetRequest
      • EdgeFacetsResponse
      • EdgeFilter
      • EdgeId
      • EdgeKind
      • ElementEvent
      • ElementKind
      • EmbedDispatchSummary
      • Entitlements
      • ErrorBody
      • ErrorDetail
      • ErrorDetails
      • EventCursorResponse
      • EventTrail
      • ExactArm
      • ExactHit
      • Extent
      • FacetAck
      • FacetPredicate
      • FacetSetRequest
      • FinalizePayload
      • FoldRelationshipRequest
      • GrantOutcome
      • GraphEdgeRow
      • GroupKeyMeta
      • HealthResponse
      • HomeCogmap
      • HomeContext
      • IdKind
      • IdProvenance
      • IdSet
      • IdpRevocation
      • IngestCreateResponse
      • IngestDelta
      • IngestPayload
      • IngestState
      • InheritedReadGrant
      • InheritedShare
      • InputSource
      • Intention
      • InvitationStatus
      • InvitationTokenRequest
      • InviteeInvitation
      • InvocationAck
      • InvocationActRow
      • InvocationCloseAck
      • InvocationId
      • InvocationSummary
      • InvocationView
      • JoinRequest
      • JoinRequestStatus
      • LensId
      • LineageNode
      • ManagedMeta
      • MatchLocation
      • MaterializeAck
      • MaterializeDelta
      • MaterializeRequest
      • MergePolicy
      • MetaUpdatePayload
      • NarrowedBy
      • NodeHome
      • OpenInvocationRequest
      • OrdOp
      • OrphanNode
      • OutcomeDeclaration
      • PlanRefusal
      • PlanRefusalDetails
      • Polarity
      • Profile
      • ProfileAuthLink
      • ProfileId
      • ProfileUpdateRequest
      • ProfileWithEntitlements
      • PropertyOp
      • PropertyPredicate
      • ProvenanceSource
      • PublicSystemSettings
      • QuantityScale
      • QueryResponse
      • ReassignAck
      • ReassignContextOutcome
      • ReassignContextRequest
      • ReassignResourceRequest
      • ReconcileCogmapRequest
      • ReconcileEdge
      • ReconcileEdgeTombstone
      • ReconcileEntry
      • ReconcileOutcome
      • ReconcileTelos
      • ReconcileTelosBlock
      • ReconcileTombstone
      • Refusal
      • RefusalReason
      • RegionHit
      • RegionId
      • RelationshipAck
      • RemoveMemberOutcome
      • RenameContextOutcome
      • RenameContextRequest
      • ResidualBucket
      • ResidualContext
      • ResidualGroups
      • ResidualOwnedReach
      • ResourceAnnotateRequest
      • ResourceCreateRequest
      • ResourceFacetRow
      • ResourceFacets
      • ResourceFacetsResponse
      • ResourceFilter
      • ResourceGrantBody
      • ResourceHit
      • ResourceId
      • ResourceLineage
      • ResourceListResponse
      • ResourceRevokeBody
      • ResourceSection
      • ResourceSortField
      • ResourceUpdateRequest
      • ResourceView
      • ReturnSpec
      • RetypeRelationshipRequest
      • RevokeOutcome
      • ReweightRelationshipRequest
      • ScoreKind
      • Scoring
      • SearchParams
      • SearchReason
      • SearchResponse
      • SearchResultRow
      • SearchScope
      • SearchScopeInfo
      • SegmentInfo
      • SegmentedBegin
      • SegmentedBeginResponse
      • ShareContextOutcome
      • ShareContextRequest
      • SlackDisconnectRequest
      • SlackDisconnectResponse
      • SlackDisconnectedPrincipal
      • SliceRequest
      • SortOrder
      • StageDisposition
      • StageInput
      • StageInputTrace
      • StageName
      • StageNode
      • StageOutput
      • StageRelation
      • StageResult
      • StageTrace
      • Standing
      • StandingShape
      • Subscription
      • SubscriptionOverride
      • SystemAccessDetails
      • TeamCreateRequest
      • TeamDetail
      • TeamInvitation
      • TeamMemberDetail
      • TeamMemberRow
      • TeamMemberSource
      • TeamRole
      • TeamRow
      • TeamUpdateRequest
      • Territory
      • TerritoryKind
      • TerritoryOverview
      • UnbindTeamOutcome
      • UnshareContextOutcome
      • VaultConfig
      • ViaEntry
      • WideArm
      • WideHit
  • Temper /api/query — the compositional read surface
    • Run a declared composition of situated acts.
    • Schemas
      • Composition
      • OutcomeDeclaration
      • ReturnSpec
      • Intention
      • StageNode
      • ActInvocation
      • CombineNode
      • StageInput
      • StageRelation
      • Disclosure
      • QueryResponse
      • StageResult
      • StageOutput
      • ScoreKind
      • Scoring
      • ResourceHit
      • CompositionTrace
      • StageTrace
      • InputSource
      • NarrowedBy
      • StageName
      • ActName
      • IdKind
      • IdSet
      • BoundTerm
      • Extent
      • StageDisposition
      • ActRefusal
      • PlanRefusal
      • RefusalReason
      • ActQuantity
      • QuantityScale
      • ResourceFilter
      • EdgeFilter
      • PropertyPredicate
      • PropertyOp
      • ResourceSection
      • EdgeKind
      • OrdOp
      • CogmapId
      • ContextId
      • IdProvenance
      • FacetPredicate
      • CombineOp
      • BlockId
      • MatchLocation
      • BodyStorage
      • ResourceId
      • IngestState
      • ManagedMeta
      • ProfileId
      • ResourceView
      • Polarity
      • ViaEntry
      • LensId
      • RegionId
      • CogmapRegionRow
      • RegionHit
      • StageInputTrace
      • Standing
      • ActorAuthority
      • Refusal
      • SystemAccessDetails
      • PlanRefusalDetails
      • ErrorDetails
      • ErrorDetail
      • ErrorBody
  1. playbooks

Standing Up a Machine Credential

For integrators. A machine principal is a non-human agent — a steward,
a CI job, an SDK client — that authenticates to Temper with its own credential
instead of a person's login. It acts as an agent profile: an ordinary
Temper profile that holds team memberships and capability grants, and is
governed by exactly the same access rules as a human with the same
memberships.
This guide covers standing one up and reasoning about what it can do: the two
ways to mint a credential, what a client does with it once minted (token
endpoint, audience, token lifetime), who is allowed to mint one, how a
machine's reach is bounded to its minter's authority, and the credential
lifecycle (rotation, rebind, revocation).

Two ways to mint a credential#

Temper supports two kinds of machine credential. Pick by who owns the
secret
.
provisionissue
Secret lives atan external IdP (Auth0 M2M app)Temper (Temper is the Authorization Server)
client_idyou supply the IdP's client idTemper mints it (tmpr_…)
Secretheld by the IdP; Temper never sees itTemper mints a 256-bit secret, returned once, stored only as a SHA-256 hash
issuer recordedauth0-m2mtemper
Use whenyou already run an IdP and want it to keep minting M2M tokensyou want Temper to be the whole loop — no external IdP
Both create the same thing on Temper's side: an agent profile, its emitter
entities, the reach you name, and a kb_machine_clients registration row. The
difference is only where the secret and token-minting live. A token from
either kind is verified the same way and rides the same authorization rails.
Registering a machine does not admit it. Every mint door births the
principal denied, so a correctly registered machine with correct reach
still gets 403 SYSTEM_ACCESS_REQUIRED on every single call until temper admin access approve <profile_id> runs. Nothing fails earlier — the secret
works, the token mints, the claims are valid — so this is the step that gets
skipped and then looks like a broken credential. Use the profile_id from
the provision/issue output; temper admin access revoke reverses it.
The issue command prints the plaintext secret exactly once. Temper
stores only its hash and cannot recover it — capture it at mint time or
rotate to get a new one.
A temper-minted client_id is prefixed tmpr_ — you can tell the two
kinds apart at a glance. The secret is 32 random bytes, base64url-encoded, and
only its SHA-256 hex ever reaches the database. rotate-secret is the only
way to get a fresh plaintext; there is no "show secret".

What the client puts in its config#

Minting the credential is half the job. The other half is the handful of
values your app's config holds — and they differ by mint path. The
audience row is where integrators get stuck.
Config valueprovision (Auth0 mints)issue (Temper mints)
token_urlyour Auth0 tenant's https://<tenant>.auth0.com/oauth/tokenyour own Temper instance's /oauth/token — e.g. https://temper.acme.com/oauth/token
client_idthe Auth0 M2M app's client idthe tmpr_… id printed by issue
client_secretthe Auth0 app's secretthe one-time secret printed by issue
audiencerequired — must equal the API's configured AUTH_AUDIENCE, or the minted token fails audience validation at the resource serveromit it — Temper's AS ignores a request-supplied audience entirely and mints with its server-side AS_AUDIENCE
That token_url row is the whole point of the issue path: Temper is the
Authorization Server
, so a temper-issued machine mints its token from your
instance
, with no IdP in the loop. /oauth/token routes to Temper's own
token handler, which carries a client_credentials grant alongside
authorization_code and refresh_token. A conformant client can discover
this rather than hard-code it — GET /.well-known/oauth-authorization-server
lists client_credentials in grant_types_supported and names the
token_endpoint.
Temper's own clients read these from TEMPER_M2M_TOKEN_URL,
TEMPER_M2M_CLIENT_ID, TEMPER_M2M_CLIENT_SECRET, and (external IdP only)
TEMPER_M2M_AUDIENCE — the same four names in the Ruby gem
(Temper::Credentials), the TypeScript client (temper-ts's
ClientCredentials), and the steward runtime, which composes the TypeScript
one. Follow the convention; it is one less thing to translate.
An audience is not part of the client_credentials protocol — it is Auth0's.
Temper's AS never reads one off the request, so a request-supplied audience is
inert there rather than wrong. Temper's clients (temper-rb, temper-ts, and
the steward through it) therefore send audience only when it is
configured
, and omit it entirely for a temper-issued (tmpr_) credential —
an empty audience would be a lie, and a client that required one could not
consume a tmpr_ credential at all.
issue presumes Temper is your instance's Authorization Server — the
mode AS_ISSUER turns on (self-hosted instances; the same AS that backs
SAML). A temper-minted token is signed by the AS key and carries iss = AS_ISSUER. An instance has exactly one issuer, so on an Auth0-fronted
instance a temper-minted token would not validate at all — register those
machines with provision and let Auth0 mint. See
self-hosting Temper and
token verification.

The token request — form-encoded, not JSON#

The token endpoint takes application/x-www-form-urlencoded, per RFC 6749 §4.
A JSON body is refused with invalid_request — Auth0 tolerates JSON as an
extension, Temper's AS does not. Credentials may travel in the form body
(client_secret_post) or in an HTTP Basic header (client_secret_basic,
which the endpoint prefers when present, per RFC 6749 §2.3.1).
The exact wire shape — content type, required params, the optional audience,
and the refusal of anything that is not form encoding — is pinned as a
cross-language contract (m2m-token-request.json). Three suites assert
against that one file today: the Ruby gem's spec and temper-ts's contract test
(the clients emit this shape) and the AS's own integration test (the server
accepts it). A future client (temper-py) pins itself against it too; a
contract asserted only against itself is not asserted at all.

Token lifetime — short, and no refresh token#

The response is { access_token, token_type: "Bearer", expires_in }.
expires_in comes from AS_ACCESS_TTL_SECONDS — default 900 seconds (15
minutes)
.
A machine token carries no refresh token, by design (RFC 6749 §4.4.3): the
credential is the refresh mechanism, so a machine simply re-mints via
client_credentials. A client should cache the token against its absolute
expiry with a small skew (60s is the convention in Temper's own clients) and
re-mint on expiry — and re-mint on a 401, since a token checked at the top
of a long unit of work can expire in the middle of it.

One claim shape, whichever issuer minted it#

Temper's mint is deliberately claim-identical to Auth0's:
sub: "<client_id>@clients", azp: "<client_id>", gty: "client-credentials", and no email. That is not cosmetic — it is why the
same registration gate and the same RBAC apply unchanged regardless of who
minted the token. The resource server normalizes one machine shape, looks the
client_id up in kb_machine_clients, and resolves the agent profile. The
issuer is an implementation detail below that line.

Who may mint one#

Registration is authorized by team ownership:
is_system_admin OR owner of the team that will own the machine.
A system admin — a profile with a governance grant — can mint any machine. A team
owner
can mint a machine owned by their own team, without an operator in
the loop. That is the point of the model: a team runs its own agents.
The owning team is set with --owner-team. It records who owns the machine
— not what the machine can reach
(see the next section). If you omit
--owner-team, the machine is teamless, and a teamless machine is
admin-only to create, read, or operate — the empty owning team fails
closed, never open.

Reach containment — a machine can reach nothing you couldn't#

A machine's reach is the teams it belongs to (--team) and the cognitive
maps it can write (--cogmap). Reach is always explicit and plural — it
is never inferred from --owner-team. See
trust boundary for the model this builds on.
The load-bearing rule: a non-admin may only grant a machine reach they could
confer on a human themselves.
Requested reachYou must hold
--team <ref>[:role]owner or maintainer on that team (can_manage), and the role may not be owner
--cogmap <ref>[:ro]can_grant on that cognitive map
So a team owner can enroll a machine into any team they manage (at
member/maintainer/watcher, never owner) and grant it write on any map
they can already delegate — and nothing beyond that. A machine also never
receives can_grant or can_delete on a map: it cannot re-delegate its own
access.
A system admin is exempt from this check (they can already confer
anything), so an admin may mint a machine with any reach.

The lifecycle — who can do what#

Once a machine exists, its owning-team owner can operate it — with one
deliberate exception.
CommandWhoNotes
provision / issueadmin, or owner of the owning teammint a credential
rotate-secretadmin, or owner of the machine's teamroll a temper-issued secret
revokeadmin, or owner of the machine's teamdeny the credential
list / showadmin sees all; a team owner sees only machines owned by their teams
rebindsystem admin onlysee below
rebind is admin-only, and it is the one exception on purpose. Every
other command merely operates on a machine's row. rebind is different in
kind: it transplants an existing agent profile's identity — and the full
reach that profile already holds — onto a new client_id
. That inherited
reach may have been granted by an admin and can exceed a team owner's own
authority, so team ownership cannot safely bound it. rebind therefore keeps
the system-admin bar, and it refuses a revoked source outright (a dead
credential is re-created with a fresh provision, never resurrected). A team
owner rotating a temper-issued credential uses rotate-secret, not
rebind; rebind is for rotating the external IdP application behind an
auth0-m2m machine.

Rotation#

rotate-secret mints a new secret and keeps the previous one valid for a
grace window (default 24h, capped at 7 days) so a running fleet can pick up
the new secret with no downtime. At most two secrets are ever live at
once; a second rotation drops the oldest. The token endpoint accepts the
previous secret only while now() is inside that window — the moment it
lapses, the old secret is dead, with no further action from you.
The grace window exists so that you never have to deploy and rotate in the
same instant
. The sequence:
Size the window to your deploy cadence, not to your patience: --grace 3600
is fine for a one-service push, and the 7-day cap exists for a fleet that
rolls slowly. Only a temper-issued (tmpr_) credential can be rotated this
way — an auth0-m2m machine's secret lives at the IdP, and rotate-secret
refuses it.

Revocation#

revoke denies the credential's authentication on the very next request,
on both the HTTP API and the MCP surface — the gate re-checks the registration
on every call, so a still-unexpired token stops working immediately.
Revocation is credential-scoped. It deliberately leaves the agent
profile's team memberships and capability grants in place
— so revoking one
credential does not tear down an agent's reach. If you need to kill an agent's
reach, that is a separate, explicit step: remove its team memberships and
grants, or deactivate the profile.

Machine RBAC comes for free#

Because a machine's reach is contained to its minter's authority and the
machine acts as an ordinary agent profile, a machine is governed by exactly
the same team-and-grant rules as a human with the same memberships.
There is
no machine-specific authorization path. A team-bound machine cannot read,
write, or grant anything outside what its profile's memberships and grants
confer.
This is what makes autonomous and managed-agent sessions safe without a human
in the loop: the agent inherits its permissions from the credential, rather
than a caller having to reconstruct what is safe from the shape of the graph.
The credential is the boundary.

Machine credentials vs. proxied human auth#

Temper also supports human authentication proxied through SAML or OAuth (see
self-hosting Temper). Choose by who is acting:
Machine credential (this guide) — the actor is a service or agent with
no human behind it. It holds its own long-lived credential, authenticates as
an agent profile, and is bounded by the reach it was minted with. Use this
for stewards, CI, and SDK clients running unattended.
SAML / OAuth-proxied — the actor is a person, authenticated through your
identity provider, acting as their own human profile with whatever teams and
grants they hold. Use this for interactive users.
An integration (temper-rb, temper-py, temper-ts) may support both: a proxied
human token for interactive use, and a machine credential for unattended
runs. They resolve to different profiles with different reach; they are not
interchangeable.

Command reference#

See the generated CLI reference for the full
temper admin machine command surface — flags, repeatable options, and
subcommand help.

See also#

Machine tokens — the claim shape both
issuers produce.
Trust boundary — how a machine's reach is
bounded.
Token verification — how a token is
validated, and the one-issuer-per-instance invariant.
Self-host Temper — standing up the instance whose
/oauth/token a temper-issued credential mints against.
Modified at 2026-08-20 02:05:41
Previous
Slack Mentions
Next
Integrating Ruby with temper-rb
Built with