H-Series

H-Grant: capability grants & credential custody

Give an agent scoped, capped authority to use a credential it never sees. How to set it up from the showroom, and how an agent acts under it against the backend.

Showroom + Agent

H-Grant is the credential-gating layer of the H-Series. An owner deposits a credential (an API key, a signing key, a funded wallet) and issues a grant: an owner-signed policy that says which agent may use it, for what action, against which target, and within what spend, count, and rate caps. The agent acts under the grant and never holds the secret. In the production nitro custody mode (LIVE, cutover 2026-07-26), the credential is decrypted only inside a hardware enclave the operator cannot read; that is operator-zero-access, live in production, with the software kms mode retained on the standby as an instant rollback.

What it does

  • Scoped authority. A grant names the grantee, the allowed action and target, and a deny-list. Authority enters only as an owner signature (TIP-712 / EIP-712 / Ed25519, CAIP-10 identity), never a console toggle.
  • Hard caps. Amount, count, rate, and velocity limits are enforced server-side in one locked transaction.
  • Custody. The credential is envelope-encrypted at rest and, in the production nitro mode (LIVE), decrypted and used only inside an attestation-gated enclave (operator-zero-access, live in production).
  • Kill switch. Owner-signed revocation stops a grant immediately; it is free.
  • Proof. Every gated release can anchor an H-Seal receipt, so the action is provable after the fact.

Using it from the showroom

The showroom is at h-series.xr-utilities.com/grant. It is for a person setting up authority for an agent.

  1. Sign in. Connect a wallet or sign in with Google. Your signature (or session) is what authorizes everything that follows.
  2. Create a vault. A vault holds one credential. Deposit the credential; in production it is sealed so it is never readable by the service operator.
  3. Build a grant. In the grant builder, choose the grantee (the agent), the action and target, and set the caps: a spend budget, a per-period rate, a count. Add optional conditions (a required behavior posture, a human approval step).
  4. Sign and publish. You sign the grant; it is anchored on chain. The agent can now act under it.
  5. Monitor and revoke. Watch usage against the cap, and revoke with one signed click if anything looks wrong.

The easiest on-ramp is the Console: it can create an agent and its budget (an H-Grant cap) for you in plain language, then run tools under it.

Using it from an agent (the backend)

The backend is at h-grant.xr-utilities.ai. An agent does not “log in” to H-Grant; it acts under a grant its owner issued, and H-Grant brokers the credential.

The shape of an agent-side integration:

  1. The owner issues the grant (once, from the showroom or via a signed API call), producing a grant id.
  2. The agent calls the gated action, presenting the grant id and paying $0.05 for the call over x402 (the facilitator is never trusted; settlement is re-verified on chain and replay-guarded). Revocation is free.
  3. H-Grant checks the grant (valid, not revoked, within caps, policy and posture satisfied), releases the credential inside the boundary, performs the upstream call, and returns only the result plus a receipt reference.
POST https://h-grant.xr-utilities.ai/call/<vaultId>
  X-Payment: <x402 payment envelope for this $0.05 call>
  {
    "grantId": "<grant id>",
    "action":  "stripe:POST /v1/charges",
    "payload": { "amount": 100, "currency": "usd", "source": "tok_visa" }
  }
  -> 200 { "result": { ... }, "receipt": { "id": "..." } }

Payment settles in USDC (or RLUSD on XRPL) across five chains: Hedera, Base, XRPL, Solana, and Stellar. The rails are stablecoin-only.

The credential is never in the request or the response. Agents that prefer a tool interface can reach the same capability through the H-Series MCP passthrough. For the exact endpoints, payloads, and signing envelope, see the H-Grant reference.

Depositing a credential (the API path)

The showroom deposits a credential for you, but an integrator can post it directly. A deposit is owner-signed: the signature must verify against the claimed ownerIdentity over a canonical body binding the vault, adapter, and a hash of what is deposited, so the depositor proves it controls the owner key before any credential is accepted. The credential is never logged or echoed.

