Skip to main content
Version: 2.x (Latest)

REST API

Alongside the GraphQL API, every Authorizer operation is also exposed as a plain JSON-over-HTTP REST endpoint under the /v1/ prefix. The REST surface is generated from the same gRPC service definition via grpc-gateway, so each REST endpoint maps one-to-one to a GraphQL query/mutation and shares the exact same request and response fields.

Use REST when a GraphQL client is overkill — server-to-server calls, shell scripts, webhooks, or platforms where a simple POST with a JSON body is the path of least resistance.

The same server also speaks gRPC (default port 9091, set with --grpc-port) and GraphQL (POST /graphql). All three are backed by the same service layer, so they behave identically.

Availability: the /v1 REST gateway is mounted only when the gRPC server is enabled (the default). It mirrors the full public and admin service surface — see Available endpoints below.

Table of Contents

Base URL & transport

PropertyValue
Base path/v1
HTTP port--http-port (default 8080)
Content typeapplication/json for request and response bodies
Field casingsnake_case (proto field names), matching the GraphQL field names
Request bodiesFlat JSON object — the RPC request message fields at the top level (no wrapper)
gRPC port--grpc-port (default 9091) — same operations over native gRPC

So if your instance is at https://auth.example.com, the permission-check endpoint is https://auth.example.com/v1/check_permissions.

Authentication

Authenticated endpoints accept the user's credentials in either of two ways — exactly as the GraphQL API does:

  • Bearer tokenAuthorization: Bearer <access_token>
  • Session cookie — see Session cookies below
curl -X GET https://auth.example.com/v1/profile \
-H "Authorization: Bearer $ACCESS_TOKEN"

