Skip to content

SDK Reference

@humanauth/sdk is the agent-side half of HumanAuth. Your agent asks a human (or a group) to authorize a specific action, waits for the decision, and — on approval — pulls a verifiable receipt whose JWS it hands to your resource server for enforcement with @humanauth/verifier.

The whole surface is one class, HumanAuth, plus the AuthorizationHandle it returns and three admin namespaces (humans, groups, receipts).

Terminal window
npm install @humanauth/sdk

The package ships as ESM with bundled type declarations and has no runtime dependencies.

import { HumanAuth } from "@humanauth/sdk";
const auth = new HumanAuth({ apiKey: process.env.HUMANAUTH_API_KEY! });

The constructor takes a single options object. apiKey is required — the constructor throws apiKey is required if it is missing or empty. Every request is sent with an Authorization: Bearer <apiKey> header.

HumanAuthOptions

FieldTypeRequiredDescription
apiKeystringYesYour tenant API key (ha_live_… / ha_test_…).
baseUrlstringNoPlatform base URL. Defaults to https://api.humanauth.ai. Point this at your own host for self-managed deployments.
fetchtypeof fetchNoCustom fetch implementation. Defaults to the global fetch. Useful for testing or for runtimes that need an explicit fetch.

The end-to-end flow: request authorization, wait for the human, then hand the receipt to your resource server.

import { HumanAuth } from "@humanauth/sdk";
const auth = new HumanAuth({ apiKey: process.env.HUMANAUTH_API_KEY! });
// 1. Ask a human to authorize one specific action.
const handle = await auth.authorize("hu_karthick_7x2f", {
action: "github:delete_repo",
description: "Delete acme/legacy-service — no longer deployed",
severity: "high",
});
// 2. Block until the human decides (or the request times out).
const result = await handle.wait();
if (result.result !== "approved") {
throw new Error(`Not authorized (${result.status})`);
}
// 3. Pull the receipt JWS and hand it to your resource server to enforce.
const jws = await auth.receipts.getJws(result.receipt_id!);
// Send `jws` as the `x-humanauth-receipt` header to your service, which calls
// verifier.requireReceipt(jws, { action, plan, idempotencyKey }) before acting.
authorize(target: string, options: AuthorizeOptions): Promise<AuthorizationHandle>

Creates an authorization request and returns an AuthorizationHandle immediately — it does not block. Call .wait() on the handle to await the decision.

The target selects who is asked:

  • A human id (hu_…) asks that one person.
  • A group id (grp_…) asks the group under its policy.

The prefix decides the routing; there is no separate flag.

AuthorizeOptions

FieldTypeRequiredDescription
actionstringYesCanonical service:operation label for the action being authorized (e.g. github:delete_repo, transfer:create). The verifier enforces this exact string, so keep it stable and specific.
descriptionstringNoHuman-readable summary shown to the approver.
severity"low" | "medium" | "high" | "critical"NoRisk level surfaced in the approval UI.
intent"authorize" | "collect" | "inform"NoThe kind of interaction requested — approve/deny, gather input, or notify only.
ttlnumberNoSeconds the request stays open before it expires. Omit to use the platform default.
policyGroupPolicyNoGroup targets only. Inline policy override for this one request (ignored for single-human targets).
callbackUrlstringNoWebhook URL notified when the request is decided.
callbackSecretstringNoHMAC secret used to sign the callbackUrl delivery.

GroupPolicy

interface GroupPolicy {
type: "quorum" | "unanimous";
quorum?: number; // number of approvals required when type is "quorum"
}

Returned by authorize(). Wraps the in-flight request so you can await, poll, or cancel it.

Properties

PropertyTypeDescription
requestIdstringThe platform request id — poll or cancel it yourself if you don’t use wait().
targetType"human" | "group"Whether the request went to a single human or a group.
expiresAtDateWhen the request expires.
wait(options?: WaitOptions): Promise<RequestDetail>

Polls the platform until the request reaches a terminal state — decided, expired, or cancelled — and resolves with the RequestDetail shown below. Throws HumanAuthTimeoutError if the timeout elapses first (the request itself keeps running platform-side; only the local wait gives up).

WaitOptions

FieldTypeDescription
pollIntervalMsnumberInterval between status polls. Defaults to 1000.
timeoutMsnumberHow long to wait before throwing. Defaults to the request TTL plus 5 seconds.
const result = await handle.wait({ pollIntervalMs: 2000, timeoutMs: 120_000 });
switch (result.result) {
case "approved":
// result.receipt_id is set — fetch the JWS and proceed.
break;
case "denied":
// result.decisions carries each approver's reason.
break;
case "expired":
// no one decided in time.
break;
}
cancel(): Promise<void>

Cancels the request while it is still pending or delivered.

RequestDetail

interface RequestDetail {
request_id: string;
status: "pending" | "delivered" | "decided" | "expired" | "cancelled";
action: string;
target_type: "human" | "group";
target_id: string;
ttl: number;
created_at: string;
decided_at: string | null;
result?: "approved" | "denied" | "expired";
receipt_id?: string; // present once an approved request has minted a receipt
decisions: DecisionDetail[];
}
interface DecisionDetail {
human_id: string;
decision: "approved" | "denied";
device_id: string;
decided_at: string;
reason: string | null;
}