POST https://h-grant.xr-utilities.ai/vault/deposit
  {
    "vaultId":        "<your vault id>",
    "ownerIdentity":  "<CAIP-10 owner>",
    "adapter":        "stripe",
    "credential":     "<the raw secret>",
    "nonce":          "<fresh nonce>",
    "issuedAt":       <unix seconds>,
    "signatureScheme":"<eip712 | tip712 | ed25519>",
    "ownerSignature": "<signature over the canonical body>",
    "ownerPublicKey": "<owner public key>"
  }
  -> 201 { "vaultId": "..." }

The signed canonical body is { vaultId, ownerIdentity, adapter, credentialHash, nonce, issuedAt } where credentialHash is the sha256 hex of the credential. Adapter ids are stripe, openai, anthropic, slack, x402, xr-sentinel, h-scope, plus the schema-driven set (mistral, deepseek, groq, together, resend, github, airtable, notion, gemini) and the no-secret external-spend and free-tools adapters. A logged-in wallet or OIDC (Google) owner can present its session instead of a per-deposit signature.

The deposit shape depends on the vault’s custody mode. In the production nitro mode, a secret-bearing adapter accepts only a sealed credential: the owner KMS-encrypts the secret to the vault key first (field sealedCredential), the owner signs the sha256 of that sealed blob, and only the attested enclave can open it. A raw credential in nitro mode, or a sealed one in software kms mode, is rejected. A no-secret adapter (external-spend) has nothing to seal, so it uses the raw path in either mode. This route is deliberately excluded from the MCP passthrough.

Listing your vaults and grantees

Two owner-signed reads back the builder’s selectors and let an integrator recover state without remembering ids. Both carry the owner authorization in headers (a GET has no body): the owner signs a canonical { ownerIdentity, issuedAt, nonce } and sends it as x-owner-identity, x-owner-signature-scheme, x-owner-signature, x-owner-public-key, x-owner-issued-at, x-owner-nonce. The response is scoped to the verified identity, never a request param, so you cannot enumerate another owner’s data. A signed-in wallet may instead pass X-Wallet-Authorization (its SIWE session) and a Google owner x-owner-oidc (its token); both are identity-only, never a write.

GET https://h-grant.xr-utilities.ai/vaults
  (x-owner-* headers)
  -> 200 { "ownerIdentity": "...", "vaults": [ { "vaultId": "...", "adapter": "stripe", "createdAt": 0 } ] }

GET https://h-grant.xr-utilities.ai/grantees
  (x-owner-* headers)
  -> 200 { "ownerIdentity": "...",
           "grantees": [ { "identity": "...", "grants": 2, "latestValidUntil": 0, "isOidc": false } ] }

GET /vaults returns metadata only (never any credential). GET /grantees lists the distinct grantees across your active, unexpired grants; it is the way to recover an oidc:<issuer>:<subject> grantee, which is anchored on chain only as a salted commitment and so is not chain-derivable (useful for importing your roster into, for example, the H-Cert principal directory).

Reading your grants as a delegation tree

GET /grants returns your grants as a flat node list for assembling the parent-to-child delegation tree. Each node carries its parentGrantId link, the non-secret terms (caps, delegation depth, maxReleases, approverPolicy, allowedActions), status, validUntil, and lifetime releasedUsdCents; the client builds the forest and the sub-tree spend aggregates from this list. Optional ?vaultId= scopes to one vault. Auth is the same x-owner-* header scheme as GET /vaults; the listing is scoped to the verified owner. Read-only and off the trust path.

GET https://h-grant.xr-utilities.ai/grants[?vaultId=<vault>]
  (x-owner-* headers)
  -> 200 { "ownerIdentity": "...",
           "grants": [ { "grantId": "...", "parentGrantId": null, "vaultId": "...",
                         "granteeIdentity": "...", "status": "active", "validUntil": 0,
                         "issuedAt": 0, "releasedUsdCents": 0, "caps": {}, "maxReleases": null,
                         "delegation": null, "approverPolicy": null, "allowedActions": [] } ] }

Delegating a sub-grant (the API path)

A sub-grant is a full signed grant carrying a parentGrantId and a delegation depth, signed by the parent grant’s grantee (the delegator), not the vault owner. The bound validator refuses anything not provably within the parent: same vault and owner, allowedActions a subset, every cap at or below, rate no looser, expiry no later. Delegation is gated off by default, so the route returns 503 (delegation is disabled) until an operator enables it.

