Skip to content
Book a demoSign in
All docs
Agent SDK

Authentication

OAuth 2.1 + DPoP at spec level. Scopes, refresh, HMAC-signed bodies, and how clinic-scoped identities work.

Updated May 28, 2026

Nyra agent identities are clinic-scoped and short-lived. Every token is bound to a single clinic and a fixed patient roster. There are no long-lived bearer tokens, no shared admin keys, and no global sandbox.

The v1 SDK ships the spec-level primitives: the scope constants, the DPoP proof claim builder, and the typed errors that any future auth layer will raise. The auth server itself is not live yet. Working code that hits a real endpoint will land in a follow-up release.

The model

ElementValue
Authorization protocolOAuth 2.1
Sender constraintDPoP (RFC 9449), ES256 key binding
Nonce TTL120 seconds
Clock skew±60 seconds
Token lifetime1 hour, refreshable
Body signingHMAC-SHA256 over body + timestamp on every write
RefreshStandard OAuth 2.1 refresh token, scoped to the original clinic identifier

Scopes

identity.read         resolve the current OAuth identity
evidence_map.read     read a patient's evidence map
scales.read           read PHQ-9 / GAD-7 instrument definitions
audit.read            read the append-only audit log
sandbox.fork          fork a patient graph into a session-scoped sandbox
simulation.run        walk a plan against the graph surrogate
draft.create          propose a clinical edit, held until clinician commit

A token grants a subset of these scopes. The SDK's typed ScopeError carries the missing scope name, so the agent's error handler can surface it to the user verbatim.

DPoP proof, the v1 spec

import { buildDpopProofClaims, DPOP_NONCE_TTL_SECONDS } from "@humyn/nyra";

const claims = buildDpopProofClaims({
  method: "POST",
  url: "https://api.nyra.us.com/v1/sandbox/fork",
  jti: crypto.randomUUID(),
  nonce: serverNonce,   // server-issued, ≤ 120s old
  accessTokenHash: ath, // sha256 of the access token, base64url
});

// Sign `claims` with your ES256 private key. The SDK does not sign for you in v1.
// We expect integrators to bring their own JWS signer (jose, node:crypto, hardware-bound).

The Python signature mirrors the TS one:

from humyn_nyra import build_dpop_proof_claims, DpopProofInput

claims = build_dpop_proof_claims(DpopProofInput(
    method="POST",
    url="https://api.nyra.us.com/v1/sandbox/fork",
    jti="...",
    nonce=server_nonce,
    access_token_hash=ath,
))

What lands at the auth boundary

Five typed errors map the marketing-spec refusals to runtime failures the SDK can hand back to your agent.

AuthError                    401  bad or expired token
ScopeError                   403  token is valid but missing a required scope
CommitNotAllowedError        403  agent attempted a clinician-only commit
ClinicianRejectedDraftError  403  commit after rejection; rejected_draft_id is on the error
InstrumentFrozenError        409  attempt to mutate PHQ-9 or GAD-7
RateLimitError               429  bucket + retry_after_seconds on the error

The marketing spec says, "the refusals are enforced at the auth layer, not at the prompt layer." The SDK error names are the contract.

Next: Function-call schemas.