{"_id":"@agentadmit/react","_rev":"34-5456068531448d04c25f8382b8edf426","name":"@agentadmit/react","dist-tags":{"latest":"2.1.0"},"versions":{"2.0.0":{"name":"@agentadmit/react","version":"2.0.0","keywords":["agentadmit","react","ai-agent","authorization","components"],"author":{"name":"Christopher Emerson"},"license":"SEE LICENSE IN LICENSE","_id":"@agentadmit/react@2.0.0","maintainers":[{"name":"agentadmit","email":"christopher@agentadmit.com"}],"homepage":"https://github.com/PhoenixCo-Founder/agentadmit-react#readme","bugs":{"url":"https://github.com/PhoenixCo-Founder/agentadmit-react/issues"},"dist":{"shasum":"ac6c2dd8452c27e4f230c128e24e9035e83f27cc","tarball":"https://registry.npmjs.org/@agentadmit/react/-/react-2.0.0.tgz","fileCount":10,"integrity":"sha512-ZHi/2p1nxl0g9/itrws6LUUsE1hpis5UPYGjqg3EpWygvIvQR0v0XQ5MqLxjp3180EJFTbgDdNVnRfH8TJ+35Q==","signatures":[{"sig":"MEYCIQC7GZTkSW9JK+7L6M/3dSZo4xbI7zJqMn6RoyW04uh3bgIhAIhg5JPlJdXEJNprazCTwZ9ppODn3dOwGnQ3I2s1uhxO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIEuUyAuSykEV4w2ioJndl9rNQCtdnTcHz1AqSBFUoQi9AiEAzAHOWXkXYplUrdck+i/kQsxMmasOvGNmIGhRa7wOgGY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentadmit%2freact@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":507190},"main":"dist/index.js","style":"dist/styles/agent-admit-panel.css","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"./styles":"./dist/styles/agent-admit-panel.css","./styles.css":"./dist/styles/agent-admit-panel.css","./dist/styles/agent-admit-panel.css":"./dist/styles/agent-admit-panel.css"},"gitHead":"d6031d4a2984837eb42003351455a333528d95a3","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts --clean && mkdir -p dist/styles && cp src/styles/agent-admit-panel.css dist/styles/agent-admit-panel.css"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6ba68fa6-b0eb-4b3a-877e-6f5302835e83"}},"overrides":{"nanoid":"3.3.18","esbuild":"0.28.1"},"repository":{"url":"git+https://github.com/PhoenixCo-Founder/agentadmit-react.git","type":"git"},"_npmVersion":"12.0.2","description":"AgentAdmit React companion components: Connect button for the hosted consent page, connections list, consent settings, relationship consent, alerts, and admin panel. The consent step runs on the AgentAdmit hosted consent page.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@simplewebauthn/browser":"13.3.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.5.1","jsdom":"29.1.1","react":"18.3.1","vitest":"4.1.10","react-dom":"18.3.1","typescript":"5.9.3","@types/react":"18.3.28","@testing-library/dom":"10.4.1","@testing-library/react":"16.3.2"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react_2.0.0_1788890989981_0.3146526015925162","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"_id":"@agentadmit/react@2.1.0","bugs":{"url":"https://github.com/PhoenixCo-Founder/agentadmit-react/issues"},"dist":{"shasum":"c5f9200d5520260d434a847ea1c0bf372886044d","tarball":"https://registry.npmjs.org/@agentadmit/react/-/react-2.1.0.tgz","fileCount":10,"integrity":"sha512-SAZ1IEZHkOdn+mQIiyUOutDIfygxMY6MvqdjjRUr3qp3jNr+/yEi8vv6YYJCgYWij+aRrj/OZrnPatDZijpEkg==","signatures":[{"sig":"MEYCIQCAMI2k7W5ppL/wJEKAbJDepXoxsbVDkYn3sTjOxK8SEwIhAOvh7uTVHzSkr+ohAjrkuCdEssbxTI+Whxzkl+UP9MFT","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCG0poyeazKiGjsitPDaQCFduuAxMYjn6MIAk0zhMpzCgIgc19diNDEV8lFbwpB0/H4+6136WcVmhqjmTv6HGL4Onc="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentadmit%2freact@2.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":511571},"main":"dist/index.js","name":"@agentadmit/react","style":"dist/styles/agent-admit-panel.css","types":"dist/index.d.ts","author":{"name":"Christopher Emerson"},"module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"./styles":"./dist/styles/agent-admit-panel.css","./styles.css":"./dist/styles/agent-admit-panel.css","./dist/styles/agent-admit-panel.css":"./dist/styles/agent-admit-panel.css"},"gitHead":"754df2c79fd0b202e86ec5b93425071e119d4bd0","license":"SEE LICENSE IN LICENSE","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","build":"tsup src/index.ts --format cjs,esm --dts --clean && mkdir -p dist/styles && cp src/styles/agent-admit-panel.css dist/styles/agent-admit-panel.css"},"version":"2.1.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6ba68fa6-b0eb-4b3a-877e-6f5302835e83"}},"homepage":"https://github.com/PhoenixCo-Founder/agentadmit-react#readme","keywords":["agentadmit","react","ai-agent","authorization","components"],"overrides":{"nanoid":"3.3.18","esbuild":"0.28.1"},"repository":{"url":"git+https://github.com/PhoenixCo-Founder/agentadmit-react.git","type":"git"},"_npmVersion":"12.0.2","description":"AgentAdmit React companion components: Connect button for the hosted consent page, connections list, consent settings, relationship consent, alerts, and admin panel. The consent step runs on the AgentAdmit hosted consent page.","directories":{},"maintainers":[{"name":"agentadmit","email":"christopher@agentadmit.com"}],"_nodeVersion":"22.23.2","dependencies":{"@simplewebauthn/browser":"13.3.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.5.1","jsdom":"29.1.1","react":"18.3.1","vitest":"4.1.10","react-dom":"18.3.1","typescript":"5.9.3","@types/react":"18.3.28","@testing-library/dom":"10.4.1","@testing-library/react":"16.3.2"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react_2.1.0_1788906591162_0.7361094173806695"}}},"time":{"created":"2026-06-03T18:48:38.947Z","modified":"2026-09-08T22:29:51.704Z","1.0.0":"2026-06-03T18:48:39.204Z","1.0.1":"2026-06-09T16:46:56.038Z","1.1.0":"2026-06-10T23:40:11.703Z","1.1.1":"2026-06-12T21:08:45.979Z","1.2.0":"2026-07-03T22:31:48.861Z","1.3.0":"2026-07-04T20:14:18.664Z","1.4.0":"2026-07-05T00:44:20.065Z","1.5.0":"2026-07-20T03:00:47.906Z","1.5.1":"2026-07-28T21:05:25.718Z","1.6.0":"2026-08-05T20:29:42.713Z","1.7.0":"2026-08-05T20:38:57.318Z","1.8.0":"2026-08-05T20:49:07.573Z","1.9.0":"2026-08-11T19:34:48.966Z","1.10.0":"2026-08-13T17:54:55.645Z","1.11.0":"2026-08-19T02:34:35.511Z","1.11.1":"2026-08-20T00:57:19.260Z","2.0.0":"2026-09-08T18:09:50.275Z","2.1.0":"2026-09-08T22:29:51.352Z"},"bugs":{"url":"https://github.com/PhoenixCo-Founder/agentadmit-react/issues"},"author":{"name":"Christopher Emerson"},"license":"SEE LICENSE IN LICENSE","homepage":"https://github.com/PhoenixCo-Founder/agentadmit-react#readme","keywords":["agentadmit","react","ai-agent","authorization","components"],"repository":{"url":"git+https://github.com/PhoenixCo-Founder/agentadmit-react.git","type":"git"},"description":"AgentAdmit React companion components: Connect button for the hosted consent page, connections list, consent settings, relationship consent, alerts, and admin panel. The consent step runs on the AgentAdmit hosted consent page.","maintainers":[{"name":"agentadmit","email":"christopher@agentadmit.com"}],"readme":"# AgentAdmit React SDK\n\nCompanion React components for apps that integrate AgentAdmit: the pages **around** the consent step, on your own site.\n\n**Where the consent step runs:** on the AgentAdmit hosted consent page, opened on your app's behalf. Your backend creates a consent session (`POST /api/v1/apps/{app_id}/consent-sessions`), your frontend sends the signed-in user to the returned `session_url`, and the hosted page owns scope selection, duration, intent in the user's own words, existing-grant review, the presence ceremony, and the one-time token. Your app never sees the token and ships no consent UI or WebAuthn code. Full walkthrough: [App Owner Guide, Step 4](https://agentadmit.com/docs/app-owner-guide).\n\n> **Get started:** Sign up at [agentadmit.com](https://agentadmit.com) → Get your test keys → Install the backend SDK → Add the consent-session call and a Connect button → Optionally add these components.\n> Test keys are available immediately after signup. Live keys become available when you subscribe an app.\n\n## What this package is for\n\n| Component | Use it for |\n|-----------|-----------|\n| `ConnectionsList` | The user's active and pending agent connections, with purpose, intent, and revoke |\n| `ConsentSettingsPanel` | The user's caller-identity consent switches (people, in-app AI, external agents) |\n| `RelationshipConsentPanel` | Per-relationship consent switches for multi-party data (subject ↔ grantee) |\n| `PromptTemplates` | After the user returns from the hosted page: templates that fit the granted scopes, with a token placeholder the user fills in |\n| `AlertsPanel`, `AgentAdmitAdminPanel` | Admin surfaces: alerts and thresholds, connections, usage, activity |\n| `ConnectAgentButton` / `useConsentSession` | Start the hosted consent page from your Agent Access page (your backend creates the session) |\n\n\n## Quick Start\n\n```bash\nnpm install @agentadmit/react\n```\n\n```jsx\nimport { ConnectAgentButton, ConnectionsList, useAgentAdmit } from '@agentadmit/react';\n// Import the default stylesheet (recommended)\nimport '@agentadmit/react/styles';\n\nfunction AgentAccessPage() {\n  const { connections, loading, revokeConnection } = useAgentAdmit({\n    apiBase: '/agentadmit',        // your backend proxy (see Backend Proxy Contract)\n    authToken: userSessionToken,   // your app's user session token\n  });\n\n  return (\n    <div className=\"agent-admit-panel\">\n      {/* Your backend creates the consent session with your aa_ API key and returns { session_url }.\n          The button sends the signed-in user to the hosted consent page; the return_url brings them back. */}\n      <ConnectAgentButton\n        createUrl=\"/api/agentadmit/consent-session\"\n        requestHeaders={{ Authorization: `Bearer ${userSessionToken}` }}\n      />\n      <ConnectionsList connections={connections} loading={loading} onRevoke={revokeConnection} />\n    </div>\n  );\n}\n```\n\n`ConnectAgentButton` (or the `useConsentSession` hook behind it) POSTs to your endpoint, expects `{ session_url }` in the response, refuses anything that is not `https:`, and navigates there. Pass `body` for fields your backend forwards (a template id, a declared `purpose`), `onSessionUrl` to open the page your own way, and `label` / `startingLabel` / `className` for copy and styling.\n\n`useAgentAdmit` lists and revokes the signed-in user's connections through your backend proxy (`GET {apiBase}/connections`, `DELETE {apiBase}/connections/{id}`). The proxy injects the user's `app_user_id` and calls AgentAdmit with your `aa_` API key.\n\n## Where to Put It\n\nAdd an \"Agent Access\" page or tab in your app with the Connect button and the connections list. Common placements:\n- Sidebar navigation item (recommended)\n- Tab within Settings or Account page\n- Dedicated route like `/settings/agent-access`, which is also a good `return_url` for the consent session\n\n## Styling & Customization\n\n### Default Styles (Recommended)\n\nThe SDK ships a production-ready default stylesheet. Import it once at your app's entry point:\n\n```js\n// In your main entry file (e.g. main.tsx, _app.tsx, layout.tsx)\nimport '@agentadmit/react/styles';\n// or equivalently:\nimport '@agentadmit/react/dist/styles/agent-admit-panel.css';\n```\n\nThe stylesheet is fully scoped to `.agent-admit-panel` - it won't affect any other part of your app.\n\n### CSS Custom Properties (Tokens)\n\nCustomize the look by overriding `--aap-*` tokens. Put this anywhere after the import:\n\n```css\n/* globals.css or a <style> tag */\n.agent-admit-panel {\n  /* Brand color */\n  --aap-color-primary: #7c3aed;\n  --aap-color-primary-hover: #6d28d9;\n  --aap-color-primary-text: #ffffff;\n\n  /* Shape */\n  --aap-radius: 10px;\n  --aap-radius-md: 14px;\n  --aap-radius-lg: 18px;\n\n  /* Typography */\n  --aap-font-family: 'Inter', sans-serif;\n}\n```\n\n**Available tokens:**\n\n| Token | Default | Description |\n|-------|---------|-------------|\n| `--aap-color-primary` | `#2563eb` | Primary action color |\n| `--aap-color-danger` | `#dc2626` | Destructive actions, errors |\n| `--aap-color-bg` | `#ffffff` | Panel background |\n| `--aap-color-surface` | `#f9fafb` | Card / section surfaces |\n| `--aap-color-text` | `#111827` | Primary text |\n| `--aap-color-text-secondary` | `#4b5563` | Secondary/description text |\n| `--aap-color-border` | `#e5e7eb` | Dividers and card borders |\n| `--aap-color-focus` | `#2563eb` | Focus ring color |\n| `--aap-font-family` | `inherit` | Inherits from host app by default |\n| `--aap-font-size-base` | `16px` | Input font size (min 16px - iOS zoom prevention) |\n| `--aap-radius` | `6px` | Default border radius |\n| `--aap-touch-target-min` | `44px` | Minimum touch target (Apple HIG, WCAG AAA) |\n\n### Dark Mode\n\nDark mode is automatic via `prefers-color-scheme: dark`. To force it:\n\n```tsx\n<ConnectionsList theme=\"dark\" />    // Forces dark (adds .aa-dark)\n<ConnectionsList theme=\"light\" />   // Forces light (adds .aa-light)\n<ConnectionsList theme=\"system\" />  // Follows OS preference (default)\n```\n\n### Custom CSS Classes\n\nEvery component accepts `className`. All internal elements use `aa-*` classes you can override:\n\n| Class | Element |\n|-------|--------|\n| `agent-admit-panel` | Root container (token scope + CSS reset) |\n| `aa-panel` | Panel layout (padding, border, shadow) |\n| `aa-btn-primary` | Primary action buttons |\n| `aa-btn-secondary` | Secondary / cancel buttons |\n| `aa-pill` | Scope permission pills |\n| `aa-duration-option` | Duration picker buttons |\n| `aa-token-display` | Token display area |\n| `aa-template-card` | Prompt template cards |\n| `aa-connection-card` | Connection list items |\n| `aa-input`, `aa-field-input` | Text inputs |\n| `aa-select` | Select dropdowns |\n| `aa-tab` | Tab bar buttons |\n\n### Responsive Behavior\n\nThe panel uses **CSS container queries** (`@container`), not viewport media queries. This means it responds to its own rendered width - not the browser window. The layout adapts correctly whether the panel is in a:\n- Full-page route\n- Modal dialog\n- Sidebar or drawer\n- Native mobile WebView\n\nNo configuration needed - just drop it in and it works at any width.\n\n### Accessibility\n\nThe default stylesheet is built to meet WCAG 2.2 AA and Apple HIG standards out of the box:\n\n- All interactive elements: `min-height: 44px` touch targets (Apple HIG, WCAG AAA)\n- All inputs: `font-size: 16px` minimum - prevents iOS Safari auto-zoom in WebViews\n- `:focus-visible` rings on all interactive elements - keyboard navigation\n- `prefers-reduced-motion` respected - all transitions disabled for motion-sensitive users\n- `forced-colors` media query - Windows High Contrast Mode supported\n- Color contrast: ≥4.5:1 for body text, ≥3:1 for UI components (WCAG AA)\n- ARIA attributes: `role`, `aria-expanded`, `aria-controls`, `aria-pressed`, `aria-checked`, `aria-live` on all components\n\nFull compliance guide: [agentadmit.com/docs/compliance](https://agentadmit.com/docs/compliance)\n\n## PromptTemplates (post-consent)\n\nAfter the user returns from the hosted consent page, show the templates that fit the scopes they granted. The user pastes the token into the template themselves; your app never sees it.\n\n```jsx\nimport { PromptTemplates } from '@agentadmit/react';\n\n<PromptTemplates\n  templates={yourTemplates}            // { id, title, requiredScopes, template, editableFields?, role?, isHero? }\n  editableFields={yourFields}          // { fieldKey: { label, placeholder, default } }\n  exampleCategories={yourExamples}     // quick one-line prompts, filtered by scope\n  selectedScopes={connection.scopes}   // the granted scopes, from the session outcome or ConnectionsList\n  userRole={user.role}\n/>\n```\n\nOmit `token`: the component then copies the template alone and the user adds the token themselves (the hosted page shows it to them once). Template and field shapes are documented in the App Owner Guide (Step 4, \"Template data structures\").\n\n## Declared purpose, user intent, and existing-grant review\n\nThese live on the hosted consent page. Your consent session's `purpose` (the app's reason, up to 300 chars) is shown before approval and recorded on the grant; the page offers the user an optional \"Your Intent\" field (their own words, recorded alongside it); and when the user already holds active grants for your app, the page blocks with a review step (revoke or knowingly keep each one) before a new grant can be created, enforced server-side. `<ConnectionsList>` shows `purpose` under the agent label and `user_intent` beside it, labeled \"Your intent\".\n\n## ConsentSettingsPanel (Caller-Identity Consent)\n\nIndependent per-user consent toggles for the three caller classes: people the user shares with, your in-app AI, and external AI agents. No toggle implies another; any combination is allowed. State lives in AgentAdmit's hosted Consent Ledger.\n\n```tsx\nimport { ConsentSettingsPanel } from '@agentadmit/react';\n\n<ConsentSettingsPanel\n  apiBase=\"/agentadmit\"\n  authToken={userSessionToken}\n  onConsentChange={(cls, granted) => console.log(cls, granted)}\n/>\n```\n\nBackend proxy contract (your server injects the user's `app_user_id` and calls AgentAdmit with your `aa_` API key, which never ships to the browser):\n\n- `GET {apiBase}/consent/settings` proxies AgentAdmit `GET /api/v1/consent/settings?app_user_id=<user>` and returns its JSON (`settings`, `effective`, `app_defaults`).\n- `PUT {apiBase}/consent/settings` with `{ caller_class, granted }` proxies AgentAdmit `PUT /api/v1/consent/settings` with `app_user_id` injected and `updated_via: \"user_page\"`.\n\nProps: `showHumanSession` (default false; most apps govern human sharing in their own UI), `heading` / `description` (override the panel copy — say WHOSE data these switches govern; this panel controls the signed-in user's OWN data and agents), `copy` (override label/description per class), `presence` (see below), `theme`, `className`, `onConsentChange`. The `useConsentSettings` hook is exported for custom layouts.\n\n### Hosted ceremony for consent changes (recommended)\n\nThe documented path for changing a user's own switches is AgentAdmit's hosted consent-change page. Your backend mints a session with `POST /api/v1/consent/sessions` (`{ app_user_id, changes: [{ caller_class, granted }], return_url }`) and your `PUT {apiBase}/consent/settings` proxy answers `200 { \"ceremony_required\": true, \"ceremony_url\": \"https://agentadmit.com/consent-change/scsess_…\" }`. The panel then sends the user there; they confirm the exact change with Face ID, Touch ID, or a security key; AgentAdmit applies the switch itself with independently verifiable evidence and returns the user to `return_url`, where the panel refetches true state. Your app ships no WebAuthn code and never writes the switch.\n\n```tsx\n<ConsentSettingsPanel\n  apiBase=\"/agentadmit\"\n  authToken={userSessionToken}\n  // optional: open the hosted page your own way (default: full-page navigation)\n  onHostedCeremony={(url) => openSheet(url)}\n/>\n```\n\n### Presence step-up on consent changes\n\nA computer-use agent operating the user's logged-in session could otherwise flip these switches. Pass `presence` to require a WebAuthn ceremony (Touch ID, Windows Hello, passkey) before a change is accepted: when your proxy answers a consent `PUT` with `403 { \"error\": \"presence_attestation_required\" }`, the panel runs the ceremony against your endpoints and retries the `PUT` once with the resulting single-use handle attached.\n\n```tsx\n<ConsentSettingsPanel\n  apiBase=\"/agentadmit\"\n  authToken={userSessionToken}\n  heading=\"Your AI & Agents\"\n  description=\"Choose what your own AI agents and this app's AI may do with your data.\"\n  presence={{\n    optionsUrl: '/agentadmit/presence/options',\n    verifyUrl: '/agentadmit/presence/verify',\n    requestHeaders: { Authorization: `Bearer ${userSessionToken}` },\n    // attestationField: 'presence_attestation_id' (default) — use\n    // 'presence_session_id' for the AgentAdmit hosted-session contract.\n  }}\n/>\n```\n\nYour proxy must return the ceremony handle from the verify endpoint (`presence_attestation_id` for an app-native WebAuthn backend, or `presence_session_id` for the hosted contract) and consume it, single-use, before applying the change — the server side is the security boundary, not the browser. For full control, pass `resolvePresence(ctx)` to `useConsentSettings` instead of `presence` and return the exact body fields to merge into the retried `PUT`. For consent changes that need independently verifiable evidence, use the hosted ceremony sessions described under \"Ceremony-confirmed changes\" instead of an app-run ceremony.\n\n## RelationshipConsentPanel (Multi-Party Caller-Identity Consent)\n\nUse `RelationshipConsentPanel` when the signed-in data owner controls how a specific third party may reach their data. A client can independently allow their trainer to view the client's data directly, use the app's in-app AI to review it, or use the trainer's external AI agents to review it. The same component fits doctor/patient, accountant/client, tutor/student, and other app-defined relationships.\n\n```tsx\nimport { RelationshipConsentPanel } from '@agentadmit/react';\n\n<RelationshipConsentPanel\n  apiBase=\"/agentadmit\"\n  authToken={clientSessionToken}\n  granteeUserId={trainer.id}\n  relationshipType=\"trainer\"\n  granteeLabel=\"your trainer\"\n  presence={{\n    optionsUrl: '/agentadmit/presence/options',\n    verifyUrl: '/agentadmit/presence/verify',\n    requestHeaders: { Authorization: `Bearer ${clientSessionToken}` },\n  }}\n  onConsentChange={(callerClass, granted) => {\n    console.log(callerClass, granted);\n  }}\n/>\n```\n\nAll three rows are always independent. The defaults deny every relationship class until the data owner grants that specific subject/grantee pair.\n\n### Relationship proxy contract\n\nYour backend is the security boundary. It MUST authenticate the signed-in data owner, derive `subject_user_id` from that session, verify that the requested grantee relationship exists in your product, and only then call AgentAdmit with your server-side `aa_` API key. Never accept `subject_user_id` from the browser.\n\n- `GET {apiBase}/consent/relationship/settings?grantee_user_id=<grantee>&relationship_type=<type>` proxies AgentAdmit `GET /api/v1/consent/relationship/settings`, adding the session-derived `subject_user_id`.\n- `PUT {apiBase}/consent/relationship/settings` receives `{ grantee_user_id, relationship_type, caller_class, granted, scope_group? }`, adds the session-derived `subject_user_id` and `updated_via: \"user_page\"`, then proxies AgentAdmit `PUT /api/v1/consent/relationship/settings`.\n\n### Ceremony-confirmed changes (strongest evidence)\n\n`RelationshipConsentPanel` writes switches through your backend proxy, which carries the **app-record** evidence tier. For decisions that need independently verifiable proof, use a **hosted ceremony session** instead: your backend calls `POST /api/v1/consent/relationship/sessions` and opens the returned `session_url` for the data owner, who confirms the exact change with a passkey on AgentAdmit's hosted page. AgentAdmit witnesses the ceremony and records verifiable consent evidence — the owner's passkey signs a cryptographic commitment to the change set and labels shown. See the App Owner Guide's \"Ceremony-confirmed relationship changes\" section. The panel and ceremony sessions compose: render current state with the panel, route the consequential changes through a ceremony.\n\nProps: `granteeUserId`, `relationshipType`, and `granteeLabel` are required. `granteeLabel` is user-facing copy (for example, `\"your trainer\"` or `\"Dr. Rivera\"`); it is never used as an authorization identifier. `scopeGroup`, `heading`, `description`, `copy`, `presence`, `theme`, `className`, and `onConsentChange` are optional. The `useRelationshipConsentSettings` hook is exported for custom layouts and supports a custom `resolvePresence` callback.\n\n## Admin Panel Component\n\nThe React SDK includes `<AgentAdmitAdminPanel>` for app owners and MCP server operators to embed in their admin dashboard:\n\n```jsx\nimport { AgentAdmitAdminPanel } from '@agentadmit/react';\n\n<AgentAdmitAdminPanel\n  apiBase=\"/agentadmit\"\n  authToken={adminJwt}\n  appId=\"app_yourappid\"\n/>\n```\n\nFour tabs: **Connections** (all users, search/filter, revoke), **Usage** (calls vs tier, overage tracking), **Alerts** (embedded AlertsPanel with thresholds + kill switch), **Activity** (full audit trail with expandable details).\n\n**Declared purpose:** when a connection carries a `purpose` field, the Connections tab shows it under the agent label, and the search box matches purpose text. Declared purpose: the user-facing reason recorded on the grant at the consent moment. Review-time record only, never an enforcement input.\n\n**User-declared intent:** when a connection carries a `user_intent` field (the user's own words, distinct from the app's declared purpose), the Connections tab shows it in the expanded card as \"User intent\", Activity rows show it beside the purpose, and both search boxes match intent text. Like the purpose, it is a review-time record, never an enforcement input.\n\n**Consent evidence (opt-in, v1.10.0):** pass `evidence` to add a \"Consent evidence\" expander to each connection card - the admin/audit view of the verifiable-consent-evidence surface (dispute resolution, \"was this connection really authorized by a ceremony?\"). Lazily fetched per card from `GET {apiBase}/admin/connections/{connection_id}/evidence` (contract below); nothing is requested until an admin asks. Off by default, so existing backends without the endpoint see no change. Tier labels keep the claim ceilings: a hosted-witnessed VCE record renders as independently verifiable; the app's own ceremony record and app-attested facts render as the app's attestation, never as independently verifiable; connections without evidence say so honestly, and fetch failures render an honest unavailable state rather than a fabricated tier. Evidence is a review-time record, never an enforcement input.\n\nApp owners see everything and can respond to abuse without leaving their app. Auto-refreshes every 30 seconds by default.\n\nAdd `theme=\"light\"` (or `\"system\"`) if your admin dashboard is not dark - the default is `\"dark\"`.\n\n## Backend Proxy Contract (Admin Panel & Alerts)\n\n`<AgentAdmitAdminPanel>` (via `useAdminData`) and `<AlertsPanel>` (via `useAlerts`) call **your backend** at `apiBase`, which proxies to AgentAdmit using your API key. Your backend must expose the endpoints below and return these exact JSON shapes - the hooks read these field names literally (e.g. `usage`, `events`, `occurred_at`). All requests carry `Authorization: Bearer <authToken>`; your backend must restrict every one of these endpoints to admin users.\n\n**Error convention (all endpoints):** any non-2xx response with a JSON body containing `error_description` shows that message in the panel's error banner.\n\n### GET `{apiBase}/admin/connections?app_id=...`\n\n```jsonc\n{\n  \"connections\": [\n    {\n      \"connection_id\": \"conn_abc123\",        // required\n      \"status\": \"active\",                    // \"active\" | \"revoked\" | \"expired\"\n      \"scopes\": [\"read:orders\"],             // string[]\n      \"user_id\": \"u_123\",\n      \"user_label\": \"jane@example.com\",      // display name; falls back to user_id\n      \"agent_id\": \"agent_9\",                 // optional\n      \"agent_label\": \"Claude\",               // display name; falls back to agent_id\n      \"purpose\": \"Reconcile June invoices\",  // optional — declared purpose, shown under the agent label\n      \"user_intent\": \"Make sure nothing is overdue\", // optional — user-declared intent, shown in the expanded card\n      \"role\": \"user\",                        // optional\n      \"created_at\": \"2026-06-12T19:00:00Z\",  // ISO 8601\n      \"last_used\": \"2026-06-12T19:26:00Z\",   // optional\n      \"expires_at\": \"2026-06-13T19:00:00Z\"   // optional\n    }\n  ],\n  \"total\": 14\n}\n```\n\n### GET `{apiBase}/admin/usage?app_id=...`\n\nThe hook reads `response.usage` - if that key is missing the Usage tab shows \"No usage data available.\"\n\n```jsonc\n{\n  \"usage\": {\n    \"app_id\": \"app_yourappid\",\n    \"tier\": {\n      \"name\": \"standard\",\n      \"call_limit\": 10000,          // number | null (null renders as unlimited / ∞)\n      \"calls_used\": 1234,\n      \"calls_remaining\": 8766,      // number | null\n      \"period_start\": \"2026-06-01T00:00:00Z\",  // optional\n      \"period_end\": \"2026-07-01T00:00:00Z\",    // optional\n      \"overage_calls\": 0,           // optional\n      \"overage_enabled\": false      // optional\n    },\n    \"active_connections\": 2,\n    \"total_connections\": 14,\n    \"breakdown\": [                  // optional - per-agent/scope/endpoint bars\n      { \"label\": \"Claude\", \"calls\": 900 }\n    ]\n  }\n}\n```\n\n### GET `{apiBase}/admin/activity?app_id=...&limit=50&offset=0`\n\nThe hook reads `response.events` and `response.total`. Omit optional fields rather than sending `null`/empty strings.\n\n```jsonc\n{\n  \"events\": [\n    {\n      \"occurred_at\": \"2026-06-12T19:26:17Z\", // required, ISO 8601\n      \"event_id\": \"evt_1\",                   // optional (falls back to list index)\n      \"connection_id\": \"conn_abc123\",\n      \"user_id\": \"u_123\",\n      \"user_label\": \"jane@example.com\",\n      \"purpose\": \"Weekly workout summaries for my coach\", // optional: declared purpose on the grant\n      \"user_intent\": \"Keep my coach in the loop\", // optional: user-declared intent on the grant\n      \"agent_id\": \"agent_9\",\n      \"agent_label\": \"Claude\",\n      \"scope\": \"read:orders\",                // scope that was used\n      \"action\": \"GET\",                       // HTTP method or action name\n      \"endpoint\": \"/api/orders\",             // resource path accessed\n      \"status_code\": 200,\n      \"details\": { \"note\": \"...\" }           // optional, shown as expandable JSON\n    }\n  ],\n  \"total\": 10\n}\n```\n\n### DELETE `{apiBase}/admin/connections/{connection_id}`\n\nRevokes any user's connection (proxy to the hosted `/api/v1/revoke` - that call is what actually kills the agent's tokens). Return any 2xx on success; the panel optimistically removes the row and then re-fetches the list.\n\n### GET `{apiBase}/admin/connections/{connection_id}/evidence` (only when `evidence` is enabled)\n\nAdmin variant of the user-facing consent-evidence route: your backend looks the connection up WITHOUT a user-ownership filter (admin guard instead), proxies the hosted `GET /api/v1/connections/{connection_id}/evidence`, and merges in your app's own ceremony record when you keep one:\n\n```jsonc\n{\n  \"connection_id\": \"conn_abc123\",\n  \"display_tier\": \"app_record\",       // \"hosted_vce\" | \"app_record\" | \"presence_fact\" | \"none\"\n  \"hosted\": {                          // the hosted evidence endpoint's answer (or your degraded stub)\n    \"evidence_available\": true,\n    \"tier\": \"presence_fact\",\n    \"reason\": \"app_attested_ceremony\",\n    \"claim\": \"…\",\n    \"ceremony\": { \"verified_at\": \"…\", \"uv\": true, \"method\": \"app:my_webauthn\", \"provenance\": \"app_attested\" },\n    \"commitment\": { \"hash\": \"…\", \"preimage_version\": 1 },   // hosted_vce only\n    \"ledger\": { \"tamper_evident\": true, \"granted_event_present\": true }\n  },\n  \"app_record\": {                      // your own ceremony record, when you keep one\n    \"present\": true, \"uv\": true, \"verified_at\": \"…\",\n    \"claim\": \"Verified by <your app>'s passkey ceremony at grant time (…; not independently verifiable).\"\n  }\n}\n```\n\nADMIN-ONLY, like the rest of these endpoints. Keep your `claim` strings inside the ceilings: never present an app record or app-attested fact as independently verifiable.\n\n### Alerts endpoints (`useAlerts` / the Alerts tab)\n\n> **ADMIN-ONLY. All three alerts endpoints (both GETs and the POST) must be restricted to admin users by your backend proxy.** The POST endpoint accepts `AlertConfig` payloads that include `kill_switch_enabled`, which controls the app-wide kill switch for all agent connections. Do not route end-user tokens to these endpoints.\n>\n> **Platform-enforced since Aug 2026:** AgentAdmit itself now rejects any *weakening* change made with API-key credentials (`403 weakening_requires_human`) — disabling an alert, raising thresholds, or disabling the kill switch requires a human in the AgentAdmit dashboard. Because your backend proxy authenticates to AgentAdmit with your API key, callers reaching these endpoints through your proxy can only **tighten** protections; an agent (or a compromised caller) cannot defang the kill switch even if your proxy's admin gating fails. Keep the admin restriction anyway — defense in depth, and alert *history* is still sensitive.\n\n| Method | Path | Returns |\n|---|---|---|\n| GET | `{apiBase}/alerts/config?app_id=...[&connection_id=...]` | `{ \"app_id\", \"app_level\": { \"<alert_type>\": AlertConfig }, \"connection_overrides\": {}, \"alert_types\": string[] }` |\n| GET | `{apiBase}/alerts?app_id=...&limit=50&offset=0[&alert_type=...]` | `{ \"events\": AlertEvent[], \"total\", \"limit\", \"offset\" }` |\n| POST | `{apiBase}/alerts` | body `{ \"app_id\", \"alert_type\", ...AlertConfig }` → 2xx on success |\n\n```jsonc\n// AlertConfig (all fields optional)\n{ \"enabled\": true, \"threshold_value\": 100, \"threshold_window_minutes\": 5,\n  \"threshold_rate_per_minute\": 20, \"stale_days\": 30,\n  \"kill_switch_enabled\": false, \"kill_switch_threshold_value\": 500,\n  \"kill_switch_threshold_window_minutes\": 5 }\n\n// AlertEvent\n{ \"id\": \"evt_1\", \"app_id\": \"app_yourappid\", \"connection_id\": \"conn_abc123\",\n  \"alert_type\": \"volume_spike\", \"triggered_at\": \"2026-06-12T19:00:00Z\",\n  \"details\": { \"message\": \"...\" } }\n```\n\nThese shapes match what the AgentAdmit hosted service returns from `/api/v1/alerts*`, so the alerts endpoints can be thin pass-through proxies; the `/admin/*` endpoints are assembled by your backend (connections + audit log + usage data), typically from the backend SDK's storage plus your own user table for `user_label`.\n\n## Important\n\n**Architecture:** AgentAdmit uses mandatory hosted introspection. All token validation goes through api.agentadmit.com on the backend. The consent step runs on the hosted consent page. This React SDK handles companion frontend UI only. Token validation is handled by the backend SDK (Python/Node/Java/PHP/Ruby/Go).\n\n**In-app AI scopes.** If your app has built-in AI features (analysis, plan generation, photo recognition), do not expose those as agent scopes. The user's AI agent can read the raw data and do the analysis itself. Exposing in-app AI endpoints to agents creates double cost for both you and your users. Define your scopes around raw data access, not in-app AI triggers.\n\n## Rate Limiting\n\nThe AgentAdmit API enforces rate limits and may return HTTP 429. Because this is a **frontend React SDK**, the hook surfaces rate limit information as state rather than auto-retrying (server-side retry is handled automatically by the backend SDKs).\n\n### Detecting rate limits\n\n```tsx\nconst {\n  revokeConnection,\n  isRateLimited,    // true when last request was 429\n  rateLimitInfo,    // { retryAfter, limit, remaining, reset }\n  clearRateLimit,\n} = useAgentAdmit({ apiBase, authToken });\n\n// In your UI\nif (isRateLimited && rateLimitInfo?.retryAfter) {\n  return <p>Too many requests. Please try again in {Math.ceil(rateLimitInfo.retryAfter)} seconds.</p>;\n}\n```\n\n### `RateLimitInfo` type\n\n```typescript\ninterface RateLimitInfo {\n  retryAfter: number | null;  // Retry-After header (seconds), or null\n  limit:      number | null;  // X-RateLimit-Limit\n  remaining:  number | null;  // X-RateLimit-Remaining\n  reset:      number | null;  // X-RateLimit-Reset (Unix timestamp)\n}\n```\n\n### Hook return values\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `isRateLimited` | `boolean` | `true` if last request returned 429 |\n| `rateLimitInfo` | `RateLimitInfo \\| null` | Rate limit details, or `null` |\n| `clearRateLimit` | `() => void` | Manually clear rate limit state |\n\nRate limit state auto-clears on the next successful request.\n\n> **Note:** Automatic server-side retries with backoff are handled by the backend SDK (Python, Node.js, Go, etc.). The React hook intentionally surfaces the rate limit as UI state so your app can display feedback to the user.\n\n## Documentation\n\nFull integration guide: https://agentadmit.com/docs/app-owner-guide\nHosted consent page + template data structures: Step 4 of the guide\n\n\n## Data Collection & Privacy\n\nThe AgentAdmit React SDK is designed for maximum privacy compliance.\n\n### What the SDK transmits\n- **Auth token** - Your user's JWT, provided by your app via the `authToken` prop. Sent as an `Authorization` header.\n- **Scope selections** - The permissions the user selects in the UI. Sent to your API endpoint.\n- **Duration preference** - The connection duration the user selects. Sent to your API endpoint.\n\n### What the SDK does NOT collect\n- No device identifiers (IDFA, GAID, or device fingerprinting)\n- No location, contacts, photos, or media\n- No analytics, telemetry, or crash reporting\n- No advertising identifiers or tracking\n- No cookies or persistent local storage\n- No Apple Required Reason APIs\n\n### Where data goes\nALL data is sent to the `apiBase` URL you configure - your own backend server. The SDK does not send data to AgentAdmit's servers or any third party. The SDK has zero hardcoded external domains.\n\n### Apple App Store\nThis package includes a `PrivacyInfo.xcprivacy` privacy manifest for React Native / iOS distribution. When filling out Apple's Privacy Nutrition Labels, the AgentAdmit SDK's data collection is minimal - see our [compliance guide](https://agentadmit.com/docs/compliance) for copy-paste answers.\n\n### Google Play\nWhen filling out the Google Play Data Safety form, the AgentAdmit SDK does not independently collect or share user data with third parties. All data processing occurs between the user's device and your own server. See our [compliance guide](https://agentadmit.com/docs/compliance) for copy-paste Data Safety form answers.\n\n## License\n\nAll rights reserved. Patent pending.\n\n## AlertsPanel Component\n\n> **ADMIN-ONLY SURFACE. Do not expose AlertsPanel to end users.**\n>\n> `AlertsPanel` (and `useAlerts`) calls the `/alerts/config` and `/alerts` endpoints, including POST requests that mutate app-level alert configuration. The `AlertConfig` payload includes `kill_switch_enabled`, which controls the app-wide kill switch for all agent connections. Exposing these endpoints or this component to end users lets any end user disable your app's kill switch. Your backend proxy MUST restrict every alerts endpoint to admin users only -- this requirement is stated in `docs/admin-proxy-contract.md`. Embed `AlertsPanel` only in your admin dashboard, behind your existing admin authentication.\n\nDrop-in component for alert history and threshold configuration. Embed this in your **admin dashboard** behind admin authentication:\n\n```tsx\nimport { AlertsPanel } from '@agentadmit/react';\n\n// authToken MUST be an admin credential -- your backend proxy enforces admin-only access\n// to all alerts endpoints (GET and POST), including kill_switch_enabled.\n// Do NOT pass a regular user session token here.\n<AlertsPanel apiBase=\"/agentadmit\" authToken={adminSession.token} appId=\"app_abc123\" />\n```\n\n### useAlerts Hook\n\n```tsx\nimport { useAlerts } from '@agentadmit/react';\n\n// authToken MUST be an admin credential -- configureAlert POSTs app-level alert config\n// including kill_switch_enabled. Use this hook only in admin contexts.\nconst { alertEvents, configureAlert, fetchAlertEvents } = useAlerts({\n  apiBase: '/agentadmit', authToken: adminSession.token, appId: 'app_abc123',\n});\nawait configureAlert('volume_spike', { enabled: true, threshold_value: 100, threshold_window_minutes: 5 });\n```\n\n\n### Notifying Your Users\n\nAgentAdmit detects anomalies, fires alerts, and (with kill switch) auto-revokes connections. **How you notify your own users is up to you.** AgentAdmit provides the data -- you deliver it through your own system (in-app notifications, email, push, etc.).\n\n- **Poll alerts** -- Use the SDK methods above from your backend to check for new events, then notify users through your existing system.\n- **Webhook delivery (coming soon)** -- Configure a webhook URL in your AgentAdmit dashboard. When an alert fires, AgentAdmit POSTs the payload to your server.\n- **React SDK** -- Embed the `<AlertsPanel>` component in your admin dashboard so admins can monitor alert history and adjust thresholds.\n","readmeFilename":"README.md"}