POST https://h-grant.xr-utilities.ai/grant/delegate
  { <a full signed grant, with parentGrantId and delegation, signed by the parent grantee> }
  -> 201 { "grantId": "<sub-grant id>" }   (or 503 when delegation is disabled)

Reading your audit trail and exports

Every release and refusal is anchored on chain; the owner-signed audit reads give you the running statement and SIEM/compliance exports over it. All four routes are owner-signed with the same header scheme as GET /vaults, plus a single-use nonce consumed on the audit surface (a before cursor paginates newest-first). Target identifiers are already hashed in storage, so nothing here leaks raw target data.

GET /audit/:vaultId[?limit=&before=]                 the running statement (newest first, with a gloss)
GET /audit/:vaultId/event/:eventId                   one event with the full narrative overlay
GET /audit/:vaultId/export?format=cef|otel|json      the same events in a SIEM-ingestable shape
GET /audit/:vaultId/compliance                        an EU AI Act Article 12/19 bundle over the vault

The single-event read is the only one that runs the advisory LLM narrative tier (when enabled), cached per event. The export cef format returns text; otel and json return records with a nextCursor. The compliance bundle references the on-chain anchors as the immutable retention root.

Cross-vault reports

Two filterable reads sit over the whole audit trail, sharing filters and output formats.

  • GET /report is the owner’s activity across all their vaults (owner-signed).
  • GET /report/activity is a grantee’s own calls across every vault (grantee-signed): the agent self-pull, scoped to the verified caller so it can only ever return that grantee’s own rows.

Both use the same x-owner-* header auth as GET /grantees. Optional filters: from/to (unix seconds), action, result (released|refused|partial|error), vault, grantee (owner report only), before (cursor), limit. format is json (default), cef, otel, or compliance, so one query feeds a human report, a SIEM ingest, and the EU AI Act bundle.

GET https://h-grant.xr-utilities.ai/report?from=&to=&action=&result=&vault=&grantee=&before=&limit=&format=json
  (x-owner-* headers)
  -> 200 { "scope": "owner", "identity": "...", "events": [ ... ], "nextCursor": null }

Polling a grant’s authoring receipt

When you publish a grant, H-Grant auto-anchors a free, self-attested H-Seal authoring receipt in the background, so the success card can show an execution-proof line. Poll for it by grant id; the poll is fast (the receipt anchors after the publish returns).

GET https://h-grant.xr-utilities.ai/grant/:grantId/receipt
  -> 200 { "status": "pending" }
       | { "status": "done", "receiptId": "...", "consensusTimestamp": null }
       | { "status": "error", "reason": "..." }
       | 404 { "status": "unknown" }

To co-sign and anchor a two-party authoring receipt (author + H-Grant as provider), post the author-signed receipt to POST /seal-receipt { grantId, receipt, signature }. It is not an open co-signing oracle: it co-signs only a receipt that attests a grant this backend published (requestHash equals the grant’s signed canonical hash) and is requested by that grant’s owner (callerIdentity equals the grant owner). It returns 202 and you poll GET /seal-receipt/:grantId for the same pending|done|error shape.

Reading an org’s approver set

GET /org/:orgId is a public read of an org account’s approver set and threshold; the approver set is anchored on chain, so it exposes no secret. Each approver is returned with any bound OIDC principal behind it, so an org’s human membership is auditable.

GET https://h-grant.xr-utilities.ai/org/:orgId
  -> 200 { "orgId": "...", "approvers": ["<key identity>", ...],
           "approverPrincipals": [ { "identity": "...", "boundOidcIdentity": null } ],
           "threshold": 2, "supersededBy": null }

An operator can bind a human OIDC identity to an approver’s signing key with POST /org/principal/bind (a signed binding body), so a key acts as an approver under an auditable human identity. The binding is advisory metadata off the M-of-N trust path (the org still authorizes via key signatures) and is gated behind the org-accounts and OIDC-owner flags, returning 503 when org accounts are disabled.

Discovery proxies for the grantee selector

Two read-only proxies let the grant builder offer a grantee without a cross-origin call; both forward public data only to a config-fixed upstream (no caller-supplied target, so no SSRF), and fail soft with a 502 the builder treats as “type the grantee manually”.

  • GET /discover?q=&category=&limit= proxies the H-Index capability registry (/endpoints) so you can pick a registered service; each result carries a granteeIdentity (the registrant’s CAIP-10) ready to drop into the grantee field.
  • GET /cert-principals?owner=<id> proxies the H-Cert principal directory (/principals) so you can pick from your own named principals; H-Cert scopes the roster to that owner.