Super-admin endpoints live under /v1/admin/* (see Authorizer Admin API Endpoints). They accept the same two mechanisms as GraphQL admin calls: x-authorizer-admin-secret or the authorizer-admin HTTP-only session cookie set by POST /v1/admin/login.

Session cookies

Cookie-based auth works the same over REST as over GraphQL. On successful auth the server returns real Set-Cookie response headers (not Grpc-Metadata-Set-Cookie). Browser clients should send credentials: 'include' on fetch (or the equivalent in your HTTP client) so the cookie is stored and resent automatically.

Cookie nameSet byPurpose
cookie_session, cookie_session_domainsignup, login, session, verify_email, verify_otpApp session pair (HTTP-only; host-scoped + domain-scoped)
mfalogin, forgot_password, resend_otp when MFA is requiredShort-lived MFA challenge session
authorizer-adminPOST /v1/admin/login, GET /v1/admin/sessionSuper-admin session

Browser example — login and call an authenticated endpoint with the session cookie:

// 1. Login — browser stores Set-Cookie automatically
await fetch('https://auth.example.com/v1/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'jane@example.com', password: 'Test@123' }),
});

// 2. Subsequent calls — cookie sent automatically
const profile = await fetch('https://auth.example.com/v1/profile', {
credentials: 'include',
});

curl example — capture cookies in a jar and reuse them:

curl -c cookies.txt -X POST https://auth.example.com/v1/login \
-H "Content-Type: application/json" \
-d '{ "email": "jane@example.com", "password": "Test@123" }'

curl -b cookies.txt https://auth.example.com/v1/profile

Server-side REST clients that are not behind a browser should forward the Cookie header on authenticated calls, or use a bearer token from the access_token field in the JSON response body instead.

CSRF note: From v2.3.0 onward, state-changing POST requests are rejected with 403 unless they carry an Origin (or Referer) header. Browsers send this automatically; server-side clients should set Origin explicitly. The official SDKs do this for you.

Public API Endpoints

Every endpoint below accepts/returns the same fields as its GraphQL counterpart. For the full field-by-field breakdown of each request and response, follow the linked anchor in the GraphQL API reference.

Response shape — no envelope wrapper. Each endpoint returns the bare domain object, byte-identical to the GraphQL response. signup, login, verify_email, verify_otp, and session return the AuthResponse fields at the top level ({ "message", "access_token", "id_token", "refresh_token", "expires_in", "user", … }) — not wrapped under an auth key. profile returns the User object directly (not under user) and meta returns the Meta object directly (not under meta). The remaining endpoints return { "message": "…" } (or their documented fields).

Example — POST /v1/login response (flat AuthResponse):

{
"message": "Logged in successfully",
"access_token": "eyJhbGciOiJIUzI1NiIs…",
"expires_in": 1718534400,
"id_token": "eyJhbGciOiJIUzI1NiIs…",
"refresh_token": "…",
"user": {
"id": "…",
"email": "jane@example.com",
"roles": ["user"]
}
}

Example — GET /v1/profile response (flat User):

{
"id": "…",
"email": "jane@example.com",
"roles": ["user"],
"given_name": "Jane"
}

POST /v1/signup

Register a new user. Request/response fields match signup.

POST /v1/login

Authenticate with email/phone + password. Returns tokens, or an MFA challenge flag (should_show_email_otp_screen / should_show_totp_screen) when OTP/TOTP is enabled. Mirrors login.

curl -X POST https://auth.example.com/v1/login \
-H "Content-Type: application/json" \
-d '{ "email": "jane@example.com", "password": "Test@123", "scope": ["openid", "profile", "email"] }'

POST /v1/magic_link_login

Start a passwordless login; emails a magic link. Mirrors magic_link_login.

POST /v1/verify_email

Complete email verification using the token from the verification email. Mirrors verify_email.

POST /v1/resend_verify_email

Re-send the email-verification message. Mirrors resend_verify_email.

POST /v1/verify_otp

Complete an MFA challenge by submitting the email/phone OTP. Mirrors verify_otp.

POST /v1/resend_otp

Re-send the MFA OTP. Mirrors resend_otp.

POST /v1/skip_mfa_setup

Completes an in-progress, token-withheld MFA offer by recording an explicit decline, then issues the withheld access token. Rejected when MFA is org-enforced (--enforce-mfa). Mirrors skip_mfa_setup.

POST /v1/lock_mfa

Records that the caller lost access to their only MFA factor(s); only allowed with no verified Email/SMS OTP fallback enrolled. Does not issue a token — the account requires admin recovery afterward. Mirrors lock_mfa.

POST /v1/email_otp_mfa_setup

Sends a one-time code to the caller's own email and creates an unverified email-OTP MFA enrollment — either for an already-authenticated caller adding a second factor, or a caller in the withheld first-time-offer state identified by the MFA session cookie. Mirrors email_otp_mfa_setup.

POST /v1/sms_otp_mfa_setup

Same as email_otp_mfa_setup, for SMS. Mirrors sms_otp_mfa_setup.

POST /v1/totp_mfa_setup

Generates a fresh TOTP secret, QR image, and recovery codes for the caller to enroll as an MFA method. Nothing is sent anywhere — the enrollment payload is returned directly, and enrollment is completed with verify_otp (is_totp: true). Mirrors totp_mfa_setup.

POST /v1/webauthn_registration_options

Returns the WebAuthn creation options (an opaque JSON string) to hand to navigator.credentials.create(). Works for an already-authenticated caller adding a passkey, or for a caller in the withheld first-time MFA-offer state identified by the MFA session cookie.

POST /v1/webauthn_registration_verify

Submits the credential produced by the browser to finish registration. Returns the full auth response: on the MFA-session-cookie path this also completes the gate and issues the withheld access token; on the ordinary authenticated path access_token is null.

POST /v1/webauthn_login_options

Returns the WebAuthn request options for navigator.credentials.get(). Omit email for the usernameless (discoverable credential) ceremony.

POST /v1/webauthn_login_verify

Submits the signed assertion to complete passkey login and issue tokens.

POST /v1/webauthn_credentials

Lists the authenticated caller's own registered passkeys. Requires authentication.

POST /v1/webauthn_delete_credential

Deletes one of the authenticated caller's own passkeys by id. Requires authentication.

POST /v1/forgot_password

Start password reset; emails a reset link. Mirrors forgot_password.

POST /v1/reset_password

Set a new password using the reset token. Mirrors reset_password.

POST /v1/logout

Invalidate the current session (auth). Mirrors logout.

GET /v1/profile

Get the authenticated user's profile (auth). Mirrors profile.

POST /v1/update_profile

Update the authenticated user's profile (auth). Mirrors update_profile.

POST /v1/deactivate_account

Deactivate (soft-delete) the authenticated user's account (auth). Mirrors deactivate_account.

POST /v1/session

Refresh / fetch the current session (auth). Mirrors session.

POST /v1/revoke

Revoke a refresh token. Mirrors revoke.

POST /v1/validate_jwt_token

Validate a JWT and, optionally, required relations. Mirrors validate_jwt_token.

POST /v1/validate_session

Validate a session cookie and required relations. Mirrors validate_session.

GET /v1/meta

Server feature flags & provider availability. Mirrors meta.

POST /v1/check_permissions

Answer authorization questions against the embedded FGA (ReBAC) engine — evaluate one or more permission checks in a single call. At least one, at most 100 checks. results come back in the same order as checks and echo each pair. The subject is pinned server-side from the caller's token/cookie; the optional user field is honored only for super-admins or when it equals the caller's own subject.

Request body

FieldTypeDescriptionRequired
checksPermissionCheckInput[]Each { relation, object, contextual_tuples? }. 1–100 entries.yes
userstringExplicit subject (type:id, or a bare id treated as user:<id>). Super-admin / self only.no

contextual_tuples are extra { user, relation, object } tuples evaluated for that one check only and never persisted — useful for "what-if" checks and request-time facts.

curl -X POST https://auth.example.com/v1/check_permissions \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"checks": [
{ "relation": "can_view", "object": "document:1" },
{ "relation": "can_edit", "object": "document:1" }
]
}'
{
"results": [
{ "relation": "can_view", "object": "document:1", "allowed": true },
{ "relation": "can_edit", "object": "document:1", "allowed": false }
]
}

With a contextual tuple:

curl -X POST https://auth.example.com/v1/check_permissions \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"checks": [
{
"relation": "can_view",
"object": "document:1",
"contextual_tuples": [
{ "user": "user:alice", "relation": "member", "object": "team:eng" }
]
}
]
}'

POST /v1/list_permissions

List what the subject can access. With both relation and object_type set it answers "which object_types can I relation?"; either or both filters may be omitted, so an empty body returns every permission the subject holds.

Request body

FieldTypeDescriptionRequired
relationstringOptional relation filter (e.g. can_view).no
object_typestringOptional object-type filter (e.g. document).no
userstringOptional explicit subject; same trust rules as above.no
curl -X POST https://auth.example.com/v1/list_permissions \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "relation": "can_view", "object_type": "document" }'
{
"objects": ["document:1", "document:42"],
"permissions": [
{ "object": "document:1", "relation": "can_view" },
{ "object": "document:42", "relation": "can_view" }
],
"truncated": false
}

truncated is true when the result was capped at 1000 entries and more permissions exist — narrow the query with relation / object_type to page through them.

Authorizer Admin API Endpoints

All admin endpoints require super-admin authentication via the x-authorizer-admin-secret request header or an authorizer.admin session cookie (set by POST /v1/admin/login). Except POST /v1/admin/login, which may be called without an existing session. The operations are grouped by domain below; each mirrors its GraphQL counterpart for request/response fields.

Note: Admin operations are available over all three surfaces — REST (below), native gRPC (AuthorizerAdminService, see the gRPC API), and the GraphQL API (the _-prefixed operations).

Admin Authentication

POST /v1/admin/login

Authenticate as super-admin with the admin secret. Sets the authorizer-admin session cookie via Set-Cookie (same as GraphQL _admin_login).

Request body

FieldTypeDescriptionRequired
admin_secretstringThe admin secret value.yes

Response { message: string }

POST /v1/admin/logout

Invalidate the current admin session.

Response { message: string }

GET /v1/admin/session

Verify the current admin session is valid.

Response { message: string }

GET /v1/admin/meta

Get admin-level metadata about the server (version, feature flags).

Response — same as GET /v1/meta but with admin-only fields

Users

POST /v1/admin/users

List all users with pagination and optional search.

Request body

FieldTypeDescriptionRequired
pagination{ page, limit }Page & limit.no
querystringCase-insensitive substring filter (matches against user ID, email, given_name, family_name, nickname).no

Response { users: User[], pagination: { page, limit, total, offset } }

POST /v1/admin/user

Get a specific user by id or email.

Request body

FieldTypeDescriptionRequired
idstringUser id.no
emailstringUser email.no

Response User

POST /v1/admin/update_user

Update user profile fields (email, roles, name, etc.).

Request body — see _update_user for the full field list.

Response User

POST /v1/admin/delete_user

Delete a user by email (and all associated OTP/verification data).

Request body

FieldTypeDescriptionRequired
emailstringUser email.yes

Response { message: string }

POST /v1/admin/verification_requests

List pending verification requests (email, phone OTP, etc.) with optional pagination.

Request body

FieldTypeDescriptionRequired
pagination{ page, limit }Page & limit.no

Response { verification_requests: VerificationRequest[], pagination: { ... } }

Access Control

POST /v1/admin/revoke_access

Revoke a user's access (set revoked_timestamp), firing the user.access_revoked webhook.

Request body

FieldTypeDescriptionRequired
user_idstringUser id.yes

Response { message: string }

POST /v1/admin/enable_access

Re-enable a previously revoked user (clear revoked_timestamp), firing the user.access_enabled webhook.

Request body

FieldTypeDescriptionRequired
user_idstringUser id.yes

Response { message: string }

POST /v1/admin/invite_members

Invite users to the platform by email, sending them an invitation link.

Request body

FieldTypeDescriptionRequired
emailsstring[]Email addresses to invite.yes
redirect_uristringWhere to redirect after signup.no

Response { message: string }

Webhooks

POST /v1/admin/add_webhook

Register a new webhook for an event.

Request body — see _add_webhook for field details.

Response { message: string }

POST /v1/admin/update_webhook

Update an existing webhook (endpoint, headers, enabled state, etc.).

Request body — see _update_webhook for field details.

Response { message: string }

POST /v1/admin/delete_webhook

Delete a webhook by id.

Request body

FieldTypeDescriptionRequired
idstringWebhook id.yes

Response { message: string }

POST /v1/admin/webhook

Get a webhook by id.

Request body

FieldTypeDescriptionRequired
idstringWebhook id.yes

Response Webhook

POST /v1/admin/webhooks

List all webhooks with pagination.

Request body

FieldTypeDescriptionRequired
pagination{ page, limit }Page & limit.no

Response { webhooks: Webhook[], pagination: { ... } }

POST /v1/admin/webhook_logs

List webhook delivery logs with optional pagination and webhook_id filter.

Request body

FieldTypeDescriptionRequired
pagination{ page, limit }Page & limit.no
webhook_idstringFilter by hook.no

Response { webhook_logs: WebhookLog[], pagination: { ... } }

POST /v1/admin/test_endpoint

Send a test webhook payload to an endpoint and return the response.

Request body

FieldTypeDescriptionRequired
event_namestringEvent to simulate.yes
endpointstringURL to call.yes
headersmap[string]stringExtra HTTP headers.no

Response { http_status: int, response: string }

Email Templates

POST /v1/admin/add_email_template

Create a new email template for an event.

Request body — see _add_email_template for field details.

Response { message: string }

POST /v1/admin/update_email_template

Update an email template.

Request body — see _update_email_template for field details.

Response { message: string }

POST /v1/admin/delete_email_template

Delete an email template by id.

Request body

FieldTypeDescriptionRequired
idstringTemplate id.yes

Response { message: string }

POST /v1/admin/email_templates

List email templates with pagination.

Request body

FieldTypeDescriptionRequired
pagination{ page, limit }Page & limit.no

Response { email_templates: EmailTemplate[], pagination: { ... } }

Audit Logs

POST /v1/admin/audit_logs

Retrieve audit log entries with optional filtering and pagination.

Request body

FieldTypeDescriptionRequired
pagination{ page, limit }Page & limit.no
actor_idstringFilter by actor id.no
actionstringFilter by action type.no
resource_typestringFilter by resource type.no
resource_idstringFilter by resource id.no
from_timestampint64Filter entries at/after this Unix timestamp.no
to_timestampint64Filter entries at/before this Unix timestamp.no

Response { audit_logs: AuditLog[], pagination: { ... } }

Authorization (FGA)

Manage the embedded fine-grained authorization (FGA) engine: the authorization model and relationship tuples. See Authorization (FGA) for the conceptual model.

GET /v1/admin/fga/model

Retrieve the active authorization model as FGA DSL. An empty store returns an empty model (not an error).

Response { id: string, dsl: string }

POST /v1/admin/fga/model

Install a new authorization model version from FGA DSL. Models are versioned and append-only.

Request body

FieldTypeDescriptionRequired
dslstringFGA DSL.yes

Response { id: string, dsl: string }

POST /v1/admin/fga/tuples

Write (persist) relationship tuples.

Request body

FieldTypeDescriptionRequired
tuples{ user, relation, object }[]Tuples to write.yes

Response { message: string }

POST /v1/admin/fga/tuples/delete

Delete relationship tuples.

Request body

FieldTypeDescriptionRequired
tuples{ user, relation, object }[]Tuples to delete.yes

Response { message: string }

POST /v1/admin/fga/tuples/read

Read stored tuples with optional filtering and pagination.

Request body

FieldTypeDescriptionRequired
userstringFilter by user.no
relationstringFilter by relation.no
objectstringFilter by object.no
page_sizeintMax tuples per page.no
continuation_tokenstringPagination token.no

Response { tuples: { user, relation, object }[], continuation_token: string }

POST /v1/admin/fga/list_users

List fully-qualified user ids that have a relation on an object (reveals the access graph; admin-only).

Request body

FieldTypeDescriptionRequired
objectstringObject to inspect.yes
relationstringRelation to resolve.yes
user_typestringType of users to list.yes

Response { users: string[] }

POST /v1/admin/fga/expand

Expand the relationship/userset tree for a relation on an object (admin-only; useful for debugging). Returns the OpenFGA userset tree as a JSON string.

Request body

FieldTypeDescriptionRequired
relationstringRelation to expand.yes
objectstringObject to expand on.yes

Response { tree: string }

POST /v1/admin/fga/reset

Delete the entire fine-grained authorization store (model, all versions, and all tuples) and start fresh. Refused if any tuples still exist. Destructive and audited.

Request body — empty

Response { message: string }

Client Registry

Manage machine/workload identity clients. All admin-only. Field-level request/response shapes and examples: Client Registry guide.

EndpointDescription
POST /v1/admin/create_clientProvision a new client; returns the secret exactly once.
POST /v1/admin/update_clientUpdate name, description, allowed scopes, or active state.
POST /v1/admin/delete_clientDelete a client by id, cascading to its trusted issuers.
POST /v1/admin/rotate_client_secretReplace the client secret, returned exactly once.
POST /v1/admin/clientGet a single client by id. Secret never surfaced.
POST /v1/admin/clientsList clients with pagination. Secrets never surfaced.

Trusted Issuers

Manage external JWT issuers for RFC 7523 private_key_jwt client assertions. All admin-only. Field-level request/response shapes: Workload Identity.

EndpointDescription
POST /v1/admin/add_trusted_issuerRegister an issuer for a client (subject_claim defaults to sub).
POST /v1/admin/update_trusted_issuerUpdate name, JWKS URL, expected audience, active state, or SPIFFE hint.
POST /v1/admin/delete_trusted_issuerDelete a trusted issuer by id.
POST /v1/admin/trusted_issuerGet a single trusted issuer by id.
POST /v1/admin/trusted_issuersList trusted issuers, optionally filtered by client id.

SAML IdP

Manage Authorizer acting as a SAML 2.0 IdP for downstream service providers, plus IdP signing-key rotation. All admin-only. Field-level request/response shapes: SAML IdP.

EndpointDescription
POST /v1/admin/create_saml_service_providerRegister a downstream SP.
POST /v1/admin/update_saml_service_providerUpdate a downstream SP's name, endpoints, certificate, or mapping.
POST /v1/admin/delete_saml_service_providerDelete a downstream SP by id.
POST /v1/admin/saml_service_providerGet a single downstream SP by id.
POST /v1/admin/saml_service_providersList downstream SPs for an org.
POST /v1/admin/rotate_saml_idp_certGenerate a new current signing keypair, demoting the previous one.
POST /v1/admin/retire_saml_idp_keyRetire a published-but-not-signing SAML IdP key by id.
POST /v1/admin/saml_idp_keysList all SAML IdP signing keys for an org.
POST /v1/admin/import_saml_sp_metadataParse pasted SP metadata XML; no record created, no remote fetch.

Organizations

Multi-tenant organizations and their membership. Super-admin, or that organization's own org-admin (the reserved authorizer:org_admin role) for the org-scoped operations. Field-level request/response shapes: Organizations.

EndpointDescription
POST /v1/admin/create_organizationCreate an organization. Super-admin only.
POST /v1/admin/update_organizationUpdate name, display name, or enabled state.
POST /v1/admin/delete_organizationDelete an organization and its scoped records. Super-admin only.
POST /v1/admin/organizationGet a single organization by id.
POST /v1/admin/organizationsList organizations. Super-admin only.
POST /v1/admin/add_org_memberAdd a user to an organization with a set of org roles.
POST /v1/admin/remove_org_memberRemove a user from an organization.
POST /v1/admin/org_membersList an organization's members.
POST /v1/admin/user_organizationsList the organizations a user belongs to, with their roles.

Organization Domains

Verified DNS-domain-to-organization mappings used for home-realm discovery — routing a login to the correct tenant IdP. Field-level shapes: Organization Domains.

EndpointDescription
POST /v1/admin/request_org_domainStart domain verification; returns the DNS TXT challenge to publish.
POST /v1/admin/verify_org_domainCheck the published TXT record and mark the domain verified.
POST /v1/admin/add_verified_org_domainTrusted-assert a domain as verified, skipping DNS. Super-admin only.
POST /v1/admin/delete_org_domainRemove a mapping; the domain stops routing to that organization.
POST /v1/admin/org_domainsList an organization's domains.

Organization SSO Connections

Per-organization upstream identity providers an org's members sign in through. The upstream client_secret is stored encrypted and never returned. Field-level shapes: Org SSO (OIDC) and Org SAML.

EndpointDescription
POST /v1/admin/create_org_oidc_connectionRegister an upstream OIDC IdP for an organization.
POST /v1/admin/update_org_oidc_connectionUpdate an OIDC connection.
POST /v1/admin/delete_org_oidc_connectionDelete an OIDC connection.
POST /v1/admin/org_oidc_connectionGet an OIDC connection by id or org id.
POST /v1/admin/create_org_saml_connectionRegister an upstream SAML IdP for an organization.
POST /v1/admin/update_org_saml_connectionUpdate a SAML connection.
POST /v1/admin/delete_org_saml_connectionDelete a SAML connection.
POST /v1/admin/org_saml_connectionGet a SAML connection by id or org id.

SCIM

Inbound SCIM 2.0 provisioning endpoints, one per organization. The bearer token is returned exactly once — at creation and at rotation — and is never retrievable afterwards. Field-level shapes: SCIM.

EndpointDescription
POST /v1/admin/create_scim_endpointEnable SCIM for an organization; returns the one-time bearer token.
POST /v1/admin/rotate_scim_tokenIssue a new token, invalidating the old one. Shown once.
POST /v1/admin/delete_scim_endpointDisable SCIM for an organization.
POST /v1/admin/scim_endpointGet an organization's SCIM endpoint metadata. Never returns the token.

Errors

REST errors return a non-200 HTTP status with a stable JSON envelope:

{
"code": "failed_precondition",
"message": "fga is not enabled on this server"
}

code is a snake_case gRPC status token (for example invalid_argument, unauthenticated, permission_denied, not_found, method_not_allowed). message is a human-readable description.

Common cases:

SituationHTTP statuscode (typical)
Missing/invalid credentials401 Unauthorizedunauthenticated
Explicit user not permitted for caller403 Forbiddenpermission_denied
FGA not enabled (--fga-store unset)400 Bad Requestfailed_precondition
Validation failure (e.g. > 100 checks)400 Bad Requestinvalid_argument
Wrong HTTP method (e.g. GET on POST)405 Method Not Allowedmethod_not_allowed

See also

  • GraphQL API — the full field-level reference shared by all transports.
  • gRPC API — the same operations over native gRPC (BSR module, reflection, health).
  • Authorization (FGA) — the relationship model behind the permission endpoints.
  • Endpoints — OAuth/OIDC and operational endpoints (/authorize, /oauth/token, /.well-known/*, /healthz).
  • MCP Server — exposing check_permissions / list_permissions to LLM agents.