If you persist handle.requestId and want to resume elsewhere, the client exposes the same operations by id:

const detail = await auth.getRequest("req_abc123"); // → RequestDetail
await auth.cancelRequest("req_abc123");

auth.receipts reads the receipts your approved requests mint. A receipt’s JWS is the offline-verifiable artifact your resource server enforces — see the Verifier SDK.

getJws(receiptId: string): Promise<string>

The common case: return just the compact v2 JWS, ready to hand to @humanauth/verifier. Throws HumanAuthError with code: "no_v2_receipt" if the receipt predates the v2 cutover and has no JWS to verify offline.

const jws = await auth.receipts.getJws(result.receipt_id!);
get(receiptId: string): Promise<Receipt>

Fetches the full receipt record.

interface Receipt {
receipt_id: string;
request_id: string;
tenant_id: string;
result: "approved" | "denied" | "expired";
jws?: string; // v2 compact JWS — hand this to @humanauth/verifier
plan_hash?: string;
issued_at?: number;
expires_at?: number;
approvers_count?: number;
v?: 2;
// Legacy v1 receipts (pre-v2) instead carry:
// action, policy, decisions, decided_at, ha_signature
}
list(options?: ListReceiptsOptions): Promise<ListReceiptsResponse>

Lists receipts for the tenant, newest first, with cursor pagination — the audit view.

const page = await auth.receipts.list({ since: "2026-04-01", limit: 50 });
for (const r of page.receipts) {
console.log(r.receipt_id, r.action, r.result, r.decided_at);
}
if (page.has_more) {
const next = await auth.receipts.list({ cursor: page.cursor! });
}
interface ListReceiptsOptions {
since?: string; // ISO 8601 lower bound on decided_at
limit?: number;
cursor?: string; // opaque cursor from a previous page
}
interface ListReceiptsResponse {
receipts: Array<{
receipt_id: string;
request_id: string;
action: string;
result: "approved" | "denied" | "expired";
decided_at: string;
}>;
has_more: boolean;
cursor: string | null;
}

auth.humans manages the roster of people in your tenant.

// Invite someone (role defaults server-side; pass "admin" or "member").
const invited = await auth.humans.invite({ email: "alice@example.com", role: "admin" });
// → { human_id, email, status, is_new }
// List the roster.
const roster = await auth.humans.list(); // → HumanRosterEntry[]
// Remove a human from the tenant.
await auth.humans.remove("hu_abc123");
interface InviteHumanRequest {
email: string;
role?: "admin" | "member";
}
interface InviteHumanResponse {
human_id: string;
email: string;
status: string;
is_new: boolean; // false if the email was already on the roster
}
interface HumanRosterEntry {
human_id: string;
email: string;
status: string;
role: "admin" | "member";
devices: number;
added_at: string;
}

auth.groups manages approval groups and their policies. Pass a group id (grp_…) to authorize() to route a request through the group.

// Create a group that needs 2 of 3 approvals.
const { group_id } = await auth.groups.create({
name: "deployers",
members: ["hu_a", "hu_b", "hu_c"],
policy: { type: "quorum", quorum: 2 },
});
// List groups.
const groups = await auth.groups.list(); // → GroupDetail[]
// Delete a group.
await auth.groups.delete("grp_xyz");
interface CreateGroupRequest {
name: string;
members: string[]; // human ids
policy: GroupPolicy; // { type: "quorum" | "unanimous", quorum? }
}
interface GroupDetail {
group_id: string;
name: string;
policy_type: "quorum" | "unanimous";
policy_quorum?: number;
}

Both error classes are exported from the package.

Thrown for any non-2xx API response.

PropertyTypeDescription
statusnumberHTTP status code from the platform.
codestringStable machine-readable error code (e.g. forbidden, no_v2_receipt).
messagestringHuman-readable detail.

Subclass of HumanAuthError, thrown by handle.wait() when the local timeout elapses before a terminal decision. Its status is 408 and its code is "timeout".

import { HumanAuth, HumanAuthError, HumanAuthTimeoutError } from "@humanauth/sdk";
try {
const result = await handle.wait({ timeoutMs: 120_000 });
// ...
} catch (err) {
if (err instanceof HumanAuthTimeoutError) {
await handle.cancel(); // no decision in the window — give up cleanly
} else if (err instanceof HumanAuthError) {
console.error(`API error ${err.status} (${err.code}): ${err.message}`);
} else {
throw err;
}
}

Everything the package exports, all verifiable against packages/sdk/src:

  • Classes — HumanAuth, AuthorizationHandle, HumanAuthError, HumanAuthTimeoutError.
  • Types — HumanAuthOptions, AuthorizeOptions, AuthorizeResponse, RequestDetail, DecisionDetail, Receipt, HumanRosterEntry, InviteHumanRequest, InviteHumanResponse, CreateGroupRequest, GroupDetail, GroupPolicy, ListReceiptsOptions, ListReceiptsResponse, WaitOptions.
  • Enums (string unions) — Plan, Intent, Severity, RequestStatus, Decision, RequestResult, PolicyType, TenantRole.

Building in Python, Go, Java, Ruby, Rust, or another language? The platform publishes an OpenAPI 3.1 specification you can use to generate a typed client. Receipt verification is offline JWS — the Verifier SDK documents how to reproduce the verification fence in any language.