Anchoring a grant from an XRPL (Xaman) wallet

XRPL wallets cannot sign arbitrary bytes, so an XRPL owner anchors a grant hash as a signed transaction memo through Xaman. POST /xaman/anchor { grantHash } (a 64-hex sha256) creates a sign request; GET /xaman/result/:uuid reports whether the user signed and the resulting transaction hash. These are an authoring convenience only: the grant is still authorized at /grant/publish by re-reading the transaction from the XRPL ledger, so the proxy holds no trust. Both return 503 until the Xaman key and secret are set. The parallel pair POST /xaman/seal-sign and GET /xaman/seal-result/:uuid does the same for anchoring an XRPL authoring receipt via H-Seal’s Xaman SignIn.

Machine discovery

GET /.well-known/agent-card.json is the public A2A agent card: capability metadata (release, publish_grant, revoke_grant, audit, config) for agents and crawlers, with no secrets or enforcement internals. It also gives the H-Series MCP server a live manifest to resolve at startup.

Wiring a grant into an H-Agent cap

When you run an agent through H-Agent, it materializes your grant as an on-chain Spend Permission by reading the grant’s terms from GET /grant/:grantId. This read is operator-authenticated and owner-scoped: it requires a valid trusted-sibling operator signature (headers x-operator-id, x-operator-owner, x-operator-ts, x-operator-signature, verified against the operator’s registered public key) and the grant’s owner matching the owner the operator claims to act for, so even a valid operator signature cannot read another owner’s grant. It returns only the non-secret terms needed to mint a cap (grantId, granteeIdentity, ownerIdentity, caps, allowedActions, status, validUntil); no credential transits. You do not call this directly; H-Agent does, on your behalf, once you point it at a grant id you own.

Custody: what “operator-zero-access” means

In nitro mode (the production posture, LIVE since the 2026-07-26 cutover), decryption and the upstream call run inside an AWS Nitro enclave. AWS KMS releases the data key only to an enclave whose attestation matches the pinned measurement, so a host operator, or a compromised process, cannot obtain the credential. That is operator-zero-access, live in production. The software kms mode is retained on the Railway standby as an instant rollback; that is an internal fallback detail, not the live posture.

Delegating a grant (sub-grants)

A grantee that already holds a grant (an agent) can hand a strictly-narrower slice of it to another agent without the owner re-signing. The result is a sub-grant, and a chain of them forms a delegation tree.

A sub-grant must be provably within its parent: same vault and owner, allowed actions a subset of the parent’s, every cap (per-call, daily, weekly) at or below the parent’s, rate limit no looser, and expiry no later. A bound validator refuses anything not provably within, both in the showroom before you sign and server-side before any release. So authority can only narrow as it moves down the tree, never widen.

Each node reports its own released spend and the aggregate across its children, so an owner reading GET /grants sees the whole tree (parent links, caps, delegation depth, lifetime released) as flat nodes.

Delegation is the “Delegate” showroom page and POST /grant/delegate (delegator-signed, the parent grantee). It is flag-gated off by default.

Org accounts (M-of-N)

An org is an account controlled by a set of principals with a threshold M. When an org owns a vault, publishing or revoking a grant (or a high-value release) takes M distinct member signatures instead of one key. Each member signs the same canonical body with their own wallet, and the request submits once M signatures are gathered.

  • The approver set and threshold are owner-signed and anchored on Hedera as org:<slug>, so they are on-chain and tamper-proof, never read live from an outside service.
  • M-of-N only raises the bar to authorize spend, never lowers it.
  • A membership change is a re-registration signed by the current set. Reading an org’s own data is 1-of-N.
  • Pending co-sign challenges collect in an approval inbox.

Exposed as the “Org” showroom page and POST /org/register (self-authorizing: signed by M of its own members). Flag-gated off by default.

Human approval over a threshold

A grant can carry an approval threshold (approvalThresholdUsdCents): a release at or below it auto-releases, while anything above it requires a fresh owner co-signature bound to that exact call (single-use, within a short window). When an over-threshold release arrives without an approval, the broker refuses it and records the challenge in the owner’s approval inbox.

The owner satisfies it either way: the agent presents a fresh co-signature inline on its retry, or the owner pre-approves from the inbox (GET /approvals, POST /approvals/:id/sign) and the agent’s next retry is satisfied by the stored approval. For an org-owned vault the same path collects M co-signatures.

What H-Grant does not hold

H-Grant is a security control, not a wallet. It never custodies your money. It holds the credential and enforces the owner-signed policy around it; the bank, Stripe, or the chain still holds and moves the funds. There is no balance to withdraw from H-Grant. The only money that reaches H-Grant is the $0.05 per-release x402 fee. If an agent spends from its own funded wallet, moving that balance out is an H-Agent operation (POST /agents/:address/withdraw), not H-Grant.

Gating a release on more than budget

A grant can require conditions beyond spend caps, all owner-signed and enforced fail-closed:

  • Agent attestation. Require the grantee to present a valid, unexpired JWT credential (an AP2 mandate or a Know-Your-Agent credential) from a trusted issuer before any release. A hijacked or unvouched agent is refused even inside its caps.
  • Behavior posture (H-Scope). Floor a release on the on-chain behavior posture of the call’s counterparty or of the grantee itself, read from H-Scope, so an agent will not act against a low-standing wallet. Fail-open by owner choice (onUnknown).
  • Behavior standing (H-Cert). Require a minimum agent behavioral standing tier via an hcert: requirement; see Grant policies with ABS and wallet behaviors.

Where to go next

Frequently asked

Can the operator read my credential? +

No. The production custody mode (`nitro`) is LIVE (cutover 2026-07-26): the credential is decrypted and used only inside an AWS Nitro enclave whose attestation matches a pinned measurement, and AWS KMS releases the data key only to that measured enclave, so even someone with full access to the host cannot read it. That is operator-zero-access, live in production. The software `kms` mode is retained on the standby as an instant rollback.

What does the agent actually receive? +

Never the raw credential. The agent presents a grant and calls the gated action; H-Grant brokers the credential inside the boundary and returns only the result. The secret never reaches the agent or the app process.

What happens when I revoke a grant? +

Revocation is owner-signed and takes effect immediately: the grant can no longer authorize a release. Revoke is free. Existing receipts and the on-chain history are preserved for audit.

How is spend capped? +

The grant carries the owner-signed limits (amount, count, rate). They are enforced server-side inside a single locked transaction, so an agent cannot exceed the cap even under concurrent calls.

What can go wrong if H-Grant is unreachable? +

Nothing widens. A release requires a valid grant, a passing policy check, and a verified payment; if any is missing the action is refused. Optional posture and approval gates fail safe.

What does a credential release cost, and how do I pay? +

$0.05 per release, settled inline via x402 before the release runs. It is accepted in USDC on Hedera, Base, Solana, and Stellar, and RLUSD on XRPL (stablecoin rails only). Publishing and revoking a grant are free; you pay only per gated call. A refused or capped call does not anchor an upstream charge.

Can I withdraw funds from H-Grant? +

There is nothing to withdraw. H-Grant is not a wallet and never custodies your money. It holds the credential (an API key, a signing key) and enforces the owner-signed policy around it; the bank, Stripe, or the chain still holds and moves the actual funds. The $0.05 per-call fee is the only money that moves to H-Grant. If you run an agent that spends from its own wallet, withdrawing that balance is an H-Agent operation (POST /agents/:address/withdraw), not H-Grant.

Are delegation and org (M-of-N) accounts available? +

Both are built and shipped but flag-gated off by default, so POST /grant/delegate and POST /org/register return 503 until an operator enables them. Single-owner grants, sub-grant cap enforcement on any existing sub-grant, and the approval inbox all work regardless.

How do I know the audit trail was not edited? +

Every release and refusal is anchored on the Hedera Consensus Service grant and audit topics (grant topic 0.0.10502566, audit topic 0.0.10502565). The audit topic is publicly writable and immutable, so authenticity does not rest on write access: each event is operator-signed and the signature is re-verified on replay. You can reconstruct the whole trail from the public mirror node and verify any anchor on HashScan.