# Delete an agent Source: https://docs.grantex.dev/api-reference/agents/delete-an-agent delete /v1/agents/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get agent by id Source: https://docs.grantex.dev/api-reference/agents/get-agent-by-id get /v1/agents/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List all agents Source: https://docs.grantex.dev/api-reference/agents/list-all-agents get /v1/agents ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Register a new agent Source: https://docs.grantex.dev/api-reference/agents/register-a-new-agent post /v1/agents ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Update an agent Source: https://docs.grantex.dev/api-reference/agents/update-an-agent patch /v1/agents/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Acknowledge alert Source: https://docs.grantex.dev/api-reference/anomalies/acknowledge-alert post /v1/anomaly/alerts/{alertId}/acknowledge ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Acknowledge an anomaly Source: https://docs.grantex.dev/api-reference/anomalies/acknowledge-an-anomaly patch /v1/anomalies/{id}/acknowledge ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create channel Source: https://docs.grantex.dev/api-reference/anomalies/create-channel post /v1/anomaly/channels ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create rule Source: https://docs.grantex.dev/api-reference/anomalies/create-rule post /v1/anomaly/rules ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete channel Source: https://docs.grantex.dev/api-reference/anomalies/delete-channel delete /v1/anomaly/channels/{channelId} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete rule Source: https://docs.grantex.dev/api-reference/anomalies/delete-rule delete /v1/anomaly/rules/{ruleId} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get metrics Source: https://docs.grantex.dev/api-reference/anomalies/get-metrics get /v1/anomaly/metrics ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List alerts Source: https://docs.grantex.dev/api-reference/anomalies/list-alerts get /v1/anomaly/alerts ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List channels Source: https://docs.grantex.dev/api-reference/anomalies/list-channels get /v1/anomaly/channels ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List detected anomalies Source: https://docs.grantex.dev/api-reference/anomalies/list-detected-anomalies get /v1/anomalies ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List rules Source: https://docs.grantex.dev/api-reference/anomalies/list-rules get /v1/anomaly/rules ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Resolve alert Source: https://docs.grantex.dev/api-reference/anomalies/resolve-alert post /v1/anomaly/alerts/{alertId}/resolve ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Response policy Source: https://docs.grantex.dev/api-reference/anomalies/response-policy get /v1/irregularities/response-policy Use `PATCH /v1/irregularities/response-policy` with `{"mode":"alert_only"}` or `{"mode":"revoke_agent_grants"}` to change this account-wide setting. It affects the legacy detector only. The server rollout flag is off by default. # Run anomaly detection Source: https://docs.grantex.dev/api-reference/anomalies/run-anomaly-detection post /v1/anomalies/detect ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get audit entry by id Source: https://docs.grantex.dev/api-reference/audit/get-audit-entry-by-id get /v1/audit/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List audit entries Source: https://docs.grantex.dev/api-reference/audit/list-audit-entries get /v1/audit/entries ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Log an audit entry Source: https://docs.grantex.dev/api-reference/audit/log-an-audit-entry post /v1/audit/log ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get current developer profile Source: https://docs.grantex.dev/api-reference/authentication/get-current-developer-profile get /v1/me ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Register a new developer account Source: https://docs.grantex.dev/api-reference/authentication/register-a-new-developer-account post /v1/signup ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Rotate api key Source: https://docs.grantex.dev/api-reference/authentication/rotate-api-key post /v1/keys/rotate ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Approve an authorization request Source: https://docs.grantex.dev/api-reference/authorization/approve-an-authorization-request post /v1/authorize/{id}/approve ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create an authorization request Source: https://docs.grantex.dev/api-reference/authorization/create-an-authorization-request post /v1/authorize ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Deny an authorization request Source: https://docs.grantex.dev/api-reference/authorization/deny-an-authorization-request post /v1/authorize/{id}/deny ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create a stripe billing portal session Source: https://docs.grantex.dev/api-reference/billing/create-a-stripe-billing-portal-session post /v1/billing/portal ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create a stripe checkout session Source: https://docs.grantex.dev/api-reference/billing/create-a-stripe-checkout-session post /v1/billing/checkout ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get current subscription Source: https://docs.grantex.dev/api-reference/billing/get-current-subscription get /v1/billing/subscription ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Stripe webhook handler Source: https://docs.grantex.dev/api-reference/billing/stripe-webhook-handler post /v1/billing/webhook ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Allocate Budget Source: https://docs.grantex.dev/api-reference/budgets/allocate Create a budget allocation for a grant, setting a spending cap for the agent. ## Endpoint ``` POST /v1/budget/allocate ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `grantId` | `string` | Yes | The grant to attach a budget to | | `initialBudget` | `number` | Yes | The initial budget amount (must be positive) | | `currency` | `string` | No | Currency code (default `"USD"`) | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/budget/allocate \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "grantId": "grnt_01HXYZ...", "initialBudget": 100.00, "currency": "USD" }' ``` ## Response -- 201 Created ```json theme={null} { "id": "ba_01HXYZ...", "grantId": "grnt_01HXYZ...", "initialBudget": 100.00, "remainingBudget": 100.00, "currency": "USD", "createdAt": "2026-04-05T12:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `id` | `string` | Unique budget allocation ID | | `grantId` | `string` | The grant this budget is attached to | | `initialBudget` | `number` | The initial budget amount | | `remainingBudget` | `number` | Current remaining budget (equals `initialBudget` on creation) | | `currency` | `string` | Currency code | | `createdAt` | `string` | ISO-8601 creation timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing `grantId` or `initialBudget` not positive | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Grant not found or does not belong to developer | | 409 | `CONFLICT` | Budget allocation already exists for this grant | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const allocation = await grantex.budgets.allocate({ grantId: 'grnt_01HXYZ...', initialBudget: 100.00, currency: 'USD', }); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") allocation = grantex.budgets.allocate( grant_id="grnt_01HXYZ...", initial_budget=100.00, currency="USD", ) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Budget Allocations Source: https://docs.grantex.dev/api-reference/budgets/allocations List all budget allocations for the authenticated developer. ## Endpoint ``` GET /v1/budget/allocations ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/budget/allocations \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "allocations": [ { "id": "ba_01HXYZ...", "grantId": "grnt_01HXYZ...", "initialBudget": 100.00, "remainingBudget": 94.50, "currency": "USD", "createdAt": "2026-04-05T12:00:00.000Z", "updatedAt": "2026-04-05T14:30:00.000Z" } ] } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `allocations` | `array` | Array of budget allocation objects | | `allocations[].id` | `string` | Unique budget allocation ID | | `allocations[].grantId` | `string` | The grant this budget is attached to | | `allocations[].initialBudget` | `number` | The original budget amount | | `allocations[].remainingBudget` | `number` | Current remaining budget | | `allocations[].currency` | `string` | Currency code | | `allocations[].createdAt` | `string` | ISO-8601 creation timestamp | | `allocations[].updatedAt` | `string` | ISO-8601 last update timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const { allocations } = await grantex.budgets.allocations(); for (const a of allocations) { console.log(`${a.grantId}: ${a.remainingBudget}/${a.initialBudget} ${a.currency}`); } ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.budgets.allocations() for a in result.allocations: print(f"{a.grant_id}: {a.remaining_budget}/{a.initial_budget} {a.currency}") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Budget Balance Source: https://docs.grantex.dev/api-reference/budgets/balance Retrieve the current budget balance for a grant. ## Endpoint ``` GET /v1/budget/balance/:grantId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Description | | - | - | - | | `grantId` | `string` | The grant ID to look up | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/budget/balance/grnt_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "id": "ba_01HXYZ...", "grantId": "grnt_01HXYZ...", "initialBudget": 100.00, "remainingBudget": 94.50, "currency": "USD", "createdAt": "2026-04-05T12:00:00.000Z", "updatedAt": "2026-04-05T14:30:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `id` | `string` | Budget allocation ID | | `grantId` | `string` | The grant this budget is attached to | | `initialBudget` | `number` | The original budget amount | | `remainingBudget` | `number` | Current remaining budget | | `currency` | `string` | Currency code | | `createdAt` | `string` | ISO-8601 creation timestamp | | `updatedAt` | `string` | ISO-8601 last update timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | No budget allocation found for this grant | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const balance = await grantex.budgets.balance('grnt_01HXYZ...'); console.log(balance.remainingBudget); // 94.50 ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") balance = grantex.budgets.balance("grnt_01HXYZ...") print(balance.remaining_budget) # 94.50 ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Debit Budget Source: https://docs.grantex.dev/api-reference/budgets/debit Debit an amount from a grant's budget allocation. Returns 402 if the budget is insufficient. ## Endpoint ``` POST /v1/budget/debit ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `grantId` | `string` | Yes | The grant whose budget to debit | | `amount` | `number` | Yes | The amount to debit (must be positive) | | `description` | `string` | No | Human-readable description of the charge | | `metadata` | `object` | No | Arbitrary metadata to attach to the transaction | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/budget/debit \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "grantId": "grnt_01HXYZ...", "amount": 5.50, "description": "GPT-4 API call", "metadata": { "model": "gpt-4", "tokens": 1500 } }' ``` ## Response -- 200 OK ```json theme={null} { "remaining": 94.50, "transactionId": "txn_01HXYZ...", "grantId": "grnt_01HXYZ..." } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `remaining` | `number` | Budget remaining after the debit | | `transactionId` | `string` | Unique transaction ID for this debit | | `grantId` | `string` | The grant that was debited | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing `grantId` or `amount` not positive | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 402 | `INSUFFICIENT_BUDGET` | The grant's remaining budget is less than the requested amount | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const result = await grantex.budgets.debit({ grantId: 'grnt_01HXYZ...', amount: 5.50, description: 'GPT-4 API call', }); // result.remaining === 94.50 ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.budgets.debit( grant_id="grnt_01HXYZ...", amount=5.50, description="GPT-4 API call", ) # result.remaining == 94.50 ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Budget Transactions Source: https://docs.grantex.dev/api-reference/budgets/transactions Retrieve paginated transaction history for a grant's budget. ## Endpoint ``` GET /v1/budget/transactions/:grantId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Description | | - | - | - | | `grantId` | `string` | The grant ID to look up transactions for | ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `page` | `number` | No | Page number (default `1`) | | `pageSize` | `number` | No | Results per page (default `50`) | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/budget/transactions/grnt_01HXYZ...?page=1&pageSize=20" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "transactions": [ { "id": "txn_01HXYZ...", "grantId": "grnt_01HXYZ...", "amount": 5.50, "description": "GPT-4 API call", "metadata": { "model": "gpt-4", "tokens": 1500 }, "balanceAfter": 94.50, "createdAt": "2026-04-05T14:30:00.000Z" } ], "total": 1 } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `transactions` | `array` | Array of transaction objects | | `transactions[].id` | `string` | Unique transaction ID | | `transactions[].grantId` | `string` | The grant this transaction belongs to | | `transactions[].amount` | `number` | Amount debited | | `transactions[].description` | `string \| null` | Human-readable description | | `transactions[].metadata` | `object` | Arbitrary metadata | | `transactions[].balanceAfter` | `number` | Remaining balance after this transaction | | `transactions[].createdAt` | `string` | ISO-8601 timestamp | | `total` | `number` | Total number of transactions | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const result = await grantex.budgets.transactions('grnt_01HXYZ...'); console.log(result.transactions); console.log(result.total); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.budgets.transactions("grnt_01HXYZ...") print(result.transactions) print(result.total) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Export audit entries for compliance Source: https://docs.grantex.dev/api-reference/compliance/export-audit-entries-for-compliance get /v1/compliance/export/audit ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Export grants for compliance Source: https://docs.grantex.dev/api-reference/compliance/export-grants-for-compliance get /v1/compliance/export/grants ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Generate compliance evidence pack Source: https://docs.grantex.dev/api-reference/compliance/generate-compliance-evidence-pack get /v1/compliance/evidence-pack ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get compliance summary Source: https://docs.grantex.dev/api-reference/compliance/get-compliance-summary get /v1/compliance/summary ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Consent Bundles API Source: https://docs.grantex.dev/api-reference/consent-bundles API reference for offline consent bundle endpoints: create, list, revoke, and check revocation status. ## Overview Consent bundles enable offline authorization for on-device AI agents. These endpoints manage the lifecycle of consent bundles: creation, listing, revocation, and status checks. All endpoints require a developer API key in the `Authorization` header unless noted otherwise. *** ## Create Consent Bundle Create a new consent bundle for offline operation. The response contains a grant token, JWKS snapshot, Ed25519 audit signing keys, and offline expiry metadata. ``` POST /v1/consent-bundles ``` ### Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ### Request Body | Field | Type | Required | Description | | - | - | - | - | | `agentId` | `string` | Yes | The agent to authorize for offline use | | `userId` | `string` | Yes | End-user / principal ID granting consent | | `scopes` | `string[]` | Yes | Scopes the agent needs while offline | | `offlineTTL` | `string` | No | Duration the bundle is valid offline (default `"72h"`). Supports `h`, `m`, `d` suffixes. Maximum `"168h"` (7 days). | | `offlineAuditKeyAlgorithm` | `string` | No | Algorithm for the audit signing key (default `"Ed25519"`) | ### Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/consent-bundles \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "agentId": "ag_01HXYZ...", "userId": "user_abc123", "scopes": ["calendar:read", "email:send"], "offlineTTL": "72h" }' ``` ### Response — 201 Created ```json theme={null} { "bundleId": "cb_01HXYZ...", "grantToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0xIn0...", "jwksSnapshot": { "keys": [ { "kty": "RSA", "kid": "key-1", "n": "0vx7agoebGcQ...", "e": "AQAB", "alg": "RS256", "use": "sig" } ], "fetchedAt": "2026-04-03T12:00:00.000Z", "validUntil": "2026-04-10T12:00:00.000Z" }, "offlineAuditKey": { "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA...\n-----END PUBLIC KEY-----", "privateKey": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIG...\n-----END PRIVATE KEY-----", "algorithm": "Ed25519" }, "checkpointAt": 1743681600000, "syncEndpoint": "https://api.grantex.dev/v1/audit/offline-sync", "offlineExpiresAt": "2026-04-06T12:00:00.000Z" } ``` ### Error Responses | Status | Code | Description | | - | - | - | | 400 | `INVALID_REQUEST` | Missing or invalid request fields | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `AGENT_NOT_FOUND` | The specified agent does not exist | | 422 | `INVALID_SCOPES` | One or more scopes are not registered for the agent | | 422 | `INVALID_TTL` | `offlineTTL` exceeds the maximum allowed (168h) | | 429 | `RATE_LIMITED` | Too many requests | ### SDK Examples ```typescript TypeScript theme={null} import { createConsentBundle } from '@grantex/gemma'; const bundle = await createConsentBundle({ apiKey: 'gx_...', agentId: 'ag_01HXYZ...', userId: 'user_abc123', scopes: ['calendar:read', 'email:send'], offlineTTL: '72h', }); ``` ```python Python theme={null} from grantex_gemma import create_consent_bundle bundle = create_consent_bundle( api_key="gx_...", agent_id="ag_01HXYZ...", user_id="user_abc123", scopes=["calendar:read", "email:send"], offline_ttl="72h", ) ``` *** ## List Consent Bundles List all consent bundles for the authenticated developer. ``` GET /v1/consent-bundles ``` ### Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ### Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `agentId` | `string` | No | Filter by agent ID | | `status` | `string` | No | Filter by status: `active`, `expired`, `revoked` | | `cursor` | `string` | No | Pagination cursor from the previous response | | `limit` | `number` | No | Results per page (default `50`, max `100`) | ### Example Request ```bash theme={null} curl https://api.grantex.dev/v1/consent-bundles?status=active \ -H "Authorization: Bearer gx_..." ``` ### Response — 200 OK ```json theme={null} { "bundles": [ { "id": "cb_01HXYZ...", "agentId": "ag_01HXYZ...", "userId": "user_abc123", "scopes": ["calendar:read", "email:send"], "offlineExpiresAt": "2026-04-06T12:00:00.000Z", "status": "active", "createdAt": "2026-04-03T12:00:00.000Z", "lastSyncAt": null, "auditEntryCount": 0 } ], "nextCursor": "2026-04-03T12:00:00.000Z" } ``` The list response does not include `grantToken` or `offlineAuditKey.privateKey` for security. Only the creation response includes these sensitive fields. *** ## Revoke Consent Bundle Revoke an active consent bundle. The bundle's grant token remains cryptographically valid, but the revocation is communicated to the device at the next sync. ``` POST /v1/consent-bundles/:id/revoke ``` ### Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The bundle ID to revoke (e.g. `cb_01HXYZ...`) | ### Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ### Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/consent-bundles/cb_01HXYZ.../revoke \ -H "Authorization: Bearer gx_..." ``` ### Response — 200 OK ```json theme={null} { "bundleId": "cb_01HXYZ...", "status": "revoked", "revokedAt": "2026-04-03T15:30:00.000Z" } ``` ### Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `BUNDLE_NOT_FOUND` | The specified bundle does not exist | | 409 | `ALREADY_REVOKED` | The bundle is already revoked | *** ## Check Revocation Status Check whether a consent bundle has been revoked. This endpoint is useful for devices that want to verify bundle validity without performing a full sync. ``` GET /v1/consent-bundles/:id/revocation-status ``` ### Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The bundle ID to check | ### Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ### Example Request ```bash theme={null} curl https://api.grantex.dev/v1/consent-bundles/cb_01HXYZ.../revocation-status \ -H "Authorization: Bearer gx_..." ``` ### Response — 200 OK ```json theme={null} { "bundleId": "cb_01HXYZ...", "status": "active", "revokedAt": null } ``` Or if revoked: ```json theme={null} { "bundleId": "cb_01HXYZ...", "status": "revoked", "revokedAt": "2026-04-03T15:30:00.000Z" } ``` ### Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `BUNDLE_NOT_FOUND` | The specified bundle does not exist | *** ## Refresh Consent Bundle Refresh an active consent bundle to extend its offline expiry, update the JWKS snapshot, and rotate the audit signing keys. ``` POST /v1/consent-bundles/:id/refresh ``` ### Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The bundle ID to refresh | ### Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ### Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/consent-bundles/cb_01HXYZ.../refresh \ -H "Authorization: Bearer gx_..." ``` ### Response — 200 OK Returns a full `ConsentBundle` object (same schema as the create response) with updated fields. ### Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `BUNDLE_NOT_FOUND` | The specified bundle does not exist | | 409 | `BUNDLE_REVOKED` | Cannot refresh a revoked bundle | | 409 | `BUNDLE_EXPIRED` | Cannot refresh an expired bundle (create a new one instead) | *** ## Rate Limits | Endpoint | Limit | | - | - | | `POST /v1/consent-bundles` | 10 requests/minute | | `GET /v1/consent-bundles` | 100 requests/minute | | `POST /v1/consent-bundles/:id/revoke` | 20 requests/minute | | `GET /v1/consent-bundles/:id/revocation-status` | 100 requests/minute | | `POST /v1/consent-bundles/:id/refresh` | 10 requests/minute | These are per source address. Every route also draws on a per-developer budget: `POST /v1/consent-bundles/:id/revoke` on the containment budget and `GET /v1/consent-bundles/:id/revocation-status` on the revocation-status budget, neither of which ordinary traffic uses up, and the others on the plan budget. See [Rate Limits](/guides/rate-limits). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Approve consent end user action Source: https://docs.grantex.dev/api-reference/consent/approve-consent-end-user-action post /v1/consent/{id}/approve ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Consent ui page Source: https://docs.grantex.dev/api-reference/consent/consent-ui-page get /consent ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Deny consent end user action Source: https://docs.grantex.dev/api-reference/consent/deny-consent-end-user-action post /v1/consent/{id}/deny ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get consent request details Source: https://docs.grantex.dev/api-reference/consent/get-consent-request-details get /v1/consent/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get credential Source: https://docs.grantex.dev/api-reference/credentials/get-credential get /v1/credentials/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List credentials Source: https://docs.grantex.dev/api-reference/credentials/list-credentials get /v1/credentials ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Status list Source: https://docs.grantex.dev/api-reference/credentials/status-list get /v1/credentials/status/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify credential Source: https://docs.grantex.dev/api-reference/credentials/verify-credential post /v1/credentials/verify ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Did document Source: https://docs.grantex.dev/api-reference/did/did-document get /.well-known/did.json ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Register Custom Domain Source: https://docs.grantex.dev/api-reference/domains/create Register a custom domain on your Grantex account. Returns a token to add as a DNS TXT record. Requires Enterprise plan. Runtime traffic routing on your domain is on the roadmap; today endpoints still resolve under *.grantex.dev. ## Endpoint ``` POST /v1/domains ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `domain` | `string` | Yes | The custom domain to register (e.g. `auth.example.com`) | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/domains \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "domain": "auth.example.com" }' ``` ## Response -- 201 Created ```json theme={null} { "id": "dom_01HXYZ...", "domain": "auth.example.com", "verified": false, "verificationToken": "grantex-verify-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "instructions": "Add a DNS TXT record: _grantex.auth.example.com = grantex-verify-a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `id` | `string` | Unique domain ID | | `domain` | `string` | The registered domain | | `verified` | `boolean` | Whether the domain has been verified (always `false` on creation) | | `verificationToken` | `string` | Token to add as a DNS TXT record for verification | | `instructions` | `string` | Human-readable DNS configuration instructions | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing `domain` field | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 402 | `PLAN_LIMIT_EXCEEDED` | Custom domains require Enterprise plan | | 409 | `CONFLICT` | Domain already registered | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const domain = await grantex.domains.create({ domain: 'auth.example.com' }); console.log(domain.verificationToken); console.log(domain.instructions); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") domain = grantex.domains.create(domain="auth.example.com") print(domain.verification_token) print(domain.instructions) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete Custom Domain Source: https://docs.grantex.dev/api-reference/domains/delete Remove a custom domain registration. ## Endpoint ``` DELETE /v1/domains/:id ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The domain ID to delete (e.g. `dom_01HXYZ...`) | ## Example Request ```bash theme={null} curl -X DELETE https://api.grantex.dev/v1/domains/dom_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 204 No Content No response body is returned on success. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Domain not found or does not belong to developer | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); await grantex.domains.delete('dom_01HXYZ...'); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") grantex.domains.delete("dom_01HXYZ...") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Custom Domains Source: https://docs.grantex.dev/api-reference/domains/list List all custom domains registered by the authenticated developer. ## Endpoint ``` GET /v1/domains ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/domains \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "domains": [ { "id": "dom_01HXYZ...", "domain": "auth.example.com", "verified": true, "verifiedAt": "2026-04-05T15:00:00.000Z", "createdAt": "2026-04-05T12:00:00.000Z" }, { "id": "dom_02ABCD...", "domain": "agents.example.com", "verified": false, "verifiedAt": null, "createdAt": "2026-04-05T13:00:00.000Z" } ] } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `domains` | `array` | Array of domain objects | | `domains[].id` | `string` | Unique domain ID | | `domains[].domain` | `string` | The domain name | | `domains[].verified` | `boolean` | Whether DNS verification has passed | | `domains[].verifiedAt` | `string \| null` | ISO-8601 timestamp of verification, or `null` | | `domains[].createdAt` | `string` | ISO-8601 creation timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const { domains } = await grantex.domains.list(); for (const d of domains) { console.log(`${d.domain} — verified: ${d.verified}`); } ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.domains.list() for d in result.domains: print(f"{d.domain} — verified: {d.verified}") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify Domain DNS Source: https://docs.grantex.dev/api-reference/domains/verify Trigger DNS verification for a registered custom domain. ## Endpoint ``` POST /v1/domains/:id/verify ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The domain ID to verify (e.g. `dom_01HXYZ...`) | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/domains/dom_01HXYZ.../verify \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK When DNS verification succeeds: ```json theme={null} { "verified": true, "message": "Domain verified successfully" } ``` If the domain was already verified: ```json theme={null} { "verified": true, "message": "Domain already verified" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `verified` | `boolean` | Whether the domain is now verified | | `message` | `string` | Human-readable status message | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `VERIFICATION_FAILED` | DNS verification failed -- ensure the TXT record is set correctly | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Domain not found or does not belong to developer | ## Rate Limits This endpoint is limited to **10 requests per minute** to prevent abuse of DNS lookups. ## DNS Setup Before calling this endpoint, add a DNS TXT record: ``` _grantex. = ``` The `verificationToken` is returned by the [Register Domain](/api-reference/domains/create) endpoint. DNS propagation may take up to 24 hours. ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const result = await grantex.domains.verify('dom_01HXYZ...'); console.log(result.verified); // true ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.domains.verify("dom_01HXYZ...") print(result.verified) # True ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Consent Notice Content Source: https://docs.grantex.dev/api-reference/dpdp/consent-notice-content The structured fields of a DPDP consent notice, the r.3 validation block, and the languages a notice may use. A consent notice ([Create Consent Notice](/api-reference/dpdp/create-consent-notice)) can carry structured fields for what DPDP Rules 2025 r.3 asks a notice under DPDP Act 2023 s.5 to contain. Every notice response and read reports which of those elements are present in a `validation` block. These obligations apply from 13 May 2027 (Rules r.1). ## What r.3 asks for A notice is to be understandable on its own and give an itemised description of the personal data, the specific purposes and the goods, services or uses they enable, and a link and means to withdraw consent (as easily as it was given), to exercise the principal's rights and to complain to the Data Protection Board, in English or a language of the Eighth Schedule to the Constitution. ## Fields | Field | Element | Description | | - | - | - | | `itemisedPersonalData` | `itemisedPersonalData` | `[{ category, description }]`, one per item of personal data (up to 100) | | `purposeDetails` | `specificPurposes` | `[{ code, description, goodsOrServices }]`, one per specific purpose; each `code` must be one of the notice's `purposes` | | `withdrawalUrl` | `withdrawalMeans` | An http or https URL where consent is withdrawn | | `rightsUrl` | `rightsMeans` | Where the principal exercises their rights | | `boardComplaintUrl` | `boardComplaintMeans` | How to complain to the Board | | `language` | `language` | English or an Eighth Schedule language (below) | | `contact` | (not an r.3 element) | The Data Protection Officer or person able to answer questions (DPDP Act s.8(9); DPDP Rules 2025 r.9): `{ name?, designation?, email?, phone?, address? }` with an email or a phone | All are optional. Notices created before these fields existed return them as `null`. ## The validation block ```json theme={null} { "basis": "DPDP Rules 2025 r.3", "enforced": false, "complete": false, "present": ["itemisedPersonalData", "specificPurposes", "language"], "missing": ["withdrawalMeans", "rightsMeans", "boardComplaintMeans"], "language": { "tag": "hi-IN", "name": "Hindi", "englishOrEighthSchedule": true } } ``` It checks that each element is present, not that the text is clear or accurate: it is evidence for the fiduciary's own review, not a verdict. By default the block is informational and a notice missing elements is still created. With `DPDP_NOTICE_REQUIRE_RULE3=true` (off by default), a notice missing any element, or in a language outside the list below, is refused with `400 NOTICE_INCOMPLETE` and `enforced` is `true`. ## Languages `language` is a BCP 47 tag. For the `language` element, its primary subtag is compared with the ISO 639 codes below, case-insensitively, so `hi`, `hin` and `hi-IN` are all Hindi. | Language | ISO 639 codes accepted | | - | - | | English | `en`, `eng` | | Assamese | `as`, `asm` | | Bengali | `bn`, `ben` | | Bodo | `brx` | | Dogri | `doi` | | Gujarati | `gu`, `guj` | | Hindi | `hi`, `hin` | | Kannada | `kn`, `kan` | | Kashmiri | `ks`, `kas` | | Konkani | `kok`, `gom` | | Maithili | `mai` | | Malayalam | `ml`, `mal` | | Manipuri | `mni` | | Marathi | `mr`, `mar` | | Nepali | `ne`, `nep` | | Odia | `or`, `ori`, `ory` | | Punjabi | `pa`, `pan` | | Sanskrit | `sa`, `san` | | Santali | `sat` | | Sindhi | `sd`, `snd` | | Tamil | `ta`, `tam` | | Telugu | `te`, `tel` | | Urdu | `ur`, `urd` | ## One version, several languages The same `noticeId` and `version` may be registered once per `language`, each with its own content and hash. A consent record names the language shown in `consentNoticeLanguage` ([Create Consent Record](/api-reference/dpdp/create-consent-record)): without a pinned version it binds the newest notice row in that language, so a translation of an older version registered later does not displace a newer version. Without `consentNoticeLanguage` the record binds, as before, the pinned version (or the version of the newest notice row in any language) and the newest row of that version, and records that row's language. With `DPDP_REQUIRE_NOTICE_LANGUAGE=true` (off by default) a version that exists in more than one language needs `consentNoticeLanguage` instead (`400 NOTICE_LANGUAGE_REQUIRED`). A version in a single language never needs it. ## The notice hash `contentHash` is the SHA-256 of `content` alone, as before. `noticeHash` covers the whole notice: it is the SHA-256 (lowercase hex) of the UTF-8 bytes of the RFC 8785 canonical JSON of an object with the members `noticeId`, `version`, `language`, `title`, `content`, `purposes`, `dataFiduciaryContact`, `grievanceOfficer`, `itemisedPersonalData`, `purposeDetails`, `withdrawalUrl`, `rightsUrl`, `boardComplaintUrl` and `contact`, each as stored and `null` when absent. Two notices with the same text but different structured fields, language or version have different notice hashes. A consent record stores the notice hash of the notice it was given against, and its signed proof carries it as `noticeHash` with `consentNoticeLanguage`. Notices created before the notice hash was kept have it computed from the stored row when read. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create Consent Notice Source: https://docs.grantex.dev/api-reference/dpdp/create-consent-notice Register a version of a consent notice (DPDP Act s.5, DPDP Rules 2025 r.3). Consent records name the version shown; its content hash is bound into the record's signed proof. ## Endpoint ``` POST /v1/dpdp/consent-notices ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `noticeId` | `string` | Yes | A developer-chosen identifier for the notice (e.g., `"privacy-notice"`) | | `version` | `string` | Yes | Version string (e.g., `"2.0"`) | | `title` | `string` | Yes | Human-readable notice title | | `content` | `string` | Yes | Full consent notice content (HTML or plain text) | | `purposes` | `object[]` | Yes | Array of purpose objects (`{ code, description }`) | | `language` | `string` | No | ISO language code (default: `"en"`). One `noticeId` and `version` may be registered once per language | | `dataFiduciaryContact` | `string` | No | Contact information for the data fiduciary | | `grievanceOfficer` | `object` | No | Grievance officer details (`{ name, email, phone? }`) | | `itemisedPersonalData` | `object[]` | No | `{ category, description }` for each item of personal data | | `purposeDetails` | `object[]` | No | `{ code, description, goodsOrServices }` for each specific purpose; `code` must be one of `purposes` | | `withdrawalUrl` | `string` | No | Where consent is withdrawn (http or https URL) | | `rightsUrl` | `string` | No | Where the principal exercises their rights | | `boardComplaintUrl` | `string` | No | How to complain to the Data Protection Board | | `contact` | `object` | No | DPO or contact person: `{ name?, designation?, email?, phone?, address? }`, with an email or a phone | The structured fields and the `validation` block in the response are described in [Consent Notice Content](/api-reference/dpdp/consent-notice-content). ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/consent-notices \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "noticeId": "privacy-notice", "version": "2.0", "title": "Data Processing Consent Notice", "content": "We collect and process your data for the following purposes...", "purposes": [ { "code": "analytics", "description": "Usage analytics for service improvement" }, { "code": "personalization", "description": "Personalized recommendations" } ], "language": "en", "grievanceOfficer": { "name": "Jane Doe", "email": "grievance@example.com" } }' ``` ## Response -- 201 Created ```json theme={null} { "id": "notice_01HXYZ...", "noticeId": "privacy-notice", "version": "2.0", "language": "en", "contentHash": "a1b2c3d4e5f6...", "noticeHash": "9f8e7d6c5b4a...", "createdAt": "2026-04-05T12:00:00.000Z", "validation": { "basis": "DPDP Rules 2025 r.3", "enforced": false, "complete": false, "present": ["language"], "missing": ["itemisedPersonalData", "specificPurposes", "withdrawalMeans", "rightsMeans", "boardComplaintMeans"], "language": { "tag": "en", "name": "English", "englishOrEighthSchedule": true } } } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `id` | `string` | Unique internal notice ID | | `noticeId` | `string` | Developer-chosen notice identifier | | `version` | `string` | Version string | | `language` | `string` | ISO language code | | `contentHash` | `string` | SHA-256 hash of the notice content | | `noticeHash` | `string` | SHA-256 over the canonical JSON of the whole notice, structured fields, language and version included (see [Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)) | | `createdAt` | `string` | ISO-8601 creation timestamp | | `validation` | `object` | Which DPDP Rules 2025 r.3 elements the notice has; see [Consent Notice Content](/api-reference/dpdp/consent-notice-content) | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing or malformed fields (`noticeId`, `version`, `title`, `content`, a non-empty `purposes` array of `{ code, description }`; `content` up to 100,000 characters; malformed structured fields) | | 400 | `NOTICE_INCOMPLETE` | Only with `DPDP_NOTICE_REQUIRE_RULE3=true`: an r.3 element is missing, or the language is not English or an Eighth Schedule language | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 409 | `CONFLICT` | This `noticeId` already has this `version` in this `language`. Only that conflict is a 409; other failures are errors, not conflicts | The notice is recorded on the audit chain as `grantex.dpdp.notice_created`. List notices with [List Consent Notices](/api-reference/dpdp/list-consent-notices) and read every version of one with [Get Consent Notice](/api-reference/dpdp/get-consent-notice). ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const notice = await grantex.dpdp.createConsentNotice({ noticeId: 'privacy-notice', version: '2.0', title: 'Data Processing Consent Notice', content: 'We collect and process your data...', purposes: [ { code: 'analytics', description: 'Usage analytics' }, ], grievanceOfficer: { name: 'Jane Doe', email: 'grievance@example.com', }, }); ``` ```python Python theme={null} from grantex import CreateConsentNoticeParams, Grantex grantex = Grantex(api_key="gx_...") notice = grantex.dpdp.create_consent_notice( CreateConsentNoticeParams( notice_id="privacy-notice", version="2.0", title="Data Processing Consent Notice", content="We collect and process your data...", purposes=[{"code": "analytics", "description": "Usage analytics"}], grievance_officer={"name": "Jane Doe", "email": "grievance@example.com"}, ) ) ``` To send the structured r.3 fields (`itemisedPersonalData`, `purposeDetails`, `withdrawalUrl`, `rightsUrl`, `boardComplaintUrl`, `contact`), use the REST call above unless your SDK version lists them. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create Consent Record Source: https://docs.grantex.dev/api-reference/dpdp/create-consent-record Record a data principal's consent against an active grant and a versioned consent notice, with a signed proof the Data Fiduciary can keep as evidence (DPDP Act s.6(10)). ## Endpoint ``` POST /v1/dpdp/consent-records ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `grantId` | `string` | Yes | The grant to attach the consent record to | | `dataPrincipalId` | `string` | Yes | The data principal (end-user) providing consent | | `purposes` | `object[]` | Yes | Array of purpose objects (`{ code, description }`) | | `consentNoticeId` | `string` | Yes | ID of the consent notice shown to the principal | | `consentNoticeVersion` | `string` | No | The notice version shown to the principal. Default: the latest version of `consentNoticeId` (in `consentNoticeLanguage` when given) | | `consentNoticeLanguage` | `string` | Only with `DPDP_REQUIRE_NOTICE_LANGUAGE=true` and a version in several languages | The language of the notice shown. With it and no pinned version, the newest notice row in this language is bound. Without it, the pinned version (or the version of the newest notice row) is bound through its newest row, whose language is recorded; with `DPDP_REQUIRE_NOTICE_LANGUAGE=true` a version registered in more than one language answers `400 NOTICE_LANGUAGE_REQUIRED` instead | | `processingExpiresAt` | `string` | Yes | ISO-8601 date-time, in the future, when data processing permission expires | The grant must be the developer's and active (not revoked, suspended or expired). `dataPrincipalId` is the end user who gave consent, which in the documented model is the grant's principal (`principal_id`). With `DPDP_ENFORCE_GRANT_PRINCIPAL=true` a record whose `dataPrincipalId` differs from the grant's principal is refused with `PRINCIPAL_MISMATCH`; with the flag off (the default) the two are not compared, and an integration that keys its data principals differently from its grant principals keeps working. Limits: `grantId`, `dataPrincipalId` and `consentNoticeId` up to 256 characters; `consentNoticeVersion` and each purpose `code` up to 128; each purpose `description` up to 1,000; at most 50 purposes. ### Purpose Object | Field | Type | Description | | - | - | - | | `code` | `string` | Machine-readable purpose code (e.g., `"analytics"`, `"personalization"`) | | `description` | `string` | Human-readable description of the purpose | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/consent-records \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "grantId": "grnt_01HXYZ...", "dataPrincipalId": "user_abc123", "purposes": [ { "code": "analytics", "description": "Usage analytics for service improvement" }, { "code": "personalization", "description": "Personalized recommendations" } ], "consentNoticeId": "notice_v2", "processingExpiresAt": "2027-01-01T00:00:00.000Z" }' ``` ## Response -- 201 Created ```json theme={null} { "recordId": "crec_01HXYZ...", "grantId": "grnt_01HXYZ...", "dataPrincipalId": "user_abc123", "consentNoticeId": "notice_v2", "consentNoticeVersion": "2.0", "consentNoticeLanguage": "en", "consentNoticeHash": "a1b2c3d4e5f6...", "noticeHash": "9f8e7d6c5b4a...", "consentProof": { "type": "JWS-EdDSA", "alg": "EdDSA", "kid": "grantex-ed25519-2026-04", "keyPersistence": "persistent", "proofJwt": "eyJ...", "jwksUri": "https://api.grantex.dev/.well-known/jwks.json", "signedAt": "2026-04-05T12:00:00.000Z" }, "processingExpiresAt": "2027-01-01T00:00:00.000Z", "retentionUntil": "2027-01-31T00:00:00.000Z", "status": "active", "createdAt": "2026-04-05T12:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `recordId` | `string` | Unique consent record ID | | `grantId` | `string` | The grant this consent is attached to | | `dataPrincipalId` | `string` | The data principal who gave consent | | `consentNoticeId` | `string` | The consent notice ID | | `consentNoticeVersion` | `string` | The notice version the record was given against | | `consentNoticeLanguage` | `string` | The language of that notice version | | `consentNoticeHash` | `string` | SHA-256 hash of that notice version's content | | `noticeHash` | `string` | The notice hash of that notice: the whole notice, structured fields and language included (see [Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)); stored on the record | | `consentProof` | `object` | Signed proof of consent (see below) | | `processingExpiresAt` | `string` | ISO-8601 timestamp when processing permission expires | | `retentionUntil` | `string` | ISO-8601 timestamp for data retention limit (30 days after processing expiry) | | `status` | `string` | Record status: `active` | | `createdAt` | `string` | ISO-8601 creation timestamp | ### Consent Proof `proofJwt` is a compact JWS (RFC 7515) signed with EdDSA over Ed25519 (RFC 8037). Its payload carries `recordId`, `grantId`, `dataPrincipalId`, `consentNoticeId`, `consentNoticeVersion`, `consentNoticeHash` (the content hash), `consentNoticeLanguage`, `noticeHash` (the whole notice), the purpose codes, `consentGivenAt`, `iss` and `iat`, and no `exp`: the proof is evidence the Data Fiduciary may need for as long as it keeps the record (DPDP Act s.6(10) puts the burden of proving consent on the fiduciary). Verify it with the key the header's `kid` names in the JWKS at `jwksUri`. Set `ED25519_PRIVATE_KEY` in production: without it each process generates its own key, and a proof cannot be verified after a restart or by another instance. `keyPersistence` says which kind of key signed the proof: | Value | Meaning | | - | - | | `persistent` | The server's configured `ED25519_PRIVATE_KEY`. The proof verifies against the published JWKS on every instance and across restarts, for as long as the key is published | | `ephemeral` | A key the serving process generated at boot because `ED25519_PRIVATE_KEY` is not set. The proof is **not verifiable across instances or restarts**: once that process restarts, or on any other instance, the JWKS no longer carries its key. Ask the operator to set `ED25519_PRIVATE_KEY` if proofs must stay verifiable | A server started with `DPDP_REQUIRE_PERSISTENT_PROOF_KEY=true` refuses to create a record while its key is ephemeral (`503 CONSENT_PROOF_KEY_NOT_PERSISTENT`, nothing stored). If the proof cannot be signed, no record is created and the request fails with `503 CONSENT_PROOF_UNAVAILABLE`. The creation is recorded on the developer's audit chain as `grantex.dpdp.consent_created`. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing or malformed fields: `purposes` not a non-empty array of `{ code, description }`, `processingExpiresAt` not a future ISO-8601 date-time, a field over its length limit | | 400 | `INVALID_GRANT` | Grant not found, not owned by the developer, or not active (revoked, suspended or expired) | | 400 | `PRINCIPAL_MISMATCH` | `dataPrincipalId` is not the grant's principal (only with `DPDP_ENFORCE_GRANT_PRINCIPAL=true`) | | 400 | `INVALID_NOTICE` | Consent notice, the pinned `consentNoticeVersion`, or that version in `consentNoticeLanguage`, not found | | 400 | `NOTICE_LANGUAGE_REQUIRED` | The notice version exists in several languages and `consentNoticeLanguage` was not given (only with `DPDP_REQUIRE_NOTICE_LANGUAGE=true`) | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 503 | `CONSENT_PROOF_UNAVAILABLE` | The consent proof could not be signed; nothing was stored | | 503 | `CONSENT_PROOF_KEY_NOT_PERSISTENT` | The signing key is ephemeral and the server sets `DPDP_REQUIRE_PERSISTENT_PROOF_KEY=true`; nothing was stored | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const record = await grantex.dpdp.createConsentRecord({ grantId: 'grnt_01HXYZ...', dataPrincipalId: 'user_abc123', purposes: [ { code: 'analytics', description: 'Usage analytics' }, ], consentNoticeId: 'notice_v2', processingExpiresAt: '2027-01-01T00:00:00.000Z', }); ``` ```python Python theme={null} from grantex import CreateConsentRecordParams, Grantex grantex = Grantex(api_key="gx_...") record = grantex.dpdp.create_consent_record( CreateConsentRecordParams( grant_id="grnt_01HXYZ...", data_principal_id="user_abc123", purposes=[{"code": "analytics", "description": "Usage analytics"}], consent_notice_id="notice_v2", processing_expires_at="2027-01-01T00:00:00.000Z", ) ) ``` To pin `consentNoticeVersion` or `consentNoticeLanguage`, use the REST call above unless your SDK version lists those fields. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create Export Source: https://docs.grantex.dev/api-reference/dpdp/create-export Generate a compliance export: a DPDP audit, a GDPR Article 15 access report for one person, or an EU AI Act evidence pack. ## Endpoint ``` POST /v1/dpdp/exports ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `type` | `string` | Yes | Export type: `dpdp-audit`, `gdpr-article-15`, `eu-ai-act-evidence`, or `eu-ai-act-conformance` (see Export Types) | | `dateFrom` | `string` | Yes | ISO-8601 start date or date-time for the export window | | `dateTo` | `string` | Yes | ISO-8601 end date or date-time; not before `dateFrom` | | `format` | `string` | No | Output format. Only `"json"` (the default) is produced; any other value is `400` | | `includeActionLog` | `boolean` | No | Include audit log entries (default: `true`) | | `includeConsentRecords` | `boolean` | No | Include consent records (default: `true`) | | `dataPrincipalId` | `string` | No | Filter the export to one data principal (see below). Needed for the `article15` block of `gdpr-article-15`, and required for that type when `DPDP_EXPORT_GDPR_REQUIRES_PRINCIPAL=true`. Not accepted for `eu-ai-act-evidence` | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/exports \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "type": "dpdp-audit", "dateFrom": "2026-01-01T00:00:00.000Z", "dateTo": "2026-04-01T00:00:00.000Z", "includeActionLog": true, "includeConsentRecords": true }' ``` ## Response -- 201 Created ```json theme={null} { "exportId": "exp_01HXYZ...", "type": "dpdp-audit", "format": "json", "recordCount": 42, "truncated": false, "auditLogLimit": 1000, "dataPrincipalId": null, "data": { "exportType": "dpdp-audit", "dateRange": { "from": "2026-01-01T00:00:00.000Z", "to": "2026-04-01T00:00:00.000Z" }, "generatedAt": "2026-04-05T14:00:00.000Z", "developerId": "dev_01HXYZ...", "consentRecords": [...], "auditLog": [...], "grievances": [...], "truncated": false, "auditLogLimit": 1000 }, "expiresAt": "2026-04-12T14:00:00.000Z", "createdAt": "2026-04-05T14:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `exportId` | `string` | Unique export ID | | `type` | `string` | Export type | | `format` | `string` | Output format | | `recordCount` | `number` | Total number of records in the export | | `truncated` | `boolean` | `true` when the audit log had more than `auditLogLimit` entries in the window and only the newest `auditLogLimit` are included. For `eu-ai-act-evidence`, `true` when any section left rows out; for `gdpr-article-15` with `dataPrincipalId`, also when the principal had more than 1,000 grievances in the window. The sections and the `article15` block also carry their own `truncated` | | `auditLogLimit` | `number` | The audit log cap (1000) | | `dataPrincipalId` | `string \| null` | The principal the export is filtered to, if any | | `data` | `object` | The export data payload (see below) | | `expiresAt` | `string` | ISO-8601 expiry timestamp (7 days from creation) | | `createdAt` | `string` | ISO-8601 creation timestamp | ### Export Data Object | Field | Type | Description | | - | - | - | | `exportType` | `string` | The export type | | `dateRange` | `object` | `{ from, to }` ISO-8601 timestamps | | `generatedAt` | `string` | ISO-8601 generation timestamp | | `developerId` | `string` | Developer who generated the export | | `consentRecords` | `object[]` | Consent records in the date range (if `includeConsentRecords`) | | `auditLog` | `object[]` | Audit log entries in the date range (if `includeActionLog`, newest first, at most 1000; see `truncated`) | | `grievances` | `object[]` | Grievances in the date range: for `dpdp-audit`, and for `gdpr-article-15` with `dataPrincipalId` (the principal's own, newest first, at most 1,000, with `description`; counted in `recordCount`) | | `truncated` | `boolean` | As above | | `auditLogLimit` | `number` | As above | ### Filtering to a data principal With `dataPrincipalId`, consent records and grievances are those of that principal. `audit_entries.principal_id` is the principal of the grant the entry was written under, which is the DPDP data principal only when an integration keys both the same way (`DPDP_ENFORCE_GRANT_PRINCIPAL=true` enforces it). The audit log therefore includes entries written under the grants of the principal's consent records, entries whose principal is `dataPrincipalId`, and the platform's DPDP audit entries about the principal. (Grievances used to be included for every principal even when the export was filtered.) Creating an export is recorded on the audit chain as `grantex.dpdp.export_created`. Exports expire after 7 days; reading one after that answers `410 GONE` and its data is purged. An erasure of a principal deletes the stored exports about that principal when the server sets `DPDP_ERASURE_EXPANDED=true`; otherwise they are kept until they expire. ## Export Types | Type | Description | Includes Grievances | | - | - | - | | `dpdp-audit` | India DPDP Act audit export | Yes | | `gdpr-article-15` | GDPR right of access (Article 15) report; with `dataPrincipalId` it adds the `article15` block below | With `dataPrincipalId` (that principal's only) | | `eu-ai-act-evidence` | EU AI Act evidence pack: structured sections instead of the generic keys; see [EU AI Act Evidence Pack](/api-reference/dpdp/eu-ai-act-evidence) | No | | `eu-ai-act-conformance` | Kept for compatibility: the generic keys as before, plus the evidence pack's sections when the export is not filtered to a principal. It is not a conformity assessment | No | ### The GDPR Article 15 block A `gdpr-article-15` export with `dataPrincipalId` adds `data.article15`, the information GDPR Art. 15(1) lists, as far as Grantex's records allow. It covers all of the principal's consent records for the developer (up to 1,000), not only those in the date window: | Field | Description | | - | - | | `purposes` | Each purpose code the principal consented to, with the records that name it; `grantPurposes` lists the purposes recorded on the grants | | `recipients` | The agents the principal authorised through grants (consent-record grants and grants whose principal is the data principal), each with its grant ids, scopes and the resource servers (`audiences`) the grants name | | `retention` | Per record `processingExpiresAt`, `retentionUntil`, `withdrawnAt` and `erasedAt`, and how audit entries are kept | | `source` | Per record the consent notice id, version and language, and when consent was given | | `grievances` | `{ count, limit, truncated, statement }` for the grievances copy in `data.grievances`: the principal's grievances filed in the date window | | `automatedDecisionMaking` | `recorded: false`: Grantex does not record GDPR Art. 22 decisions | | `truncated` | `true` when more than 1,000 records, grants or grievances exist | The copy of the data itself is in `consentRecords`, `auditLog` and `grievances`. Personal data the controller processes in its own systems is not held by Grantex. Without `dataPrincipalId` the export is produced as before, without the block. With `DPDP_EXPORT_GDPR_REQUIRES_PRINCIPAL=true` (off by default) a `gdpr-article-15` export without `dataPrincipalId` answers `400`. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing required fields, invalid `type`, an invalid date, `dateFrom` after `dateTo`, a `format` other than `json`, `dataPrincipalId` with `eu-ai-act-evidence`, or (under `DPDP_EXPORT_GDPR_REQUIRES_PRINCIPAL=true`) `gdpr-article-15` without `dataPrincipalId` | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const report = await grantex.dpdp.createExport({ type: 'dpdp-audit', dateFrom: '2026-01-01T00:00:00.000Z', dateTo: '2026-04-01T00:00:00.000Z', }); // report.recordCount → 42 // report.data.consentRecords → [...] ``` ```python Python theme={null} from grantex import CreateExportParams, Grantex grantex = Grantex(api_key="gx_...") report = grantex.dpdp.create_export( CreateExportParams( type="dpdp-audit", date_from="2026-01-01T00:00:00.000Z", date_to="2026-04-01T00:00:00.000Z", ) ) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # EU AI Act Evidence Pack Source: https://docs.grantex.dev/api-reference/dpdp/eu-ai-act-evidence An export that maps what Grantex records to the record-keeping, human oversight, deployer, transparency and incident articles of the EU AI Act. ## Endpoint ``` POST /v1/dpdp/exports ``` with `"type": "eu-ai-act-evidence"`. It takes `dateFrom`, `dateTo` and `format` as [Create Export](/api-reference/dpdp/create-export) describes; it covers the developer's records, so `dataPrincipalId` is refused with `400`. The export is stored, read back and expires like any other. ## What it is, and is not The pack draws on Grantex's records to help evidence obligations under Regulation (EU) 2024/1689 (the AI Act), as amended by Regulation (EU) 2026/1744. **It is not a conformity assessment, a certification or a statement that a system complies**; it supports the operator's own assessment and covers only what Grantex records. Every export carries this as `data.disclaimer`. Whether an obligation applies depends on the operator's role (provider or deployer) and on how its AI system is classified. An authorisation layer is not itself a high-risk AI system; an agent doing work listed in Annex III may be. `data.applicability` gives the dates: | Field | Date | What applies | | - | - | - | | `art50TransparencyFrom` | 2 August 2026 | Art. 50 transparency obligations | | `highRiskAnnexIIIFrom` | 2 December 2027 | High-risk obligations for Annex III (stand-alone) systems | | `highRiskAnnexIFrom` | 2 August 2028 | High-risk obligations for Annex I (product) systems | ## Sections Each section names its data source (`source`, or `sources`) and has a `truncated` flag; the export's top-level `truncated` is `true` when any section left rows out. Periods use `dateFrom` and `dateTo`. ### `art12RecordKeeping` Art. 12 (automatic recording of events) and the log-keeping duties of Art. 19(1) (providers) and Art. 26(6) (deployers). | Field | Description | | - | - | | `source` | `audit_entries`, the developer's hash-chained audit log | | `eventCount` | Exact number of entries in the period | | `firstEventAt`, `lastEventAt` | The time span of those entries | | `chainIntegrity` | The same check as the compliance evidence pack: each entry's hash recomputed and linked to the previous one (`valid`, `checkedEntries`, `firstBrokenAt`, `reason`), with `complete: false` when the period holds more than 50,000 entries and only the oldest were checked | | `actions` | Entry counts by action | | `events` | The newest 1,000 entries | | `retention` | `oldestRetainedEventAt`, `retainedDays` and a statement: Arts. 19(1) and 26(6) require logs to be kept for at least six months, a minimum rather than a maximum; Grantex does not delete or rewrite audit entries, so how long they are kept is the operator's database retention | ### `art14HumanOversight` Art. 14 (human oversight), and Art. 26 for deployers. `sources` lists every table read: | Field | Source | Description | | - | - | - | | `consentDecisions` | `auth_requests` | Authorisation requests created in the period, by status, and how many were FIDO-verified. In sandbox mode requests are approved automatically, and the note says those are not human decisions | | `decisionApprovals` | `decision_requests`, `decision_grants` | Human approvals of individual agent actions: requests by status, approvals issued, consumed and revoked, second approvals | | `paymentApprovals` | `wallet_payment_approval_requests` | Payment approvals decided in the period, by status | | `revocations` | `grants`, `grant_revocation_events` | Grants revoked in the period, and revocation events by action | | `emergencyStops` | `emergency_stops` | Operator overrides in the period (up to 500 listed) | | `consentWithdrawals` | `dpdp_consent_records` | Consents withdrawn in the period | ### `art26Deployer` Art. 26 (obligations of deployers). From `grants` and `agents`: for each agent given grants in the period (up to 500), its name and DID, the grants issued, active and revoked, and the scopes granted. ### `art50Transparency` `recorded: false`. Grantex does not record whether people were told they were interacting with an AI system (Art. 50(1)), whether generated content was marked (Art. 50(2)) or other Art. 50 disclosures; that evidence has to come from the operator's own systems. ### `art73Incidents` From the [breach register](/api-reference/dpdp/record-breach): breaches the fiduciary became aware of in the period (up to 500). These are personal data breaches under DPDP Act 2023 s.8(6), not an Art. 73 classification. Whether any is a serious incident is the operator's decision; under Art. 73 a serious incident is reported not later than 15 days after awareness (2 days for a widespread infringement or one affecting critical infrastructure, 10 days in the event of a death). Grantex does not classify or report incidents. ## Example ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/exports \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "type": "eu-ai-act-evidence", "dateFrom": "2026-08-01", "dateTo": "2026-09-01" }' ``` ```json theme={null} { "exportId": "exp_01HXYZ...", "type": "eu-ai-act-evidence", "format": "json", "recordCount": 214, "truncated": false, "data": { "exportType": "eu-ai-act-evidence", "applicability": { "regulation": "Regulation (EU) 2024/1689 ..., as amended by Regulation (EU) 2026/1744", "art50TransparencyFrom": "2026-08-02", "highRiskAnnexIIIFrom": "2027-12-02", "highRiskAnnexIFrom": "2028-08-02" }, "disclaimer": "This pack is evidence drawn from Grantex records to support the operator's own assessment ...", "art12RecordKeeping": { "eventCount": 180, "chainIntegrity": { "valid": true, "complete": true }, "truncated": false }, "art14HumanOversight": { "sources": ["auth_requests", "..."], "truncated": false }, "art26Deployer": { "agentCount": 3, "truncated": false }, "art50Transparency": { "recorded": false, "truncated": false }, "art73Incidents": { "count": 0, "truncated": false } } } ``` ## `eu-ai-act-conformance` The older type is kept for compatibility. It returns the generic keys (`consentRecords`, `auditLog`) as before and, when the export is not filtered to a data principal, the sections, `applicability` and `disclaimer` above as well. Prefer `eu-ai-act-evidence`. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # File Grievance Source: https://docs.grantex.dev/api-reference/dpdp/file-grievance Record a grievance from a data principal, with the response period the Data Fiduciary publishes (DPDP Act s.13; DPDP Rules 2025 r.14(3): at most 90 days). ## Endpoint ``` POST /v1/dpdp/grievances ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `dataPrincipalId` | `string` | Yes | The data principal filing the grievance | | `type` | `string` | Yes | Grievance type (e.g., `"consent-violation"`, `"data-breach"`, `"unauthorized-processing"`) | | `description` | `string` | Yes | Detailed description of the grievance | | `recordId` | `string` | No | Related consent record ID. It must be one of the developer's records, about this `dataPrincipalId` | | `evidence` | `object` | No | Supporting evidence as a JSON object (up to 16 KB) | | `responsePeriodDays` | `integer` | No | The response period the Data Fiduciary publishes, 1 to 90 days. The default of 7 is a product default, not a statutory period: DPDP Rules 2025 r.14(3) require the fiduciary to publish its period, which may not exceed 90 days | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/grievances \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "dataPrincipalId": "user_abc123", "type": "unauthorized-processing", "description": "Data was processed beyond the consented purposes", "recordId": "crec_01HXYZ...", "evidence": { "observedAction": "marketing emails sent without consent", "timestamp": "2026-04-04T10:00:00.000Z" } }' ``` ## Response -- 202 Accepted ```json theme={null} { "grievanceId": "grv_01HXYZ...", "referenceNumber": "GRV-2026-01JAB3XK7Q9M2V5W8Y0Z4C6D1E", "type": "unauthorized-processing", "status": "submitted", "responsePeriodDays": 7, "expectedResolutionBy": "2026-04-12T14:00:00.000Z", "createdAt": "2026-04-05T14:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `grievanceId` | `string` | Unique grievance ID | | `referenceNumber` | `string` | Human-readable reference number (format: `GRV-YYYY-`, not sequential) | | `type` | `string` | Grievance type | | `status` | `string` | Initial status: `submitted` | | `responsePeriodDays` | `integer` | The response period applied | | `expectedResolutionBy` | `string` | ISO-8601 deadline: filing time plus `responsePeriodDays` | | `createdAt` | `string` | ISO-8601 creation timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing required fields (`dataPrincipalId`, `type`, `description`), `responsePeriodDays` outside 1 to 90, or malformed `evidence` | | 400 | `INVALID_RECORD` | `recordId` is not one of the developer's records, or is about another data principal | | 401 | `UNAUTHORIZED` | Invalid or missing API key | Filing is recorded on the audit chain as `grantex.dpdp.grievance_filed`. Move a grievance through review with [Update Grievance](/api-reference/dpdp/update-grievance) and list grievances with [List Grievances](/api-reference/dpdp/list-grievances). ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const grievance = await grantex.dpdp.fileGrievance({ dataPrincipalId: 'user_abc123', type: 'unauthorized-processing', description: 'Data was processed beyond consented purposes', recordId: 'crec_01HXYZ...', }); // grievance.referenceNumber → "GRV-2026-01JAB3XK7Q9M2V5W8Y0Z4C6D1E" ``` ```python Python theme={null} from grantex import FileGrievanceParams, Grantex grantex = Grantex(api_key="gx_...") grievance = grantex.dpdp.file_grievance( FileGrievanceParams( data_principal_id="user_abc123", type="unauthorized-processing", description="Data was processed beyond consented purposes", record_id="crec_01HXYZ...", ) ) ``` To send `responsePeriodDays`, use the REST call above unless your SDK version lists the field. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Breach Source: https://docs.grantex.dev/api-reference/dpdp/get-breach Read one breach from the DPDP breach register, with its deadlines and the principal intimations recorded against it. ## Endpoint ``` GET /v1/dpdp/breaches/:breachId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `breachId` | `string` | Yes | The breach ID | ## Response -- 200 OK The breach as [Record Breach](/api-reference/dpdp/record-breach) returns it, with the deadlines computed at the time of the read, `principalIntimation` counted over every intimation, and `principalIntimations` listing the newest 200: ```json theme={null} { "breachId": "brch_01HXYZ...", "status": "initial_intimated", "boardDetailedReportDueAt": "2027-06-04T10:00:00.000Z", "boardDetailedReportOverdue": false, "principalIntimation": { "due": "without_delay", "intimatedCount": 2, "pendingCount": 1, "lastIntimatedAt": "2027-06-01T12:00:00.000Z" }, "principalIntimations": [ { "intimationId": "bint_01HXYZ...", "channel": "email", "intimatedAt": "2027-06-01T12:00:00.000Z", "principalCount": 2, "contentIncluded": ["description", "likely_consequences", "mitigation", "safety_measures", "contact"], "contentMissing": [], "createdAt": "2027-06-01T12:01:00.000Z" } ] } ``` ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Breach not found | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Consent Notice Source: https://docs.grantex.dev/api-reference/dpdp/get-consent-notice Read every version of one DPDP consent notice, with its content, newest first. ## Endpoint ``` GET /v1/dpdp/consent-notices/:noticeId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `noticeId` | `string` | Yes | The notice ID given when the notice was created | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/dpdp/consent-notices/data-processing \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "noticeId": "data-processing", "versions": [ { "id": "notice_01HXYZ...", "version": "2.0", "language": "en", "title": "Data Processing Notice", "content": "We process your data for service delivery and analytics.", "purposes": [ { "code": "service", "description": "Service delivery" } ], "dataFiduciaryContact": "privacy@merchant.example", "grievanceOfficer": { "name": "Grievance Officer", "email": "grievance@merchant.example" }, "contentHash": "a1b2c3d4e5f6...", "noticeHash": "9f8e7d6c5b4a...", "createdAt": "2026-04-05T12:00:00.000Z" } ] } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `noticeId` | `string` | The notice ID | | `versions` | `object[]` | Every version of the notice, newest first | ### Version Object | Field | Type | Description | | - | - | - | | `id` | `string` | The stored version's ID | | `version` | `string` | The version | | `language` | `string` | Language code | | `title` | `string` | Title | | `content` | `string` | The notice text | | `purposes` | `object[]` | `{ code, description }` purposes | | `dataFiduciaryContact` | `string \| null` | Contact given when the version was created | | `grievanceOfficer` | `object \| null` | `{ name, email, phone? }` given when the version was created | | `contentHash` | `string` | SHA-256 hash of `content`; a consent record's `consentNoticeHash` matches the version it was given against | | `noticeHash` | `string` | SHA-256 over the canonical JSON of the whole notice (see [Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)); a consent record's `noticeHash` matches the notice it was given against | | `createdAt` | `string` | ISO-8601 creation timestamp | Each version also carries the structured fields (`itemisedPersonalData`, `purposeDetails`, `withdrawalUrl`, `rightsUrl`, `boardComplaintUrl`, `contact`; `null` when not given) and a `validation` block computed as of the read; see [Consent Notice Content](/api-reference/dpdp/consent-notice-content). ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | The developer has no notice with this ID | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Consent Record Source: https://docs.grantex.dev/api-reference/dpdp/get-consent-record Retrieve a single DPDP consent record by ID. A developer read has no side effects. ## Endpoint ``` GET /v1/dpdp/consent-records/:recordId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `recordId` | `string` | Yes | The consent record ID | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/dpdp/consent-records/crec_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "recordId": "crec_01HXYZ...", "grantId": "grnt_01HXYZ...", "dataPrincipalId": "user_abc123", "dataFiduciaryName": "Example Retail Ltd", "purposes": [ { "code": "analytics", "description": "Usage analytics for service improvement" } ], "scopes": ["calendar:read", "email:send"], "consentNoticeId": "notice_v2", "consentNoticeVersion": "2.0", "consentNoticeLanguage": "en", "noticeHash": "9f8e7d6c5b4a...", "status": "active", "consentGivenAt": "2026-04-05T12:00:00.000Z", "processingExpiresAt": "2027-01-01T00:00:00.000Z", "retentionUntil": "2027-01-31T00:00:00.000Z", "accessCount": 0, "lastAccessedAt": null, "withdrawnAt": null, "withdrawnReason": null, "erasedAt": null, "createdAt": "2026-04-05T12:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `recordId` | `string` | Unique consent record ID | | `grantId` | `string` | The associated grant ID | | `dataPrincipalId` | `string` | The data principal who gave consent | | `dataFiduciaryName` | `string` | Name of the data fiduciary (developer) | | `purposes` | `object[]` | Array of purpose objects (`{ code, description }`) | | `scopes` | `string[]` | Scopes from the associated grant | | `consentNoticeId` | `string` | ID of the consent notice | | `consentNoticeVersion` | `string \| null` | The notice version the record was given against (`null` for an older record whose version could not be recovered) | | `consentNoticeLanguage` | `string \| null` | The language of that notice (`null` when not known for an older record) | | `noticeHash` | `string \| null` | The notice hash of that notice, also signed into the proof ([Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)); `null` for records created before it was kept | | `status` | `string` | Record status: `active`, `withdrawn`, `expired`, or `erased` | | `consentGivenAt` | `string` | ISO-8601 timestamp of original consent | | `processingExpiresAt` | `string` | ISO-8601 processing expiry timestamp | | `retentionUntil` | `string` | ISO-8601 data retention limit | | `accessCount` | `number` | The stored access count. Developer reads through this API do not change it | | `lastAccessedAt` | `string \| null` | The stored last-access timestamp, `null` if never set. Developer reads do not change it | | `withdrawnAt` | `string \| null` | ISO-8601 timestamp of withdrawal (if withdrawn) | | `withdrawnReason` | `string \| null` | Reason for withdrawal (if withdrawn) | | `erasedAt` | `string \| null` | ISO-8601 timestamp of erasure (if erased) | | `createdAt` | `string` | ISO-8601 creation timestamp | This is a developer read: it has no side effects. It used to increment `accessCount` and set `lastAccessedAt` on every call. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Consent record not found | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const record = await grantex.dpdp.getConsentRecord('crec_01HXYZ...'); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") record = grantex.dpdp.get_consent_record("crec_01HXYZ...") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Erasure Request Source: https://docs.grantex.dev/api-reference/dpdp/get-erasure-request Read a completed DPDP erasure request: what was erased, what was revoked, and what was retained and why. ## Endpoint ``` GET /v1/dpdp/erasure-requests/:requestId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `requestId` | `string` | Yes | The `requestId` [Request Erasure](/api-reference/dpdp/request-erasure) returned | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/dpdp/erasure-requests/ER-2026-01J9Z6ZC4Q8F2V1K3M5N7P9R0S \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK The same object [Request Erasure](/api-reference/dpdp/request-erasure) returned: `requestId`, `dataPrincipalId`, `status`, `recordsErased`, `grantsRevoked`, `delegatedGrantsRevoked`, `grievancesRedacted`, `exportsDeleted`, `retained`, `submittedAt`, `completedAt` and the deprecated `expectedCompletionBy` (equal to `completedAt`). ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | No such request for this developer | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Export Source: https://docs.grantex.dev/api-reference/dpdp/get-export Retrieve a previously generated compliance export by ID. ## Endpoint ``` GET /v1/dpdp/exports/:exportId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `exportId` | `string` | Yes | The export ID | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/dpdp/exports/exp_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "exportId": "exp_01HXYZ...", "type": "dpdp-audit", "dateFrom": "2026-01-01T00:00:00.000Z", "dateTo": "2026-04-01T00:00:00.000Z", "format": "json", "status": "complete", "recordCount": 42, "truncated": false, "auditLogLimit": 1000, "dataPrincipalId": null, "data": { "exportType": "dpdp-audit", "dateRange": { "from": "2026-01-01T00:00:00.000Z", "to": "2026-04-01T00:00:00.000Z" }, "generatedAt": "2026-04-05T14:00:00.000Z", "developerId": "dev_01HXYZ...", "consentRecords": [...], "auditLog": [...], "grievances": [...] }, "expiresAt": "2026-04-12T14:00:00.000Z", "createdAt": "2026-04-05T14:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `exportId` | `string` | Unique export ID | | `type` | `string` | Export type: `dpdp-audit`, `gdpr-article-15`, or `eu-ai-act-conformance` | | `dateFrom` | `string` | ISO-8601 start date of the export window | | `dateTo` | `string` | ISO-8601 end date of the export window | | `format` | `string` | Output format | | `status` | `string` | Export status: `complete` | | `recordCount` | `number` | Total number of records in the export | | `truncated` | `boolean` | Whether the audit log hit the 1000-entry cap | | `auditLogLimit` | `number` | The audit log cap (1000) | | `dataPrincipalId` | `string \| null` | The principal the export is filtered to, if any | | `data` | `object` | The export data payload | | `expiresAt` | `string` | ISO-8601 expiry timestamp (exports expire after 7 days) | | `createdAt` | `string` | ISO-8601 creation timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Export not found (or deleted by an erasure) | | 410 | `GONE` | The export has expired; its data was purged and is not served | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const report = await grantex.dpdp.getExport('exp_01HXYZ...'); // report.status → "complete" // report.data → { consentRecords: [...], auditLog: [...] } ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") report = grantex.dpdp.get_export("exp_01HXYZ...") print(report.status, report.record_count) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Grievance Source: https://docs.grantex.dev/api-reference/dpdp/get-grievance Retrieve the status and details of a DPDP grievance by ID. ## Endpoint ``` GET /v1/dpdp/grievances/:grievanceId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `grievanceId` | `string` | Yes | The grievance ID | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/dpdp/grievances/grv_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "grievanceId": "grv_01HXYZ...", "dataPrincipalId": "user_abc123", "recordId": "crec_01HXYZ...", "type": "unauthorized-processing", "description": "Data was processed beyond the consented purposes", "evidence": { "observedAction": "marketing emails sent without consent", "timestamp": "2026-04-04T10:00:00.000Z" }, "status": "submitted", "referenceNumber": "GRV-2026-01JAB3XK7Q9M2V5W8Y0Z4C6D1E", "expectedResolutionBy": "2026-04-12T14:00:00.000Z", "responsePeriodDays": 7, "resolvedAt": null, "resolution": null, "createdAt": "2026-04-05T14:00:00.000Z", "updatedAt": null } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `grievanceId` | `string` | Unique grievance ID | | `dataPrincipalId` | `string` | The data principal who filed the grievance | | `recordId` | `string \| null` | Related consent record ID (if applicable) | | `type` | `string` | Grievance type | | `description` | `string` | Detailed description | | `evidence` | `object` | Supporting evidence | | `status` | `string` | Current status: `submitted`, `in_review`, `resolved`, or `rejected` | | `referenceNumber` | `string` | Human-readable reference number | | `expectedResolutionBy` | `string` | ISO-8601 resolution deadline | | `responsePeriodDays` | `integer` | The published response period applied (days) | | `resolvedAt` | `string \| null` | ISO-8601 resolution timestamp (if resolved) | | `resolution` | `string \| null` | Resolution details (if resolved) | | `createdAt` | `string` | ISO-8601 creation timestamp | | `updatedAt` | `string \| null` | ISO-8601 timestamp of the last status change or redaction | If the data principal's data was erased, `description` and `evidence` hold a fixed marker instead of the original text. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Grievance not found | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const grievance = await grantex.dpdp.getGrievance('grv_01HXYZ...'); // grievance.status → "submitted" | "in_review" | "resolved" | "rejected" ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") grievance = grantex.dpdp.get_grievance("grv_01HXYZ...") print(grievance.status, grievance.reference_number) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Breaches Source: https://docs.grantex.dev/api-reference/dpdp/list-breaches List the breaches in the DPDP breach register, newest first, optionally by status. ## Endpoint ``` GET /v1/dpdp/breaches ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `status` | `string` | No | `open`, `initial_intimated`, `reported` or `closed` | | `limit` | `integer` | No | 1 to 200, default 50 | | `cursor` | `string` | No | The `nextCursor` of the previous page | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/dpdp/breaches?status=open&limit=20" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "breaches": [ { "breachId": "brch_01HXYZ...", "status": "open", "awareAt": "2027-06-01T10:00:00.000Z", "affectedCount": 3, "boardDetailedReportDueAt": "2027-06-04T10:00:00.000Z", "boardDetailedReportOverdue": false, "principalIntimationRequired": true } ], "nextCursor": null } ``` Each item has the fields [Record Breach](/api-reference/dpdp/record-breach) returns (shortened above), without `affectedDataPrincipalIds`, `principalIntimation` and `principalIntimations`; read one breach for those. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Unknown `status`, invalid `limit` or `cursor` | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Consent Notices Source: https://docs.grantex.dev/api-reference/dpdp/list-consent-notices List the developer's DPDP consent notice versions, newest first, one page at a time. ## Endpoint ``` GET /v1/dpdp/consent-notices ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `limit` | `integer` | No | Page size, 1 to 200 (default 50) | | `cursor` | `string` | No | The `nextCursor` of the previous page | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/dpdp/consent-notices?limit=20" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "notices": [ { "id": "notice_01HXYZ...", "noticeId": "data-processing", "version": "2.0", "language": "en", "title": "Data Processing Notice", "contentHash": "a1b2c3d4e5f6...", "noticeHash": "9f8e7d6c5b4a...", "createdAt": "2026-04-05T12:00:00.000Z" } ], "nextCursor": null } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `notices` | `object[]` | One page of notice versions, newest first; each version of a notice is its own entry | | `nextCursor` | `string \| null` | Pass as `cursor` for the next page; `null` on the last page | ### Notice Object | Field | Type | Description | | - | - | - | | `id` | `string` | The stored notice version's ID | | `noticeId` | `string` | The notice ID consent records refer to | | `version` | `string` | The version | | `language` | `string` | Language code | | `title` | `string` | Title | | `contentHash` | `string` | SHA-256 hash of the content | | `noticeHash` | `string` | SHA-256 over the canonical JSON of the whole notice (see [Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)) | | `createdAt` | `string` | ISO-8601 creation timestamp | Read a notice's full text with [Get Consent Notice](/api-reference/dpdp/get-consent-notice). Each version also carries the structured fields (`itemisedPersonalData`, `purposeDetails`, `withdrawalUrl`, `rightsUrl`, `boardComplaintUrl`, `contact`; `null` when not given) and a `validation` block computed as of the read; see [Consent Notice Content](/api-reference/dpdp/consent-notice-content). ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | `limit` outside 1 to 200, or an invalid `cursor` | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Consent Records Source: https://docs.grantex.dev/api-reference/dpdp/list-consent-records List all DPDP consent records for the developer, with optional filtering by data principal. ## Endpoint ``` GET /v1/dpdp/consent-records ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `dataPrincipalId` | `string` | No | Filter records by data principal ID | | `limit` | `integer` | No | Page size, 1 to 200 (default 50 once paging). Sending `limit` or `cursor` turns paging on | | `cursor` | `string` | No | The `nextCursor` of the previous page | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/dpdp/consent-records?dataPrincipalId=user_abc123" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "records": [ { "recordId": "crec_01HXYZ...", "grantId": "grnt_01HXYZ...", "dataPrincipalId": "user_abc123", "dataFiduciaryName": "Example Retail Ltd", "purposes": [ { "code": "analytics", "description": "Usage analytics" } ], "scopes": ["calendar:read"], "consentNoticeId": "notice_v2", "consentNoticeVersion": "2.0", "consentNoticeLanguage": "en", "noticeHash": "9f8e7d6c5b4a...", "status": "active", "consentGivenAt": "2026-04-05T12:00:00.000Z", "processingExpiresAt": "2027-01-01T00:00:00.000Z", "retentionUntil": "2027-01-31T00:00:00.000Z", "accessCount": 0, "lastAccessedAt": null, "withdrawnAt": null, "withdrawnReason": null, "erasedAt": null, "createdAt": "2026-04-05T12:00:00.000Z" } ], "totalRecords": 1, "nextCursor": null } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `records` | `object[]` | One page of consent records, newest first | | `totalRecords` | `number` | Total number of records matching the filter, across all pages | | `nextCursor` | `string \| null` | Pass as `cursor` for the next page; `null` on the last page | Without `limit` or `cursor` the list returns what it always did: the newest 100 records unfiltered (with a `nextCursor` when there are more), and every matching record with `dataPrincipalId` (`nextCursor` is `null`). Sending `limit` or `cursor` pages: 50 records by default and at most 200; follow `nextCursor` to read them all. ### Record Object | Field | Type | Description | | - | - | - | | `recordId` | `string` | Unique consent record ID | | `grantId` | `string` | The associated grant ID | | `dataPrincipalId` | `string` | The data principal who gave consent | | `dataFiduciaryName` | `string` | Name of the data fiduciary (developer) | | `purposes` | `object[]` | Array of purpose objects (`{ code, description }`) | | `scopes` | `string[]` | Scopes from the associated grant | | `consentNoticeId` | `string` | ID of the consent notice | | `consentNoticeVersion` | `string \| null` | The notice version the record was given against (`null` for an older record whose version could not be recovered) | | `consentNoticeLanguage` | `string \| null` | The language of that notice (`null` when not known for an older record) | | `noticeHash` | `string \| null` | The notice hash of that notice, also signed into the proof ([Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)); `null` for records created before it was kept | | `status` | `string` | Record status: `active`, `withdrawn`, `expired`, or `erased` | | `consentGivenAt` | `string` | ISO-8601 timestamp of original consent | | `processingExpiresAt` | `string` | ISO-8601 processing expiry timestamp | | `retentionUntil` | `string` | ISO-8601 data retention limit | | `accessCount` | `number` | The stored access count. Developer reads through this API do not change it | | `lastAccessedAt` | `string \| null` | The stored last-access timestamp, `null` if never set. Developer reads do not change it | | `withdrawnAt` | `string \| null` | ISO-8601 timestamp of withdrawal (if withdrawn) | | `withdrawnReason` | `string \| null` | Reason for withdrawal (if withdrawn) | | `erasedAt` | `string \| null` | ISO-8601 timestamp of erasure (if erased) | | `createdAt` | `string` | ISO-8601 creation timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); // List all consent records const all = await grantex.dpdp.listConsentRecords(); // Filter by data principal const filtered = await grantex.dpdp.listConsentRecords('user_abc123'); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") # List all consent records all_records = grantex.dpdp.list_consent_records() # Filter by data principal filtered = grantex.dpdp.list_consent_records(principal_id="user_abc123") ``` These SDK calls read the first page. To pass `limit` and `cursor`, use the REST call above unless your SDK version lists them. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Grievances Source: https://docs.grantex.dev/api-reference/dpdp/list-grievances List the developer's DPDP grievances, newest first, filtered by status or data principal. ## Endpoint ``` GET /v1/dpdp/grievances ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `status` | `string` | No | `submitted`, `in_review`, `resolved` or `rejected` | | `dataPrincipalId` | `string` | No | Only this data principal's grievances | | `limit` | `integer` | No | Page size, 1 to 200 (default 50) | | `cursor` | `string` | No | The `nextCursor` of the previous page | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/dpdp/grievances?status=submitted" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "grievances": [ { "grievanceId": "grv_01HXYZ...", "dataPrincipalId": "user_abc123", "recordId": null, "type": "consent-violation", "status": "submitted", "referenceNumber": "GRV-2026-01J9Z6ZC4Q8F2V1K3M5N7P9R0S", "expectedResolutionBy": "2026-04-12T14:00:00.000Z", "responsePeriodDays": 7, "resolvedAt": null, "resolution": null, "createdAt": "2026-04-05T14:00:00.000Z", "updatedAt": null } ], "nextCursor": null } ``` List entries leave out `description` and `evidence`; read them with [Get Grievance](/api-reference/dpdp/get-grievance). ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Unknown `status`, `limit` outside 1 to 200, or an invalid `cursor` | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Principal Records Source: https://docs.grantex.dev/api-reference/dpdp/list-principal-records List the consent records held about one data principal. Supports a response to an access request under DPDP Act s.11. ## Endpoint ``` GET /v1/dpdp/data-principals/:principalId/records ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `principalId` | `string` | Yes | The data principal ID | ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `limit` | `integer` | No | Page size, 1 to 200 (default 50 once paging). Sending `limit` or `cursor` turns paging on | | `cursor` | `string` | No | The `nextCursor` of the previous page | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/dpdp/data-principals/user_abc123/records \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "dataPrincipalId": "user_abc123", "records": [ { "recordId": "crec_01HXYZ...", "grantId": "grnt_01HXYZ...", "dataFiduciaryName": "Example Retail Ltd", "purposes": [ { "code": "analytics", "description": "Usage analytics" } ], "scopes": ["calendar:read"], "consentNoticeId": "notice_v2", "consentNoticeVersion": "2.0", "consentNoticeLanguage": "en", "noticeHash": "9f8e7d6c5b4a...", "status": "active", "consentGivenAt": "2026-04-05T12:00:00.000Z", "processingExpiresAt": "2027-01-01T00:00:00.000Z", "retentionUntil": "2027-01-31T00:00:00.000Z", "accessCount": 0, "lastAccessedAt": null, "withdrawnAt": null, "withdrawnReason": null, "erasedAt": null, "createdAt": "2026-04-05T12:00:00.000Z" } ], "totalRecords": 1, "nextCursor": null } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `dataPrincipalId` | `string` | The data principal ID | | `records` | `object[]` | One page of consent records, newest first | | `totalRecords` | `number` | Total number of the principal's records, across all pages | | `nextCursor` | `string \| null` | Pass as `cursor` for the next page; `null` on the last page | Without `limit` or `cursor` every record of the principal is returned, as before, and `nextCursor` is `null`. Sending `limit` or `cursor` pages: 50 records by default and at most 200. ### Record Object | Field | Type | Description | | - | - | - | | `recordId` | `string` | Unique consent record ID | | `grantId` | `string` | The associated grant ID | | `dataFiduciaryName` | `string` | Name of the data fiduciary (developer) | | `purposes` | `object[]` | Array of purpose objects (`{ code, description }`) | | `scopes` | `string[]` | Scopes from the associated grant | | `consentNoticeId` | `string` | ID of the consent notice | | `consentNoticeVersion` | `string \| null` | The notice version the record was given against (`null` for an older record whose version could not be recovered) | | `consentNoticeLanguage` | `string \| null` | The language of that notice (`null` when not known for an older record) | | `noticeHash` | `string \| null` | The notice hash of that notice, also signed into the proof ([Consent Notice Content](/api-reference/dpdp/consent-notice-content#the-notice-hash)); `null` for records created before it was kept | | `status` | `string` | Record status: `active`, `withdrawn`, `expired`, or `erased` | | `consentGivenAt` | `string` | ISO-8601 timestamp of original consent | | `processingExpiresAt` | `string` | ISO-8601 processing expiry timestamp | | `retentionUntil` | `string` | ISO-8601 data retention limit | | `accessCount` | `number` | The stored access count. Developer reads through this API do not change it | | `lastAccessedAt` | `string \| null` | The stored last-access timestamp, `null` if never set. Developer reads do not change it | | `withdrawnAt` | `string \| null` | ISO-8601 timestamp of withdrawal (if withdrawn) | | `withdrawnReason` | `string \| null` | Reason for withdrawal (if withdrawn) | | `erasedAt` | `string \| null` | ISO-8601 timestamp of erasure (if erased) | | `createdAt` | `string` | ISO-8601 creation timestamp | This is a developer read, not the data principal's own access: it has no side effects and does not change `accessCount` or `lastAccessedAt`. (It used to increment them on every call.) ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const { records, totalRecords } = await grantex.dpdp.listPrincipalRecords('user_abc123'); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.dpdp.list_principal_records("user_abc123") print(result.records, result.total_records) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Record Breach Source: https://docs.grantex.dev/api-reference/dpdp/record-breach Record a personal data breach in the DPDP breach register, with the Board and Data Principal deadlines computed. ## Endpoint ``` POST /v1/dpdp/breaches ``` ## Authentication Requires a developer API key in the `Authorization` header. ## What this does and does not do Under the Digital Personal Data Protection Act, 2023, s.8(6) and the DPDP Rules, 2025, r.7, a Data Fiduciary that becomes aware of a personal data breach informs each affected Data Principal without delay, informs the Data Protection Board without delay, and sends the Board a detailed report within 72 hours of becoming aware (extendable on written request). There is no risk threshold. These obligations apply from 13 May 2027 (Rules r.1). This endpoint keeps the fiduciary's register and computes the deadlines. **Grantex does not notify Data Principals and does not file anything with the Board.** It records what the fiduciary tells it it sent, and emits webhook events the fiduciary can act on. ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `description` | `string` | Yes | What happened (up to 5,000 characters) | | `nature` | `string` | Yes | The nature of the breach, for example `confidentiality` (up to 1,000 characters) | | `extent` | `string` | Yes | Its extent (up to 5,000 characters) | | `occurredAt` | `string` | No | ISO 8601 date-time the breach occurred; not in the future and not after `awareAt` | | `awareAt` | `string` | No | When the fiduciary became aware. Defaults to now; not in the future. The 72-hour period runs from here | | `location` | `string` | No | Where it occurred (up to 1,000 characters) | | `likelyImpact` | `string` | No | Likely consequences for Data Principals (up to 5,000 characters) | | `affectedDataPrincipalIds` | `string[]` | One of these two | Up to 10,000 affected principal ids | | `affectedCount` | `integer` | One of these two | Number affected; defaults to the number of ids and may not be smaller | | `mitigation` | `string` | No | Mitigation measures taken (up to 5,000 characters) | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/breaches \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "description": "Unauthorised read of an order history export", "nature": "confidentiality", "extent": "Order history of three customers", "occurredAt": "2027-06-01T08:00:00Z", "awareAt": "2027-06-01T10:00:00Z", "affectedDataPrincipalIds": ["user_abc123", "user_def456", "user_ghi789"], "mitigation": "Credential revoked and storage policy narrowed" }' ``` ## Response -- 201 Created ```json theme={null} { "breachId": "brch_01HXYZ...", "status": "open", "description": "Unauthorised read of an order history export", "nature": "confidentiality", "extent": "Order history of three customers", "occurredAt": "2027-06-01T08:00:00.000Z", "awareAt": "2027-06-01T10:00:00.000Z", "location": null, "likelyImpact": null, "affectedCount": 3, "affectedDataPrincipalIds": ["user_abc123", "user_def456", "user_ghi789"], "mitigation": "Credential revoked and storage policy narrowed", "detailedReport": { "updatedDetails": null, "factsCircumstancesReasons": null, "mitigation": null, "causeFindings": null, "remedialMeasures": null }, "boardInitialIntimationRequired": true, "boardInitialIntimationSentAt": null, "boardDetailedReportDueAt": "2027-06-04T10:00:00.000Z", "boardDetailedReportSentAt": null, "boardDetailedReportOverdue": false, "boardDetailedReportLate": false, "extension": null, "principalIntimationRequired": true, "principalIntimation": { "due": "without_delay", "intimatedCount": 0, "pendingCount": 3, "lastIntimatedAt": null }, "principalIntimations": [], "createdAt": "2027-06-01T10:05:00.000Z", "updatedAt": null, "closedAt": null } ``` | Field | Description | | - | - | | `boardDetailedReportDueAt` | `awareAt` + 72 hours, or the granted extension's `newDueAt` | | `boardDetailedReportOverdue` | `true` when no detailed report is recorded as sent and the due time has passed | | `principalIntimationRequired` | Always `true`: each affected principal is to be informed without delay | | `principalIntimation.pendingCount` | Listed principals not yet recorded as intimated; `null` when only a count was given | ## Events and audit The breach is recorded on the audit chain as `grantex.dpdp.breach_recorded`, and two webhook events are emitted: `dpdp.breach.recorded` (`breachId`, `status`, `awareAt`, `affectedCount`, `boardDetailedReportDueAt`) and `dpdp.breach.principal_intimation_due` (`breachId`, `awareAt`, `affectedCount`, `due: "without_delay"`). Neither carries principal ids or the breach text. With `DPDP_BREACH_DEADLINE_ALERTS_ENABLED=true`, a worker emits `dpdp.breach.board_report_due` before and after the 72-hour deadline (see [Self-hosting](/guides/self-hosting)). ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing or malformed fields, a time in the future, `occurredAt` after `awareAt`, neither ids nor a count, or a count below the number of ids | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## Related [List Breaches](/api-reference/dpdp/list-breaches), [Get Breach](/api-reference/dpdp/get-breach), [Update Breach](/api-reference/dpdp/update-breach), [Record Principal Intimation](/api-reference/dpdp/record-breach-intimation). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Record Principal Intimation Source: https://docs.grantex.dev/api-reference/dpdp/record-breach-intimation Record that the Data Fiduciary informed affected Data Principals of a breach: who, by which channel, when, and with which content. ## Endpoint ``` POST /v1/dpdp/breaches/:breachId/principal-intimations ``` ## Authentication Requires a developer API key in the `Authorization` header. Grantex does not send the intimation. The fiduciary sends it through its own channels and records here that it did. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `breachId` | `string` | Yes | The breach ID | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `dataPrincipalIds` | `string[]` | Yes | 1 to 10,000 principal ids. When the breach lists its affected principals, each must be among them | | `channel` | `string` | Yes | How they were told, for example `email`, `sms`, `in_app` or `post` | | `intimatedAt` | `string` | No | ISO 8601 date-time; default now; not before the breach's `awareAt`, not in the future | | `contentIncluded` | `string[]` | Yes | Which of the DPDP Rules 2025 r.7(1)(a)-(e) elements the intimation carried (below) | | Value | Element | | - | - | | `description` | A description of the breach: its nature, extent, and the timing and location of its occurrence | | `likely_consequences` | The consequences relevant to the principal that are likely to arise | | `mitigation` | The mitigation measures implemented or being implemented | | `safety_measures` | Safety measures the principal may take | | `contact` | Business contact information of a person able to respond on the fiduciary's behalf | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/breaches/brch_01HXYZ.../principal-intimations \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "dataPrincipalIds": ["user_abc123", "user_def456"], "channel": "email", "contentIncluded": ["description", "likely_consequences", "mitigation", "safety_measures", "contact"] }' ``` ## Response -- 201 Created ```json theme={null} { "breachId": "brch_01HXYZ...", "intimationId": "bint_01HXYZ...", "channel": "email", "intimatedAt": "2027-06-01T12:00:00.000Z", "principalCount": 2, "contentIncluded": ["description", "likely_consequences", "mitigation", "safety_measures", "contact"], "contentMissing": [], "createdAt": "2027-06-01T12:00:00.000Z", "principalIntimation": { "due": "without_delay", "intimatedCount": 2, "pendingCount": 1, "lastIntimatedAt": "2027-06-01T12:00:00.000Z" } } ``` `contentMissing` lists the elements the intimation did not carry. The intimation is recorded on the audit chain as `grantex.dpdp.breach_principals_intimated`, with the count of principals, not their ids. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing or malformed fields, unknown content element, a principal not listed on the breach, or a time before `awareAt` or in the future | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Breach not found | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Request Erasure Source: https://docs.grantex.dev/api-reference/dpdp/request-erasure Act on a data principal's erasure request (DPDP Act s.12): revokes active grants, marks consent records erased, and reports what is retained and why; with expanded erasure enabled it also redacts grievances and deletes stored exports. ## Endpoint ``` POST /v1/dpdp/data-principals/:principalId/erasure ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `principalId` | `string` | Yes | The data principal requesting erasure | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/data-principals/user_abc123/erasure \ -H "Authorization: Bearer gx_..." ``` ## Response -- 201 Created `201` for a new erasure. Repeating the request for a principal whose records are all erased already returns the completed request with `200` instead of creating another. ```json theme={null} { "requestId": "ER-2026-01J9Z6ZC4Q8F2V1K3M5N7P9R0S", "dataPrincipalId": "user_abc123", "status": "completed", "recordsErased": 3, "grantsRevoked": 2, "delegatedGrantsRevoked": 0, "grievancesRedacted": 0, "exportsDeleted": 0, "retained": [ { "category": "consent_records", "count": 3, "reason": "Kept and marked erased, not deleted: ..." }, { "category": "audit_log", "reason": "Audit entries are neither modified nor deleted: ..." }, { "category": "grievances", "count": 1, "reason": "Kept unchanged, description and evidence included, ... Expanded erasure is not enabled on this deployment (DPDP_ERASURE_EXPANDED), so they were not redacted." }, { "category": "stored_exports", "count": 1, "reason": "Stored compliance exports about the data principal are kept until they expire ... so they were not deleted." }, { "category": "fiduciary_data", "reason": "Grantex holds no personal data the Data Fiduciary processed ..." } ], "submittedAt": "2026-04-05T14:00:00.000Z", "completedAt": "2026-04-05T14:00:00.120Z", "expectedCompletionBy": "2026-04-05T14:00:00.120Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `requestId` | `string` | Erasure request reference (format: `ER-YYYY-`); read it back with [Get Erasure Request](/api-reference/dpdp/get-erasure-request) | | `dataPrincipalId` | `string` | The data principal | | `status` | `string` | Request status: `completed` | | `recordsErased` | `number` | Consent records this request marked erased | | `grantsRevoked` | `number` | The principal's grants this request revoked (only grants that were still active are counted) | | `delegatedGrantsRevoked` | `number` | Grants delegated from those, revoked in the same cascade; `0` unless the server sets `DPDP_REVOCATION_CASCADE=true` | | `grievancesRedacted` | `number` | Grievances whose description and evidence were replaced by a fixed marker; `0` unless the server sets `DPDP_ERASURE_EXPANDED=true` | | `exportsDeleted` | `number` | Stored exports deleted: those filtered to the principal and any whose data contains the principal id; `0` unless the server sets `DPDP_ERASURE_EXPANDED=true` | | `retained` | `object[]` | What was kept rather than erased, as `{ category, count?, reason }` | | `submittedAt` | `string` | ISO-8601 timestamp of the request | | `completedAt` | `string` | ISO-8601 timestamp the erasure completed | | `expectedCompletionBy` | `string` | Deprecated; equal to `completedAt`. It used to be seven days after submission while `status` already said `completed` | ## What Happens on Erasure In one transaction: 1. The grants of the principal's consent records that are still active are revoked, with `revokedAt`, the revocation cache and a `grant.revoked` event each. By default only those grants are revoked, as this endpoint always did; with `DPDP_REVOCATION_CASCADE=true` the grants delegated from them, their credentials and wallet reservations are revoked through the grant cascade too. 2. The principal's consent records are set to `status: 'erased'` with `erasedAt`; they are **retained**, not deleted. 3. With `DPDP_ERASURE_EXPANDED=true` only: the principal's grievances keep their reference, type, status and dates, and their description and evidence are replaced by a fixed marker. 4. With `DPDP_ERASURE_EXPANDED=true` only: stored exports filtered to the principal, or containing the principal id, are deleted. 5. The request is stored and recorded on the audit chain as `grantex.dpdp.erasure_completed`, and a `dpdp.erasure.completed` event is emitted. What is retained, and why, is returned in `retained`: * **Consent records** are kept, marked erased, because the Data Fiduciary bears the burden of proving consent (DPDP Act s.6(10)) and DPDP Rules 2025 r.8(3) require processing logs and associated data to be retained for at least one year. * **Audit entries** are neither modified nor deleted: DPDP Rules 2025 r.6(1)(e) and r.8(3) require logs to be retained for at least one year, and the entries form a tamper-evident hash chain. * **Grievances** are kept as the record of grievance handling: redacted with `DPDP_ERASURE_EXPANDED=true`, otherwise unchanged, and the reason says expanded erasure is not enabled. * **Stored exports** (`stored_exports`, only without `DPDP_ERASURE_EXPANDED=true`) about the principal are kept until they expire, seven days after creation, and the reason says expanded erasure is not enabled. * **The fiduciary's processed data** is not held by Grantex; erasing it in the fiduciary's own systems and its processors' (DPDP Act s.8(7)) is the fiduciary's step. These obligations apply from the commencement of the DPDP Rules' substantive provisions (13 May 2027); the endpoint behaves the same before then. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | `principalId` longer than 256 characters | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | No consent records found for this data principal | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const result = await grantex.dpdp.requestErasure('user_abc123'); // result.recordsErased → 3 // result.grantsRevoked → 2 ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.dpdp.request_erasure("user_abc123") print(f"Erased {result.records_erased} records, revoked {result.grants_revoked} grants") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Update Breach Source: https://docs.grantex.dev/api-reference/dpdp/update-breach Record the detailed report to the Board, the Board intimation times, an extension, and move a breach through its statuses. ## Endpoint ``` PATCH /v1/dpdp/breaches/:breachId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `breachId` | `string` | Yes | The breach ID | ## Request Body At least one field. Each field given replaces the stored value. | Field | Type | Description | | - | - | - | | `status` | `string` | `initial_intimated`, `reported` or `closed` (see below) | | `detailedReport` | `object` | Any of `updatedDetails`, `factsCircumstancesReasons`, `mitigation`, `causeFindings`, `remedialMeasures` (each up to 5,000 characters) | | `boardInitialIntimationSentAt` | `string` | When the fiduciary sent the Board its initial intimation; not before `awareAt`, not in the future | | `boardDetailedReportSentAt` | `string` | When it sent the detailed report; not before `awareAt`, not in the future | | `extension` | `object` | `requestedAt` (default now), `granted` (`true`, `false` or `null` while undecided), `newDueAt` (required when `granted` is `true`; after `awareAt`) | The detailed report fields follow DPDP Rules 2025 r.7(2)(b): updated and detailed information on the description; the broad facts, circumstances and reasons leading to the breach; the mitigation measures implemented or proposed; any findings about the person who caused it; and the remedial measures taken to prevent recurrence. ## Transitions | From | To | Also | | - | - | - | | `open` | `initial_intimated` | Sets `boardInitialIntimationSentAt` to now unless given or already set | | `initial_intimated` | `reported` | Needs all five `detailedReport` fields (stored or in this request); sets `boardDetailedReportSentAt` to now unless given or already set | | `reported` | `closed` | Sets `closedAt` | Any other move answers `409 INVALID_TRANSITION`. A closed breach cannot be changed (`409 BREACH_CLOSED`). A request that fails changes nothing. ## Deadline `boardDetailedReportDueAt` is `awareAt` + 72 hours. Once an extension is recorded with `granted: true`, it is the extension's `newDueAt`. A moved deadline is alerted afresh by the deadline worker. ## Example Request ```bash theme={null} curl -X PATCH https://api.grantex.dev/v1/dpdp/breaches/brch_01HXYZ... \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "status": "reported", "detailedReport": { "updatedDetails": "Three customer records were read by an expired credential", "factsCircumstancesReasons": "The credential was not revoked when the contract ended", "mitigation": "Credential revoked, storage policy narrowed", "causeFindings": "Former contractor account; no onward disclosure found", "remedialMeasures": "Offboarding now revokes storage credentials" } }' ``` ## Response -- 200 OK The updated breach, as [Get Breach](/api-reference/dpdp/get-breach) returns it (without `principalIntimations`). Each change is recorded on the audit chain as `grantex.dpdp.breach_updated`. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Nothing to update, unknown field, malformed time, a time before `awareAt` or in the future, or an extension granted without `newDueAt` | | 400 | `REPORT_INCOMPLETE` | Moving to `reported` without the full detailed report | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Breach not found | | 409 | `INVALID_TRANSITION` | The breach is not in the status this move starts from | | 409 | `BREACH_CLOSED` | The breach is closed | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Update Grievance Source: https://docs.grantex.dev/api-reference/dpdp/update-grievance Move a DPDP grievance through review: submitted to in_review, then resolved or rejected with a resolution. ## Endpoint ``` PATCH /v1/dpdp/grievances/:grievanceId ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `grievanceId` | `string` | Yes | The grievance ID | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `status` | `string` | Yes | `in_review`, `resolved` or `rejected` | | `resolution` | `string` | For `resolved` and `rejected` | What was done, or why the grievance was rejected (up to 5,000 characters) | ## Transitions | From | To | | - | - | | `submitted` | `in_review` | | `in_review` | `resolved` or `rejected` | Nothing leaves `resolved` or `rejected`. Moving to `resolved` or `rejected` sets `resolvedAt` and `resolution`. The Data Fiduciary answers within the response period it publishes, at most 90 days (DPDP Rules 2025 r.14(3)); the grievance's `expectedResolutionBy` reflects the period given when it was filed. ## Example Request ```bash theme={null} curl -X PATCH https://api.grantex.dev/v1/dpdp/grievances/grv_01HXYZ... \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "status": "resolved", "resolution": "Processing for marketing stopped and the data deleted." }' ``` ## Response -- 200 OK The updated grievance, as [Get Grievance](/api-reference/dpdp/get-grievance) returns it. Each change is recorded on the audit chain as `grantex.dpdp.grievance_updated` and emits a `dpdp.grievance.updated` event with `grievanceId`, `referenceNumber`, `previousStatus` and `status`. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Unknown `status`, or `resolved` / `rejected` without a `resolution` | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Grievance not found | | 409 | `INVALID_TRANSITION` | The grievance is not in the status this move starts from | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Withdraw Consent Source: https://docs.grantex.dev/api-reference/dpdp/withdraw-consent Withdraw a DPDP consent record. Optionally revokes the underlying grant, and asks the developer to delete processed data. ## Endpoint ``` POST /v1/dpdp/consent-records/:recordId/withdraw ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Path Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `recordId` | `string` | Yes | The consent record ID to withdraw | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `reason` | `string` | Yes | Reason for consent withdrawal (up to 1,000 characters) | | `revokeGrant` | `boolean` | No | If `true`, revokes the underlying grant (and every grant delegated from it when the server sets `DPDP_REVOCATION_CASCADE=true`). Default `false`, or `true` when the server sets `DPDP_WITHDRAWAL_REVOKES_GRANT=true` | | `deleteProcessedData` | `boolean` | No | If `true`, emits a `dpdp.data_deletion.requested` webhook event asking the developer to delete the data it processed under this consent | The body must be a JSON object; a missing or malformed body is `400`. A Data Principal may withdraw consent at any time, and withdrawing is to be as easy as giving consent (DPDP Act s.6(4)); offering that means is the fiduciary's user interface, which calls this endpoint. After a withdrawal the Data Fiduciary, and its processors, must cease processing within a reasonable time (s.6(6)). These provisions apply from 13 May 2027 (DPDP Rules 2025 r.1). Revoking the grant stops Grantex-mediated processing under that grant: it is marked revoked with `revokedAt`, the revocation cache is updated and a `grant.revoked` event is emitted. By default only the record's own grant is revoked, as this endpoint always did; grants delegated from it keep working. A server that sets `DPDP_REVOCATION_CASCADE=true` revokes through the same cascade as `DELETE /v1/grants/:id` instead (delegated grants, credentials, wallet reservations). Only an active grant is revoked. A server operator who wants every withdrawal to revoke unless the caller says otherwise sets `DPDP_WITHDRAWAL_REVOKES_GRANT=true`; an explicit `revokeGrant: false` still wins. Grantex holds no personal data the fiduciary processed. `deleteProcessedData` therefore does not delete or rewrite anything in Grantex (the audit log is a tamper-evident hash chain and is never modified); it asks the developer, by webhook, to delete the data in its own systems. The withdrawal, the grant revocation and the audit entry (`grantex.dpdp.consent_withdrawn`) are one transaction. Only an `active` record can be withdrawn, so of two concurrent withdrawals exactly one succeeds. ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/consent-records/crec_01HXYZ.../withdraw \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "reason": "No longer wish to share data for analytics", "revokeGrant": true, "deleteProcessedData": true }' ``` ## Response -- 200 OK ```json theme={null} { "recordId": "crec_01HXYZ...", "status": "withdrawn", "withdrawnAt": "2026-04-05T14:00:00.000Z", "grantRevoked": true, "dataDeleted": false, "dataDeletionRequested": true } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `recordId` | `string` | The consent record ID | | `status` | `string` | Updated status: `withdrawn` | | `withdrawnAt` | `string` | ISO-8601 timestamp of withdrawal | | `grantRevoked` | `boolean` | Whether this withdrawal revoked the grant (`false` if it was not asked to, or the grant was no longer active) | | `dataDeleted` | `boolean` | Whether Grantex itself deleted data. Always `false`: Grantex holds none of the fiduciary's processed data | | `dataDeletionRequested` | `boolean` | Whether a `dpdp.data_deletion.requested` event was sent to the developer | ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing or non-object body, missing `reason`, or a non-boolean flag | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Consent record not found | | 409 | `ALREADY_WITHDRAWN` | Consent has already been withdrawn | | 409 | `CONSENT_ERASED` | The record was erased | | 409 | `CONSENT_EXPIRED` | The record has expired | ## SDK Examples ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const result = await grantex.dpdp.withdrawConsent('crec_01HXYZ...', { reason: 'No longer wish to share data', revokeGrant: true, deleteProcessedData: true, }); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.dpdp.withdraw_consent( "crec_01HXYZ...", reason="No longer wish to share data", revoke_grant=True, delete_processed_data=True, ) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Stream Source: https://docs.grantex.dev/api-reference/events/stream get /v1/events/stream ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Websocket Source: https://docs.grantex.dev/api-reference/events/websocket get /v1/events/ws ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delegate a grant to a sub agent Source: https://docs.grantex.dev/api-reference/grants/delegate-a-grant-to-a-sub-agent post /v1/grants/delegate ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get grant by id Source: https://docs.grantex.dev/api-reference/grants/get-grant-by-id get /v1/grants/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List grants Source: https://docs.grantex.dev/api-reference/grants/list-grants get /v1/grants ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Revoke a grant Source: https://docs.grantex.dev/api-reference/grants/revoke-a-grant delete /v1/grants/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify a grant tokens revocation status Source: https://docs.grantex.dev/api-reference/grants/verify-a-grant-tokens-revocation-status post /v1/grants/verify ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # API Reference Source: https://docs.grantex.dev/api-reference/introduction Complete REST API reference for the Grantex Auth Service The Grantex Auth Service exposes a REST API covering agent registration, authorization flows, token management, audit logging, policy enforcement, anomaly detection, compliance exports, SCIM provisioning, SSO, billing, FIDO2/WebAuthn passkey management, W3C Verifiable Credentials, and DID infrastructure. ## Base URLs | Environment | URL | | - | - | | **Production** | `https://api.grantex.dev` | | **Local** | `http://localhost:3001` | ## Authentication Most endpoints require a **Bearer token** in the `Authorization` header. This is your developer API key, obtained via `POST /v1/signup`. ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ https://api.grantex.dev/v1/agents ``` SCIM 2.0 user endpoints use a separate **SCIM Bearer token**, created via `POST /v1/scim/tokens`. Public endpoints (health, JWKS, consent UI, SSO flow) require no authentication. ## Rate Limits These are current repository defaults. Source implementation does not by itself confirm that a managed environment has been deployed with the same configuration. | Endpoint | Limit | | - | - | | Default Fastify IP policy (routes without an override) | 5,000 requests/minute per source IP | | Standard developer API-key - Free plan | 100 requests/minute per developer | | Standard developer API-key - Pro plan | 500 requests/minute per developer | | Standard developer API-key - Enterprise plan | 2,000 requests/minute per developer | | Containment: `DELETE /v1/grants/:id`, `POST /v1/tokens/revoke`, `POST /v1/emergency-stop`, `POST /v1/passport/:id/revoke`, `POST /v1/consent-bundles/:id/revoke` | 2,000 requests/minute per developer on every plan, instead of the plan budget | | Revocation status: `GET /v1/revocations`, `/v1/revocations/status`, `/v1/revocations/stream`, `GET /v1/consent-bundles/:id/revocation-status` | 6,000 requests/minute per developer on every plan, instead of the plan budget | | `POST /v1/authorize` | 10 requests/minute per developer | | `POST /v1/token` | 20 requests/minute | | `POST /v1/token/refresh` | 20 requests/minute | | `GET /.well-known/jwks.json` | Not rate-limited | A route-specific Fastify limit replaces the 5,000 requests/minute default for that route. The Redis-backed standard developer API-key plan budget is an additional policy whenever standard API-key authentication applies; on containment and revocation-status routes the budget of that name takes its place. Containment routes are not refused when the Redis counter is unavailable; every other standard API-key route fails closed with `503 RATE_LIMIT_UNAVAILABLE`. See [Rate Limits](/guides/rate-limits). Commerce, the SCIM Bearer data-plane routes under `/scim/v2/*`, admin, and other custom-auth routes do not consume the standard developer API-key plan bucket; their active Fastify policy still applies. The standard API-key-authenticated `/v1/scim/tokens` management routes do consume the plan bucket. ## OpenAPI Spec The full OpenAPI 3.1 specification is available at [`openapi.yaml`](https://github.com/mishrasanjeev/grantex/blob/main/docs/openapi.yaml). You can import it into Swagger Editor, Postman, or any OpenAPI-compatible tool. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Offline Sync API Source: https://docs.grantex.dev/api-reference/offline-sync API reference for syncing offline audit log entries to the Grantex cloud. ## Overview The offline sync endpoint receives audit log entries produced by on-device agents operating with consent bundles. The server verifies the Ed25519 signatures and hash chain integrity, then reconciles entries with the cloud audit log. *** ## Sync Offline Audit Entries Upload a batch of offline audit log entries to the Grantex cloud. ``` POST /v1/audit/offline-sync ``` ### Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ### Request Body | Field | Type | Required | Description | | - | - | - | - | | `bundleId` | `string` | Yes | The consent bundle ID that authorized these entries | | `entries` | `SignedAuditEntry[]` | Yes | Array of signed, hash-chained audit entries | ### SignedAuditEntry Schema | Field | Type | Description | | - | - | - | | `seq` | `number` | Monotonically increasing sequence number | | `timestamp` | `string` | ISO-8601 timestamp of the action | | `action` | `string` | Action identifier (e.g. `"calendar.read"`) | | `agentDID` | `string` | DID of the agent that performed the action | | `grantId` | `string` | Grant ID authorizing the action | | `scopes` | `string[]` | Scopes on the grant at the time of the action | | `result` | `string` | Outcome: `"success"`, `"auth_failure"`, `"scope_violation"`, `"execution_error"` | | `metadata` | `object` | Optional arbitrary metadata | | `prevHash` | `string` | SHA-256 hash of the previous entry (or `"0000000000000000"` for the first entry) | | `hash` | `string` | SHA-256 hash of this entry | | `signature` | `string` | Ed25519 signature of the `hash` field (hex-encoded) | ### Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/audit/offline-sync \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "bundleId": "cb_01HXYZ...", "entries": [ { "seq": 1, "timestamp": "2026-04-03T12:00:00.000Z", "action": "calendar.read", "agentDID": "did:grantex:ag_01HXYZ...", "grantId": "grnt_01HXYZ...", "scopes": ["calendar:read"], "result": "success", "metadata": {"eventCount": 12}, "prevHash": "0000000000000000", "hash": "a3f29c81b4e2f1a7d8c3e5b6a9f0d1c2e4b7a8c9d0e1f2a3b4c5d6e7f8a9b0c1", "signature": "4d5e6f7a8b9c0d1e..." } ] }' ``` ### Response -- 200 OK ```json theme={null} { "accepted": 1, "rejected": 0, "revocationStatus": "active", "revokedAt": null, "errors": [] } ``` ### Response Fields | Field | Type | Description | | - | - | - | | `accepted` | `number` | Number of entries successfully stored | | `rejected` | `number` | Number of entries rejected | | `revocationStatus` | `string` | Current grant revocation status: `"active"` or `"revoked"` | | `revokedAt` | `string \| null` | ISO-8601 timestamp of revocation, or `null` if active | | `errors` | `object[]` | Details of rejected entries | ### Error Array Schema Each element in the `errors` array: | Field | Type | Description | | - | - | - | | `seq` | `number` | Sequence number of the rejected entry | | `code` | `string` | Error code | | `message` | `string` | Human-readable error message | ### Error Codes for Rejected Entries | Code | Description | | - | - | | `INVALID_HASH` | Recomputed hash does not match the entry's `hash` field | | `INVALID_SIGNATURE` | Ed25519 signature does not verify against the bundle's public key | | `BROKEN_CHAIN` | Entry's `prevHash` does not match the previous entry's `hash` | | `DUPLICATE_SEQ` | Sequence number already exists (entry already synced) | | `SEQ_GAP` | Non-consecutive sequence number (entries are missing) | ### Error Responses (HTTP Level) | Status | Code | Description | | - | - | - | | 400 | `INVALID_REQUEST` | Missing or malformed request body | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `BUNDLE_NOT_FOUND` | The specified bundle does not exist | | 413 | `PAYLOAD_TOO_LARGE` | Batch exceeds maximum size (1000 entries per request) | | 429 | `RATE_LIMITED` | Too many requests | *** ## Batch Processing The endpoint processes entries in order. If an entry fails validation, subsequent entries in the same batch may still be accepted if their hash chain is internally consistent. **Recommended batch sizes:** | Scenario | Batch Size | | - | - | | Mobile device, metered connection | 50 | | Raspberry Pi, Wi-Fi | 100 (default) | | Server, reliable connection | 500 | Maximum entries per request: **1000**. *** ## Sync Flow The recommended sync flow: ``` Device Grantex API │ │ │── POST /v1/audit/offline-sync ─────────────►│ │ { bundleId, entries[1..100] } │ │ │ │◄── 200 { accepted: 98, rejected: 2, ... } ──│ │ │ │ (check revocationStatus) │ │ if "revoked" → stop, delete bundle │ │ if "active" → mark entries synced │ │ │ │── POST /v1/audit/offline-sync ─────────────►│ │ { bundleId, entries[101..150] } │ │ │ │◄── 200 { accepted: 50, rejected: 0, ... } ──│ │ │ │ (all entries synced) │ │ │ ``` *** ## Idempotency Syncing the same entries multiple times is safe. The server deduplicates by `(bundleId, seq)` pair. If an entry with the same sequence number already exists and has a matching hash, it is silently accepted. If the hash differs, it is rejected with `DUPLICATE_SEQ`. *** ## SDK Examples ```typescript TypeScript theme={null} import { syncAuditLog } from '@grantex/gemma'; const result = await syncAuditLog(auditLog, { endpoint: 'https://api.grantex.dev', apiKey: 'gx_...', bundleId: 'cb_01HXYZ...', batchSize: 100, }); console.log(`Synced: ${result.syncedCount}`); if (result.hasErrors) { console.warn('Errors:', result.errors); } ``` ```python Python theme={null} result = audit_log.sync( api_key="gx_...", bundle_id="cb_01HXYZ...", ) print(f"Accepted: {result.accepted}") print(f"Rejected: {result.rejected}") print(f"Revocation: {result.revocation_status}") ``` *** ## Rate Limits | Endpoint | Limit | | - | - | | `POST /v1/audit/offline-sync` | 20 requests/minute | The rate limit applies per API key. If you need higher throughput, use larger batch sizes rather than more frequent requests. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Active bundle Source: https://docs.grantex.dev/api-reference/policies/active-bundle get /v1/policies/bundles/active ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create an access policy Source: https://docs.grantex.dev/api-reference/policies/create-an-access-policy post /v1/policies ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete a policy Source: https://docs.grantex.dev/api-reference/policies/delete-a-policy delete /v1/policies/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get policy by id Source: https://docs.grantex.dev/api-reference/policies/get-policy-by-id get /v1/policies/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List access policies Source: https://docs.grantex.dev/api-reference/policies/list-access-policies get /v1/policies ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List bundles Source: https://docs.grantex.dev/api-reference/policies/list-bundles get /v1/policies/bundles ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Sync policy bundle Source: https://docs.grantex.dev/api-reference/policies/sync-policy-bundle post /v1/policies/sync ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Sync webhook Source: https://docs.grantex.dev/api-reference/policies/sync-webhook post /v1/policies/sync/webhook ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Update a policy Source: https://docs.grantex.dev/api-reference/policies/update-a-policy patch /v1/policies/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create principal session Source: https://docs.grantex.dev/api-reference/principal-sessions/create-principal-session post /v1/principal-sessions ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get principal audit Source: https://docs.grantex.dev/api-reference/principal-sessions/get-principal-audit get /v1/principal/audit ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List principal grants Source: https://docs.grantex.dev/api-reference/principal-sessions/list-principal-grants get /v1/principal/grants ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Revoke principal grant Source: https://docs.grantex.dev/api-reference/principal-sessions/revoke-principal-grant delete /v1/principal/grants/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create a scim provisioning token Source: https://docs.grantex.dev/api-reference/scim/create-a-scim-provisioning-token post /v1/scim/tokens ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create a scim user Source: https://docs.grantex.dev/api-reference/scim/create-a-scim-user post /scim/v2/Users ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete a scim token Source: https://docs.grantex.dev/api-reference/scim/delete-a-scim-token delete /v1/scim/tokens/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete a scim user Source: https://docs.grantex.dev/api-reference/scim/delete-a-scim-user delete /scim/v2/Users/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get scim user by id Source: https://docs.grantex.dev/api-reference/scim/get-scim-user-by-id get /scim/v2/Users/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List scim tokens Source: https://docs.grantex.dev/api-reference/scim/list-scim-tokens get /v1/scim/tokens ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List scim users Source: https://docs.grantex.dev/api-reference/scim/list-scim-users get /scim/v2/Users ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Patch a scim user Source: https://docs.grantex.dev/api-reference/scim/patch-a-scim-user patch /scim/v2/Users/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Replace a scim user Source: https://docs.grantex.dev/api-reference/scim/replace-a-scim-user put /scim/v2/Users/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Scim service provider configuration Source: https://docs.grantex.dev/api-reference/scim/scim-service-provider-configuration get /scim/v2/ServiceProviderConfig ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Send verification Source: https://docs.grantex.dev/api-reference/signup/send-verification post /v1/signup/verify ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify token Source: https://docs.grantex.dev/api-reference/signup/verify-token get /v1/signup/verify/{token} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Configure oidc sso Source: https://docs.grantex.dev/api-reference/sso/configure-oidc-sso post /v1/sso/config ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create sso connection Source: https://docs.grantex.dev/api-reference/sso/create-sso-connection post /v1/sso/connections ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete sso connection Source: https://docs.grantex.dev/api-reference/sso/delete-sso-connection delete /v1/sso/connections/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get sso configuration Source: https://docs.grantex.dev/api-reference/sso/get-sso-configuration get /v1/sso/config ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Initiate sso login Source: https://docs.grantex.dev/api-reference/sso/initiate-sso-login get /sso/login ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # LDAP Callback Source: https://docs.grantex.dev/api-reference/sso/ldap-callback post /sso/callback/ldap Authenticate with the LDAP direct-bind preview and create an SSO session; directory, attribute, and group search are not performed. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List sso connections Source: https://docs.grantex.dev/api-reference/sso/list-sso-connections get /v1/sso/connections ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List sso sessions Source: https://docs.grantex.dev/api-reference/sso/list-sso-sessions get /v1/sso/sessions ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Oidc callback Source: https://docs.grantex.dev/api-reference/sso/oidc-callback post /sso/callback/oidc ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Remove sso configuration Source: https://docs.grantex.dev/api-reference/sso/remove-sso-configuration delete /v1/sso/config ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Revoke sso session Source: https://docs.grantex.dev/api-reference/sso/revoke-sso-session delete /v1/sso/sessions/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Saml callback Source: https://docs.grantex.dev/api-reference/sso/saml-callback post /sso/callback/saml ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Set sso enforcement Source: https://docs.grantex.dev/api-reference/sso/set-sso-enforcement post /v1/sso/enforce ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Sso callback Source: https://docs.grantex.dev/api-reference/sso/sso-callback get /sso/callback ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Test sso connection Source: https://docs.grantex.dev/api-reference/sso/test-sso-connection post /v1/sso/connections/{id}/test ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Update sso connection Source: https://docs.grantex.dev/api-reference/sso/update-sso-connection patch /v1/sso/connections/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Developer dashboard Source: https://docs.grantex.dev/api-reference/system/developer-dashboard get /dashboard ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Health check Source: https://docs.grantex.dev/api-reference/system/health-check get /health ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Json web key set Source: https://docs.grantex.dev/api-reference/system/json-web-key-set get /.well-known/jwks.json ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Principal permissions dashboard Source: https://docs.grantex.dev/api-reference/system/principal-permissions-dashboard get /dashboard/principal ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Exchange authorization code for grant token Source: https://docs.grantex.dev/api-reference/tokens/exchange-authorization-code-for-grant-token post /v1/token ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Refresh a grant token Source: https://docs.grantex.dev/api-reference/tokens/refresh-a-grant-token post /v1/token/refresh ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Revoke a grant token Source: https://docs.grantex.dev/api-reference/tokens/revoke-a-grant-token post /v1/tokens/revoke ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify a grant token online Source: https://docs.grantex.dev/api-reference/tokens/verify-a-grant-token-online post /v1/tokens/verify ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Current Usage Source: https://docs.grantex.dev/api-reference/usage/current Retrieve usage metrics for the current UTC date. ## Endpoint ``` GET /v1/usage ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/usage \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "developerId": "dev_01HXYZ...", "period": "2026-04-05", "tokenExchanges": 142, "authorizations": 58, "verifications": 1203, "totalRequests": 1403 } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `developerId` | `string` | The authenticated developer's ID | | `period` | `string` | Current date in `YYYY-MM-DD` format | | `tokenExchanges` | `number` | Number of token exchange requests today | | `authorizations` | `number` | Number of authorization requests today | | `verifications` | `number` | Number of token verification requests today | | `totalRequests` | `number` | Sum of all request types | Usage counters are served from Redis for real-time accuracy. They reset at midnight UTC. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const usage = await grantex.usage.current(); console.log(`Total requests today: ${usage.totalRequests}`); console.log(`Token exchanges: ${usage.tokenExchanges}`); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") usage = grantex.usage.current() print(f"Total requests today: {usage.total_requests}") print(f"Token exchanges: {usage.token_exchanges}") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Usage History Source: https://docs.grantex.dev/api-reference/usage/history Retrieve daily usage breakdown over a specified number of days. ## Endpoint ``` GET /v1/usage/history ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `days` | `number` | No | Number of days to look back (default `30`, maximum `90`) | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/usage/history?days=7" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "developerId": "dev_01HXYZ...", "days": 7, "entries": [ { "date": "2026-04-05", "tokenExchanges": 142, "authorizations": 58, "verifications": 1203, "totalRequests": 1403 }, { "date": "2026-04-04", "tokenExchanges": 130, "authorizations": 45, "verifications": 1100, "totalRequests": 1275 } ] } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `developerId` | `string` | The authenticated developer's ID | | `days` | `number` | The number of days included in the response | | `entries` | `array` | Array of daily usage entries, sorted by date descending | | `entries[].date` | `string` | Date in `YYYY-MM-DD` format | | `entries[].tokenExchanges` | `number` | Number of token exchange requests that day | | `entries[].authorizations` | `number` | Number of authorization requests that day | | `entries[].verifications` | `number` | Number of token verification requests that day | | `entries[].totalRequests` | `number` | Sum of all request types for that day | Historical usage data is read from PostgreSQL. Days with no activity may be omitted from the response. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const history = await grantex.usage.history({ days: 7 }); for (const entry of history.entries) { console.log(`${entry.date}: ${entry.totalRequests} requests`); } ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") history = grantex.usage.history(days=7) for entry in history.entries: print(f"{entry.date}: {entry.total_requests} requests") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete Vault Credential Source: https://docs.grantex.dev/api-reference/vault/delete Permanently delete a vault credential. ## Endpoint ``` DELETE /v1/vault/credentials/:id ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The vault credential ID to delete (e.g. `vc_01HXYZ...`) | ## Example Request ```bash theme={null} curl -X DELETE https://api.grantex.dev/v1/vault/credentials/vc_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 204 No Content No response body is returned on success. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Credential not found or does not belong to developer | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); await grantex.vault.delete('vc_01HXYZ...'); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") grantex.vault.delete("vc_01HXYZ...") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Exchange Grant Token for Service Credential Source: https://docs.grantex.dev/api-reference/vault/exchange Exchange a valid Grantex grant token for a stored upstream service credential. ## Endpoint ``` POST /v1/vault/credentials/exchange ``` ## Authentication Requires a **Grantex grant token** (not an API key) in the `Authorization` header. This is a public endpoint -- no developer API key is needed. The grant token must also carry the explicit, exactly-matched exchange scope for the requested service: * `vault::exchange` (for example `vault:github:exchange`) **Rollout note (2026-09-13, security sweep).** Releasing the raw upstream token is strictly more powerful than a `:read` grant, so exchange no longer accepts wildcard or read-style scopes (`*`, `:*`, `:read`, `:credentials:read`, `vault::*`, `vault::read`, `vault:credentials:exchange`). Grants issued with only those scopes will receive `403 FORBIDDEN` from this endpoint after the rollout; re-issue them with `vault::exchange`. In addition, key-bound grant tokens (those carrying `cnf.jkt`) must now be presented with a `DPoP` proof (`Authorization: DPoP ` plus a `DPoP` header whose `htu` is this endpoint and whose `ath` hashes the token); a bare `Bearer` presentation of a key-bound token is rejected with `401`. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `service` | `string` | Yes | The service whose credential to retrieve (e.g. `"github"`, `"slack"`) | | `delivery` | `string` | No | `token` (the default) returns the credential. `reference` returns a short-lived credential reference instead; the relying party that holds the developer API key (for example `@grantex/gateway` with `credentialReference: on`) redeems it with [Resolve Reference](/api-reference/vault/resolve) and injects the credential upstream, so the agent never holds the secret. Needs `VAULT_CREDENTIAL_REFERENCES_ENABLED=true` on the auth service; otherwise refused with `400 CREDENTIAL_REFERENCE_DISABLED` | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/vault/credentials/exchange \ -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ -H "Content-Type: application/json" \ -d '{ "service": "github" }' ``` ## Response -- 200 OK ```json theme={null} { "accessToken": "ghp_xxxxxxxxxxxx", "service": "github", "credentialType": "oauth2", "tokenExpiresAt": "2026-04-06T12:00:00.000Z", "metadata": { "scopes": ["repo", "read:org"] } } ``` ## Response -- 200 OK (`delivery: "reference"`) ```json theme={null} { "credentialRef": "vcr_01J9ZK3X6Q0Z6W7F0X2Y1V8K3M", "service": "github", "credentialType": "oauth2", "tokenExpiresAt": "2026-04-06T12:00:00.000Z", "metadata": { "scopes": ["repo", "read:org"] }, "referenceExpiresAt": "2026-04-06T09:05:00.000Z" } ``` No `accessToken` is returned. The reference is bound to the grant that obtained it, expires after `VAULT_CREDENTIAL_REFERENCE_TTL_SECONDS` (300 by default), never later than the grant token that obtained it, and is refused once the grant is revoked, stopped or expired. The agent presents it to the gateway as the `Grantex-Credential-Ref` request header. ## Response Fields | Field | Type | Description | | - | - | - | | `accessToken` | `string` | The decrypted upstream access token | | `service` | `string` | Service identifier | | `credentialType` | `string` | Credential type (e.g. `"oauth2"`) | | `tokenExpiresAt` | `string \| null` | ISO-8601 token expiry, or `null` | | `metadata` | `object` | Arbitrary metadata stored with the credential | This endpoint returns the raw access token. The grant token's `sub` (principal), `dev` (developer), and `scp` (scopes) claims are enforced before decrypting a credential. Keep grant scopes and expiry narrow. ## Rate Limits This endpoint is limited to **20 requests per minute** per grant token. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing `service` field, or `delivery` is neither `token` nor `reference` | | 400 | `CREDENTIAL_REFERENCE_DISABLED` | `delivery: "reference"` requested but references are not enabled on this auth service | | 401 | `UNAUTHORIZED` | Missing, invalid, or expired grant token | | 403 | `FORBIDDEN` | Grant token does not include a service-matching credential scope | | 404 | `NOT_FOUND` | No credential found for this principal and service | ## How It Works 1. The agent presents its Grantex grant token. 2. The server verifies the JWT signature and extracts the `sub` (principal ID), `dev` (developer ID), and `scp` (scopes) claims. 3. The requested `service` must match one of the token credential scopes listed above. 4. The server looks up the vault credential matching `(developerId, principalId, service)`. 5. If found, the encrypted access token is decrypted and returned. This allows agents to access upstream services without ever seeing the raw credentials at configuration time -- credentials are stored once by the developer and retrieved at runtime by authorized agents. ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); // The agent calls this with its grant token const cred = await grantex.vault.exchange({ grantToken: 'eyJhbGciOiJSUzI1NiIs...', service: 'github', }); console.log(cred.accessToken); // "ghp_xxxxxxxxxxxx" ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") cred = grantex.vault.exchange( grant_token="eyJhbGciOiJSUzI1NiIs...", service="github", ) print(cred.access_token) # "ghp_xxxxxxxxxxxx" ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Get Vault Credential Source: https://docs.grantex.dev/api-reference/vault/get Retrieve metadata for a specific vault credential. Does not return raw tokens. ## Endpoint ``` GET /v1/vault/credentials/:id ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Path Parameters | Parameter | Type | Description | | - | - | - | | `id` | `string` | The vault credential ID (e.g. `vc_01HXYZ...`) | ## Example Request ```bash theme={null} curl https://api.grantex.dev/v1/vault/credentials/vc_01HXYZ... \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "id": "vc_01HXYZ...", "principalId": "user_abc123", "service": "github", "credentialType": "oauth2", "tokenExpiresAt": "2026-04-06T12:00:00.000Z", "metadata": { "scopes": ["repo", "read:org"] }, "createdAt": "2026-04-05T12:00:00.000Z", "updatedAt": "2026-04-05T12:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `id` | `string` | Unique vault credential ID | | `principalId` | `string` | The principal who owns this credential | | `service` | `string` | Service identifier | | `credentialType` | `string` | Credential type (e.g. `"oauth2"`) | | `tokenExpiresAt` | `string \| null` | ISO-8601 token expiry, or `null` if not set | | `metadata` | `object` | Arbitrary metadata | | `createdAt` | `string` | ISO-8601 creation timestamp | | `updatedAt` | `string` | ISO-8601 last update timestamp | ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | | 404 | `NOT_FOUND` | Credential not found or does not belong to developer | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const cred = await grantex.vault.get('vc_01HXYZ...'); console.log(cred.service); // "github" console.log(cred.tokenExpiresAt); // "2026-04-06T12:00:00.000Z" ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") cred = grantex.vault.get("vc_01HXYZ...") print(cred.service) # "github" print(cred.token_expires_at) # "2026-04-06T12:00:00.000Z" ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List Vault Credentials Source: https://docs.grantex.dev/api-reference/vault/list List vault credential metadata for the authenticated developer. Does not return raw tokens. ## Endpoint ``` GET /v1/vault/credentials ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | ## Query Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `principalId` | `string` | No | Filter by principal ID | | `service` | `string` | No | Filter by service name | ## Example Request ```bash theme={null} curl "https://api.grantex.dev/v1/vault/credentials?principalId=user_abc123" \ -H "Authorization: Bearer gx_..." ``` ## Response -- 200 OK ```json theme={null} { "credentials": [ { "id": "vc_01HXYZ...", "principalId": "user_abc123", "service": "github", "credentialType": "oauth2", "tokenExpiresAt": "2026-04-06T12:00:00.000Z", "metadata": { "scopes": ["repo", "read:org"] }, "createdAt": "2026-04-05T12:00:00.000Z", "updatedAt": "2026-04-05T12:00:00.000Z" } ] } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `credentials` | `array` | Array of credential metadata objects | | `credentials[].id` | `string` | Unique vault credential ID | | `credentials[].principalId` | `string` | The principal who owns this credential | | `credentials[].service` | `string` | Service identifier | | `credentials[].credentialType` | `string` | Credential type (e.g. `"oauth2"`) | | `credentials[].tokenExpiresAt` | `string \| null` | ISO-8601 token expiry, or `null` if not set | | `credentials[].metadata` | `object` | Arbitrary metadata | | `credentials[].createdAt` | `string` | ISO-8601 creation timestamp | | `credentials[].updatedAt` | `string` | ISO-8601 last update timestamp | Raw access tokens and refresh tokens are never included in list responses. Use the [Exchange](/api-reference/vault/exchange) endpoint to retrieve a decrypted access token with a valid grant token. ## Error Responses | Status | Code | Description | | - | - | - | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const { credentials } = await grantex.vault.list({ principalId: 'user_abc123' }); for (const c of credentials) { console.log(`${c.service} — expires: ${c.tokenExpiresAt}`); } ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") result = grantex.vault.list(principal_id="user_abc123") for c in result.credentials: print(f"{c.service} — expires: {c.token_expires_at}") ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Resolve a Credential Reference Source: https://docs.grantex.dev/api-reference/vault/resolve Redeem a short-lived credential reference for the stored upstream credential; for the relying party that injects credentials on the agent's behalf. ## Endpoint ``` POST /v1/vault/credentials/resolve ``` ## Authentication Requires the **developer API key** (not a grant token). The caller is the relying party that injects the credential upstream, such as `@grantex/gateway` with `credentialReference: on`. The agent itself never calls this endpoint and never holds the credential. Available when the auth service runs with `VAULT_CREDENTIAL_REFERENCES_ENABLED=true` (the exchange hands out references only then). ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `credentialRef` | `string` | Yes | The reference the exchange returned (`vcr_...`) | | `grantId` | `string` | Yes | The grant the agent presented; must be the grant the reference was issued to | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/vault/credentials/resolve \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "credentialRef": "vcr_01J9ZK3X6Q0Z6W7F0X2Y1V8K3M", "grantId": "grnt_01J9ZK3X6Q0Z6W7F0X2Y1V8K3N" }' ``` ## Response -- 200 OK ```json theme={null} { "accessToken": "ghp_xxxxxxxxxxxx", "service": "github", "credentialType": "oauth2", "tokenExpiresAt": "2026-04-06T12:00:00.000Z", "metadata": { "scopes": ["repo", "read:org"] }, "grantId": "grnt_01J9ZK3X6Q0Z6W7F0X2Y1V8K3N", "principalId": "user_123", "agentDid": "did:grantex:agent:01J9..." } ``` ## What is checked 1. The reference exists and was issued under the caller's developer account. 2. `grantId` is the grant the reference was issued to. 3. The reference has not expired (`VAULT_CREDENTIAL_REFERENCE_TTL_SECONDS`, 300 by default; a reference is also never issued beyond the expiry of the grant token that obtained it). 4. The grant is still active and unexpired: a revoked grant, one an emergency stop ended, or one past its own expiry refuses the reference with it. Each resolution is recorded on the reference (`resolved_count`, `last_resolved_at`) and emits `vault.credential.resolved`. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | `credentialRef` or `grantId` missing or malformed | | 401 | `UNAUTHORIZED` | Missing or invalid API key | | 403 | `GRANT_MISMATCH` | The reference was issued to another grant | | 403 | `GRANT_INACTIVE` | The grant is revoked, stopped or otherwise no longer active | | 404 | `NOT_FOUND` | No such reference under this developer account | | 410 | `CREDENTIAL_REFERENCE_EXPIRED` | The reference has expired; the agent exchanges again | ## Rate Limits This endpoint is limited to **120 requests per minute** per API key. # Store Vault Credential Source: https://docs.grantex.dev/api-reference/vault/store Store an encrypted service credential in the Grantex Vault for a principal. ## Endpoint ``` POST /v1/vault/credentials ``` ## Authentication Requires a developer API key in the `Authorization` header. ## Request Headers | Header | Value | | - | - | | `Authorization` | `Bearer ` | | `Content-Type` | `application/json` | ## Request Body | Field | Type | Required | Description | | - | - | - | - | | `principalId` | `string` | Yes | The principal (end-user) who owns this credential | | `service` | `string` | Yes | Service identifier (e.g. `"github"`, `"slack"`, `"google"`). Use 1-64 characters: letters, numbers, `.`, `_`, or `-`. | | `accessToken` | `string` | Yes | The access token to store (encrypted at rest) | | `credentialType` | `string` | No | Credential type (default `"oauth2"`) | | `refreshToken` | `string` | No | Optional refresh token (encrypted at rest) | | `tokenExpiresAt` | `string` | No | ISO-8601 expiry timestamp for the access token | | `metadata` | `object` | No | Arbitrary metadata (e.g. scopes, account info) | ## Example Request ```bash theme={null} curl -X POST https://api.grantex.dev/v1/vault/credentials \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{ "principalId": "user_abc123", "service": "github", "accessToken": "ghp_xxxxxxxxxxxx", "refreshToken": "ghr_xxxxxxxxxxxx", "tokenExpiresAt": "2026-04-06T12:00:00.000Z", "metadata": { "scopes": ["repo", "read:org"] } }' ``` ## Response -- 201 Created ```json theme={null} { "id": "vc_01HXYZ...", "principalId": "user_abc123", "service": "github", "credentialType": "oauth2", "createdAt": "2026-04-05T12:00:00.000Z" } ``` ## Response Fields | Field | Type | Description | | - | - | - | | `id` | `string` | Unique vault credential ID | | `principalId` | `string` | The principal who owns this credential | | `service` | `string` | Service identifier | | `credentialType` | `string` | Credential type | | `createdAt` | `string` | ISO-8601 creation timestamp | If a credential already exists for the same `(developerId, principalId, service)` combination, it will be updated (upsert behavior). The raw access token and refresh token are never returned in any response -- they are stored encrypted and only retrievable via the [Exchange](/api-reference/vault/exchange) endpoint. ## Error Responses | Status | Code | Description | | - | - | - | | 400 | `BAD_REQUEST` | Missing `principalId`, `service`, or `accessToken` | | 400 | `BAD_REQUEST` | Invalid `service` identifier | | 401 | `UNAUTHORIZED` | Invalid or missing API key | ## SDK Examples ```typescript TypeScript theme={null} import Grantex from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); const cred = await grantex.vault.store({ principalId: 'user_abc123', service: 'github', accessToken: 'ghp_xxxxxxxxxxxx', refreshToken: 'ghr_xxxxxxxxxxxx', metadata: { scopes: ['repo', 'read:org'] }, }); ``` ```python Python theme={null} from grantex import Grantex grantex = Grantex(api_key="gx_...") cred = grantex.vault.store( principal_id="user_abc123", service="github", access_token="ghp_xxxxxxxxxxxx", refresh_token="ghr_xxxxxxxxxxxx", metadata={"scopes": ["repo", "read:org"]}, ) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Create enrollment session Source: https://docs.grantex.dev/api-reference/webauthn/create-enrollment-session post /v1/webauthn/enrollment-sessions Call only from your server after authenticating the principal. Do not log or share the returned bearer link. See [FIDO2 / WebAuthn](/features/fido-webauthn). # Delete credential Source: https://docs.grantex.dev/api-reference/webauthn/delete-credential delete /v1/webauthn/credentials/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Generate assertion options Source: https://docs.grantex.dev/api-reference/webauthn/generate-assertion-options post /v1/webauthn/assert/options ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Generate registration options Source: https://docs.grantex.dev/api-reference/webauthn/generate-registration-options post /v1/webauthn/register/options ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Hosted enrollment Source: https://docs.grantex.dev/api-reference/webauthn/hosted-enrollment get /passkey-enroll The browser page uses `POST /v1/webauthn/enroll/options` and `POST /v1/webauthn/enroll/verify` to register one passkey with a valid ticket. The ticket is supplied in the URL fragment and removed from browser history. # List credentials Source: https://docs.grantex.dev/api-reference/webauthn/list-credentials get /v1/webauthn/credentials ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify assertion Source: https://docs.grantex.dev/api-reference/webauthn/verify-assertion post /v1/webauthn/assert/verify ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verify registration Source: https://docs.grantex.dev/api-reference/webauthn/verify-registration post /v1/webauthn/register/verify ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Delete a webhook Source: https://docs.grantex.dev/api-reference/webhooks/delete-a-webhook delete /v1/webhooks/{id} ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # List webhooks Source: https://docs.grantex.dev/api-reference/webhooks/list-webhooks get /v1/webhooks ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Register a webhook Source: https://docs.grantex.dev/api-reference/webhooks/register-a-webhook post /v1/webhooks ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # 4 Agent Security Breaches That Should Change How You Think About API Keys Source: https://docs.grantex.dev/blog/agent-security-breaches-2025-2026 Shai-Hulud, SANDWORM_MODE, OpenClaw, and CVE-2026-21852 — the 2025-2026 incidents that proved AI agents need their own authorization layer, not shared secrets. In 2025, 18.1 million API keys were stolen. 29 million new secrets were committed to GitHub — a 34% year-over-year increase. The average time from initial access to lateral movement dropped to 29 minutes. The fastest recorded breach went from zero to full exfiltration in 4 minutes. These are not hypothetical risks. These are the numbers from production. And now agents — autonomous software that reads files, calls APIs, sends emails, and spends money — are using those same leaked keys with no identity, no scoping, and no revocation mechanism. Here are four incidents from the past 12 months that make the case for why agent authorization is no longer optional. ## 1. Shai-Hulud — 500+ npm Packages, 2.6 Billion Weekly Downloads In mid-2025, security researchers identified a supply chain worm — codenamed Shai-Hulud — that had silently compromised over 500 npm packages with a combined 2.6 billion weekly downloads. The payload was subtle: it harvested every cloud credential accessible from CI/CD runners and exfiltrated them to attacker-controlled endpoints. CISA issued a formal alert. The blast radius was enormous because CI pipelines routinely inject long-lived secrets as environment variables — AWS keys, GCP service account tokens, database credentials. Every compromised package had access to all of them. **Why this matters for agents:** AI agents running in CI/CD (code review bots, deployment agents, test orchestrators) inherit the same environment. If the agent's framework pulls in a compromised dependency, every secret the agent can see is compromised. With Grantex, the agent would hold a scoped, time-limited JWT — not raw cloud credentials. Compromising the token gives the attacker access to `calendar:read` for 1 hour, not `root` on your AWS account forever. ## 2. SANDWORM\_MODE — 19 Malicious npm Packages Targeting AI IDEs In February 2026, researchers discovered 19 npm packages designed to install rogue MCP (Model Context Protocol) servers inside Claude Code, Cursor, and Windsurf. The packages masqueraded as popular utilities and, once installed, silently registered themselves as MCP tool providers. The rogue servers targeted API keys from 9 LLM providers — OpenAI, Anthropic, Cohere, Mistral, and others. Because MCP servers have broad access to the IDE's tool-calling context, the malicious servers could intercept and exfiltrate keys as they were used in API calls. **Why this matters for agents:** MCP is the emerging standard for giving agents access to tools. But MCP has no native authentication layer. Any server can register itself, and agents trust whatever tools are available. [Grantex's MCP Auth Server](/integrations/mcp) adds OAuth 2.1 + PKCE to any MCP server — meaning even if a rogue server registers, it cannot obtain a valid grant token without the human explicitly approving the scope in the consent flow. ## 3. OpenClaw — 21,639 Exposed Instances Leaking OAuth Tokens A February 2026 Censys scan revealed 21,639 publicly accessible instances of a popular open-source automation tool leaking OAuth tokens and plaintext credentials to anyone who could reach the endpoint. No authentication was required — the instances were deployed with default configurations that exposed their internal token stores. The leaked tokens included OAuth refresh tokens for Google Workspace, Microsoft 365, Slack, and GitHub — many with broad scopes and no expiry. **Why this matters for agents:** Agents that automate workflows across SaaS tools accumulate tokens — often stored in plaintext or weakly encrypted databases. A single misconfigured deployment leaks every integration. Grantex's [Credential Vault](/guides/security-best-practices) solves this by letting agents exchange their grant token for upstream credentials at runtime. The master keys never leave the vault. If the agent's deployment is compromised, the attacker gets a scoped, time-limited Grantex JWT — not the raw SaaS tokens. ## 4. CVE-2026-21852 — Opening a Repo Exfiltrates Your API Key This one is the most chilling because it required zero user interaction beyond opening a repository. CVE-2026-21852 demonstrated that simply opening a crafted Git repository in Claude Code was enough to exfiltrate the developer's active Anthropic API key. The attack exploited the fact that the IDE's agent runtime had ambient access to the API key — it was loaded into the environment for legitimate use and was accessible to any code the agent executed, including code from the opened repository. **Why this matters for agents:** The root cause is ambient credentials. The API key exists as an environment variable or config file, and everything running in that context can read it. Grantex eliminates ambient credentials entirely. The agent authenticates via a grant flow and receives a JWT scoped to exactly the permissions it needs. No environment variable to steal. No master key to exfiltrate. And if the token is somehow compromised, one `POST /v1/tokens/revoke` call invalidates it — and every delegated sub-token — instantly. ## The Common Thread All four incidents share a root cause: **shared, long-lived, unscoped secrets used as identity.** | Incident | Root Cause | Grantex Mitigation | | - | - | - | | Shai-Hulud | CI injects raw cloud creds as env vars | Agent holds scoped JWT, not raw keys | | SANDWORM\_MODE | MCP servers have no auth layer | MCP Auth Server adds OAuth 2.1 + PKCE | | OpenClaw | OAuth tokens stored in plaintext | Credential Vault — master keys never leave | | CVE-2026-21852 | Ambient API key in agent runtime | No ambient creds — grant flow only | The fix is not better secret scanning. The fix is not rotating keys faster. The fix is **not using shared secrets as agent identity in the first place**. Agents need their own authorization protocol — one where every agent has a cryptographic identity, every action is scoped and consented to by a human, and every token can be revoked in real time. That is what Grantex is. ## Get Started * [Quickstart](/quickstart) — authorize your first agent in under 5 minutes * [Security Best Practices](/guides/security-best-practices) — the full hardening guide * [MCP Auth Server](/integrations/mcp) — add OAuth 2.1 to any MCP server * [Protocol Specification](/protocol/specification) — the open standard, IETF Internet-Draft ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # AI Agent Authorization Guide for 2026: Scopes, Consent, and Revocation Source: https://docs.grantex.dev/blog/ai-agent-authorization-guide Implement AI agent authorization with scoped grants, consent, JWT verification, current revocation checks, and TypeScript and Python examples. AI agent authorization is the policy and enforcement layer that determines which agent may perform which action for which principal. It supplements authentication: identifying a workload or API client is not the same as deciding whether a particular agent may invoke a particular tool now. This guide explains the implementation boundaries that matter in production: agent identity, scoped consent, token verification, action-boundary enforcement, current revocation state, and explicit audit integration. ## What Is AI Agent Authorization? An agent often selects tools at runtime. The protected service therefore needs more context than a valid application credential: * the agent identity; * the principal or organization that approved the authority; * the exact scopes granted to that agent; * the token audience and expiry; * any delegation constraints; and * the current grant state when revocation freshness matters. The service that executes the action is the final enforcement point. A prompt, tool description, or framework wrapper can reduce mistakes, but it should not be the only authorization control. ## How API Keys, OAuth, and Agent Grants Fit Together These mechanisms solve overlapping but distinct problems; they are not mutually exclusive. **API keys and service credentials** can be appropriate for application-to-service authentication. Providers may support restricted keys, rotation, access policy, and audit records. A shared credential usually does not, by itself, identify the individual agent and approving principal for each action. Keep upstream credentials behind the service boundary where possible. **OAuth 2.0 and OpenID Connect** provide mature authorization and identity building blocks, including scopes, consent, token expiry, and revocation mechanisms. An implementation can model agents with OAuth-native concepts. Agent-specific identity, delegation-chain rules, and per-tool policy still need explicit claim semantics and enforcement. **Grantex grant tokens** carry agent, principal, scope, expiry, and delegation context for the protected service to verify. Grantex complements upstream OAuth, identity providers, secret managers, and provider credentials rather than replacing them. For MCP over HTTP, use an authorization server that follows the current MCP authorization specification for transport access. Add agent-specific checks inside the MCP tool or protected downstream service when tool authority must be narrower than transport access. ## A Delegated Agent Authorization Flow A typical flow has seven explicit steps: 1. **Register the agent.** The developer registers the agent and its permitted scope catalog. Grantex returns an agent ID and DID. 2. **Request authority.** The application requests exact scopes for a known principal and redirect URI. 3. **Collect consent.** The principal reviews the request. The application receives a code only after approval. 4. **Exchange the code.** The application exchanges the callback code for a signed grant token. 5. **Verify at the action boundary.** The protected service validates the signature, issuer, audience, expiry, and required scopes before executing the action. 6. **Check current state when required.** Local JWT validation cannot discover a later revocation on its own. Use an online grant check or synchronized revocation data for current status. 7. **Record configured events.** The application or gateway explicitly records authorization decisions and action outcomes. External tool calls are not automatically audited merely because a token exists. Scope names should describe the resource and action clearly, such as `calendar:read` or `payments:initiate:max_500`. The SDK manifest enforcer uses `tool:{connector}:{permission}`, for example `tool:expense-api:write`. ## Install the Current SDKs The SDKs are independently versioned. These pins match the current published releases documented in the machine-readable implementation guide: ```bash theme={null} npm install @grantex/sdk@0.5.1 python -m pip install grantex==0.3.14 ``` Check [release status](/release-status) before changing versions. ## TypeScript: Register, Authorize, Exchange, and Verify ```typescript theme={null} import { Grantex, verifyGrantToken } from '@grantex/sdk'; const gx = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const principalId = 'user_alice'; const agent = await gx.agents.register({ name: 'expense-report-agent', description: 'Reads receipts and creates expense reports after approval', scopes: ['tool:expense-api:read', 'tool:expense-api:write'], }); const auth = await gx.authorize({ agentId: agent.id, userId: principalId, scopes: ['tool:expense-api:read', 'tool:expense-api:write'], redirectUri: 'https://app.example.com/auth/callback', }); console.log('Send the principal to: ' + auth.consentUrl); // Call this from the registered callback after reading its code parameter. export async function finishAuthorization(code: string) { const token = await gx.tokens.exchange({ code, agentId: agent.id }); const localGrant = await verifyGrantToken(token.grantToken, { jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', requiredScopes: ['tool:expense-api:write'], }); // Online verification checks current server-side grant state. const currentGrant = await gx.grants.verify(token.grantToken); return { token, localGrant, currentGrant }; } ``` `verifyGrantToken()` validates cryptographic and JWT claims after resolving the issuer's JWKS. The TypeScript verifier keeps a bounded process-level JWKS resolver cache. That cache is about signing keys, not grant revocation. `gx.grants.verify()` performs an online check. Use it, or an equivalent synchronized state mechanism, when the service must reject a grant that was revoked after the JWT was issued. ## Python: The Same Flow with Current Types ```python theme={null} import os from grantex import ( AuthorizeParams, ExchangeTokenParams, Grantex, VerifyGrantTokenOptions, verify_grant_token, ) client = Grantex(api_key=os.environ["GRANTEX_API_KEY"]) principal_id = "user_alice" agent = client.agents.register( name="expense-report-agent", description="Reads receipts and creates expense reports after approval", scopes=["tool:expense-api:read", "tool:expense-api:write"], ) auth = client.authorize(AuthorizeParams( agent_id=agent.id, user_id=principal_id, scopes=["tool:expense-api:read", "tool:expense-api:write"], redirect_uri="https://app.example.com/auth/callback", )) print(f"Send the principal to: {auth.consent_url}") # Call this from the registered callback after reading its code parameter. def finish_authorization(code: str): token = client.tokens.exchange(ExchangeTokenParams( code=code, agent_id=agent.id, )) local_grant = verify_grant_token( token.grant_token, VerifyGrantTokenOptions( jwks_uri="https://api.grantex.dev/.well-known/jwks.json", required_scopes=["tool:expense-api:write"], ), ) current_grant = client.grants.verify(token.grant_token) return token, local_grant, current_grant ``` The current Python verifier retrieves JWKS during each `verify_grant_token()` call. It still checks signatures and claims, not current revocation state. The online `client.grants.verify()` call supplies that state check. ## Enforce a Tool Manifest Before Execution The TypeScript SDK can map connector tools to permission levels. Loading a manifest does not intercept every tool automatically; your application must call `enforce()`, use a configured wrapper, or place equivalent middleware at the execution boundary. ```typescript theme={null} import { Permission, ToolManifest } from '@grantex/sdk'; gx.loadManifest(new ToolManifest({ connector: 'expense-api', description: 'Internal expense service', tools: { read_receipt: Permission.READ, create_report: Permission.WRITE, }, })); // Obtain token from finishAuthorization() above. await gx.grants.verify(token.grantToken); // current-state check const decision = await gx.enforce({ grantToken: token.grantToken, connector: 'expense-api', tool: 'create_report', }); if (!decision.allowed) { throw new Error(decision.reason); } // Call the expense API only after the decision is allowed. // After the action completes, explicitly record the outcome if desired: await gx.audit.log({ agentId: agent.id, agentDid: agent.did, grantId: token.grantId, principalId, action: 'expense-api.create_report', status: 'success', }); ``` `enforce()` verifies the JWT through the configured JWKS URI and evaluates the loaded manifest. It does not perform the online grant-state check shown immediately before it. Keep both checks at the same trusted boundary when current revocation is required. Audit logging is also explicit. `gx.audit.log()` records the event submitted by the application; it does not prove that every external side effect was captured. Log denied decisions, attempted actions, and final outcomes according to your threat model. ## Local Verification Versus Current Revocation This distinction is central to JWT-based authorization: | Check | What it establishes | What it does not establish | | - | - | - | | Local JWT verification | Trusted signature, expected claims, expiry, audience, and required scopes | Whether the grant was revoked after issuance | | Online grant verification | Current status known to the authorization service | Availability without a network path | | Synchronized revocation data | Current status up to the last successful synchronization | Changes newer than the local snapshot | | Short token lifetime | Bounds how long an unrefreshed token remains usable | Immediate revocation | Revoking a grant updates authorization state: ```typescript theme={null} await gx.grants.revoke(token.grantId); ``` An enforcement point learns about that change only when it performs an online check, consumes updated revocation state, or waits for the token to expire. Key rotation and JWKS refresh are separate from grant revocation. ## Design Checklist Before enabling an agent action: 1. Give each agent a stable identity separate from the application credential. 2. Request only the scopes required for the current workflow. 3. Bind tokens to the intended audience where supported. 4. Verify signature and claims at the service that executes the action. 5. Map every exposed tool to an explicit required scope or permission. 6. Choose an online or synchronized revocation strategy based on freshness and availability needs. 7. Keep upstream provider credentials behind the enforcement boundary. 8. Record authorization decisions and action outcomes explicitly. 9. Test missing scopes, unknown tools, expired tokens, revoked grants, stale state, and issuer mismatches. ## Frequently Asked Questions ### Are API keys unsuitable for every AI agent? No. API keys can authenticate an application to a provider and may support useful restrictions. The risk is treating a shared application credential as sufficient evidence that a particular agent was authorized by a particular principal for a particular action. ### Does agent authorization replace OAuth 2.0? No. OAuth can provide transport authorization, delegated user access, token lifecycle, and identity integration. Agent-specific claims and per-tool policy can be built with OAuth or layered alongside it. Grantex focuses on that agent-specific authority and enforcement context. ### Does local verification immediately reject a revoked grant? No. Local JWT verification sees the signed token and available keys. It needs an online state check or synchronized revocation data to learn that an otherwise unexpired token was revoked. ### Does Grantex automatically audit every agent action? No. The SDK exposes audit APIs and integrations, but the application or gateway must submit the relevant decision and outcome events at the execution boundary. ### How does this relate to MCP? MCP connects clients to tools and defines OAuth-based authorization for HTTP transports. Agent-specific authorization answers a narrower action question: which agent may call which tool for which principal. Use both layers when both transport access and delegated tool authority matter. ## Next Steps * Follow the [quickstart](/quickstart) for the complete callback flow. * Read [scope enforcement](/guides/scope-enforcement) for manifest and middleware patterns. * Review [offline verification](/sdks/typescript/offline-verification) before choosing a revocation strategy. * See the [MCP authorization guide](/blog/mcp-server-oauth-authentication) for transport and tool-boundary architecture. * Check the [protocol specification](/protocol/specification) for the normative grant model. * Inspect the [Apache-2.0 repository](https://github.com/mishrasanjeev/grantex). The important design decision is not which label to use. It is where the protected action is enforced, which claims are verified there, and how that enforcement point obtains current authorization state. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # The Buyer Journey From Question To Prepared Purchase Handoff Source: https://docs.grantex.dev/blog/buyer-journey-prepared-purchase-handoff How OACP moves from discovery to prepared handoff without faking execution. # The Buyer Journey From Question To Prepared Purchase Handoff ## Summary OACP supports a safe buyer journey: ask, compare, select, check freshness, verify provider capability, then prepare a handoff or refuse. ## Target Audience Buyer-experience designers, channel teams, and operators. ## Architecture Diagram ```mermaid theme={null} flowchart LR ask[Ask] --> compare[Compare] compare --> select[Select product] select --> freshness[Check OACP freshness] freshness --> provider[Check provider capability] provider --> prepare[Prepared handoff] provider --> refuse[Blocker] ``` ## End-To-End Flow The buyer asks a product question. AgenticOrg answers from cache with source labels. The buyer asks to buy. AgenticOrg checks catalog, price, inventory, policy, mandate capability, protocol adapter, and freshness records. It returns a prepared handoff if allowed or a clear blocker. ## What Is Implemented Now AgenticOrg has buyer Q\&A, product listing, protocol adapters, bridge endpoints, provider capability verification, and purchase-preparation routes. Grantex authority artifacts support the source and policy inputs. ## What Requires External Approval Or Config Provider rail execution, merchant order system integration, channel approvals, and public launch acceptance. ## Failure Modes * Product no longer in valid cache. * Price/inventory artifact missing. * Provider evidence missing or stale. * Buyer requests payment success from the agent. ## Safe User Wording Examples * "I can prepare a handoff for review." * "The provider capability evidence is stale; no payment was attempted." * "The merchant system remains the source of order status." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Why OACP Commerce Needs A Readiness Gate Source: https://docs.grantex.dev/blog/commerce-live-readiness-gate How Grantex separates OACP authority evidence from live payment, provider, POS, and merchant execution. Agentic commerce needs more than a working demo. A commerce agent can search a catalog, prepare a handoff, ask for consent, and verify provider capability metadata, but that does not prove the system is ready to touch live payment, bank, POS, order, refund, or fulfillment rails. This article is retained as a readiness-boundary note. The current OACP split is that AgenticOrg owns merchant self-service runtime and buyer/seller agent channels, while Grantex owns OACP authority and artifact verification. Merchant configuration, including Shopify today and future WooCommerce/ERP/bank/provider refs, is not by itself live execution approval. ## What Works Today In The Current OACP Split The current OACP runtime path covers: * AgenticOrg merchant self-service config scoped by tenant, merchant, and seller agent. * Shopify read-only Admin GraphQL runtime sync when the merchant provides valid credentials. * WooCommerce, ERP, PIM, OMS, WMS, custom API, bank-owned rail, fintech rail, and custom provider setup as pending-adapter config, not fake-live support. * Grantex OACP authority requests, signed/internal artifact issuance or refusal, artifact verification, and protocol adapter governance. * AgenticOrg OACP cache and buyer-channel answers with source/freshness labels. * Plural/Pine provider-owned capability verification where configured, without claiming payment execution. * Offline POS handoff packets and verified confirmation evidence refs without Grantex executing POS transactions. That is enough to prepare buyer-safe answers and handoffs. It is not a claim that every merchant, channel, provider, bank, POS, or order path is live. ## Why Flags Are Not Enough Live payment or order execution depends on legal language, provider or bank contracts, webhook signature behavior, data-residency review, final buyer confirmation, merchant support ownership, audit verification, incident runbooks, monitoring, and rollback ownership. Those are not just deployment details. They are part of the safety model. Commerce V1 therefore exposes a machine-readable live-readiness snapshot. The snapshot reports only safe values: booleans, requirement keys, env names, and blocker strings. It does not carry passports, bearer tokens, provider secrets, raw payloads, DB URLs, Redis URLs, or webhook secrets. If any readiness acknowledgement is missing, provider, bank, POS, or merchant execution paths must fail closed with a precise blocker. ## The Required Evidence The readiness gate requires acknowledgements for legal approval, provider or bank contract confirmation, sandbox E2E completion, webhook signature confirmation, data residency, final user confirmation text, merchant approval, OACP E2E evidence, production secret review, audit verification, operator runbook approval, support ownership, and rollback owner assignment. This keeps the release posture clear: * Shopify runtime sync can continue when credentials are valid. * Future WooCommerce/ERP/bank/provider configs can be saved as pending-adapter setup without implying live support. * Public docs and demos must distinguish OACP authority evidence from provider settlement, bank authorization, POS confirmation, channel approval, or certification claims. * AgenticOrg must preserve OACP artifacts, source/freshness labels, and fail-closed blockers across buyer channels. * Execution starts only after the evidence packet, provider/bank/POS approval, and runtime readiness snapshot all clear. ## Current Status As of June 25, 2026, the current public OACP posture is not a broad live-payment or live-order claim. AgenticOrg owns merchant self-service config and Shopify runtime sync; Grantex owns authority artifacts; provider, bank, POS, and merchant systems own execution. Public wording must keep those owners separate. That boundary must stay visible in code, tests, docs, and operations: "OACP authority artifacts are available" is different from "a merchant/provider/bank has approved live execution." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # DPDP Act 2023 and AI Agents: What Your Engineering Team Must Know Source: https://docs.grantex.dev/blog/dpdp-act-ai-agents India's DPDP Act creates specific obligations for AI agent deployments. Here's what engineering teams need to implement. **Updated 2026-09-30.** This post first said the DPDP Act's obligations were active law. That was wrong. The DPDP Rules 2025 (G.S.R. 846(E), notified 13 November 2025) bring the Act into force in stages: the Board and institutional provisions from 13 November 2025, Consent Manager registration from 13 November 2026, and the notice, consent, security, breach, erasure, rights and penalty provisions from **13 May 2027**. We have also corrected section numbers (withdrawal is s.6(4); grievances are s.8(10) and s.13; access is s.11 and erasure s.12), replaced code examples that showed an API Grantex does not have, and removed claims that any product makes you "compliant". The EU AI Act dates now follow Regulation (EU) 2026/1744. India's Digital Personal Data Protection Act 2023 is on the statute book, and its main obligations apply from 13 May 2027. If your AI agents process personal data of people in India (reading emails, accessing calendars, analysing documents, managing contacts), your engineering team has specific obligations to prepare for. This post breaks down what the DPDP Act requires for AI agent deployments, why agents create unique compliance risks, and what you need to build. ## What DPDP Requires The DPDP Act establishes a framework around three roles: 1. **Data Principal** — the individual whose personal data is processed (your end user) 2. **Data Fiduciary** — the organization that determines how and why data is processed (usually you, the developer) 3. **Data Processor** — a person that processes data on behalf of the fiduciary, such as a hosting or service provider. Your AI agent is software, not a Data Processor; its processing is the fiduciary's, or a processor's where one runs it The Act creates obligations in six areas that directly affect AI agent deployments: * **Consent (s.6):** Consent must be free, specific, informed, unconditional and unambiguous, given by a clear affirmative action. Bundling unrelated purposes into a single consent does not meet that standard. * **Purpose limitation (s.4, s.6(1)):** Data can only be processed for a lawful purpose the user consented to (or a legitimate use). An email-reading agent should not start accessing files without consent for that purpose. * **Notice (s.5, DPDP Rules r.3):** Before or when consent is asked for, the user receives a notice that itemises the data and the purposes and explains how to withdraw, exercise rights and complain to the Board. * **Withdrawal (s.6(4)):** The user must be able to withdraw consent at any time, and withdrawal must be as easy as granting consent. After withdrawal, processing must cease within a reasonable time (s.6(6)). * **Data principal rights (s.11, s.12):** Users can request a summary of their data and its processing (s.11), and correction and erasure (s.12). * **Grievance mechanism (s.8(10), s.13):** You must provide an effective way for users to raise grievances, and publish a response period of no more than 90 days (DPDP Rules r.14(3)). ## Why AI Agents Are a Risk Vector Traditional web applications have a relatively bounded compliance surface. The user fills out a form, the backend processes it, data is stored in a database. The data flow is predictable and auditable. AI agents are fundamentally different: **Agents are autonomous.** Once authorized, an agent makes decisions about what data to access and what actions to take. A calendar agent might read 500 events to find a free slot. An email summarizer processes every email in the inbox. The scope of data access is determined at runtime by the agent, not at design time by the developer. **Agents delegate.** Multi-agent pipelines mean one agent hands off tasks to sub-agents. The email summarizer might call a translation agent, which calls a formatting agent. Each delegation extends the data processing chain — and each link must maintain the original consent boundaries. **Agents are opaque.** LLM-based agents make non-deterministic decisions. You cannot predict exactly which data an agent will access or what actions it will take. This makes traditional "data processing inventory" approaches insufficient. **Agents scale horizontally.** A single deployment might serve thousands of users simultaneously, each with different consent profiles. Manual consent tracking does not work at this scale. These characteristics mean that consent boundaries are hard to bolt on after the agent is built. The authorization layer is a natural place to bound what an agent can do. ## Four Things Your Engineering Team Should Build ### 1. Structured Consent Records Every agent authorization that relies on consent should have a structured consent record that captures: * **Who** gave consent (the data principal) * **What** was consented to (specific purposes, not vague descriptions) * **When** consent was given * **How** the user was informed (the exact notice version shown) * **How long** processing may continue This is not a checkbox in your terms of service. The fiduciary bears the burden of proving consent (s.6(10)), so the record has to stand up on its own. In Grantex, a consent record binds a data principal to an existing, active grant and to a versioned consent notice, and carries a signed proof (a JWS) over both: ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const consent = await grantex.dpdp.createConsentRecord({ grantId: 'grnt_01HXYZ...', dataPrincipalId: 'user_abc123', purposes: [ { code: 'email_summary', description: 'Read email subjects and bodies to generate daily summaries', }, ], consentNoticeId: 'email-summarizer-notice', processingExpiresAt: '2027-12-31T23:59:59Z', }); ``` The record stores the purposes and the grant's scopes side by side. Grantex does not check an agent's later actions against the purposes; the grant's scopes are what bound the agent, so choose scopes that match the purposes. ### 2. Purpose Enforcement Consent without enforcement is just documentation. The DPDP Act allows processing only for a lawful purpose (s.4). For AI agents, a practical design is: * The agent's grant token contains only the scopes needed for the consented purposes * Every API call verifies the token's scopes before executing * If the agent tries to access data outside its scope, the request is denied and the denial is logged This is where the authorization layer helps. The grant token is a signed JWT with a `scp` claim, and services verify the scope before executing any action. An email-reading agent cannot access files, calendar entries, or contacts: the token does not contain those scopes, and the check fails. Mapping purposes to scopes remains your design decision. ### 3. Right to Withdrawal Section 6(4) requires that withdrawal of consent is as easy as giving consent. If granting consent takes one click, withdrawal should not take more. You cannot bury the withdrawal mechanism in a settings page behind three navigation levels. The withdrawal control lives in your application; Grantex records the withdrawal and, if you ask, revokes the grant: ```typescript theme={null} const result = await grantex.dpdp.withdrawConsent(consent.recordId, { reason: 'User withdrew consent in account settings', revokeGrant: true, }); // The grant and every grant delegated from it are revoked; an audit entry is written. ``` Without `revokeGrant: true` (or the server flag `DPDP_WITHDRAWAL_REVOKES_GRANT=true`), the grant stays active. Stopping processing in your own systems after a withdrawal is still your step (s.6(6)). ### 4. Audit-Ready Exports When the Data Protection Board inquires into a complaint or a breach, you will need to produce evidence. Useful material includes: * Consent records and the notice versions they were given against * Withdrawal history with timestamps * Authorization and audit logs (what was each agent allowed to do, and what happened?) * Grievance records with the response period you published * Your breach register Grantex's export endpoint produces a JSON package of what it holds for a period: ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/exports \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "dpdp-audit", "dateFrom": "2027-05-13T00:00:00Z", "dateTo": "2027-06-30T23:59:59Z" }' ``` It covers what Grantex records, not the data your own systems process. ## The EU AI Act Is Next While building DPDP controls, assess whether the EU AI Act applies to your deployment. Under Regulation (EU) 2024/1689 as amended by Regulation (EU) 2026/1744, the Art. 50 transparency obligations apply from 2 August 2026, high-risk obligations for Annex III systems from 2 December 2027, and for product-embedded Annex I systems from 2 August 2028. Depending on your role and risk classification, relevant supporting controls may include: * **Risk management documentation** (Art. 9) — what controls limit what your agents can do? * **Record-keeping** (Art. 12) — can you show what your agents were authorised to do and when? * **Human oversight mechanisms** (Art. 14) — can operators see what agents are doing and stop them? Some of the same controls help with both laws: consent and grant records support human oversight evidence, and the audit log supports record-keeping. They do not cover the EU AI Act on their own; its Art. 50 disclosures, risk management, technical documentation and conformity assessment are separate work. Grantex's export endpoint supports `dpdp-audit`, `gdpr-article-15` and `eu-ai-act-evidence` exports from the same underlying records. ## What To Do Now 1. **Audit your current agent authorization.** Are your agents using shared API keys or structured, scoped, consent-backed grants? Shared keys make it hard to show what each agent was allowed to do for whom. 2. **Map your data processing purposes.** For each agent, document exactly what personal data it accesses and why. This becomes your consent record schema. 3. **Implement structured consent.** Create consent records for every agent authorization that relies on consent, with the TypeScript, Python or Go SDK, the CLI, or the REST API. This is the foundational step. 4. **Build the data-principal experience.** Use the authenticated DPDP APIs or SDKs to list consent records, withdraw consent, request exports/erasure, and file grievances from your own application. Grantex does not currently provide a separate or embeddable DPDP end-user portal, and it is not a Consent Manager. 5. **Set up audit exports.** Run a test export now, and check it against what your counsel says you will need to produce. The main DPDP obligations apply from 13 May 2027. Penalties are set in the Schedule to the Act (up to Rs 250 crore for failing to take reasonable security safeguards). The engineering work is straightforward if you start early with the right infrastructure. This post is not legal advice. ## Learn More * [DPDP Act 2023 Compliance Guide](/compliance/dpdp-act-2023) — detailed section-by-section mapping * [EU AI Act Compliance Guide](/compliance/eu-ai-act) — article-by-article mapping * [DPDP Compliance Module](/features/dpdp-compliance) — SDK reference and API * [Compliance Matrix](/guides/compliance-matrix) — cross-framework mapping table * [OWASP Agentic Top 10 and Compliance](/blog/owasp-agentic-top-10-compliance) — security framework mapping ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # From Copilot to Autonomous: Why Authorization Must Evolve with AI Source: https://docs.grantex.dev/blog/from-copilot-to-autonomous-agents AI went from autocomplete to autonomous in three years. Authorization did not keep up. Here is why the shift from assisted to agentic AI demands a new security model. The AI industry went through three distinct phases in under four years. Authorization infrastructure did not evolve at all. ## Phase 1: AI Assisted (2018-2022) Autocomplete. Suggestions. Answering questions. The human clicked every button. In this era, AI was a tool inside an application. GitHub Copilot suggested lines of code — you hit Tab to accept. ChatGPT drafted an email — you read it, edited it, and clicked Send yourself. Google Smart Compose filled in sentences — you decided which ones to keep. The security model was simple: the human had the credentials, the human performed the actions, and the AI was just text generation sandboxed inside the UI. There was nothing to authorize because the AI never acted. OAuth 2.0 handled this perfectly. The user logged in, the app got a token, and every action was the user's action. ## Phase 2: AI Generated (2023-2024) Copilot writes the code. ChatGPT drafts the email. The human still reviews everything. This was the generation era. AI produced artifacts — code, text, images, data analysis — but the human was always in the loop before anything reached production. You reviewed the PR. You proofread the draft. You checked the analysis before presenting it. The security model held, barely. The AI generated content, but the human executed actions. The boundary between "AI suggested it" and "the human did it" was clear. Credentials still belonged to humans, and humans still made the final call. OAuth 2.0 still worked. A bit awkwardly — who authored the code, the human or the AI? — but the token holder was always human. ## Phase 3: AI Acts (2025-Now) Agents read files, push code, call APIs, send emails, spend money — autonomously. This is where everything breaks. In 2025, AI agents stopped being generators and became actors. An agent does not suggest a calendar invite — it creates one. It does not draft a Slack message — it sends one. It does not recommend a purchase — it makes one. And it does all of this without a human reviewing each step, because the entire point of an agent is autonomous execution. The security model collapsed. OAuth 2.0 was designed for a human at a keyboard. The token represents the user's session. The scopes were chosen at app registration time. Revocation is advisory, not instant. There is no concept of delegation depth, cascade invalidation, or per-agent identity. **No auth layer exists for this phase.** ## Why This Matters Now The shift from Phase 2 to Phase 3 is not gradual — it is a cliff. Consider what changes: | | Phase 2: Generated | Phase 3: Acts | | - | - | - | | **Who holds the credential?** | Human | Agent | | **Who performs the action?** | Human | Agent | | **Review before execution?** | Always | Sometimes, often never | | **Blast radius of compromise?** | Limited to human session | Everything the key can reach | | **Attribution** | Clear — human did it | Unclear — which agent, authorized by whom? | | **Revocation urgency** | Hours acceptable | Seconds required | Every row in that table represents a broken assumption in the current security stack. API key management, OAuth token flows, secrets managers — all of them were designed for a world where humans hold credentials and humans perform actions. ## The Gap Is Being Exploited Attackers figured this out before defenders did. In 2025 alone, 18.1 million API keys were stolen (SpyCloud). 29 million secrets were committed to GitHub — up 34% year-over-year. The average breakout time from initial access to lateral movement is now 29 minutes. But here is the critical insight: **agents make the damage worse**. A stolen API key in a human's hands is dangerous. A stolen API key in an agent's hands — running autonomously, 24/7, at machine speed — is catastrophic. A human with a compromised key might access a few files before someone notices. An agent with the same key will systematically traverse every accessible resource, exfiltrate data, and pivot to connected services in minutes. And it will do so silently, because agents do not trigger the behavioral heuristics designed to catch human attackers. ## What Authorization Needs to Look Like The authorization layer for autonomous agents needs five properties that do not exist in OAuth 2.0: ### 1. Per-Agent Identity Every agent instance needs its own cryptographic identity — not a shared `client_id`, not an inherited API key. When an action happens, you need to know *which specific agent* did it, not just which application it came from. Grantex assigns each agent a DID (Decentralized Identifier) backed by a key pair. The agent's identity is verifiable, rotatable, and independent of the platform it runs on. ### 2. Human Consent Before Action The user must see — in plain language — what the agent wants to do and explicitly approve it. Not a checkbox buried in a Terms of Service. A clear consent screen: "This agent wants to read your calendar and send emails on your behalf." This is not just good UX. It maps to OWASP ASI-01, it supports the human oversight the EU AI Act requires for high-risk AI systems (Art. 14), and it is basic trust architecture. ### 3. Scoped, Time-Limited Tokens The agent gets exactly the permissions it needs — `calendar:read`, `email:send` — not unrestricted access to every API you have ever integrated. The token expires. The scopes are signed into the JWT and verified at every API call. If the token is compromised, the attacker gets limited access for a limited time. Not root forever. ### 4. Instant, Cascading Revocation One API call revokes the agent's token. If the agent delegated to sub-agents, all their tokens are invalidated in the same operation. The revocation takes effect immediately — not at the next token expiry. In a world where breach-to-exfiltration takes 4 minutes, you cannot afford 1-hour token windows. ### 5. Tamper-Proof Audit Trails Every action is cryptographically linked to the grant that authorized it. Who approved it, when, what scopes were active, what the agent did with them. Hash-chained, append-only, exportable for compliance. When the auditor asks "what did this agent do?", you have an answer that cannot be retroactively edited. ## The Standard Is Here Grantex implements all five properties as an open protocol. Apache 2.0 licensed. IETF Internet-Draft submitted. SOC 2 readiness control mapping published; formal third-party attestation is not published. SDKs for TypeScript, Python, and Go. Integrations with major agent frameworks. The shift from copilot to autonomous agent happened fast. Authorization needs to catch up. ## Get Started * [How Grantex Works](/concepts/how-it-works) — the 5-step authorization flow * [Grantex vs. OAuth 2.0](/concepts/vs-oauth) — a detailed comparison * [Quickstart](/quickstart) — authorize your first agent in 5 minutes * [Protocol Specification](/protocol/specification) — the full open standard ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Gemma 4 Just Shipped Offline AI Agents. Here's How to Secure Them. Source: https://docs.grantex.dev/blog/gemma-4-offline-agents-security Gemma 4 enables offline agentic workflows on-device. But offline agents with no auth story are a security gap. Here's how @grantex/gemma closes it. Google just shipped Gemma 4, and it changes the agent security landscape in a way that existing authorization protocols are not prepared for. Gemma 4's headline feature is on-device agentic execution. A model small enough to run on a Raspberry Pi 5 or a mid-range Android phone can now call tools, chain reasoning steps, and execute multi-step workflows -- all without touching the cloud. The model handles function calling natively, generates structured tool invocations, and supports multi-turn interactions with local context. This is a significant shift. Until now, AI agents were cloud-hosted by definition. Authorization meant verifying a token on each API call, revoking access in real time, and logging every action to a cloud audit trail. The agent was always one HTTP hop away from the authorization server. On-device agents break all three assumptions. ## The Security Gap Consider a concrete example: a home automation agent running Gemma 4 on a Raspberry Pi. The user has consented to let the agent read temperature sensors and control the thermostat. The Pi connects to Wi-Fi daily but operates offline most of the time. With a cloud-hosted agent, authorization is straightforward: 1. The agent calls the thermostat API 2. The API verifies the grant token with Grantex 3. If the token is valid and the scopes match, the action proceeds 4. An audit entry is logged in the cloud Every step requires connectivity. Now remove the network: 1. The agent calls the thermostat API (local network, or direct GPIO) 2. **Who verifies the grant token?** The Grantex API is unreachable 3. **Who checks whether the grant has been revoked?** The revocation list is on the server 4. **Who logs the action?** The cloud audit log is unreachable The obvious workaround is to skip authorization while offline. The agent just runs. When it reconnects, it syncs results. But this creates an accountability gap: there is no record of what happened, no proof that the user consented, and no mechanism to enforce scope boundaries. For home automation, the stakes are modest. For medical devices, industrial controllers, or financial agents, the gap is a compliance failure. ## Consent Bundles: The Missing Piece The core insight is that offline authorization does not need a live server -- it needs a **pre-authorized cryptographic package** that the device can verify locally. We call this a **consent bundle**. It contains: * **A signed grant token** (RS256 JWT) -- the same token format Grantex uses online, with all the standard claims (`agt`, `sub`, `scp`, `grnt`, `exp`) * **A JWKS snapshot** -- the server's public keys at the time of issuance, used to verify the token's RSA signature without a network call * **An Ed25519 key pair** -- used to sign every offline audit entry, so the server can verify their authenticity at sync time * **An offline expiry** -- a hard deadline after which the bundle is no longer valid, separate from the token's own `exp` claim The bundle is created in a single API call while the device is online. After that, the device operates independently. ## How It Works Here is the complete flow on a Raspberry Pi running the Python SDK: ```python theme={null} from grantex_gemma import ( create_consent_bundle, store_bundle, load_bundle, create_offline_verifier, create_offline_audit_log, ) # ---- Phase 1: Provisioning (online) ---- bundle = create_consent_bundle( api_key=os.environ["GRANTEX_API_KEY"], agent_id="ag_thermostat_01", user_id="user_alice", scopes=["sensor:read", "thermostat:write"], offline_ttl="72h", ) # Encrypt and save to disk store_bundle(bundle, "/data/bundle.enc", os.environ["BUNDLE_KEY"]) # ---- Phase 2: Offline operation ---- bundle = load_bundle("/data/bundle.enc", os.environ["BUNDLE_KEY"]) verifier = create_offline_verifier( jwks_snapshot=bundle.jwks_snapshot, require_scopes=["sensor:read"], ) audit_log = create_offline_audit_log( signing_key=bundle.offline_audit_key, log_path="/data/audit.jsonl", ) # Before every agent action: grant = verifier.verify(bundle.grant_token) # ~3ms on Pi 5 # grant.scopes == ["sensor:read", "thermostat:write"] # After the action: audit_log.append( action="sensor.read", agent_did=grant.agent_did, grant_id=grant.grant_id, scopes=grant.scopes, result="success", metadata={"temperature": 22.5}, ) ``` The `verifier.verify()` call runs in under 5ms on a Pi 5. It decodes the JWT, resolves the signing key from the JWKS snapshot by `kid`, verifies the RS256 signature, checks expiry with clock-skew tolerance, and enforces required scopes. No network call. No latency spike. The audit log appends a JSONL entry with a SHA-256 hash chain linking each entry to the previous one, plus an Ed25519 signature from the key pair in the bundle. Tampering with any entry breaks the chain and invalidates all subsequent signatures. ## What Happens at Sync Time When the Pi reconnects, it uploads the offline audit entries: ```python theme={null} result = audit_log.sync( api_key=os.environ["GRANTEX_API_KEY"], bundle_id=bundle.bundle_id, ) print(result.accepted) # 47 print(result.rejected) # 0 print(result.revocation_status) # "active" ``` The server does three things: 1. **Verifies the hash chain** -- each entry's `prevHash` must match the previous entry's `hash`. If any entry has been modified, inserted, or deleted, the chain breaks. 2. **Verifies Ed25519 signatures** -- the server stored the public key when the bundle was created. It verifies that each entry was signed by the corresponding private key. This proves the entries came from the device that received the original bundle. 3. **Checks revocation** -- if the grant was revoked while the device was offline, the response includes `revocation_status: "revoked"` with a timestamp. The device learns that it should stop operating and can determine which actions occurred before versus after the revocation. Entries that pass all checks are stored in the cloud audit log alongside online entries. The offline entries are tagged with the bundle ID so they can be distinguished from real-time entries in queries and compliance exports. ## What About Revocation? This is the honest limitation: if a grant is revoked while the device is offline, the device will not know until it syncs. During that window, the agent continues operating with the cached token. This is inherent to any offline system. Even certificate revocation in TLS has the same problem: if you cannot reach the CRL or OCSP responder, you are working with stale revocation data. The mitigation is straightforward: use short `offlineTTL` values for sensitive operations. A medical device agent should use `1h`, not `72h`. A home automation agent with daily Wi-Fi connectivity can safely use `72h`. The `shouldRefresh()` function alerts when 80% of the TTL has elapsed, giving the device time to refresh the bundle during the next connectivity window. ## Platform Coverage `@grantex/gemma` (TypeScript) and `grantex-gemma` (Python) ship today. They work anywhere Gemma 4 runs: | Platform | SDK | Storage | Notes | | - | - | - | - | | Raspberry Pi | Python | AES-256-GCM encrypted file | \~3ms verify on Pi 5 | | Linux / macOS | TypeScript or Python | Encrypted file | Server-side agents with intermittent connectivity | | Android | Kotlin (Nimbus JOSE) | EncryptedSharedPreferences | Hardware-backed Keystore | | iOS | Swift (CryptoKit) | Keychain | Secure Enclave on supported devices | The Google ADK and LangChain integrations verify tokens through their configured JWKS endpoint before scope enforcement; they are not automatically network-free. For a truly disconnected Gemma deployment, use the consent bundle's preloaded JWKS snapshot and offline audit log shown above. ## Try It Install the SDK: ```bash theme={null} npm install @grantex/gemma # TypeScript pip install grantex-gemma # Python ``` Follow the [quickstart guide](/examples/quickstart-gemma) for a step-by-step walkthrough, or jump to the platform-specific guides: * [Raspberry Pi](/guides/raspberry-pi) * [Android](/guides/android-gemma4) * [iOS](/guides/ios-gemma4) The full API reference is at [Gemma 4 SDK](/sdks/gemma). The security model, architecture diagram, and limitation analysis are documented in [Offline Authorization](/features/offline-authorization). If you are evaluating this for a compliance-sensitive use case, start there. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Why Grantex Is Not A Transaction Toll Booth Source: https://docs.grantex.dev/blog/grantex-not-transaction-toll-booth Why OACP authority signs artifacts without relaying every buyer and seller interaction. # Why Grantex Is Not A Transaction Toll Booth ## Summary Grantex is the OACP authority, not a relay for every buyer message. Valid cached artifacts let AgenticOrg answer non-binding questions while commitment requests remain gated by merchant, provider, bank, POS, and channel evidence. ## Target Audience Architects, operators, and platform buyers. ## Architecture Diagram ```mermaid theme={null} flowchart LR agentic[AgenticOrg runtime] -->|authority refresh| grantex[Grantex] grantex -->|artifacts| cache[AgenticOrg cache] cache -->|valid non-binding answer| buyer[Buyer] buyer -->|commitment request| prep[Purchase preparation] prep -->|provider/merchant approval needed| provider[Provider and merchant systems] ``` ## End-To-End Flow Grantex signs the trust artifact. AgenticOrg stores a scoped cache. Buyer questions use the cache until freshness or revocation fails. Purchase requests must use approved merchant/provider/bank/POS/channel paths and cannot be converted into success by cache alone. ## What Is Implemented Now The authority endpoint issues/refuses artifacts. AgenticOrg cache and bridge routes consume them for Q\&A and prepared handoff. AgenticOrg merchant config records Shopify runtime setup plus WooCommerce/ERP/bank/custom provider pending-adapter intent without making Grantex the config owner. ## What Requires External Approval Or Config Public tenant launch, provider or bank rail execution, POS callback approval, and channel rollout. ## Failure Modes * Cache used past TTL. * Commitment request treated like a discovery request. * Grantex outage misunderstood as permission to execute. ## Safe User Wording Examples * "Grantex signed the source artifact; AgenticOrg answered from valid cache." * "A valid cache supports discovery, not payment authority." * "This request needs provider or merchant approval before execution." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Grantex v0.1: What's Included Source: https://docs.grantex.dev/blog/grantex-v0-1-whats-included A complete tour of everything shipping in the Grantex v0.1 release -- SDKs, integrations, CLI, enterprise features, and more. Grantex v0.1 is our first public release, and we wanted it to be comprehensive. Whether you are building a single-agent prototype or deploying a multi-agent system in production, the tooling is here. Let's walk through everything that ships today. ## SDKs ### TypeScript (`@grantex/sdk`) The TypeScript SDK is the primary client library. It covers the full API surface: agent registration, authorization, token exchange, offline and online verification, grants management, audit logging, and every enterprise endpoint. It uses native `fetch`, ships as ESM, and requires Node 18+. ```bash theme={null} npm install @grantex/sdk ``` [TypeScript SDK docs](/sdks/typescript/overview) ### Python (`grantex`) The Python SDK mirrors the TypeScript API with idiomatic Python conventions -- snake\_case methods, dataclass responses, and `httpx` under the hood. It supports Python 3.9+ and includes full type annotations for editor autocomplete. ```bash theme={null} pip install grantex ``` [Python SDK docs](/sdks/python/overview) ## Framework Integrations We built integrations for every major agent framework so you can add Grantex authorization to your existing stack without rewriting your agent logic. | Integration | Package | What It Does | | - | - | - | | **LangChain** | `@grantex/langchain` | LangChain tools that wrap Grantex operations -- use them as agent tools in any LangChain pipeline. | | **Vercel AI** | `@grantex/vercel-ai` | Vercel AI SDK tools for building authorized AI chatbots and assistants. | | **AutoGen** | `@grantex/autogen` | AutoGen function tools for multi-agent conversations with scoped authorization. | | **CrewAI** | `grantex-crewai` | CrewAI tools that let crew agents request and verify grants. | | **OpenAI Agents SDK** | `grantex-openai-agents` | Python tools for the OpenAI Agents SDK with built-in authorization. | | **Google ADK** | `grantex-adk` | Google Agent Development Kit tools for authorized agent actions. | Each integration ships with its own documentation page and a runnable example in the `examples/` directory. [Integrations overview](/integrations/overview) ## MCP Server (`@grantex/mcp`) The MCP server exposes 13 Grantex tools as an MCP (Model Context Protocol) service. Point Claude Desktop, Cursor, Windsurf, or any MCP-compatible client at it and your AI assistant can manage agents, grants, and tokens through natural language. ```bash theme={null} npx @grantex/mcp ``` [MCP server docs](/integrations/mcp) ## CLI (`@grantex/cli`) The Grantex CLI gives you full control from your terminal. Register agents, initiate authorization flows, exchange tokens, query audit logs, manage policies -- everything the SDKs can do, the CLI can do. ```bash theme={null} npx @grantex/cli agents list npx @grantex/cli tokens verify npx @grantex/cli audit entries --agentId ``` [CLI docs](/integrations/cli) ## Developer Portal The developer portal at [grantex.dev/dashboard](https://grantex.dev/dashboard) provides a web UI for managing your Grantex account. View registered agents, browse active grants, inspect audit logs, and configure policies -- all without writing code. ## Enterprise Features Grantex v0.1 includes a full suite of enterprise capabilities, available out of the box: * **Policy Engine** -- define JSON-based authorization policies that are evaluated on every grant request. Restrict by scope, time window, IP range, or custom attributes. ([Policies docs](/sdks/typescript/policies)) * **Anomaly Detection** -- automated analysis of agent behavior patterns. Flags unusual activity like scope escalation, off-hours access, or velocity spikes. ([Anomalies docs](/sdks/typescript/anomalies)) * **SCIM Provisioning** -- sync users and groups from your identity provider (Okta, Azure AD, OneLogin) to Grantex. ([SCIM docs](/sdks/typescript/scim)) * **SSO** -- SAML and OIDC single sign-on for the developer portal. ([SSO docs](/sdks/typescript/sso)) * **Compliance Exports** -- generate audit reports that can support SOC 2, ISO 27001 and EU AI Act evidence reviews; they are not certifications. ([Compliance docs](/sdks/typescript/compliance)) * **Billing** -- Stripe-backed usage metering and plan management with automatic enforcement of plan limits. ([Billing docs](/sdks/typescript/billing)) ## Auth Service The auth service is a production-ready Fastify server backed by PostgreSQL and Redis. It handles authorization flows, token issuance and verification, webhook delivery with retry, rate limiting, and PKCE (S256). It deploys to Cloud Run and is the reference implementation of the Grantex protocol. [Self-hosting guide](/guides/self-hosting) ## What's Next This is v0.1, not v1.0. We are actively working on: * Additional framework integrations based on community demand * Expanded policy engine with OPA (Open Policy Agent) support * SDK support for more languages (Go, Java, Rust) * Hosted multi-tenant service for teams that do not want to self-host Star the repo, try the [quickstart](/quickstart), and let us know what you would like to see next on [GitHub](https://github.com/mishrasanjeev/grantex). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Grantex v2.2: Policy Engines, A2A, and the Managed Cloud Source: https://docs.grantex.dev/blog/grantex-v2-2 v2.2 brings OPA and Cedar policy backends, Google A2A agent-to-agent bridging, a managed cloud offering, and SDK 0.2.0 across all three languages. When we shipped Grantex v1.0 three months ago, the goal was straightforward: give AI agents real authorization instead of all-or-nothing API keys. Since then, the ecosystem has grown from a spec and two SDKs to 30+ packages spanning three languages, 11 framework integrations, and a production authorization server handling real traffic. v2.2 closes the loop on enterprise readiness. Here is what shipped. ## Policy Engines: OPA and Cedar The built-in policy engine handles simple allow/deny rules well, but production deployments need richer policy languages. v2.2 adds pluggable policy backends for [Open Policy Agent](https://www.openpolicyagent.org/) (Rego) and [Cedar](https://www.cedarpolicy.com/) (Amazon's authorization policy language). Configuration is a single environment variable: ```bash theme={null} POLICY_BACKEND=opa OPA_URL=http://localhost:8181 ``` The authorization server translates every authorization request into an evaluation context aligned with the [OpenID AuthZEN](https://openid.net/specs/openid-authzen-authorization-api-1_0.html) subject/resource/action/context model, then forwards it to the configured PDP. If the PDP is unreachable, a configurable fallback kicks in — deny (default), allow, or fall back to the built-in engine. Policy-as-code is also here: upload policy bundles via `POST /v1/policies/sync` or wire up a git webhook for automatic sync on push. ## A2A Bridge: Agent-to-Agent with Grantex Auth Google's [Agent-to-Agent (A2A) protocol](https://google.github.io/A2A/) defines how agents discover and communicate with each other. The missing piece was authorization — A2A assumes agents can authenticate, but does not specify how. `@grantex/a2a` (TypeScript) and `grantex-a2a` (Python) bridge that gap. They wrap A2A task creation and status polling with Grantex grant token authentication: ```typescript theme={null} import { A2AClient } from '@grantex/a2a'; const a2a = new A2AClient({ grantex: { apiKey: process.env.GRANTEX_API_KEY }, agentCard: 'https://agent.example.com/.well-known/agent.json', }); const task = await a2a.createTask({ message: 'Book a flight from DEL to BOM on March 15', grantToken: token.grantToken, }); ``` Every A2A request carries a Grantex grant token. The receiving agent verifies it before processing. ## Managed Cloud Self-hosting is still fully supported, but for teams that want to skip the infrastructure work: the managed cloud is live at `api.grantex.dev`. Sign up via the developer portal, get an API key, and start authorizing agents. Usage metering (`GET /v1/usage`), custom domains with DNS TXT verification, email verification for new accounts, and per-plan dynamic rate limiting are all included. ## SDK 0.2.0 All three SDKs — TypeScript (`@grantex/sdk`), Python (`grantex`), and Go (`grantex-go`) — ship at 0.2.0 with full coverage of the v2.2 feature set: * `usage.current()` / `usage.history()` — usage metering * `domains.create()` / `domains.list()` / `domains.verify()` / `domains.delete()` — custom domain management * Budget controls, event streaming, and vault APIs (shipped in 0.1.9) The conformance test suite (`@grantex/conformance` v0.1.4) validates all of the above. ## By the Numbers | Metric | Count | | - | - | | npm packages | 14 | | PyPI packages | 6 | | Go modules | 1 | | Framework integrations | 11 | | Auth service tests | \~362 | | SDK tests (combined) | 317 | | Mintlify doc pages | 174+ | | IETF draft revision | -02 published; -03 under review | ## What's Next The active individual IETF draft (`draft-mishra-oauth-agent-grants-02`) has been narrowed to an OAuth authorization profile; budget controls, event streaming, credential vaults, and external policy backends are implementation-specific extension work. A revision `-03` candidate and implementation report are maintained in the repository. We also published a draft NIST NCCoE comment mapping DAAP capabilities to the AI Risk Management Framework; no public submission receipt or NIST endorsement is claimed. Standards alignment is the theme for the next phase. The AuthZEN conformance mapping is published, and we are exploring formal IETF working group adoption. * [OPA integration guide](/guides/opa-integration) * [Cedar integration guide](/guides/cedar-integration) * [Policy-as-code guide](/guides/policy-as-code) * [A2A integration docs](/integrations/a2a) * [Usage metering guide](/guides/usage-metering) * [Custom domains guide](/guides/custom-domains) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Introducing Grantex: OAuth 2.0 for AI Agents Source: https://docs.grantex.dev/blog/introducing-grantex Grantex is an open delegated authorization protocol that brings human-approved, scoped, revocable permissions to AI agents. AI agents are shipping fast. They book flights, send emails, move money, and deploy code. But here is the uncomfortable truth: most of them operate with all-or-nothing API keys and zero audit trail. If an agent goes rogue, you find out after the damage is done. We built Grantex to fix that. ## The Problem OAuth 2.0 was designed for human users clicking "Allow" on a consent screen. It works brilliantly for that. But agents are not humans. They operate autonomously, spawn sub-agents, and chain actions across services. OAuth was never designed for: * **Agent identity** -- agents need their own cryptographic identity, not borrowed user credentials. * **Delegation chains** -- a parent agent granting a sub-agent a narrower set of permissions. * **Action-level auditing** -- knowing exactly what an agent did, not just that it authenticated. * **Real-time revocation** -- killing a misbehaving agent's access in milliseconds, not minutes. ## The Solution Grantex is an open protocol (Apache 2.0) that provides all of the above. Every agent gets a DID-based identity. Every permission is a scoped, time-limited grant token (JWT) that a human explicitly approved. Every action is logged in an append-only, hash-chained audit trail. And any grant can be revoked instantly. Here is what the flow looks like in TypeScript: ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const gx = new Grantex({ apiKey: process.env.GRANTEX_API_KEY, baseUrl: 'https://api.grantex.dev', }); // 1. Register an agent const agent = await gx.agents.create({ name: 'travel-booking-agent', description: 'Books flights and hotels for users', }); // 2. Request authorization from a user const auth = await gx.authorize({ agentId: agent.id, userId: 'user_alice', scopes: ['flights:book', 'hotels:search'], callbackUrl: 'https://app.example.com/callback', }); // → redirect user to auth.consentUrl // 3. Exchange the authorization code for a grant token const token = await gx.tokens.exchange({ code: callbackCode, agentId: agent.id, }); // 4. Verify the token before acting const result = await gx.tokens.verify(token.grantToken); console.log(result.scopes); // ['flights:book', 'hotels:search'] ``` The same flow works in Python, and across every integration we ship. ## What Ships Today * **Protocol spec v1.0** (final) -- the full specification is public and frozen. * **TypeScript SDK** (`@grantex/sdk`) and **Python SDK** (`grantex`) -- production-ready. * **7 framework integrations** -- LangChain, AutoGen, CrewAI, Vercel AI, OpenAI Agents SDK, Google ADK, and an MCP server for Claude Desktop. * **CLI** (`@grantex/cli`) -- manage agents, grants, and tokens from your terminal. * **Enterprise features** -- policy engine, SCIM/SSO, anomaly detection, compliance exports, and Stripe billing. ## Get Started * [Quickstart guide](/quickstart) -- up and running in under 5 minutes. * [GitHub repository](https://github.com/mishrasanjeev/grantex) -- star, fork, contribute. * [Protocol specification](/protocol/specification) -- read the full spec. * [grantex.dev](https://grantex.dev) -- project homepage. We believe that as agents become more capable, proper authorization becomes more critical, not less. Grantex is our answer to that challenge. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. See his [author profile and ownership contacts](/ownership). # How to Add Permissions to LangChain Agent Tools Source: https://docs.grantex.dev/blog/langchain-agent-permissions Add scoped permissions to configured LangChain tools with verified Grantex JWTs, manifest enforcement, and optional grant-aware audit callbacks. LangChain agent permissions are application-enforced rules that decide whether a configured tool may run for a particular principal and grant. LangChain supplies tool and callback abstractions; your application still has to install the authorization check at each protected execution boundary. This guide covers the two permission paths implemented by Grantex: 1. `createGrantexTool()` for an exact required-scope check on one configured function. 2. `Grantex.wrapTool()` for manifest-based `read`, `write`, `delete`, and `admin` permission levels. **Implementation snapshot, updated July 12, 2026:** these examples target `@grantex/langchain@0.1.7`, `@grantex/sdk@0.5.1`, and the repository's resolved `@langchain/core@1.2.2`. The adapter declares `@langchain/core >=0.3.0`, but that range is not a promise of compatibility with every future LangChain package or agent runtime. ## Does LangChain have permissions for agent tools? A LangChain agent can choose among the tools its host application registers. LangChain also offers callbacks, tracing, and extension points that applications can use to build controls. LangChain does not, by itself, understand a Grantex grant token, principal, scope, or revocation state. In a typical unwrapped tools array: ```typescript theme={null} import { DynamicTool } from '@langchain/core/tools'; const searchContactsTool = new DynamicTool({ name: 'search_contacts', description: 'Search CRM contacts', func: async (input) => JSON.stringify(await searchContacts(input)), }); const deleteContactTool = new DynamicTool({ name: 'delete_contact', description: 'Delete a CRM contact by ID', func: async (input) => JSON.stringify(await deleteContact(input)), }); const tools = [searchContactsTool, deleteContactTool]; ``` both tools are available to whichever agent or executor receives that array. Your application can filter tools, add middleware, or implement its own checks. The trusted execution boundary, not the model, must verify authority before the protected function runs. Separate three controls: * **Availability:** which tools the application exposes. * **Authorization:** which exposed calls a verified grant permits. * **Audit:** which configured execution events are recorded. Grantex supplies authorization wrappers and an optional audit callback. Neither is a LangChain default, and neither covers a tool where it is not installed. ## Install the tested package snapshot Add the integration to an existing LangChain application with exact versions: ```bash theme={null} npm install --save-exact @grantex/langchain@0.1.7 @grantex/sdk@0.5.1 @langchain/core@1.2.2 ``` Node.js 18 or later is required. Pin your model-provider and higher-level `langchain` packages separately for the agent runtime you use. ## Add an exact scope check with `createGrantexTool` `createGrantexTool()` returns a LangChain `DynamicTool`. On invocation it verifies the configured grant token and requires one exact scope string before calling `func`. ```typescript theme={null} import { createGrantexTool } from '@grantex/langchain'; export function buildCrmTools(grantToken: string) { const searchTool = createGrantexTool({ name: 'search_contacts', description: 'Search CRM contacts', grantToken, requiredScope: 'contacts:read', audience: 'https://crm.example.com', func: async (input) => JSON.stringify(await searchContacts(input)), }); const deleteTool = createGrantexTool({ name: 'delete_contact', description: 'Delete a CRM contact by ID', grantToken, requiredScope: 'contacts:delete', audience: 'https://crm.example.com', func: async (input) => JSON.stringify(await deleteContact(input)), }); return [searchTool, deleteTool]; } ``` A verified token with `contacts:read` but not `contacts:delete` causes the delete wrapper to throw before `deleteContact()` runs. The wrapper delegates to `verifyGrantToken()` in `@grantex/sdk`. It checks: * RS256 signature against the configured JWKS. * Expected issuer and JWT expiry. * Optional `audience` and clock tolerance. * Exact membership of `requiredScope` in the verified `scp` claim. The SDK reuses a bounded, process-level set of JOSE remote-JWKS resolvers. Initial lookup, an unknown key ID, or key rotation can still require network access. There is no latency or network-free guarantee. Local signature and claim verification does not query current grant revocation state. Add online or synchronized state where immediate revocation matters, and use bounded token lifetimes as an additional exposure limit. ### Configured-tool boundary `createGrantexTool()` protects only its configured `func`. It does not discover or rewrite other tools. Wrap each protected function and pass the protected instances, not unwrapped equivalents, to the agent. The `grantToken` option is a string captured by the tool. Build request-, principal-, or session-specific tools when different users need different grants. Do not share one token-bearing tool instance across unrelated principals. This path uses exact scope strings. It does **not** apply the manifest hierarchy below. ## Add manifest-based permissions with `wrapTool` For connector-style tools, `@grantex/sdk` maps tool names to permission levels. `loadManifest()` stores a mapping, and `wrapTool()` applies it when the returned object's `invoke()` runs. ```typescript theme={null} import { Grantex, Permission, ToolManifest } from '@grantex/sdk'; const gx = new Grantex({ apiKey: process.env.GRANTEX_API_KEY, issuer: 'https://grantex.dev', jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', }); gx.loadManifest(new ToolManifest({ connector: 'crm', tools: { search_contacts: Permission.READ, create_contact: Permission.WRITE, delete_contact: Permission.DELETE, export_all: Permission.ADMIN, }, })); let currentGrantToken = initialGrantToken; const protectedSearch = gx.wrapTool(searchContactsTool, { connector: 'crm', tool: 'search_contacts', grantToken: () => currentGrantToken, }); const protectedDelete = gx.wrapTool(deleteContactTool, { connector: 'crm', tool: 'delete_contact', grantToken: () => currentGrantToken, }); const protectedTools = [protectedSearch, protectedDelete]; ``` Loading a manifest alone does not protect anything. Enforcement occurs only through `gx.enforce()` or the object returned by `gx.wrapTool()`. Keep original unwrapped tools out of the executable tool list. The token getter is synchronous. If the application refreshes a token, update the state read by the getter before the next invocation. ### Permission hierarchy for manifest enforcement | Granted level | Covers | | - | - | | `read` | `read` | | `write` | `read`, `write` | | `delete` | `read`, `write`, `delete` | | `admin` | all four levels | For connector `crm`, `tool:crm:write` can cover `search_contacts` and `create_contact`, but not `delete_contact` or `export_all`. Unknown connectors and tools are denied in the default strict mode. This hierarchy applies to `gx.enforce()` and `gx.wrapTool()`, not to `createGrantexTool()`. ## Use the pre-built manifests that exist `@grantex/sdk@0.5.1` exports 53 manifests. Salesforce, HubSpot, Jira, and Stripe are included: ```typescript theme={null} import { jiraManifest, salesforceManifest, stripeManifest, } from '@grantex/sdk/manifests'; gx.loadManifests([salesforceManifest, jiraManifest, stripeManifest]); ``` Inspect the version-pinned CLI catalog: ```bash theme={null} npx --yes @grantex/cli@0.2.5 manifest list npx --yes @grantex/cli@0.2.5 manifest show salesforce ``` A manifest is a permission mapping, not a connector implementation. It does not create a Salesforce, Jira, or Stripe tool, and it does not prove a third-party tool name matches the mapping. ## Define a custom manifest ```typescript theme={null} import { Permission, ToolManifest } from '@grantex/sdk'; const billingManifest = new ToolManifest({ connector: 'internal-billing', tools: { get_invoice: Permission.READ, create_invoice: Permission.WRITE, void_invoice: Permission.DELETE, run_period_close: Permission.ADMIN, }, }); gx.loadManifest(billingManifest); ``` The CLI can generate a draft from supported source patterns: ```bash theme={null} npx --yes @grantex/cli@0.2.5 manifest generate agent_tools.py --connector internal-billing --out billing-manifest.json ``` Generation uses name-based inference. Review every discovered tool and permission; it is not a security-policy generator. ## Add the optional Grantex audit callback `GrantexAuditHandler` writes through `client.audit.log()` only where the callback is attached. ```typescript theme={null} import { Grantex } from '@grantex/sdk'; import { GrantexAuditHandler } from '@grantex/langchain'; const client = new Grantex({ apiKey: process.env.GRANTEX_API_KEY, }); const auditHandler = new GrantexAuditHandler({ client, agentId: 'ag_crm_assistant', agentDid: 'did:grantex:ag_crm_assistant', principalId: 'user_alice', grantToken, }); const result = await executor.invoke( { input: 'Find contacts in Bengaluru' }, { callbacks: [auditHandler] }, ); ``` The current `0.1.7` behavior is specific: * `handleToolStart()` writes `tool:` with `status: "success"` when invocation starts. That is not completion proof. * `handleToolError()` writes generic `tool:error` with `status: "failure"`. * The constructor decodes `grnt`, falling back to `jti`, for attribution. That decode is not authorization. * Tool input and error text enter audit metadata. Apply an appropriate sensitive-data logging policy. * Calls outside the callback-enabled executor or chain are not recorded by this handler. The implemented audit client can list stored entries: ```typescript theme={null} const page = await client.audit.list({ agentId: 'ag_crm_assistant', pageSize: 50, }); ``` Audit and authorization are independent. Keep the scope wrapper at the protected boundary even when the callback is attached. ## Put the controls into a LangChain agent safely For the agent-construction API used by your pinned LangChain application: 1. Obtain a grant for the correct principal, agent, audience, and scopes. 2. Build `createGrantexTool()` or `wrapTool()` protected instances. 3. Pass only protected instances for actions requiring Grantex authorization. 4. Attach `GrantexAuditHandler` only if its current event and metadata behavior fits your policy. 5. Test permitted, missing-scope, malformed, expired, and bypass cases. 6. Add current revocation-state handling if expiry alone is insufficient. Do not call the entire agent protected because one tool is wrapped. Coverage is the set of execution boundaries where verification is installed and tested. ## Which LangChain permission approach should you use? | Requirement | Use | | - | - | | One exact scope per configured function | `createGrantexTool()` | | Connector permission hierarchy | `gx.wrapTool()` plus `ToolManifest` | | Decision without wrapping | `gx.enforce()` | | Grant-aware tool-start and error records | Optional `GrantexAuditHandler` | | Immediate revocation awareness | Online or synchronized state | | Framework-wide coverage | Inventory and wrap every protected boundary | ## Validation checklist * The agent receives protected tool objects, not originals. * Each protected function has the intended exact scope or manifest entry. * Token issuer and audience match the receiving service. * Missing, expired, malformed, and wrongly scoped tokens fail before the underlying function. * Manifest names match the actual LangChain tool names. * Audit callbacks do not capture prohibited data. * Revocation freshness and key rotation meet the threat model. * The pinned LangChain and Grantex versions pass application integration tests. ## Next steps * [LangChain integration reference](/integrations/langchain) * [Scope enforcement guide](/guides/scope-enforcement) * [Custom manifests guide](/guides/custom-manifests) * [Token verification and revocation boundaries](/guides/token-verification) * [Current package versions and limitations](/release-status) * [Grantex source repository](https://github.com/mishrasanjeev/grantex) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # MCP Server Authorization with OAuth 2.1 and Grantex Source: https://docs.grantex.dev/blog/mcp-server-oauth-authentication Current MCP HTTP authorization, the OAuth 2.1 draft profile, and safe evaluation limits for @grantex/mcp-auth 2.0.2. **Release status — updated July 12, 2026:** `@grantex/mcp-auth@2.0.2` exposes useful OAuth endpoint and token-validation building blocks, but it is single-process evaluation software. It is not a complete implementation of the current MCP HTTP authorization specification. The [MCP Auth feature guide](/features/mcp-auth-server) is the authoritative package reference. Remote MCP servers often need transport-level authorization before a client can call sensitive tools. The current [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) defines an optional authorization flow for HTTP-based transports. It profiles a selected set of OAuth standards, including OAuth 2.0 Protected Resource Metadata (RFC 9728), authorization-server discovery, resource indicators, access-token audience validation, and an OAuth 2.1 authorization-code flow. OAuth 2.1 is still an [IETF Internet-Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/), not a published RFC. In this article, “OAuth 2.1” means the draft-based profile required by the current MCP HTTP authorization specification. Authorization is optional in MCP, and the HTTP flow is not the credential model for local STDIO servers. > **Short answer:** for production, use an established, MCP-compatible > authorization server for the HTTP OAuth flow. Then enforce agent-specific > Grantex grants again at each sensitive tool or service boundary. ## MCP authorization and Grantex solve different layers MCP HTTP authorization establishes whether an MCP client may access a protected MCP resource on behalf of a resource owner. Grantex can add a narrower decision at tool execution time: which agent may perform which action, for which principal and resource, under which constraints. A production architecture should keep those responsibilities explicit: 1. **Established authorization server:** use a maintained authorization server that supports the current MCP profile, user authentication and consent, durable authorization-code state, secure client onboarding, token issuance, and revocation. 2. **MCP protected resource:** expose RFC 9728 protected-resource metadata, advertise the permitted authorization server, and validate every access token's issuer, audience, expiry, and required MCP scopes. 3. **Grantex tool enforcement:** after transport authorization succeeds, use a primary Grantex SDK or direct, validated grant verification at each sensitive tool boundary. Bind the decision to the agent, principal, action, resource, constraints, and current revocation state. 4. **Audit and deny safely:** record both the transport authorization context and the tool-level Grantex decision. Deny the call if either layer fails. This makes Grantex complementary to the MCP authorization server; it does not ask `@grantex/mcp-auth@2.0.2` to serve as the production OAuth control plane. ## What @grantex/mcp-auth 2.0.2 implements For local inspection, `createMcpAuthServer()` returns a Fastify instance with these six routes: | Endpoint | Implemented behavior | | - | - | | `/.well-known/oauth-authorization-server` | Authorization-server metadata discovery | | `/register` | Dynamic client registration backed by `ClientStore` | | `/authorize` | PKCE S256 plus client and redirect-URI validation, followed by a Grantex authorization request | | `/token` | PKCE validation plus Grantex exchange and refresh calls | | `/introspect` | JWT signature and claim validation against JWKS | | `/revoke` | Decode the JWT `jti` and request Grantex revocation | The package also exports Express and Hono middleware that validates JWT signatures, claims, algorithms, expiry, and required scopes. This endpoint surface does not itself provide the MCP server's RFC 9728 protected-resource metadata or make the end-to-end flow conformant with the current MCP authorization specification. ## Reproducible evaluation install Use exact package versions and commit both `package.json` and the generated lockfile: ```bash theme={null} npm install --save-exact @grantex/mcp-auth@2.0.2 @grantex/sdk@0.5.1 ``` Use Node.js 18 or newer. After the lockfile is committed, reproduce the same dependency graph in CI with: ```bash theme={null} npm ci ``` Do not replace the version pins with `latest` in an evaluation record. ## Start the evaluation endpoint surface ```typescript theme={null} import { Grantex } from '@grantex/sdk'; import { createMcpAuthServer } from '@grantex/mcp-auth'; const grantex = new Grantex({ baseUrl: 'https://api.grantex.dev', apiKey: process.env.GRANTEX_API_KEY!, }); const authServer = await createMcpAuthServer({ grantex, agentId: 'ag_your_mcp_server', scopes: ['tools:read', 'tools:execute'], issuer: 'https://your-auth-server.example.com', }); await authServer.listen({ port: 3001 }); ``` This starts the published endpoint surface for inspection. It does not create a production MCP authorization deployment or prove end-to-end token issuance. ## Six material limits in 2.0.2 1. **Process-local state:** client registrations use process memory unless you provide a `ClientStore`, and authorization codes always use the non-configurable `InMemoryCodeStore`. Restarts and multi-replica authorize/token flows are unsafe. 2. **No consent page:** `consentUi` adds discovery metadata only. The package does not register `/consent` or render a user-approval interface. 3. **Incomplete code handoff:** the package does not persist the authorization code returned by the Grantex SDK for the token handler. A real backend exchange can therefore fail. 4. **Inactive issuance hook:** the `onTokenIssued` type is declared but the hook is not invoked in `2.0.2`. 5. **No current revocation lookup:** introspection and middleware perform local JWT validation but do not query current revocation state. Use an online state check or synchronized revocation data when immediate revocation matters. 6. **No server-wide redirect allowlist:** `allowedRedirectUris` is declared but is not enforced as a server-wide allowlist in this release. Authorization checks the redirect URI registered for the individual client instead. ## Evaluation-only middleware boundary ```typescript theme={null} import { requireMcpAuth } from '@grantex/mcp-auth/express'; app.use('/mcp', requireMcpAuth({ issuer: 'https://your-auth-server.example.com', scopes: ['tools:execute'], })); ``` This middleware performs local signature, claim, algorithm, expiry, and scope checks. It is not protected-resource discovery, a live revocation check, user consent, or tool-level agent authorization. ## Production checklist * Follow the current MCP HTTP authorization specification rather than treating an older endpoint list as the protocol. * Use an established authorization server and maintained MCP SDK, with exact versions pinned in your own lockfile. * Publish and validate RFC 9728 protected-resource metadata for the actual MCP resource identifier. * Validate issuer, audience, expiry, scopes, and revocation before accepting the transport token. * Enforce the narrower Grantex grant at every sensitive tool or downstream service boundary. * Test denial paths, consent, authorization-code replay, redirect URIs, multi-replica state, token expiry, and revocation end to end. ## Related documentation * [MCP Auth Server package status](/features/mcp-auth-server) * [Grantex release status](/release-status) * [MCP tool-server integration](/integrations/mcp) * [Self-hosting guide](/guides/self-hosting) * [Current MCP HTTP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization) * [OAuth 2.1 Internet-Draft status](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Agent Identity for Machine Payments: Why MPP Needs a Passport Layer Source: https://docs.grantex.dev/blog/mpp-agent-identity Grantex adds verifiable agent and principal credential context that merchants can validate alongside independent payment and risk controls. Machine Payments Protocol shipped on March 18. Within 48 hours, thousands of agents were making HTTP 402 payments for inference, compute, and data services. The payment mechanics work beautifully — streaming sessions, USDC on Tempo, sub-second settlement. But there is a structural gap that becomes obvious the moment you move beyond \$0.10 API calls: **MPP has no identity layer.** ## The Problem: Wallet Addresses Are Not Identity When an agent makes an MPP payment today, the `source` field is a wallet address. That is it. No human name, no organization, no authorization chain. The merchant knows someone paid, but not: * **Who sent this agent?** Is there a human behind it, or is it a rogue bot? * **Was it authorized?** Does the agent have permission to spend this amount on this category? * **By whom?** Which organization is liable if something goes wrong? * **For what scope?** Is an inference agent accidentally buying storage? For $0.10 API calls, nobody cares. For $500 B2B procurement transactions, this is a compliance blocker. And with agents increasingly chaining payments across services, the liability question compounds at every hop. ## The Proprietary Trap Visa and Mastercard see this gap. Visa's Trusted Agent Protocol and Mastercard's AgentPay are both building agent identity networks. But these are: * **Closed** — tied to their card networks * **Non-interoperable** — a Visa-verified agent cannot present credentials to a Mastercard merchant * **Permission-gated** — you need network approval to participate The crypto rails that MPP uses (Tempo, USDC) need an open, standards-based identity layer. Not another walled garden. ## The Solution: AgentPassportCredential We built `AgentPassportCredential` — a W3C Verifiable Credential (VC 2.0) that binds agent identity, human delegation, spending limits, and payment categories into a single offline-verifiable document. Here is the flow: | Step | Who | What | | - | - | - | | **1. Issue** | Human | Issues `AgentPassportCredential` — W3C VC 2.0, Ed25519 signed, with categories, spending limit, and expiry | | **2. Store** | Agent | Receives and stores the passport credential | | **3. Pay** | Agent | Makes MPP request with `Authorization: Payment` + `X-Grantex-Passport` headers | | **4. Verify** | Merchant | Checks signature, expiry, categories, and amount using cached JWKS; latency depends on the deployment | | **5. Deliver** | Merchant | Returns the resource after configured passport-claim and merchant-policy checks pass | | **6. Record** | Application | Records configured verification and action events; current status depends on synchronized status material | The credential carries inputs for a merchant trust decision. Merchants must combine them with independent payment, order, fraud, and settlement controls: | Field | What It Tells the Merchant | | - | - | | `humanPrincipal` | The DID of the human who authorized this agent | | `organizationDID` | The org responsible (e.g., `did:web:acme.com`) | | `allowedMPPCategories` | What the agent can buy: `inference`, `compute`, `data`, etc. | | `maxTransactionAmount` | Spending ceiling per transaction (e.g., 50 USDC) | | `delegationDepth` | How many hops from the original human grant (0 = direct) | | `grantId` | Links back to the full Grantex audit trail | | `credentialStatus` | StatusList2021 revocation — check if passport is still valid | ## What Makes This Different ### 1. Offline Cryptographic Verification The passport is a self-contained JWT signed with the issuer's key. With cached JWKS and status material, merchants can perform signature and claim checks locally without a per-request Grantex verification call. Latency and current revocation freshness depend on deployment and synchronization choices. ```typescript theme={null} import { requireAgentPassport } from '@grantex/mpp'; // One line. That is the entire integration. app.use('/api/inference', requireAgentPassport({ requiredCategories: ['inference'], maxAmount: 10, })); // req.agentPassport is now populated: // humanDID: "did:grantex:user_alice" // orgDID: "did:web:acme.com" // categories: ["inference", "compute"] // maxAmount: 50 USDC // delegation: depth 0 (direct grant) ``` ### 2. Category-Scoped Permissions MPP defines service categories: inference, compute, data, storage, search, media, delivery, browser, general. Each maps to a Grantex scope (`payments:mpp:inference`, etc.). If an inference agent tries to buy storage, the passport verification fails with `CATEGORY_MISMATCH` before any money moves. This is not a convention. It is enforced cryptographically. The categories are signed into the credential at issuance time. ### 3. Delegation-Aware Agents delegate to sub-agents. A "procurement agent" might delegate to a "compute buyer" sub-agent with a narrower budget. The passport carries `delegationDepth` and the full chain is traceable through `grantId`. The four delegation invariants from the DAAP spec apply: * Sub-agent categories must be a subset of the parent's * Spending limits can only narrow, never widen * Expiry is bounded by the parent's expiry * Revoking a parent cascades to all descendants ### 4. Instant Revocation Passports use StatusList2021 — a compressed bitstring where each credential has an index. Revoking a passport flips one bit. Merchants checking revocation status can fetch the status list and verify locally. No webhook needed, no polling, no race conditions. ```typescript theme={null} await grantex.passports.revoke('urn:grantex:passport:01HXYZ...'); // Subsequent verifyPassport() calls → PASSPORT_REVOKED ``` ### 5. Public Trust Registry Before fulfilling a high-value request, merchants can check the organization's trust level: ```bash theme={null} curl https://api.grantex.dev/v1/trust-registry/did:web:acme.com ``` ```json theme={null} { "organizationDID": "did:web:acme.com", "trustLevel": "verified", "verificationMethod": "dns-txt", "domains": ["acme.com"] } ``` Three trust levels: `basic` (self-declared), `verified` (DNS TXT proof), `soc2` (audited). The endpoint is public — no auth required. ## The Agent-Side Integration Issuing a passport is three lines: ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const passport = await grantex.passports.issue({ agentId: 'ag_01HXYZ...', grantId: 'grnt_01HXYZ...', allowedMPPCategories: ['inference', 'compute'], maxTransactionAmount: { amount: 50, currency: 'USDC' }, paymentRails: ['tempo'], expiresIn: '24h', }); ``` Attaching it to outgoing requests is one more line: ```typescript theme={null} import { createMppPassportMiddleware } from '@grantex/mpp'; const middleware = createMppPassportMiddleware({ passport, onRefresh: () => grantex.passports.issue(originalOptions), // auto-refresh before expiry }); const request = new Request('https://merchant.example.com/api/inference', { headers: { 'Authorization': 'Payment ' }, }); const enriched = await middleware(request); // X-Grantex-Passport header attached automatically ``` The middleware handles expiry checking and proactive refresh. When the passport is within 5 minutes of expiry, it automatically calls `onRefresh()` in the background. ## What Happens When Things Go Wrong Every failure mode has a typed error code: | Scenario | Error Code | What Happens | | - | - | - | | Passport expired | `PASSPORT_EXPIRED` | 403 — agent must re-issue | | Passport revoked by human | `PASSPORT_REVOKED` | 403 — human pulled the plug | | Inference agent tries to buy storage | `CATEGORY_MISMATCH` | 403 — wrong category | | $100 purchase with $50 limit | `AMOUNT_EXCEEDED` | 403 — over budget | | Tampered credential | `INVALID_SIGNATURE` | 403 — cryptographic proof failed | | Unknown issuer | `UNTRUSTED_ISSUER` | 403 — not a recognized authority | No ambiguous error messages. No silent failures. The merchant knows exactly why a passport was rejected, and the agent can surface that to the user. ## Standards Alignment This is not a proprietary format. The entire stack is built on open standards: * **W3C Verifiable Credentials Data Model v2.0** — the credential format * **Ed25519Signature2020** — the proof type * **StatusList2021** — revocation mechanism * **did:web** — issuer identity resolution * **DAAP IETF Internet-Draft** — the underlying authorization protocol Any party can verify a Grantex passport using the published DID document at `did:web:grantex.dev`. No SDK required. No Grantex account required. Just fetch the public key and verify the JWT. ## Try It Now The entire implementation is open source and live in production. **Install:** ```bash theme={null} npm install @grantex/mpp @grantex/sdk ``` **Interactive demo:** [grantex.dev/mpp-demo](https://grantex.dev/mpp-demo) — issue a passport, simulate payment flows, and verify credentials in the browser. **Trust registry (live):** [api.grantex.dev/v1/trust-registry/did:web:grantex.dev](https://api.grantex.dev/v1/trust-registry/did:web:grantex.dev) **Documentation:** [docs.grantex.dev/features/mpp-agent-passport](https://docs.grantex.dev/features/mpp-agent-passport) **GitHub:** [github.com/mishrasanjeev/grantex](https://github.com/mishrasanjeev/grantex) — `packages/mpp/` *** *MPP addresses payment transport. Grantex can add verifiable agent and principal authority context without replacing merchant, payment, order, settlement, fraud, or compliance controls.* ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # How ChatGPT, Claude, Gemini, Perplexity, WhatsApp, And Telegram Can Shop Through Seller Agents Source: https://docs.grantex.dev/blog/oacp-buyer-surfaces-chatgpt-claude-gemini-perplexity-whatsapp-telegram The buyer-surface bridge model for OACP-backed AgenticOrg seller agents. # How ChatGPT, Claude, Gemini, Perplexity, WhatsApp, And Telegram Can Shop Through Seller Agents ## Summary Buyer surfaces differ in transport, not truth. AgenticOrg should route web, MCP, OpenAPI, A2A, WhatsApp, and Telegram through the same OACP cache-backed answer path. ## Target Audience Developer platform teams, channel partners, and customer-success teams. ## Architecture Diagram ```mermaid theme={null} flowchart LR web[Web] --> runtime[AgenticOrg buyer runtime] mcp[ChatGPT/Claude-style MCP] --> runtime openapi[Gemini/Perplexity-style OpenAPI] --> runtime a2a[A2A agent card] --> runtime wa[WhatsApp] --> runtime tg[Telegram] --> runtime runtime --> cache[OACP cache] cache --> answer[Answer with source/freshness] cache --> refuse[Refuse unsafe request] ``` ## End-To-End Flow 1. Channel receives buyer question. 2. AgenticOrg normalizes to the buyer-session ask path. 3. Runtime checks OACP cache and source labels. 4. Low-risk answers are returned consistently. 5. Commitment-bound requests use purchase preparation and provider capability checks. ## What Is Implemented Now AgenticOrg exposes web ask, OpenAPI ask/schema, A2A card, surface matrix, WhatsApp webhook, Telegram webhook, product listing, protocol adapters, and purchase preparation routes. ## What Requires External Approval Or Config Channel webhook secrets, channel policies, public client review, and provider rail approval. ## Failure Modes * Webhook secret missing. * Channel payload cannot be normalized. * Cache is stale. * Buyer requests commitment without valid provider evidence. ## Safe User Wording Examples * "I found this from the merchant source snapshot." * "This channel can prepare a handoff, but it cannot complete payment." * "The Telegram/WhatsApp bridge needs verified webhook setup before it can accept messages." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Why OACP Keeps Buyer Agents Honest Source: https://docs.grantex.dev/blog/oacp-keeps-buyer-agents-honest Why source, freshness, revocation, and non-enablement fields keep buyer agents from inventing commerce facts. # Why OACP Keeps Buyer Agents Honest ## Summary OACP gives buyer agents a hard evidence model: source refs, freshness, TTL, revocation posture, risk tier, and blocked capabilities. If those fields do not support the answer, the agent must refresh or refuse. ## Target Audience Buyer-agent builders, safety reviewers, and commerce operators. ## Architecture Diagram ```mermaid theme={null} flowchart TD source[Merchant source] --> evidence[Public-safe evidence] evidence --> artifact[OACP artifact] artifact --> cache[AgenticOrg cache verifier] cache --> decision{Answer, refresh, prepare, or refuse} decision --> answer[Grounded answer] decision --> blocker[Safe blocker] ``` ## End-To-End Flow The buyer asks a question. AgenticOrg checks cached OACP artifacts for scope, source, freshness, revocation, and risk. Low-risk questions can be answered with labels. Commitment-bound questions require stricter checks and can only prepare handoff or refuse. ## What Is Implemented Now Grantex emits artifact families and non-enablement flags. AgenticOrg checks cache records before answering and uses safe blockers for stale or unsupported requests. ## What Requires External Approval Or Config Channel launch, provider capability checks, merchant policy scope, and any future execution controller path. ## Failure Modes * Artifact missing from cache. * Artifact expired. * Revocation snapshot too old for the action. * Buyer asks for payment/order/mandate without provider evidence. * Source and adapter fields disagree. ## Safe User Wording Examples * "I can answer from the current merchant source snapshot." * "I cannot invent stock or price beyond the artifact." * "No checkout, payment, or order has occurred." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # How OACP Maps To Schema.org, UCP, ACP, AP2, A2A, And MCP Source: https://docs.grantex.dev/blog/oacp-maps-to-protocols A compatibility-mapping guide for protocol partners. # How OACP Maps To Schema.org, UCP, ACP, AP2, A2A, And MCP ## Summary OACP is the canonical trust artifact. Protocol adapters project OACP facts into known client and partner shapes while preserving source, freshness, and non-execution boundaries. ## Target Audience Protocol partners, developers, and technical reviewers. ## Architecture Diagram ```mermaid theme={null} flowchart TD oacp[Canonical OACP artifacts] --> schema[Schema.org Product/Offer] oacp --> ucp[UCP-style capability] oacp --> acp[ACP-style commerce profile] oacp --> ap2[AP2-style evidence] oacp --> a2a[A2A metadata] oacp --> mcp[MCP metadata] ``` ## End-To-End Flow Grantex issues OACP artifacts. AgenticOrg caches them. Adapter payloads are generated from those artifacts for a channel. Unsupported fields stay unsupported instead of being filled with guessed execution state. ## What Is Implemented Now Grantex contains Schema.org, UCP-style, ACP-style, and AP2-style preview helpers. AgenticOrg generates protocol adapter payloads including Schema.org, UCP-style, ACP-style, AP2-style, A2A, MCP, and OpenAPI views. ## What Requires External Approval Or Config Any external program claim requires partner review and published evidence. Current docs describe compatibility mapping only. ## Failure Modes * Adapter field has no canonical artifact lineage. * Adapter implies checkout or payment execution. * Source or freshness labels are dropped. ## Safe User Wording Examples * "This is an OACP compatibility mapping." * "The payload is buyer-safe metadata, not payment authority." * "Unsupported execution fields are omitted or blocked." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Merchant Self-Service Config Without Moving Commerce Truth Into Grantex Source: https://docs.grantex.dev/blog/oacp-merchant-self-service-config How AgenticOrg lets merchants configure sources, channels, provider rails, public publishing, and Offline POS while Grantex remains OACP authority. Agentic commerce needs merchant-controlled setup. A Shopify store, a future WooCommerce storefront, an ERP-backed catalog, a bank-owned mandate rail, and an offline POS terminal all have different operational owners. OACP should describe the trust boundary between those systems; it should not move every setting or every buyer turn into Grantex. ## The Split AgenticOrg owns the merchant self-service runtime. A merchant operator can configure source connectors, buyer channels, provider-owned payment rails, public catalog publishing, and Offline POS metadata during Seller Commerce Agent onboarding or later from the same settings surface. Grantex owns OACP authority. It receives redacted authority requests, signs or refuses artifacts, verifies TTL/source/freshness/scope, and governs protocol adapter mappings. It does not store Shopify tokens, WooCommerce keys, ERP credentials, bank secrets, payment URLs, raw provider callbacks, or POS transaction payloads. ```mermaid theme={null} flowchart LR merchant[Merchant operator] --> agentic[AgenticOrg config] agentic --> source[Shopify runtime sync or pending adapter refs] source --> evidence[Public-safe evidence] evidence --> grantex[Grantex OACP authority] grantex --> artifacts[Artifacts or blockers] artifacts --> cache[AgenticOrg buyer channels] ``` ## What Is Runtime-Supported Now Shopify is the current runtime source connector. AgenticOrg can use read-only Shopify Admin GraphQL sync, reconcile webhook-triggered refresh state, request OACP artifacts, and answer buyers from cache with source/freshness labels. Pine Labs Plural/P3P is the current provider-owned mandate capability verifier where credentials are configured. Capability evidence is not payment execution. ## What Is Configurable But Not Live WooCommerce, ERP, PIM, OMS, WMS, custom APIs, bank-owned rails, fintech rails, and custom providers can be saved as merchant intent. Those configs are valuable because they show what the merchant wants to connect, but they remain pending adapter until approved connector code, sandbox tests, webhook verification, source precedence policy, and rollout approval exist. ## Public Catalog And Search AgenticOrg can publish seller profile pages, product pages, buyer-safe catalog JSON, Schema.org JSON-LD, sitemap, and llms.txt only when the merchant enables public publishing and cached artifacts are fresh enough. Grantex may sign public discovery-state artifacts; it does not host the catalog pages. ## Safe Claim The safe claim is simple: merchants can configure their own commerce runtime in AgenticOrg; Grantex remains the OACP trust authority; Shopify is runtime-supported; future sources and bank/provider rails are adapter-ready but non-executing until approved. No OACP certification, UCP/ACP/AP2 approval, IETF/NIST acceptance, universal channel launch, live payment execution, live order creation, or live POS settlement is implied by merchant configuration. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # How A Shopify Merchant Becomes An Agentic Commerce Seller Source: https://docs.grantex.dev/blog/oacp-shopify-merchant-agentic-commerce-seller The Grantex authority view of Shopify onboarding through AgenticOrg and OACP artifacts. # How A Shopify Merchant Becomes An Agentic Commerce Seller ## Summary A Shopify merchant becomes an agentic commerce seller through AgenticOrg merchant self-service configuration, Seller Commerce Agent onboarding, read-only Shopify sync, Grantex OACP authority artifacts, and buyer-safe cache consumption. ## Target Audience Merchants, AgenticOrg operators, and Grantex reviewers. ## Architecture Diagram ```mermaid theme={null} flowchart LR merchant[Merchant] --> agentic[AgenticOrg Seller Commerce Agent] agentic --> shopify[Read-only Shopify sync] shopify --> evidence[Redacted source evidence] evidence --> grantex[Grantex OACP authority] grantex --> artifacts[OACP artifacts or blockers] artifacts --> cache[AgenticOrg cache] cache --> buyers[Buyer surfaces] ``` ## End-To-End Flow 1. Merchant saves tenant/merchant/store-scoped source, channel, provider, public publishing, and POS settings in AgenticOrg. 2. Merchant creates or updates a Seller Commerce Agent in AgenticOrg. 3. AgenticOrg stores Shopify credential custody outside Grantex. 4. AgenticOrg syncs public-safe catalog, price, image, and inventory evidence. 5. AgenticOrg requests Grantex C6Z authority artifacts. 6. Grantex issues or refuses OACP artifacts. 7. AgenticOrg caches artifacts and answers buyer questions with labels. 8. Purchase intent becomes a prepared provider/POS/merchant handoff or blocker. ## What Is Implemented Now Grantex has the C6Z authority route, internal artifact issuance, adapter mapping, artifact verification helpers, and focused tests. AgenticOrg owns merchant self-service config, seller onboarding, Shopify sync, cache intake, buyer Q\&A, bridge endpoints, public catalog publishing, Offline POS handoff, and Plural/Pine capability verification. WooCommerce, ERP, PIM, OMS, WMS, custom API, bank-owned rail, fintech rail, and custom provider setup can be recorded in AgenticOrg as pending-adapter config. Grantex should not treat those refs as live source or payment execution until an approved adapter and evidence path exists. ## What Requires External Approval Or Config Merchant Shopify credentials, AgenticOrg tenant allowlist, provider or bank rail approval, channel webhook approvals, POS callback approval, and partner review for any public program claim. ## Failure Modes * Shopify credentials missing or invalid. * Source evidence stale. * Tenant not allowlisted. * Artifact scope requests private or executable authority. * Provider capability evidence missing or stale. ## Safe User Wording Examples * "This answer is based on a Shopify snapshot authorized by Grantex." * "I can prepare a review handoff, but no payment or order was created." * "The source evidence is stale. I need a refresh before continuing." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OWASP Agentic Top 10: What It Means for Your AI Security Stack Source: https://docs.grantex.dev/blog/owasp-agentic-top-10-compliance OWASP published the Agentic Security Top 10 in December 2025. The EU AI Act applies in phases, and NIST AI RMF is active now. Here is how the controls map to Grantex. Three regulatory frameworks now directly address AI agent security. If you are deploying autonomous agents in production, compliance is no longer optional — it is a procurement requirement, an audit finding, and increasingly a legal obligation. This post maps each framework's agent-relevant requirements to concrete technical controls and shows where Grantex provides coverage. ## OWASP Agentic Security Top 10 (December 2025) The OWASP Foundation published the Agentic Security Initiative (ASI) Top 10 in December 2025 — the first industry-standard threat taxonomy specifically for AI agents. Four of the ten risks are directly addressed by authorization infrastructure. ### ASI-01: Agent Goal Hijacking **The risk:** An attacker manipulates an agent's goals through prompt injection, causing it to perform actions the user never intended. **The control:** Scoped permissions. Even if the agent's goals are hijacked, it cannot exceed the permissions explicitly granted in its token. A calendar agent that has been prompt-injected to "send all files to an external server" will fail because it does not hold `files:read` or `network:external` scopes. **Grantex implementation:** Every grant token contains a `scp` claim with the exact scopes the human approved. Services verify the scope before executing the action. Hijacking the agent's intent does not expand its permissions. ### ASI-03: Agent Identity and Privilege Abuse **The risk:** Agents share credentials, escalate privileges, or operate without verifiable identity — making it impossible to attribute actions to specific agents. **The control:** Per-agent cryptographic identity. Each agent instance has a unique DID and key pair. The grant token's `agt` claim binds the token to a specific agent. Tokens are non-transferable. **Grantex implementation:** Agent registration assigns a DID (`did:grantex:ag_...`). The JWT `agt` claim is set at token issuance and verified at every API call. An agent cannot use another agent's token — the signature verification fails. ### ASI-05: Privilege Escalation **The risk:** An agent acquires permissions beyond what was originally granted, either through delegation chains or by exploiting weak access controls. **The control:** Delegation with scope narrowing. Child agents must have strictly fewer permissions than their parent. Depth limits prevent unbounded delegation chains. **Grantex implementation:** The SPEC enforces four delegation invariants: 1. Child scopes must be a subset of parent scopes 2. Expiry cannot exceed parent expiry 3. Budget limits can only narrow, never widen 4. Revoking a parent cascades to all children The `delegationDepth` claim is signed into the JWT. The protocol rejects delegation requests that violate any invariant. ### ASI-10: Rogue Agents (No Revocation) **The risk:** A compromised or misbehaving agent continues operating because there is no mechanism to revoke its access in real time. **The control:** Instant, cascading revocation. One API call invalidates the agent's token and all child delegations. **Grantex implementation:** `POST /v1/tokens/revoke` blocklists the JTI in Redis. The next `POST /v1/tokens/verify` call returns `valid: false`. A StatusList2021 bitstring can be checked without a network call only after the relevant list has already been fetched and cached; disconnected checks can be stale until refresh. *** ## EU AI Act (Phased Implementation) The EU AI Act is the world's first comprehensive AI regulation. For high-risk AI systems, three of its articles bear directly on agent deployments; under Regulation (EU) 2026/1744 they apply to Annex III systems from 2 December 2027 and to Annex I systems from 2 August 2028. ### Article 9 — Risk Management Systems **Requirement:** Providers of high-risk AI systems must establish risk management processes that identify and mitigate risks throughout the system's lifecycle. **What this means for agents:** You need documented controls for what agents can do, how they escalate, and how you stop them. "We rotate the API key weekly" is not a risk management system. **Grantex coverage:** * Scoped grants with explicit permissions per agent * Budget controls with per-transaction limits * Anomaly detection that flags unusual agent behavior * Policy-as-code integration (OPA, Cedar) for fine-grained rules ### Article 13 — Transparency **Requirement:** High-risk AI systems must be designed so that their operation is sufficiently transparent. Users must be able to understand the AI system's capabilities and limitations. **What this means for agents:** Every autonomous action must be attributable — who authorized it, what scopes were active, and what the agent actually did. **Grantex coverage:** * Human consent flow shows in plain English what the agent will do * Grant tokens carry the human principal's identity (`sub` claim) * Verifiable Credentials provide portable, third-party-verifiable proof of authorization * SD-JWT enables selective disclosure — show the auditor only the claims they need ### Article 14 — Human Oversight **Requirement:** High-risk AI systems must be designed to allow effective oversight by natural persons, including the ability to intervene, correct, or stop the system. **What this means for agents:** You must be able to see what agents are doing and stop them. In real time, not after the fact. **Grantex coverage:** * Principal Sessions dashboard shows active grants per user * Real-time event streaming (SSE/WebSocket) surfaces agent actions as they happen * One-click revocation from the dashboard or via API * Cascade revocation invalidates the entire delegation tree *** ## NIST AI Risk Management Framework + Executive Order 14110 NIST's AI RMF provides the US government's approach to AI risk, and EO 14110 mandates its adoption across federal agencies and their contractors. ### Govern 1.1 — AI Roles and Accountability **Requirement:** Organizations must define roles and responsibilities for AI systems, including who is accountable for autonomous actions. **Grantex coverage:** Every grant token traces back to a human principal (`sub` claim) and the developer who deployed the agent (`dev` claim). The audit trail links actions to grants to humans — the accountability chain is unbroken. ### Map 5.1 — Agent Action Attribution **Requirement:** Organizations must be able to attribute actions taken by AI systems to specific agents and their operators. **Grantex coverage:** The `agt` claim identifies the agent. The `grnt` claim identifies the grant. The audit subsystem (`POST /v1/audit/log`, `GET /v1/audit/entries`) provides hash-chained, tamper-evident logs of every action the agent took under that grant. ### Measure 2.5 — Audit Trails for Autonomous Operations **Requirement:** Organizations must maintain audit trails that document the actions of autonomous AI systems. **Grantex coverage:** Grantex's audit trail is: * **Append-only** — entries cannot be modified after creation * **Hash-chained** — each entry references the hash of the previous one, making tampering detectable * **Filterable** — query by agent, grant, principal, time range, or action type * **Exportable** — compliance evidence packs include grants, tokens, and audit entries in a single bundle *** ## The Compliance Matrix | Requirement | Framework | Grantex Feature | | - | - | - | | Scoped permissions | OWASP ASI-01 | JWT `scp` claim, verified at every API call | | Per-agent identity | OWASP ASI-03 | DID per agent, `agt` claim in JWT | | Delegation depth limits | OWASP ASI-05 | `delegationDepth` claim, 4 invariants enforced | | Instant revocation | OWASP ASI-10 | Redis blocklist + StatusList2021 | | Risk management | EU AI Act Art. 9 | Budget controls, anomaly detection, policy-as-code | | Transparency | EU AI Act Art. 13 | Consent flow, VCs, SD-JWT selective disclosure | | Human oversight | EU AI Act Art. 14 | Principal dashboard, event streaming, cascade revoke | | Accountability | NIST Govern 1.1 | `sub` + `dev` claims, grant-to-human traceability | | Action attribution | NIST Map 5.1 | `agt` + `grnt` claims, audit log | | Audit trails | NIST Measure 2.5 | Hash-chained, append-only, exportable audit entries | This matrix is also available as a dedicated page in our documentation: [Compliance Matrix](/guides/compliance-matrix). *** ## What To Do Now If you are deploying agents in production — especially in regulated industries or enterprise environments — these frameworks are not future concerns. OWASP's taxonomy is already appearing in security questionnaires. The EU AI Act applies in phases: under Regulation (EU) 2024/1689 as amended by Regulation (EU) 2026/1744, Art. 50 transparency obligations apply from 2 August 2026, Annex III high-risk obligations from 2 December 2027, and product-embedded Annex I high-risk obligations from 2 August 2028. NIST's framework is active now for US government contractors. 1. **Audit your current agent auth** — are agents using shared API keys or scoped, verifiable tokens? 2. **Map your exposure** — which OWASP ASI risks apply to your deployment? 3. **Integrate Grantex** — [quickstart in under 5 minutes](/quickstart), or [schedule a technical evaluation](mailto:sanjeev@orchestrum.in) The compliance requirements are clear. The protocol exists. The window to be proactive — rather than reactive — is closing. ## Learn More * [Compliance Evidence Pack API](/api-reference/compliance/generate-compliance-evidence-pack) — export grants, tokens, and audit entries for auditors * [SOC 2 readiness control mapping](/security/soc2-report) — control mapping; not third-party certification * [IETF Internet-Draft](/community/ietf-draft) — the open standard submission * [NIST AI RMF Comment](/community/nist-comment) — our public comment to NIST ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # How Pine Labs Plural/P3P Mandate Capability Fits Into Agentic Commerce Source: https://docs.grantex.dev/blog/pine-labs-plural-p3p-oacp The provider-owned mandate capability boundary in the OACP model. # How Pine Labs Plural/P3P Mandate Capability Fits Into Agentic Commerce ## Summary Plural/Pine owns the mandate and payment rail. AgenticOrg verifies provider-owned capability metadata and Grantex can carry redacted evidence refs in OACP artifacts. ## Target Audience Fintech partners, payment operators, and commerce risk teams. ## Architecture Diagram ```mermaid theme={null} flowchart LR buyer[Buyer intent] --> agentic[AgenticOrg verifier] agentic --> pine[Pine Labs Plural/P3P] pine --> ref[Redacted capability evidence ref] ref --> cache[OACP cache] cache --> handoff[Prepared handoff or blocker] ``` ## End-To-End Flow AgenticOrg checks whether provider capability metadata is available for the merchant and requested action. The result becomes a redacted evidence ref. Grantex OACP policy can require that evidence before a handoff is prepared. ## What Is Implemented Now AgenticOrg has a Plural/Pine capability verifier and purchase-preparation blocker. Grantex models mandate capability as an OACP artifact family and marks provider execution authority as provider-owned. ## What Requires External Approval Or Config Provider credentials, environment approval, webhook/reconciliation path, merchant approval, and rollback owner. ## Failure Modes * Provider config missing. * Sandbox/non-production environment mismatch. * Capability evidence older than allowed TTL. * Buyer asks to create a mandate from cache only. ## Safe User Wording Examples * "Provider capability evidence is present for preparation only." * "No mandate, payment, or order was created." * "Provider setup is missing; the request is blocked." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Shopify, Grantex Commerce, And OACP: Current Boundary Source: https://docs.grantex.dev/blog/shopify-oacp-commerce-live-flow Separates the Grantex Commerce payment-control pilot from the blocked AgenticOrg C6Z OACP runtime artifact vertical. This draft is retained as a boundary note, not as a launch announcement. Grantex Commerce payment-control pilot work and the AgenticOrg C6Z OACP runtime artifact vertical are separate. The payment-control pilot can discuss Grantex Commerce V1, consent, Commerce Passport, payment intent, webhook intake, reconciliation, and audit inside its own approved scope. It must not be used as proof that the AgenticOrg production OACP artifact vertical is complete. ## Current C6Z OACP Runtime Status As of June 18, 2026, the real production C6Z OACP runtime vertical is blocked: * AgenticOrg production is healthy at commit `2fdccc7ca1337b3b2caa20a2e9ac1d03c7bfbc9c`. * Grantex production is healthy at commit `7fd4dd3c865fbd8187d92aec792cff249c9c01c3`. * AgenticOrg Shopify Admin GraphQL read-only sync for `mgx0n6-22.myshopify.com` fails with `401 Unauthorized`. * A direct status-only Shopify GraphQL probe using the mounted AgenticOrg C6Z Shopify token also returns `401`. * Grantex C6Z authority called with the AgenticOrg-configured internal token returns `422 tenant_not_provisioned`. * Grantex C6Z authority called by a platform-admin operator can issue 11 internal artifact families on the launch-closure branch, all verifier-valid and all non-executing. ## Correct Architecture * AgenticOrg is the buyer and seller AI-agent runtime. * Grantex is the trust, protocol, policy, and canonical OACP artifact authority. * Shopify remains the operational system of record for merchant catalog facts. * Plural/Pine and other provider or fintech rails own mandate and payment execution. * Valid cached OACP artifacts may support non-binding buyer answers without routing every buyer turn through Grantex. ## What Is Not Claimed This draft does not claim: * OACP certification, conformance, compliance, or external standardization. * Public OACP discovery. * Merchant approval, production payment approval, mandate approval, or live-provider readiness from OACP work. * Checkout, payment, order, mandate, refund, return, shipment, inventory hold, provider execution, or merchant-private API mutation. ## Required Before A Launch Post 1. Rotate or replace the AgenticOrg C6Z Shopify Admin token and confirm read-only GraphQL access. 2. Provision or replace the AgenticOrg-to-Grantex internal token so the configured path can issue artifacts without platform-admin substitution. 3. Re-run the full production vertical and capture redacted evidence for: Shopify product/variant counts, Grantex artifact issuance, AgenticOrg cache records, buyer answer source/freshness labels, MCP seller facts, Plural/Pine capability metadata, public discovery disabled, and no execution. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Source, Freshness, And Trust In AI Commerce Source: https://docs.grantex.dev/blog/source-freshness-trust-ai-commerce How OACP makes source and freshness visible to buyer agents. # Source, Freshness, And Trust In AI Commerce ## Summary AI commerce fails when agents invent old prices, stale inventory, or unsupported payment outcomes. OACP makes source and freshness first-class fields. ## Target Audience Product managers, buyer-agent designers, and operators. ## Architecture Diagram ```mermaid theme={null} flowchart TD source[Shopify observed_at] --> artifact[OACP issued_at/expires_at] artifact --> cache[Cache freshness label] cache --> answer[Buyer-safe answer] cache --> stale[Stale refusal] ``` ## End-To-End Flow Source systems timestamp facts. Grantex artifacts carry issue and expiry windows. AgenticOrg cache computes freshness labels. Buyer responses show source and freshness. Stale data blocks commitment-bound action. ## What Is Implemented Now Artifact TTLs, source refs, freshness labels, revocation posture, cache verification, and stale blockers exist in Grantex and AgenticOrg runtime code. ## What Requires External Approval Or Config Merchant-specific source precedence, public channel wording review, and source conflict policy. ## Failure Modes * A source has not been refreshed. * Different source systems disagree. * The response hides source/freshness labels. * A stale artifact is used for commitment. ## Safe User Wording Examples * "Source: Shopify via Grantex artifact." * "Freshness: current within the configured artifact window." * "This snapshot is stale, so I cannot prepare purchase handoff." ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Why AI Agents Need Their Own Authorization Protocol Source: https://docs.grantex.dev/blog/why-ai-agents-need-authorization OAuth 2.0 was built for human users. AI agents have fundamentally different requirements -- here's why they need a purpose-built protocol. Every time you click "Sign in with Google" or grant an app access to your calendar, OAuth 2.0 is doing the work. It has been the backbone of delegated authorization for over a decade. So why can't we just use it for AI agents? The short answer: agents are not apps. ## The Four Gaps ### 1. Agent Identity vs. App Identity OAuth issues tokens to registered applications. An app has a `client_id`, a redirect URI, and a set of scopes. This model assumes a fixed, known piece of software. AI agents are different. A single orchestrator might spawn dozens of sub-agents at runtime, each with a different purpose. These agents need their own cryptographic identity -- not a shared `client_id`. Grantex assigns each agent a DID (Decentralized Identifier) backed by a key pair. The agent's identity is verifiable, rotatable, and independent of the platform it runs on. ### 2. Scope Delegation Chains OAuth supports a flat model: a user grants scopes to an app, and that is the end of the story. But agents delegate work to other agents. A "research assistant" agent might call a "web search" sub-agent and a "summarizer" sub-agent, each of which should only get the scopes it actually needs. Grantex models this explicitly. When a parent agent delegates to a child, the child's grant token carries `parentAgt`, `parentGrnt`, and `delegationDepth` claims. The child's scopes must be a strict subset of the parent's. This is not a convention -- it is enforced at the protocol level. ``` Root user grants: [files:read, files:write, email:send] └─ Parent agent: [files:read, files:write] (delegationDepth: 0) └─ Child agent: [files:read] (delegationDepth: 1) ``` If the parent's grant is revoked, every child grant in the chain is automatically invalidated. ### 3. Real-Time Revocation OAuth token revocation is defined in RFC 7009, but it is advisory. Resource servers are free to keep accepting a token until it expires. For a 1-hour access token, that is a 1-hour window of exposure. With agents performing high-stakes actions autonomously, you cannot afford that window. Grantex supports local signature-and-claim validation after JWKS key retrieval, and online verification (`POST /v1/tokens/verify`) that checks revocation state in real time. Current standalone SDK verifiers fetch JWKS per call. After revocation, the next online verification returns `valid: false`. ### 4. Action-Level Audit Trails OAuth gives you an access log at the authorization server. You know when a token was issued and when it was refreshed. You do not know what happened next. Grantex includes a first-class audit subsystem. Every action an agent performs can be logged via `POST /v1/audit/log`, and the entries are append-only and hash-chained -- each entry references the hash of the previous one, making tampering detectable. You can query the full trail with `GET /v1/audit/entries` filtered by agent, grant, principal, or time range. ## The Compliance Dimension This is not just a technical nicety. The EU AI Act applies in phases and can require logging, transparency, and human oversight depending on the system's classification and the organization's role. Grantex's audit trail, scoped grants, and revocation controls can support those obligations, but do not by themselves establish compliance. Similarly, SOC 2 auditors want to see evidence that access is scoped, time-limited, and revocable. Grant tokens with explicit `exp`, `scp`, and revocation support provide that evidence out of the box. ## Why Not Extend OAuth? We considered it. The problem is that the primitives do not exist. OAuth has no concept of agent identity, delegation depth, or hash-chained audit logs. Bolting these onto OAuth would mean a constellation of non-standard extensions that no existing library supports. Grantex is a clean-sheet protocol that speaks the same language as OAuth where it makes sense (JWTs, JWKs, RS256, scopes, authorization codes) but adds the agent-specific primitives as first-class concepts. If you know OAuth, Grantex will feel familiar. But it will also handle the cases OAuth was never designed for. ## Learn More * [How Grantex Works](/concepts/how-it-works) -- visual walkthrough of the authorization flow. * [Delegation Deep Dive](/concepts/delegation) -- how multi-agent delegation works. * [Protocol Specification](/protocol/specification) -- the full v1.0 spec. * [Quickstart](/quickstart) -- get a working integration in under 5 minutes. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # AgenticOrg Case Study Source: https://docs.grantex.dev/case-studies/agenticorg How AgenticOrg enforces scope on 35 AI agents, 53+ connectors, and 339+ tools using Grantex tool manifests. ## Overview [AgenticOrg](https://github.com/mishrasanjeev/agentic-org) is an enterprise AI agent orchestration platform with **35 agents** across 6 domains, connected to **56 enterprise systems** via **339+ tools**. It is live at [agenticorg.ai](https://agenticorg.ai). AgenticOrg agents call real APIs — Salesforce, Jira, HubSpot, SAP, S3, Gmail, Stripe, and 47 more. Each agent has a shared service account credential for each system. The challenge: how do you prevent an AP Processor agent from deleting contacts when it should only read invoices? *** ## The Problem AgenticOrg's 35 agents share the same underlying service account credentials for each connected system. The credentials themselves do not restrict what an agent can do: * **Agent A** (AP Processor) should only read invoices, not delete contacts * **Agent B** (Sales Rep) should create leads, not approve payments * **Agent C** (Recruiter) should post jobs, not terminate employees Without per-agent enforcement, any agent with access to the Salesforce credential could call any Salesforce API — reads, writes, deletes, admin operations. *** ## How Grantex Solved It AgenticOrg integrated Grantex as the authorization layer between agents and their tool connectors: ``` Grantex Token Shared Creds +--------------+ -----------------> +---------------------+ -----------------> +------------+ | AI Agent | | Tool Gateway | | Salesforce | | (no creds) | | verify token -> | | S3, Jira | | | <-- scoped resp -- | check scope -> | <-- response ----- | SAP, FTP | +--------------+ | connector | +------------+ +---------------------+ | v audit Grantex Cloud ``` **The flow:** 1. **Agent creation** — auto-registers on Grantex, gets a DID, tools mapped to scopes 2. **Human approves** — Grantex issues an RS256-signed grant token with specific scopes 3. **Every tool call** — Tool Gateway verifies the token, checks scopes against the manifest, then calls the connector 4. **Agent never sees** the Salesforce/S3/Jira credentials — only the Grantex token 5. **Revoke a grant** — agent immediately loses access across ALL connected systems *** ## The Remaining Gap: Keyword Guessing AgenticOrg's initial Tool Gateway guessed `read` vs `write` from tool name keywords: ```python theme={null} # Fragile -- "process_refund" passes as "read" because no keyword matches permission = "write" if any(w in tool_name for w in ("create", "delete", ...)) else "read" ``` This approach had real problems: * `process_refund` was classified as `read` because no keyword matched * `file_gstr3b` was classified as `read` (the word "file" as in "file a return" was not recognized) * `void_envelope` was classified as `read` (no "delete" keyword) * Any new tool defaulted to `read` even if it performed destructive operations **Grantex solved this with tool manifests**: explicit permission declarations for every tool on every connector, so no platform has to guess. *** ## The Solution: Tool Manifests + enforce() AgenticOrg replaced the keyword-guessing logic with Grantex tool manifests — a mix of pre-built manifests for well-known connectors and custom manifests for their internal APIs: ```python theme={null} from grantex import Grantex, ToolManifest, Permission from grantex.manifests.salesforce import manifest as sf from grantex.manifests.hubspot import manifest as hs from grantex.manifests.jira import manifest as jira # ... pre-built manifests for well-known connectors grantex = Grantex(api_key=os.environ["GRANTEX_API_KEY"]) grantex.load_manifests([sf, hs, jira, ...]) # Custom manifests for AgenticOrg's internal APIs — no Grantex release needed grantex.load_manifest(ToolManifest( connector="agenticorg-tasks", description="Internal task orchestration API", tools={ "get_task_status": Permission.READ, "assign_agent": Permission.WRITE, "terminate_workflow": Permission.ADMIN, }, )) # In the tool execution hot path -- before every tool call: result = grantex.enforce( grant_token=agent_token, connector=connector_name, tool=tool_name, ) if not result.allowed: raise PermissionError(result.reason) # Proceed with tool execution... ``` Every one of AgenticOrg's 340+ tools now has an explicit permission level declared in a manifest — whether it's a Salesforce API or an internal service. No guessing, no keywords, no ambiguity. *** ## Architecture ``` Agent Pool (35 agents) ├── Finance Domain │ ├── AP Processor → scopes: tool:netsuite:read:*, tool:banking_aa:read:* │ ├── Revenue Analyst → scopes: tool:stripe:read:*, tool:quickbooks:read:* │ └── Tax Filing Agent → scopes: tool:gstn:write:*, tool:income_tax:write:* ├── Sales Domain │ ├── Lead Generator → scopes: tool:salesforce:write:*, tool:hubspot:write:* │ └── Deal Closer → scopes: tool:salesforce:write:*, tool:docusign:write:* ├── HR Domain │ ├── Recruiter → scopes: tool:greenhouse:write:*, tool:linkedin_talent:write:* │ └── Payroll Admin → scopes: tool:darwinbox:admin:*, tool:keka:admin:* └── ... Tool Gateway ├── grantex.enforce() on every call (< 1ms) ├── 53 pre-built manifests for well-known connectors ├── 3 custom manifests for AgenticOrg's internal APIs ├── Any new connector: define a manifest, enforce immediately └── Fail-closed: unknown tools denied ``` *** ## Results "35 AI agents, 53 pre-built + 3 custom connectors, 339+ tools -- all scope-enforced through one grantex.enforce() call per tool execution. The keyword-guessing approach misclassified 12% of our tools. With manifests, it is zero." \-- AgenticOrg engineering team **Key metrics after deploying Grantex scope enforcement:** | Metric | Before | After | | - | - | - | | Tool misclassification rate | 12% | 0% | | Enforcement latency (p99) | N/A (keyword regex) | \< 1ms | | Permission coverage | \~60% of tools (others defaulted to read) | 100% of tools | | Time to add a new connector | Write keyword regex + manual review | Load pre-built manifest (1 line) or define custom (5 lines) | | Incident response (revoke access) | Per-connector credential rotation | Single grant revocation | **Enforcement in the hot path:** `grantex.enforce()` runs on every tool call in AgenticOrg's production pipeline. The call decodes the JWT, resolves the permission from the manifest, and checks scope coverage — all in under 1 millisecond, with no network round-trip required for the manifest lookup. *** ## Try It Yourself 1. **Browse the manifests** — see all 53 bundled connectors and their tools: ```bash theme={null} grantex manifest list ``` 2. **Add enforcement to your agent** — five lines of code: ```python theme={null} from grantex import Grantex from grantex.manifests.salesforce import manifest as sf grantex = Grantex(api_key="gx_...") grantex.load_manifest(sf) result = grantex.enforce(grant_token=token, connector="salesforce", tool="delete_contact") ``` 3. **Read the full guide** — [Scope Enforcement](/guides/scope-enforcement) *** ## Related * [AgenticOrg repository](https://github.com/mishrasanjeev/agentic-org) * [Custom Manifests guide](/guides/custom-manifests) — define your own manifests * [Scope Enforcement guide](/guides/scope-enforcement) * [Tool Manifests concept](/concepts/tool-manifests) * [Python SDK enforce()](/sdks/python/enforce) * [TypeScript SDK enforce()](/sdks/typescript/enforce) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # grantex enforce Source: https://docs.grantex.dev/cli/enforce Dry-run scope enforcement from the command line. Test whether a grant token permits a specific tool call before deploying. ## Overview `grantex enforce test` lets you dry-run scope enforcement against a real grant token without writing any code. Pass a token, connector, and tool name to see whether the call would be allowed or denied, and why. ```bash theme={null} npm install -g @grantex/cli ``` Prefer a secret-safe token source for agent automation: ```bash theme={null} grantex --json enforce test \ --token-env GRANTEX_GRANT_TOKEN \ --connector salesforce \ --tool create_lead ``` Use `--token-file ` or `--token-stdin` as alternatives. Allowed decisions exit `0`; denied or invalid decisions exit non-zero, including with `--json`. *** ## grantex enforce test Test whether a grant token permits a specific tool call. ```bash theme={null} grantex enforce test --token --connector --tool ``` ### Allowed Example ```bash theme={null} grantex enforce test \ --token "eyJhbGciOiJSUzI1NiIs..." \ --connector salesforce \ --tool create_lead ``` ``` Scope Enforcement Test ─────────────────────────────────────────── Result ALLOWED Connector salesforce Tool create_lead Permission write (from manifest) Token Scopes └ tool:salesforce:write:* Grant ID grnt_01HXYZ Agent DID did:grantex:ag_01HXYZ ───────────────────────────────────────────── ``` ### Denied Example ```bash theme={null} grantex enforce test \ --token "eyJhbGciOiJSUzI1NiIs..." \ --connector salesforce \ --tool delete_contact ``` ``` Scope Enforcement Test ─────────────────────────────────────────── Result DENIED Connector salesforce Tool delete_contact Permission delete (from manifest) Token Scopes └ tool:salesforce:write:* Reason write scope does not permit delete operations Grant ID grnt_01HXYZ Agent DID did:grantex:ag_01HXYZ ───────────────────────────────────────────── ``` *** ## Capped Scopes Use the `--amount` flag to test enforcement against capped scopes. From the next SDK release, a capped scope denies a test without `--amount` (`amount_missing`): ```bash theme={null} grantex enforce test \ --token "eyJ..." \ --connector stripe \ --tool create_payment_intent \ --amount 750 ``` ``` Scope Enforcement Test ─────────────────────────────────────────── Result DENIED Connector stripe Tool create_payment_intent Permission write (from manifest) Token Scopes └ tool:stripe:write:*:capped:500 Reason amount 750 exceeds cap of 500 ───────────────────────────────────────────── ``` When within the cap: ```bash theme={null} grantex enforce test \ --token "eyJ..." \ --connector stripe \ --tool create_payment_intent \ --amount 200 ``` ``` Scope Enforcement Test ─────────────────────────────────────────── Result ALLOWED Connector stripe Tool create_payment_intent Permission write (from manifest) Amount 200 (within cap of 500) Token Scopes └ tool:stripe:write:*:capped:500 ───────────────────────────────────────────── ``` *** ## Grant Token Audience Available in `@grantex/cli@0.4.0` with SDK 0.8.0. Node.js 22.12+ is required; audience binding and default online revocation are breaking changes. A grant token requested with an `audience` carries it in the `aud` claim, and `enforce()` denies it unless the relying party expects that audience (see [Grant token audience](/sdks/typescript/enforce#grant-token-audience)). Pass the audience your service is issued tokens for with `--audience`: ```bash theme={null} grantex enforce test \ --token "eyJ..." \ --connector salesforce \ --tool create_lead \ --audience https://api.merchant.example ``` Without `--audience`, a token that carries `aud` is reported as denied with `token_invalid` / `audience_unconfigured`; a token whose `aud` does not contain the `--audience` value is denied with `token_invalid` / `audience_mismatch`. `--audience-check off` ignores `aud`, as earlier releases did: ```bash theme={null} grantex enforce test \ --token "eyJ..." \ --connector salesforce \ --tool create_lead \ --audience-check off ``` `--audience-check` takes `on` (the default) or `off`; any other value is refused. `--audience` cannot be empty or combined with `--audience-check off`, and both options are refused when the installed `@grantex/sdk` does not check the audience, instead of being ignored. *** ## JSON Output Use `--json` for machine-readable output, useful for scripting and CI pipelines: ```bash theme={null} grantex enforce test \ --token "eyJ..." \ --connector salesforce \ --tool delete_contact \ --json ``` ```json theme={null} { "allowed": false, "connector": "salesforce", "tool": "delete_contact", "permission": "delete", "scopes": ["tool:salesforce:write:*"], "reason": "write scope does not permit delete operations", "grantId": "grnt_01HXYZ", "agentDid": "did:grantex:ag_01HXYZ" } ``` Allowed result: ```json theme={null} { "allowed": true, "connector": "salesforce", "tool": "create_lead", "permission": "write", "scopes": ["tool:salesforce:write:*"], "reason": "", "grantId": "grnt_01HXYZ", "agentDid": "did:grantex:ag_01HXYZ" } ``` *** ## Options | Flag | Description | | - | - | | `--token ` | The grant token to test against (required) | | `--connector ` | The connector name (required) | | `--tool ` | The tool name (required) | | `--amount ` | Amount to test against capped scopes | | `--audience ` | The grant token audience the relying party expects, passed to `enforce()` for this call. From version 0.4.0. | | `--audience-check ` | The client's grant token audience check: `on` (default) or `off`, which ignores `aud`. From version 0.4.0. | | `--json` | Output machine-readable JSON | *** ## Exit Codes | Code | Meaning | | - | - | | `0` | Tool call is allowed | | `1` | Tool call is denied | | `2` | Usage error (missing arguments, invalid token) | *** ## Related Commands | Command | Description | | - | - | | [`grantex manifest list`](/cli/manifest) | Browse pre-built manifests (or load your own) | | [`grantex manifest show `](/cli/manifest#grantex-manifest-show) | Inspect tools and permissions for a connector | | [`grantex manifest validate`](/cli/manifest#grantex-manifest-validate) | Validate agent tools against a manifest | | [`grantex verify`](/cli/verify) | Inspect a grant token's scopes, expiry, and delegation chain | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # grantex manifest Source: https://docs.grantex.dev/cli/manifest Browse, generate, and validate tool manifests — define your own or use 53 pre-built ones. ## Overview The `grantex manifest` commands let you browse, generate, and validate tool manifests. Grantex ships 53 pre-built manifests as a convenience, but you can define manifests for any connector — the enforcement engine treats custom and pre-built manifests identically. ```bash theme={null} npm install -g @grantex/cli ``` *** ## grantex manifest list List all available pre-built manifests, optionally filtered by category. ```bash theme={null} grantex manifest list ``` ### Output ``` Available manifests (53 connectors, 339 tools): FINANCE (11 connectors, 78 tools) banking_aa 5 tools gstn 8 tools netsuite 8 tools oracle_fusion 10 tools quickbooks 6 tools sap 7 tools stripe 8 tools tally 6 tools zoho_books 7 tools pinelabs_plural 6 tools income_tax_india 7 tools HR (8 connectors, 56 tools) darwinbox 10 tools docusign 6 tools epfo 6 tools greenhouse 8 tools keka 6 tools linkedin_talent 6 tools okta 8 tools zoom 6 tools MARKETING (16 connectors, 94 tools) salesforce 6 tools hubspot 13 tools mailchimp 10 tools ... OPS (7 connectors, 46 tools) jira 11 tools confluence 6 tools servicenow 6 tools zendesk 8 tools pagerduty 6 tools sanctions_api 5 tools mca_portal 4 tools COMMS (11 connectors, 65 tools) gmail 4 tools slack 7 tools github 9 tools ... ``` ### Filter by Category ```bash theme={null} grantex manifest list --category finance ``` ``` Finance manifests (11 connectors, 78 tools): banking_aa 5 tools gstn 8 tools netsuite 8 tools oracle_fusion 10 tools quickbooks 6 tools sap 7 tools stripe 8 tools tally 6 tools zoho_books 7 tools pinelabs_plural 6 tools income_tax_india 7 tools ``` ### JSON Output ```bash theme={null} grantex manifest list --json ``` ```json theme={null} { "manifests": [ { "connector": "banking_aa", "category": "finance", "toolCount": 5, "tools": ["fetch_bank_statement", "check_account_balance", "get_transaction_list", "request_consent", "fetch_fi_data"] }, { "connector": "salesforce", "category": "marketing", "toolCount": 6, "tools": ["create_lead", "update_opportunity", "query", "create_task", "get_account", "list_opportunities"] } ], "totalConnectors": 53, "totalTools": 339 } ``` *** ## grantex manifest show Show the tools and permission levels for a specific connector. ```bash theme={null} grantex manifest show ``` ### Example ```bash theme={null} grantex manifest show salesforce ``` ``` salesforce (6 tools) — Marketing Tool Permission ────────────────────── ────────── create_lead write update_opportunity write query read create_task write get_account read list_opportunities read ``` ```bash theme={null} grantex manifest show okta ``` ``` okta (8 tools) — HR Tool Permission ────────────────────── ────────── provision_user write deactivate_user delete assign_group write remove_group delete get_access_log read reset_mfa admin list_active_sessions read suspend_user delete ``` ### JSON Output ```bash theme={null} grantex manifest show salesforce --json ``` ```json theme={null} { "connector": "salesforce", "category": "marketing", "toolCount": 6, "tools": { "create_lead": "write", "update_opportunity": "write", "query": "read", "create_task": "write", "get_account": "read", "list_opportunities": "read" } } ``` *** ## grantex manifest validate Validate that a set of agent tools all have entries in a connector manifest. Useful for CI/CD checks before deployment. ```bash theme={null} grantex manifest validate --agent-tools --connector ``` ### Example ```bash theme={null} grantex manifest validate --agent-tools create_lead,query,delete_contact --connector salesforce ``` ``` Validating 3 tools against salesforce manifest... create_lead write (salesforce) query read (salesforce) delete_contact NOT FOUND 1 tool missing from salesforce manifest. Add "delete_contact" to a custom manifest or remove it from the agent's tool set. Exit code: 1 ``` When all tools are found: ```bash theme={null} grantex manifest validate --agent-tools create_lead,query,get_account --connector salesforce ``` ``` Validating 3 tools against salesforce manifest... create_lead write (salesforce) query read (salesforce) get_account read (salesforce) All 3 tools have manifest entries. ``` ### JSON Output ```bash theme={null} grantex manifest validate --agent-tools create_lead,query --connector salesforce --json ``` ```json theme={null} { "connector": "salesforce", "totalTools": 2, "matched": 2, "missing": 0, "tools": [ { "name": "create_lead", "permission": "write", "found": true }, { "name": "query", "permission": "read", "found": true } ] } ``` *** ## Options | Flag | Description | Applies To | | - | - | - | | `--category ` | Filter manifests by category (`finance`, `hr`, `marketing`, `ops`, `comms`) | `list` | | `--agent-tools ` | Comma-separated list of tool names to validate | `validate` | | `--connector ` | The connector manifest to validate against | `validate` | | `--json` | Output machine-readable JSON instead of formatted text | All commands | *** ## Exit Codes | Code | Meaning | | - | - | | `0` | Success (list/show completed, or all tools validated) | | `1` | Validation failed (one or more tools missing from manifest) | | `2` | Usage error (missing arguments, unknown connector) | *** ## Related Commands | Command | Description | | - | - | | [`grantex enforce test`](/cli/enforce) | Dry-run scope enforcement against a real token | | [`grantex verify`](/cli/verify) | Inspect a grant token's scopes, expiry, and delegation chain | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # grantex verify Source: https://docs.grantex.dev/cli/verify Inspect any Grantex grant token from the command line. See scopes, expiry, delegation chain, and revocation status. Works offline — no account needed. ## Overview `grantex verify` is a single command that lets any developer paste a grant token and instantly see everything about it: who issued it, scopes, expiry, delegation chain, revocation status. Works as a standalone tool — no Grantex account needed. ```bash theme={null} npm install -g @grantex/cli ``` For unattended agents, keep the token out of process arguments: ```bash theme={null} grantex verify --env GRANTEX_GRANT_TOKEN --json grantex verify --file /run/secrets/grant.jwt --json printf '%s' "$GRANTEX_GRANT_TOKEN" | grantex verify --stdin --json ``` ## Usage ```bash theme={null} # Basic verify (offline, no auth needed) grantex verify eyJhbGciOiJSUzI1NiIs... # Verbose: show full JWT header + claims grantex verify eyJ... --verbose # Check live revocation status (requires network) grantex verify eyJ... --check-revocation # Machine-readable JSON output grantex verify eyJ... --json # Verify from file grantex verify --file ./token.jwt # Verify against specific JWKS URL grantex verify eyJ... --jwks https://api.grantex.dev/.well-known/jwks.json # Verify offline with local JWKS file grantex verify eyJ... --jwks-file ./jwks.json # Read token from stdin pipe echo "eyJ..." | grantex verify --stdin ``` ## Output ### Default (Pretty) ``` Grantex Grant Token ─────────────────────────────────────────── Status ✓ Valid Grant ID grnt_01HXYZ Agent DID did:grantex:ag_01HXYZ (travel-booker) Principal user_abc @ acme.com Issued Apr 3, 2026 09:00 UTC (4h ago) Expires Apr 4, 2026 09:00 UTC (in 20h) Scopes (3) ├ calendar:read ├ email:send:max_10 (6 remaining) └ payments:initiate:max_500 (₹500 cap) Delegation └ depth: 0 (direct grant, no sub-delegation) Signature RS256 ✓ kid: grantex-2026-01 Revocation ○ Not checked (use --check-revocation) ───────────────────────────────────────────── Verified in 2ms (offline) ``` ### JSON Output (`--json`) ```json theme={null} { "valid": true, "grantId": "grnt_01HXYZ", "agentDID": "did:grantex:ag_01HXYZ", "principalDID": "did:grantex:user_01HABC", "issuedAt": "2026-04-03T09:00:00Z", "expiresAt": "2026-04-04T09:00:00Z", "scopes": ["calendar:read", "email:send:max_10", "payments:initiate:max_500"], "delegationDepth": 0, "signatureValid": true, "algorithm": "RS256", "keyId": "grantex-2026-01", "revocationStatus": "not_checked", "verifiedInMs": 2, "errors": [] } ``` ### Verbose (`--verbose`) Includes everything above plus raw JWT header and claims: ``` JWT Header { "alg": "RS256", "kid": "grantex-2026-01", "typ": "JWT" } JWT Claims { "iss": "https://api.grantex.dev", "sub": "ag_01HXYZ", "agt": "did:grantex:ag_01HXYZ", "dev": "dev_acme", "scp": ["calendar:read", "email:send:max_10"], "iat": 1743674400, "exp": 1743760800, "jti": "grnt_01HXYZ", "delegationDepth": 0 } ``` ## Error States **Expired token:** ``` Status ✗ Expired Expired 2h ago (Apr 3, 2026 08:00 UTC) ✗ This token cannot be used. To issue a new grant: grantex authorize --agent ag_01HABC ``` **Invalid signature:** ``` Status ✗ Invalid Signature ⚠ WARNING: This token may have been tampered with. The RS256 signature does not match any key in the JWKS. ✗ Do not accept this token. ``` ## Options | Flag | Description | | - | - | | `--verbose` | Show full JWT header and claims JSON | | `--json` | Output machine-readable JSON (for scripting) | | `--check-revocation` | Make a network call to check live revocation status | | `--jwks ` | Verify against a specific JWKS URL | | `--jwks-file ` | Verify offline using a local JWKS JSON file | | `--file ` | Read token from a file instead of CLI argument | | `--stdin` | Read token from stdin (for piping) | ## Related Commands | Command | Description | | - | - | | `grantex decode ` | Decode JWT without signature verification (like jwt.io) | | `grantex audit inspect ` | View entries in an offline audit log (JSONL) | | `grantex audit verify ` | Verify hash chain integrity of an audit log | | `grantex registry lookup ` | Look up an organization in the Trust Registry | | `grantex init gemma` | Scaffold a new Gemma 4 + Grantex project | ## Exit Codes | Code | Meaning | | - | - | | `0` | Token is valid | | `1` | Token is invalid (expired, bad signature, malformed) | | `2` | Usage error (missing arguments) | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OpenID AuthZEN Mapping Source: https://docs.grantex.dev/community/authzen-mapping How Grantex maps to the OpenID AuthZEN Authorization API for interoperable policy evaluation. ## Overview The [OpenID AuthZEN Authorization API](https://openid.net/specs/openid-authzen-authorization-api-1_0.html) defines a standard interface between Policy Enforcement Points (PEPs) and Policy Decision Points (PDPs). Grantex aligns its external policy backend integration with the AuthZEN subject/resource/action/context model. ## How It Works When an authorization request arrives at the Grantex server and an external policy backend is configured (OPA or Cedar), the server translates the request into an AuthZEN-aligned evaluation context: | AuthZEN Element | DAAP Source | | - | - | | **Subject** | Agent DID, developer ID, principal ID | | **Resource** | Grant with requested scopes | | **Action** | `authorize`, `delegate`, `verify`, or `debit` | | **Context** | Timestamp, IP address, budget state | ## Example A DAAP authorization request: ```json theme={null} { "agentId": "ag_01HXYZ123abc", "principalId": "user_abc123", "scopes": ["calendar:read", "payments:initiate:max_500"] } ``` Becomes the following AuthZEN evaluation request: ```json theme={null} { "subject": { "type": "agent", "id": "did:grantex:ag_01HXYZ123abc", "properties": { "developer": "org_yourcompany", "principalId": "user_abc123" } }, "resource": { "type": "grant", "properties": { "scopes": ["calendar:read", "payments:initiate:max_500"] } }, "action": { "name": "authorize" }, "context": { "timestamp": "2026-02-01T12:00:00Z" } } ``` ## Backend-Specific Details ### OPA (Rego) OPA receives the context in its `input` field and evaluates Rego policies: ```bash theme={null} POLICY_BACKEND=opa OPA_URL=http://localhost:8181 ``` See the [OPA integration guide](/guides/opa-integration) for policy examples. ### Cedar Cedar maps the context to typed entities (Agent, Grant, Action): ```bash theme={null} POLICY_BACKEND=cedar CEDAR_URL=http://localhost:8180 ``` See the [Cedar integration guide](/guides/cedar-integration) for policy examples. ## Full Documentation * [AuthZEN conformance mapping](https://github.com/mishrasanjeev/grantex/blob/main/docs/standards/authzen-conformance-mapping.md) — complete subject/resource/action/context mapping * [AuthZEN evaluation API alignment](https://github.com/mishrasanjeev/grantex/blob/main/docs/standards/authzen-evaluation-api.md) — OPA and Cedar PDP integration details ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Contributing Source: https://docs.grantex.dev/community/contributing How to contribute to the Grantex project. Thanks for your interest in contributing. Grantex is an open protocol for delegated AI agent authorization — contributions help shape a safer agentic internet. ## Where to Start | Area | What's needed | Skill | | - | - | - | | New integrations | Add Grantex support to more frameworks | TypeScript / Python | | Examples & tutorials | End-to-end walkthroughs for common use cases | Writing | | Bug reports | Found a bug? Open an issue with reproduction steps | Any | | Protocol feedback | Review SPEC.md, open issues for gaps | Any | | Docs | Improve quickstart, add translations | Writing | Look for issues tagged [`good first issue`](https://github.com/mishrasanjeev/grantex/issues?q=is%3Aissue+label%3A%22good+first+issue%22). ## Development Setup ```bash theme={null} # Prerequisites: Node.js 24 LTS, Python 3.9+, Docker git clone https://github.com/mishrasanjeev/grantex cd grantex # Start the full local stack docker compose up --build -d # TypeScript SDK npm --prefix packages/sdk-ts ci npm --prefix packages/sdk-ts run typecheck npm --prefix packages/sdk-ts test npm --prefix packages/sdk-ts run build # Python SDK cd packages/sdk-py pip install -e ".[dev]" pytest cd ../.. # Auth service npm --prefix apps/auth-service ci npm --prefix apps/auth-service run typecheck npm --prefix apps/auth-service test # Integration packages npm --prefix packages/langchain ci npm --prefix packages/langchain run typecheck npm --prefix packages/langchain test ``` For real PostgreSQL audit tests, complete Docker E2E checks and upgrade gates, see [Dependency Updates and Validation](/guides/dependency-updates). ## Repository Structure ``` grantex/ ├── SPEC.md ← Protocol specification (v1.0 final) ├── packages/ │ ├── sdk-ts/ ← TypeScript SDK (@grantex/sdk) │ ├── sdk-py/ ← Python SDK (grantex) │ ├── langchain/ ← LangChain integration │ ├── autogen/ ← AutoGen / OpenAI integration │ ├── vercel-ai/ ← Vercel AI SDK integration │ ├── crewai/ ← CrewAI integration │ ├── openai-agents/ ← OpenAI Agents SDK integration │ ├── google-adk/ ← Google ADK integration │ ├── mcp/ ← MCP server │ └── cli/ ← CLI tool ├── apps/ │ ├── auth-service/ ← Fastify auth service │ └── portal/ ← Developer portal (React) ├── examples/ ← Runnable examples ├── deploy/ ← Docker, Helm, Nginx configs └── docs/ ← This docs site ``` ## PR Guidelines 1. **Open an issue first** for anything non-trivial 2. **One PR per concern** — don't bundle unrelated changes 3. **Tests required** — all new code needs tests 4. **Spec changes need discussion** — protocol changes require a Discussion with at least 72 hours for community input 5. **Changelog entry** — add a line to `CHANGELOG.md` under `Unreleased` ## Code Style * TypeScript: ESLint + Prettier * Python: Ruff + Black * Commits: [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `spec:`) ## Protocol Changes (RFCs) Changes to SPEC.md follow a lightweight RFC process: 1. Open a GitHub Discussion tagged `[RFC]` 2. Describe the change, motivation, and alternatives 3. 72-hour minimum comment period 4. Maintainer merges or closes with reasoning ## Questions? * [GitHub Discussions](https://github.com/mishrasanjeev/grantex/discussions) for design questions * [GitHub Issues](https://github.com/mishrasanjeev/grantex/issues) for bugs and feature requests * [Discord](https://discord.gg/grantex) for real-time chat ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # IETF Internet-Draft Source: https://docs.grantex.dev/community/ietf-draft Delegated Agent Authorization Protocol (DAAP) — active individual Internet-Draft with revision 02 published and revision 03 under review. **Current Datatracker draft:** `draft-mishra-oauth-agent-grants-02` **Prepared source candidate:** `draft-mishra-oauth-agent-grants-03` **Target:** IETF OAuth Working Group (`oauth@ietf.org`) **Status:** Active individual Internet-Draft; not IETF-endorsed and not OAuth WG-adopted **Author contacts:** [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com) (primary) and [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) (alternate) The authoritative record is the [IETF Datatracker](https://datatracker.ietf.org/doc/draft-mishra-oauth-agent-grants/). An individual Internet-Draft is not endorsed by the IETF and has no formal standards standing unless the working group adopts it and the standards process advances it. Revision `-02` was posted on 2026-08-30. Revision `-03` is a repository candidate and will not appear on the Datatracker until it is uploaded and confirmed through the IETF submission flow. The source repository now contains self-assessed client, authorization-server, and resource-server implementations of the candidate profile, with unit and Docker E2E evidence. This is not independent certification or IETF endorsement. > **Submission hold:** Revision `-03` must not be uploaded before 2026-09-09. A final review is scheduled for 2026-09-06 through 2026-09-09, followed by a new explicit approval. No automated submission is authorized. ## Implementation Snapshot | Role or artifact | Repository status | | - | - | | Authorization server | RFC 8414 metadata, PAR, authorization code plus PKCE, DPoP binding, refresh rotation, RFC 8693 attenuation, and RFC 7009 revocation implemented | | Resource server | DPoP authorization scheme, `ath`, audience, scope, sender-key, expiry, grant-state, and revocation checks implemented | | TypeScript client | Discovery, PAR, callback validation, token lifecycle, DPoP, and protected-resource helper implemented in repository source | | Evidence | 2,138 auth-service tests, 456 TypeScript SDK tests, and 255 Docker E2E tests passed; encrypted refresh recovery also passed an auth-service restart test | | Independent interoperability | Not yet established; requires another implementation and cross-vendor testing | See the [OAuth Agent Grants implementation guide](/guides/oauth-agent-grants) for the hosted endpoint map, flow, operational requirements, and published-SDK boundary. ## Document The Internet-Draft source is written in [kramdown-rfc2629](https://github.com/cabo/kramdown-rfc) format, a Markdown superset that compiles to RFC XML and from there to canonical IETF text and HTML. The prepared revision source is at [`docs/ietf-draft/draft-mishra-oauth-agent-grants-03.md`](https://github.com/mishrasanjeev/grantex/blob/main/docs/ietf-draft/draft-mishra-oauth-agent-grants-03.md). The implementation report is at [`docs/ietf-draft/implementation-report.md`](https://github.com/mishrasanjeev/grantex/blob/main/docs/ietf-draft/implementation-report.md). ## Revision 03 Focus * Explicit conformance roles and RFC 8414 authorization-server metadata. * DPoP as mandatory to implement, including authorization-code binding through PAR. * RFC 9207 authorization-response issuer validation for mix-up defense. * Exact, same-instance and same-resource scope attenuation through RFC 8693 Token Exchange. * RFC 7009 revocation with refresh-family invalidation. * Precise `cnf.jkt` and `cnf."x5t#S256"` sender-confirmation members. * Lost-response refresh recovery clearly marked as non-interoperable implementation guidance. * Direct comparison with current OAuth agent authorization, attenuation, identity-chaining, and transaction-token work. ## Rendering Locally ```bash theme={null} cd docs/ietf-draft kdrfc draft-mishra-oauth-agent-grants-03.md xml2rfc draft-mishra-oauth-agent-grants-03.xml --text xml2rfc draft-mishra-oauth-agent-grants-03.xml --html ``` If local tooling is unavailable, use the [IETF Author Tools](https://author-tools.ietf.org/) renderer. ## Submitting to the Datatracker The following procedure is intentionally inactive until the submission hold has ended, the final artifacts have passed review, and a new explicit approval has been recorded. 1. Set the draft date to the actual upload date and render `draft-mishra-oauth-agent-grants-03.xml`. 2. Go to [datatracker.ietf.org/submit](https://datatracker.ietf.org/submit/). 3. Upload `draft-mishra-oauth-agent-grants-03.xml` or `.txt`. 4. Confirm via the email link sent to the author address. 5. Verify that the Datatracker page shows revision `-03`. ## Working Group Follow-Up After the Datatracker accepts `-03`, announce it on the OAuth WG mailing list: * List: `oauth@ietf.org` * Archive: [mailarchive.ietf.org/arch/browse/oauth](https://mailarchive.ietf.org/arch/browse/oauth/) The main ask should be technical review of scope and overlap: * Should DAAP remain a single profile or be split into smaller OAuth drafts? * Which parts should align with identity chaining and transaction-token work? * Is DPoP mandatory to implement the right sender-constraint baseline? * Is same-resource, exact-scope attenuation narrow enough for a first profile? * Should the OAuth WG discuss DAAP at IETF 127? ## Dates * `-02` expiry: 2027-03-03 * IETF 127: 2026-11-14 through 2026-11-20, San Francisco * BOF proposal cutoff: 2026-09-18 * WG meeting request cutoff: 2026-10-02 * IETF 127 I-D submission cutoff: 2026-11-02 23:59 UTC ## Path to Standards Track | Stage | Action | | - | - | | Individual I-D | Submit the `-03` candidate to Datatracker | | Mailing list review | Request OAuth WG feedback | | Agenda time | Ask OAuth chairs whether DAAP should be discussed at IETF 127 | | Scope refinement | Split or narrow the draft if the WG prefers | | WG adoption | Request adoption after support appears on-list | | WG I-D | Rename to `draft-ietf-oauth-agent-grants-00` if adopted | | WGLC | Working Group Last Call | | IESG review | Internet Engineering Steering Group review | | RFC publication | RFC Editor publication if approved | ## Related Work | Work | Relationship | | - | - | | OAuth 2.0 | Foundation for the DAAP grant model | | OAuth 2.0 Security BCP | Security baseline DAAP does not relax | | OAuth Identity Chaining | Complementary cross-domain identity and authorization context | | OAuth Transaction Tokens | Complementary call-chain context propagation | | OAuth Rich Authorization Requests | Possible future encoding profile for DAAP authorization details | | DPoP and mTLS | Sender-constraining mechanisms for high-stakes DAAP use | | OAuth Client Instance Assertion and AI Agent Instance Profile | Stable, attested agent-instance identity that DAAP deliberately does not derive from a sender key | | PACT | Complementary CIBA-based privacy and consent profile with structured capabilities | | OAuth Delegated Authorization | Cross-instance, client-issued delegation-chain work; DAAP limits core RFC 8693 use to same-instance attenuation | | OAuth Actor Profile and Actor Chains | Richer actor disclosure and cryptographic chain proofs beyond DAAP's RFC 8693 baseline | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Draft NIST NCCoE Public Comment Source: https://docs.grantex.dev/community/nist-comment Grantex response to NIST NCCoE AI challenge areas — mapping DAAP to the AI Risk Management Framework. ## Overview The repository publishes a draft comment for the NIST National Cybersecurity Center of Excellence (NCCoE), mapping DAAP protocol capabilities to the NIST AI Risk Management Framework (AI RMF 1.0). The NCCoE comment period closed on April 2, 2026 and NIST lists the project as reviewing comments. Grantex does not currently publish a submission receipt or a NIST response, so this page must not be read as evidence of NIST acceptance, endorsement, or confirmed receipt. ## Key Mappings The comment maps DAAP capabilities to all four AI RMF functions: | AI RMF Function | DAAP Capabilities | | - | - | | **Map** | Agent scope declarations, policy engine risk tolerance, anomaly baselines | | **Measure** | Usage metering, hash-chained audit trail, budget controls, conformance suite | | **Manage** | Real-time revocation, cascade revocation, event streaming, webhooks | | **Govern** | OPA/Cedar policy backends, principal sessions, compliance exports, policy-as-code | ## Implementation Evidence The comment includes evidence from the Grantex reference implementation: * **Authorization server**: Fastify/PostgreSQL/Redis reference service deployed on Cloud Run, with CI-backed unit, integration, security, conformance, and end-to-end tests * **SDK coverage**: TypeScript, Python, and Go; use each registry/module tag as the release source of truth * **Framework integrations**: 11 production-ready integrations * **Conformance suite**: `@grantex/conformance`, with results tied to the tested package version and commit ## Recommendations 1. Adopt agent-specific identity standards (DIDs over API keys) 2. Require action-level audit trails with tamper evidence 3. Mandate real-time revocation with cascade semantics 4. Define budget control requirements for financial agents 5. Encourage external policy backend integration (OPA, Cedar, AuthZEN) 6. Reference interoperability test suites in standards ## Full Document The complete comment is available in the repository at [`docs/standards/nist-nccoe-comment.md`](https://github.com/mishrasanjeev/grantex/blob/main/docs/standards/nist-nccoe-comment.md). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # BFSI platform baseline Source: https://docs.grantex.dev/compliance/bfsi-platform-baseline How the authority layer contributes to a bank-grade enterprise AI platform baseline, and where it stops. A regulated financial institution expects an enterprise AI platform to satisfy a baseline of residency, isolation and control conditions plus a long list of technical and functional capabilities. The platform side of that baseline lives in AgenticOrg (`docs/bfsi/` in that repository). This page lists the items where Grantex is the authority primitive the platform relies on, what exists today with repository evidence, and the capability group each item belongs to. Identifiers follow the AgenticOrg catalogue. ## Where Grantex is the primitive | ID | Capability | Status | What exists | Group | | - | - | - | - | - | | BASE-01 | In-country data residency | Partial | A grant can be issued with a data region, and relying parties with the region check on refuse it elsewhere (both SDKs and the gateway, opt-in this release); the hosted service is still pinned to one foreign region. (`apps/auth-service/src/lib/purpose.ts`, `packages/sdk-ts/src/client.ts`, `packages/sdk-py/src/grantex/_client.py`) | WP-19 | | BASE-02 | Isolated production instance | Partial | Self-hosting gives a dedicated instance; the hosted service is multi-tenant with application-level tenant scoping. (`deploy/helm/grantex/values.yaml`, `docker-compose.prod.yml`, `docs/self-hosting.md`) | WP-19 | | BASE-03 | In-country disaster recovery | Gap | Single region, no secondary DR site; backup and restore runbook not written. (`docs/compliance/data-residency.md`, `docs/self-hosting.md`) | WP-19 | | BASE-04 | No training on institution data | Gap | No prompt processing or training; no written no-training control. | WP-19 | | BASE-05 | Emergency operator override | Partial | Emergency stop by grant/agent/principal/developer with issuance freeze and revocation feed; off unless EMERGENCY\_STOP\_ENABLED; cannot halt models or pipelines; the gateway checks JWTs locally with no online revocation. (`apps/auth-service/src/routes/emergency-stop.ts`, `apps/auth-service/src/lib/revocation/emergency-stop.ts`, `apps/auth-service/src/lib/revocation/issuance-freeze.ts`) | WP-01 | | INF-04 | Managed Kubernetes | Partial | Helm chart with autoscaler and disruption budget for managed Kubernetes. (`deploy/helm/grantex/templates/hpa.yaml`, `deploy/helm/grantex/templates/pdb.yaml`) | WP-00 | | INF-05 | Managed serverless containers | Partial | Deploys to serverless containers on one provider. (`deploy/gcp/setup.sh`, `.github/workflows/deploy.yml`) | WP-00 | | SEC-02 | Key management and BYOK | Partial | AES-256-GCM vault key and encrypted signing keys with publish-then-sign rotation; no KMS/HSM/BYOK. (`apps/auth-service/src/lib/vault-crypto.ts`, `apps/auth-service/src/lib/signing-keys.ts`, `apps/auth-service/src/cli/rotate-signing-key.ts`) | WP-00 | | SEC-03 | Secrets management | Partial | Encrypted credential vault and secret-manager runtime secrets; no managed DB credential or certificate rotation. (`apps/auth-service/src/routes/vault.ts`, `deploy/gcp/setup.sh`, `apps/auth-service/src/routes/event-sources.ts`) | WP-00 | | SEC-04 | Web application firewall | Partial | Rate limiting only; no WAF ruleset. (`deploy/nginx/nginx.conf`, `apps/auth-service/src/plugins/dynamicRateLimit.ts`) | WP-00 | | SEC-06 | Security posture management | Partial | CI-time image scans, CodeQL, secret scanning; no runtime posture management. (`.github/workflows/security-scan.yml`, `.github/workflows/codeql.yml`, `scripts/scan-container.sh`) | WP-00 | | SEC-07 | Threat detection | Partial | Irregularity detection on authorization events; no control-plane or network detection. | WP-00 | | SEC-08 | SIEM and security lake | Partial | SIEM sinks and audit export; no open-schema security lake. (`packages/destinations/src/destinations/splunk.ts`, `packages/destinations/src/destinations/datadog.ts`, `docs/guides/siem-splunk.mdx`) | WP-00 | | SEC-09 | Identity and access management | Partial | Scopes, short-lived DPoP-bound tokens, OIDC/SAML SSO with group mapping, SCIM, passkeys; no general role model; admin MFA delegated to the identity provider. (`apps/auth-service/src/routes/sso.ts`, `apps/auth-service/src/routes/scim.ts`, `apps/auth-service/src/routes/webauthn.ts`) | WP-00 | | DATA-08 | Managed relational databases | Partial | Managed Postgres without HA configuration. (`deploy/gcp/setup.sh`, `docs/self-hosting.md`) | WP-00 | | AIINF-10 | Agent tool gateway | Partial | Grant-token proxy with audience and data-region checks, MCP resource guard, credential exchange and an MCP server registry; the gateway verifies tokens locally by default and against the issuer with currentAuthorityCheck on. (`packages/gateway/src/proxy.ts`, `packages/gateway/src/server.ts`, `packages/mcp-auth/src/resource/guard.ts`) | WP-01 | | AIINF-11 | Agent lifecycle governance | Partial | Agent registration, active/suspended, key rotation, passport issue/revoke; no versioning, traffic routing or rollback. (`apps/auth-service/src/routes/agents.ts`, `apps/auth-service/src/routes/agent-keys.ts`, `apps/auth-service/src/routes/passport.ts`) | WP-05 | | NET-01 | Private service endpoints | Partial | Private VPC for data stores. (`deploy/gcp/setup.sh`) | WP-00 | | NET-02 | Enterprise API gateway | Partial | API-key auth, plan rate limits, WebSocket/SSE events, header-injecting proxy. (`packages/gateway/src/server.ts`, `apps/auth-service/src/plugins/dynamicRateLimit.ts`, `apps/auth-service/src/routes/events.ts`) | WP-00 | | OPS-01 | Distributed tracing for AI | Partial | OpenTelemetry in the auth service with agent and grant attributes. (`apps/auth-service/src/lib/tracing.ts`, `apps/auth-service/src/lib/traceAttributes.ts`, `docs/guides/opentelemetry.mdx`) | WP-09 | | OPS-02 | Centralised platform logging | Partial | Structured logs, Prometheus metrics, Grafana dashboards. (`apps/auth-service/src/lib/logger.ts`, `apps/auth-service/src/lib/metrics.ts`, `deploy/grafana/overview-dashboard.json`) | WP-09 | | OPS-03 | Infrastructure as code | Partial | Terraform provider and Helm chart; no drift detection. (`packages/terraform-provider-grantex/internal/provider/provider.go`, `deploy/helm/grantex/Chart.yaml`, `docs/guides/pulumi.mdx`) | WP-00 | | OPS-06 | Managed CI/CD | Partial | GitHub Actions CI/CD. (`.github/workflows/ci.yml`, `.github/workflows/deploy.yml`, `.github/workflows/release.yml`) | WP-00 | | ACQ-01 | Lawful automated acquisition | Partial | Purpose-bound manifests for registry connectors; no acquisition engine. (`packages/sdk-ts/src/manifests/epfo.ts`, `packages/sdk-ts/src/manifests/gstn.ts`, `packages/sdk-ts/src/manifests/mca_portal.ts`) | WP-16 | | ACQ-02 | Provenance metadata | Partial | Evidence records carry keyed digests, provider, upstream record references, receipt time and chain hash. (`spec/evidence-package.md`, `apps/auth-service/src/lib/evidence/build.ts`, `apps/auth-service/src/routes/evidence.ts`) | WP-16 | | CONV-04 | Confirmed transaction invocation | Partial | Human consent and action-bound decision grants before execution. (`apps/auth-service/src/routes/decisions.ts`, `packages/mcp-auth/src/resource/grantex-decisions.ts`, `apps/auth-service/src/routes/authorize.ts`) | WP-11 | | RAG-09 | Access-aware retrieval | Partial | Per-connector and per-tool scope and purpose restriction; no document-level filtering. (`docs/concepts/scopes.mdx`, `docs/concepts/tool-manifests.mdx`) | WP-04 | | TXN-01 | Entity-centric aggregation | Partial | Evidence grouped per case. (`apps/auth-service/src/routes/evidence.ts`, `spec/evidence-package.md`) | WP-15 | | TXN-06 | Consolidated evidence context | Covered | Signed hash-chained per-case evidence package with export and offline verification. (`spec/evidence-package.md`, `apps/auth-service/src/routes/evidence.ts`, `apps/auth-service/src/lib/evidence/verify.ts`) | WP-15 | | GW-07 | Model access policies | Partial | Grant-token proxy can restrict any upstream endpoint by agent and grant scope. (`packages/gateway/src/proxy.ts`, `packages/gateway/gateway.example.yaml`) | WP-02 | | GW-08 | Gateway audit trail | Partial | Evidence entries record model versions and policy evaluations. (`spec/evidence-package.md`, `apps/auth-service/src/lib/evidence/schema-1.0.ts`) | WP-02 | | MLOPS-01 | Governed model registry | Gap | Not in scope for the authority layer. | WP-05 | | MLOPS-02 | Controlled promotion | Gap | Not in scope for the authority layer. | WP-05 | | REG-01 | Searchable agent catalogue | Partial | Agent list, registry lookup by DID and key, trust-registry search with category filter. (`apps/auth-service/src/routes/agents.ts`, `apps/auth-service/src/routes/registry-lookup.ts`, `apps/auth-service/src/routes/trust-registry.ts`) | WP-05 | | REG-02 | Agent card | Partial | A2A agent card, passport claims, per-tool permission manifests; no model or I/O schema fields. (`packages/a2a/src/agent-card.ts`, `spec/agent-passport-1.0.md`, `packages/agent-passport/src/passport.ts`) | WP-05 | | REG-03 | Agent lifecycle states | Partial | Lifecycle states draft, active, suspended and retired with a transition table and recorded reasons, behind AGENT\_LIFECYCLE\_STATES\_ENABLED; no review, approved or published states and no approval attestation type in the registry. (`apps/auth-service/src/routes/agents.ts`, `docs/openapi.yaml`) | WP-05 | | REG-04 | Agent templates | Gap | Prebuilt tool manifests, no agent templates. (`packages/sdk-ts/src/manifests/index.ts`) | WP-05 | | REG-05 | Dependency visualisation | Gap | No visual map. | WP-05 | | REG-06 | Agent approval workflow | Partial | Accredited-issuer attestations and trust levels; no internal approval workflow. (`apps/auth-service/src/routes/registry-attestations.ts`, `apps/auth-service/src/lib/registry/trust-level.ts`, `spec/attestation-1.0.md`) | WP-05 | | REG-07 | Environment version management | Gap | Sandbox and live only. | WP-05 | | REG-08 | Ratings and reliability metrics | Gap | No ratings. | WP-05 | | ORCH-03 | Multi-agent collaboration | Partial | Scoped delegation with depth limits; A2A calls. (`apps/auth-service/src/routes/delegate.ts`, `packages/a2a/src/server.ts`, `docs/concepts/delegation.mdx`) | WP-17 | | ORCH-04 | Tool registration | Partial | Schema-validated tool manifests and MCP server registration. (`docs/guides/custom-manifests.mdx`, `spec/manifest-0.6.schema.json`, `apps/auth-service/src/routes/mcp-servers.ts`) | WP-17 | | ORCH-05 | Sandboxed authorised tool execution | Partial | enforce, the gateway and the MCP guard check scope, tool, purpose, caps and decisions fail-closed; no sandboxing. (`packages/sdk-ts/src/client.ts`, `packages/gateway/src/proxy.ts`, `packages/mcp-auth/src/resource/guard.ts`) | WP-17 | | ORCH-07 | Human approval checkpoints | Covered | Decision grants with OIDC approver sign-in, step-up, four-eyes, dwell time, single-use consumption and audit. (`apps/auth-service/src/routes/decisions.ts`, `apps/auth-service/src/routes/decision-page.ts`, `spec/decision-grant.md`) | WP-17 | | ORCH-08 | Execution limits and loop detection | Partial | Per-tool caps and budgets; no step, duration or loop detection. (`packages/sdk-ts/src/caps/index.ts`, `docs/concepts/caps-and-metering.md`, `apps/auth-service/src/routes/budget.ts`) | WP-17 | | ORCH-09 | Per-agent tool restrictions | Covered | Manifests map tools to read, write, delete or admin; agent and grant scopes enforce it, and with tool-qualified scopes on, a scope naming a tool covers that tool only. (`docs/concepts/tool-manifests.mdx`, `packages/mcp-auth/src/resource/tool-policy.ts`, `spec/manifest-0.6.md`) | WP-17 | | ORCH-10 | Debugging console | Partial | Audit and enforcement log viewers. (`apps/portal/src/pages/audit/AuditLog.tsx`, `apps/portal/src/pages/enforce/EnforceLog.tsx`) | WP-17 | | ORCH-11 | Agents as callable capabilities | Partial | Agents exposed over A2A with grant auth. (`packages/a2a/src/server.ts`, `packages/a2a/src/agent-card.ts`) | WP-17 | | AIGOV-01 | Live asset inventory | Partial | Inventory of agents, grants, MCP servers, manifests; software SBOM and provenance; no AIBOM. (`apps/auth-service/src/routes/agents.ts`, `apps/auth-service/src/routes/mcp-servers.ts`, `.github/workflows/publish-auth-service-image.yml`) | WP-08 | | AIGOV-02 | Configurable AI policies | Partial | Allow/deny policies with OPA and Cedar backends, tool, purpose and caps rules; no model, prompt or output governance. (`apps/auth-service/src/lib/policy.ts`, `apps/auth-service/src/lib/backends/opa.ts`, `apps/auth-service/src/lib/backends/cedar.ts`) | WP-08 | | AIGOV-03 | Regulatory risk tiers | Partial | Tool manifests declare a risk tier and a high-risk tool needs a decision grant on every call in both SDKs and the MCP tool policy; the tiers classify tools, not AI workloads or models. (`spec/manifest-0.6.schema.json`, `packages/sdk-ts/src/manifest.ts`, `packages/sdk-py/src/grantex/manifest.py`) | WP-08 | | AIGOV-04 | Operator override | Partial | Emergency stop for grant, agent, principal and developer; no model or pipeline throttling. (`apps/auth-service/src/routes/emergency-stop.ts`, `apps/auth-service/src/lib/revocation/emergency-stop.ts`, `apps/auth-service/src/routes/revocations.ts`) | WP-01 | | AIGOV-05 | Dependency graph | Gap | No dependency graph. | WP-08 | | AIGOV-06 | Model cards | Gap | Software SBOM and provenance only. (`.github/workflows/release.yml`) | WP-08 | | TRUST-02 | Sensitive-data controls | Partial | DPDP consent lifecycle, pseudonyms in evidence, SD-JWT selective disclosure; no PII detection in AI inputs and outputs. (`apps/auth-service/src/routes/dpdp.ts`, `packages/dpdp/src/index.ts`, `apps/auth-service/src/lib/decisions/personal-data.ts`) | WP-03 | | TRUST-05 | Agent access policies | Covered | Access governed by agent, principal, tool, connector, purpose, caps, time, OPA and Cedar; no data-classification attribute. (`docs/concepts/tool-manifests.mdx`, `docs/concepts/purpose-bound-grants.md`, `apps/auth-service/src/lib/policy.ts`) | WP-08 | | TRUST-06 | Approval for high-risk actions | Covered | requires\_decision and four\_eyes tools need approval; fail closed. (`spec/decision-grant.md`, `apps/auth-service/src/routes/decisions.ts`, `packages/mcp-auth/README.md`) | WP-17 | | TRUST-07 | Central credential vault | Covered | Encrypted vault; the exchange can hand out a short-lived credential reference (VAULT\_CREDENTIAL\_REFERENCES\_ENABLED) that the gateway redeems with its own key and injects upstream (credentialReference: on), so the agent never holds the secret. (`apps/auth-service/src/routes/vault.ts`, `packages/gateway/src/credentials.ts`, `docs/api-reference/vault/resolve.mdx`) | WP-21 | | OBS-05 | Correlation identifiers | Partial | Request ids, OpenTelemetry spans, caseId linking evidence. (`apps/auth-service/src/server.ts`, `packages/sdk-ts/src/http.ts`, `apps/auth-service/src/lib/traceAttributes.ts`) | WP-09 | | OBS-06 | Tamper-evident audit | Covered | Hash-chained audit log and signed, anchored evidence packages. (`apps/auth-service/src/lib/audit-chain.ts`, `apps/auth-service/src/routes/audit.ts`, `spec/evidence-package.md`) | WP-09 | | FIN-01 | Usage attribution | Partial | Usage metering per developer and per-grant cost units. (`apps/auth-service/src/lib/usage.ts`, `apps/auth-service/src/routes/usage.ts`, `apps/auth-service/src/routes/budget.ts`) | WP-10 | | FIN-02 | Budget thresholds | Partial | Budget alerts at 50 and 80 percent, caps that deny, plan rate limits. (`docs/guides/budget-controls.mdx`, `packages/sdk-ts/src/caps/index.ts`, `apps/auth-service/src/plugins/dynamicRateLimit.ts`) | WP-10 | | FE-08 | Governed API exposure | Partial | Grant-token governance in front of any API. (`packages/gateway/src/proxy.ts`, `docs/openapi.yaml`) | WP-00 | | FE-12 | Paused-task review queue | Partial | Per-request approval page; no central queue. (`apps/auth-service/src/routes/decision-page.ts`, `apps/auth-service/src/routes/prepaid-wallets.ts`) | WP-18 | | FE-13 | Business configuration console | Partial | Portal forms for policies, rules and budgets. (`apps/portal/src/pages/policies/PolicyForm.tsx`, `apps/portal/src/pages/budgets/BudgetList.tsx`) | WP-18 | ## Reading the status * **Covered**: the primitive exists in product form with tests. **Partial**: part of it exists or it exists for one surface only. **Gap**: nothing usable exists yet. * The gateway (`packages/gateway`) verifies grant tokens locally by default; with `currentAuthorityCheck: true` it verifies every request against the issuer, so revocations and emergency stops take effect on the next request rather than at token expiry. It is the documented default for stop-sensitive deployments (see the gateway integration page); a gateway without it accepts stale authority for up to a token lifetime. * Capability groups name the product area that owns an item: WP-01 operator override, WP-05 agent registry and certification, WP-08 governance inventory, WP-19 residency controls, WP-21 authority layer hardening (credential-by-reference exchange, lifecycle states and attestation types, risk tiers on manifests, tool-qualified scopes in the SDKs). A group carries no delivery commitment. # Cookie policy Source: https://docs.grantex.dev/compliance/cookie-policy # Cookie Policy (Draft) **Status:** Draft. Subject to legal review. **Last reviewed:** 2026-05-24 **Applies to:** the marketing site (`grantex.dev`), the documentation site (`docs.grantex.dev`), and the developer portal (`grantex.dev/dashboard`). This page describes the cookies and similar storage mechanisms used by the public Grantex sites today, based on what is wired up in this repository. Grantex is owned by Orchestrum Technologies LLP; inventor and owner: Sanjeev Kumar. **Self-hosted Grantex deployments serve the marketing site only if the operator publishes it; the analytics cookie below is in the source under the Orchestrum-controlled GA ID and is easy to remove for a fork.** ## Categories in use ### Strictly necessary These are required to operate the developer portal and consent UI. They cannot be disabled. | Name | Purpose | Set by | Duration | | - | - | - | - | | `__session` | Developer-portal authenticated session | `apps/portal` | Session | | `csrf` | CSRF double-submit token on consent and portal forms | auth-service consent challenge route | Session | No third-party cookies are set by the auth-service or the developer portal. ### Analytics The marketing site only uses Google Analytics for aggregate traffic data, which we use to decide where to invest in docs and integrations. | Name | Purpose | Set by | Duration | | - | - | - | - | | `_ga`, `_gid`, `_ga_*` | Google Analytics aggregate site traffic | Google Analytics (GA4 property `G-DNXYVLS5NY`) | Up to 2 years | You can opt out of Google Analytics by using a browser extension such as the official Google Analytics Opt-out, by enabling Do Not Track, or by blocking the `googletagmanager.com` and `google-analytics.com` domains in your browser. The marketing site does not currently surface a per-visitor banner-style consent prompt. **TBD:** a CMP-style consent banner is on the follow-up list for the EU launch — see `docs/reports/enterprise-readiness-brutal-review-2026-05-24.md` item H-3 (legal-link work). ### Marketing / advertising None. Grantex does not currently run third-party ad pixels (no Facebook Pixel, no LinkedIn Insight Tag, no Google Ads tag) and does not retarget visitors. ### Session replay or behavioural recording None. No session-replay tooling (FullStory, Hotjar, LogRocket) is referenced anywhere in the repo. ## Local / session storage The interactive playgrounds (`web/commerce-playground.html`, `web/x402-playground/`, `web/mpp-demo/`) use `localStorage` to remember the API base URL, sample payloads, and form values you typed. Nothing is sent off-device beyond what you explicitly submit through the UI. ## How to clear Use your browser's standard "Clear site data" controls for `grantex.dev` and `docs.grantex.dev`. Clearing site data signs you out of the portal and resets the playgrounds to defaults. ## Contact Cookie or tracking questions: `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`. Ownership and IP: `sanjeev@orchestrum.in`, `mishra.sanjeev@gmail.com`. # Data residency Source: https://docs.grantex.dev/compliance/data-residency # Data Residency Statement **Status:** Current public disclosure. Subject to legal review. Not a certification document. **Last reviewed:** 2026-05-24 This page describes where Grantex stores and processes customer data on the **hosted** offering (`api.grantex.dev`), and the residency choices available to operators running their **own** Grantex deployment. Grantex is owned by Orchestrum Technologies LLP; inventor and owner: Sanjeev Kumar. ## Hosted Grantex (`api.grantex.dev`) — single region today All production-grade data for the hosted service is currently pinned to **Google Cloud `us-central1` (Council Bluffs, Iowa, USA)**. This is sourced from `deploy/gcp/setup.sh`: ``` REGION="us-central1" SQL_INSTANCE="grantex-pg16" → us-central1 REDIS_INSTANCE="grantex-redis" → us-central1 AR_REPO="grantex-images" → us-central1 CR_SERVICE="grantex-auth" → us-central1 ``` | Data class | Storage location | Notes | | - | - | - | | Agents, grants, audit, commerce records | Cloud SQL PostgreSQL, `us-central1` | Encrypted at rest by GCP (AES-256). Backups stored in the same region. | | Rate-limit + idempotency state | Memorystore Redis, `us-central1` | TTL-bound, no long-lived PII. | | Container images | Artifact Registry, `us-central1` | Build artifacts only. | | Runtime secrets | Secret Manager, `us-central1` | Vault encryption key, JWT signing keys. | | Marketing site (`grantex.dev`) | Firebase Hosting | Global CDN edge (Google anycast); origin in US. | | DNS records | Cloud DNS | Global anycast. | **Cross-border transfers.** Operating the hosted service from `us-central1` means that requests originating outside the United States cross a border at request time. Customer payloads are not replicated to other regions by Grantex. ## EU / India / multi-region availability A multi-region offering (EU and India) is on the public roadmap (`ROADMAP.md`, item "Managed Cloud"). **It is not available today.** Until then: * Customers with EU data-residency obligations under GDPR should evaluate the self-hosting option below. * Customers designated as Significant Data Fiduciaries under the DPDP Act (India) may be required to keep specified personal data within India where the Central Government specifies it (DPDP Rules 2025 r.13(4), from 13 May 2027); self-hosting is one way to do that. The DPDP Act otherwise restricts transfers only to countries the Government notifies (s.16), not by general localisation. See `docs/compliance/dpdp-act-2023.mdx` for the technical control mapping. ## Self-hosted Grantex — operator-chosen residency Grantex is Apache 2.0 and ships with a Helm chart (`deploy/helm/grantex/`) and Docker Compose stack (`docker-compose.prod.yml`). When you self-host, **the data never leaves the infrastructure you control** — Grantex maintainers receive no telemetry from your deployment by default. Operator-controlled residency knobs: * **Compute** — anywhere your Kubernetes cluster or container host runs. * **PostgreSQL** — point `DATABASE_URL` at any Postgres 14+ instance in your region of choice. * **Redis** — point `REDIS_URL` at any Redis 6+ instance in your region of choice. * **Object storage destination** (optional audit export) — choose any S3-compatible bucket region. * **OpenTelemetry sink** (optional) — point `OTEL_EXPORTER_OTLP_ENDPOINT` at a collector in your region. See `docs/self-hosting.md` for the full operator runbook. ## Backup, retention, and deletion * **Backups:** WAL-archive backups recommended per the Cloud SQL default policy on hosted; operator-defined on self-hosted. A formal backup/restore runbook is **TBD** as a follow-up doc. * **Retention:** audit-log retention is operator-configurable; defaults documented in `apps/auth-service/src/config.ts`. * **Deletion:** DPDP erasure flow (`/v1/dpdp/data-principals/:id/erasure`) and consent withdrawal flows are implemented in `apps/auth-service/src/routes/dpdp.ts`. Erasure revokes the principal's active grants, marks the consent records erased (they are retained as evidence of consent), and, with `DPDP_ERASURE_EXPANDED=true`, redacts grievance text and deletes stored exports about the principal (otherwise the response lists them as retained); audit entries are retained unmodified, since they form a tamper-evident hash chain. ## Contact Residency or transfer questions: `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`. Ownership and IP: `sanjeev@orchestrum.in`, `mishra.sanjeev@gmail.com`. # Dpa Source: https://docs.grantex.dev/compliance/dpa # Data Processing Addendum (Template) **Status:** Draft template. Subject to legal review. **Not a legally executed agreement and not legal advice.** **Last reviewed:** 2026-05-24 This template forms the basis of the Data Processing Addendum that Grantex offers to customers of the hosted service. It is published so procurement teams can review the terms before requesting a signed copy. The signed copy that the parties execute will be a PDF derived from this template, countersigned by both parties; this Markdown file does not constitute that signed agreement. Customers requiring a signed DPA should email `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com` with their legal counter-party details. Ownership or IP questions may also be sent to `sanjeev@orchestrum.in` or `mishra.sanjeev@gmail.com`. *** ## 1. Parties * **Processor:** Orchestrum Technologies LLP, owner and operator of the hosted Grantex service at `api.grantex.dev`. * **Inventor and owner contact:** Sanjeev Kumar (`sanjeev@orchestrum.in`, `mishra.sanjeev@gmail.com`). * **Controller:** the customer organization identified in the executed cover sheet. This addendum supplements the Apache 2.0 license of the open-source software and any commercial agreement covering the hosted service. In a conflict, the executed cover sheet governs. ## 2. Subject matter and duration The Processor processes personal data on behalf of the Controller solely to deliver the Grantex Delegated Authorization Platform hosted service. Processing continues for the term of the underlying agreement and during any wind-down period defined in Section 10 (Return or Deletion). ## 3. Nature and purpose of processing * Issuing, verifying, and revoking agent grant tokens. * Recording consent records and audit entries linked to data principals. * Operating the supporting administrative, support, and security functions of the platform. ## 4. Categories of personal data | Category | Examples | | - | - | | Account / developer | Email, name, organization, hashed API key | | End-user / data principal | Principal identifier supplied by the Controller (typically a pseudonymous ID), optional WebAuthn credential references | | Authorization events | Scope strings, timestamps, agent DIDs, IP addresses captured for rate limiting | | Audit metadata | Action, status, request ID, hashed references to third-party identifiers | | Optional compliance | DPDP consent records, grievance references, erasure request IDs | Grantex does **not** request payment-card data, government identifiers, or special-category personal data. Customers must not transmit such data to the platform. ## 5. Categories of data subjects * The Controller's developers and administrators. * The Controller's end users (data principals) whose authorization is brokered by Grantex. * Authorized agents acting on behalf of those end users. ## 6. Sub-processors A current list is maintained at [docs/compliance/sub-processors.md](./sub-processors.md). The Processor will provide at least 30 days' notice (via CHANGELOG and the developer portal) before engaging a new production sub-processor; the Controller may object in writing within that window, in which case the parties will discuss an alternative arrangement in good faith. ## 7. Technical and organisational measures The Processor maintains the following safeguards, summarised here and described in detail in `docs/security/` and `SECURITY.md`: * **Encryption in transit** — TLS 1.2+ enforced on all public endpoints. * **Encryption at rest** — provider-native (Cloud SQL, Memorystore, Secret Manager) plus an application-level vault key for sensitive fields. * **Key management** — JWT signing keys and the vault encryption key live in Google Secret Manager and are rotatable without redeploy. * **Access controls** — least-privilege IAM on infrastructure; CODEOWNERS-gated reviews on the source; CI-required status checks on `main`. * **Multi-tenancy isolation** — every row is developer-scoped and proven by `tests/security-multi-tenancy.test.ts`. * **Vulnerability management** — published disclosure policy and SLA in `SECURITY.md`; weekly Dependabot updates configured in `.github/dependabot.yml`. * **Live commerce side-effects fail closed** — central guard in `apps/auth-service/src/lib/commerce/live-mode-guard.ts`. ## 8. Confidentiality Personnel with access to personal data are bound by confidentiality obligations and receive security training appropriate to their role. ## 9. Breach notification The Processor will notify the Controller **without undue delay and in any event within 72 hours** of becoming aware of a personal-data breach affecting the Controller's data. The notification will include: nature of the breach, categories and approximate number of data subjects and records affected, likely consequences, and measures taken or proposed. ## 10. Return or deletion of data On termination of the underlying agreement, the Controller may, within 30 days, request: * **Export** — a JSON export of all personal data the Processor holds about the Controller's tenants; or * **Deletion** — irreversible deletion of that data and a written confirmation. The DPDP erasure endpoint (`POST /v1/dpdp/data-principals/:id/erasure`, see `apps/auth-service/src/routes/dpdp.ts`) is available throughout the term for per-principal erasure: it revokes the principal's grants, marks consent records erased, redacts grievance text and deletes stored exports, and retains consent records and audit entries as evidence where retention is required (for example DPDP Rules 2025 r.8(3)); its response lists what was retained and why. ## 11. International transfers The Processor relies on the European Commission's Standard Contractual Clauses (Module Two: Controller-to-Processor) for any transfer of EEA-origin personal data outside the EEA, plus the UK International Data Transfer Addendum and the Swiss FDPIC adequacy mechanism as applicable. Today, all hosted processing occurs in `us-central1`; see [data-residency.md](./data-residency.md). ## 12. Audits The Controller may request, no more than once per year and on at least 60 days' notice, evidence of the Processor's compliance with this Addendum. The Processor will respond with the most recent SOC 2 attestation **when available** (status today: SOC 2 readiness control mapping published in `docs/security/soc2-report.mdx`; formal third-party attestation pending). On-site audits are by mutual written agreement. ## 13. Liability Liability under this Addendum is subject to the limits in the underlying commercial agreement. Open-source self-hosted use is governed solely by the Apache 2.0 license. ## 14. Governing law The governing law and venue are those specified in the executed cover sheet. The template defaults to Delaware, United States, pending company-formation decisions. ## 15. Signature block This template becomes binding only when both parties sign the executed cover sheet that incorporates these terms by reference. **The Markdown file alone is not a signed agreement.** # DPDP Act 2023 and DPDP Rules 2025 Source: https://docs.grantex.dev/compliance/dpdp-act-2023 How Grantex features map to India's Digital Personal Data Protection Act 2023 and the DPDP Rules 2025 for AI agent deployments, what Grantex provides and what remains the Data Fiduciary's responsibility. ## Overview The **Digital Personal Data Protection Act, 2023** (Act 22 of 2023, the DPDP Act) is India's data protection law. It sets duties for **Data Fiduciaries** (who decide the purpose and means of processing), rights and duties for **Data Principals** (the individuals the data is about), and a **Data Protection Board of India** to enforce it. The **Digital Personal Data Protection Rules, 2025** (G.S.R. 846(E), notified 13 November 2025) are the final implementing rules; the January 2025 text (G.S.R. 02(E)) was a draft. When an AI agent reads a mailbox, places an order or processes documents for a person, it processes that person's digital personal data. The organisation that decides why and how is usually the Data Fiduciary. The agent is software, not a Data Processor: a Data Processor is a person that processes data on a fiduciary's behalf (s.2(k)). This page maps Grantex features to DPDP provisions. It is not legal advice and not a certification. Grantex is a technical control that helps a Data Fiduciary evidence some obligations; it is not a registered Consent Manager, and it does not notify the Board or Data Principals. Consult qualified counsel about your own obligations. Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership or IP questions may be sent to [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). ## When the obligations apply The Rules bring the Act into force in three stages (Rules r.1). As of 30 September 2026 only the first has started. | Date | What comes into force | | - | - | | **13 November 2025** | The Board and institutional provisions: Act ss.1(2), 2, 18-26, 35, 38-43, 44(1) and 44(3); Rules 1, 2 and 17-21 | | **13 November 2026** | Registration of Consent Managers: Rule 4 and the First Schedule; Act ss.6(9) and 27(1)(d) | | **13 May 2027** | The substantive obligations: notice, consent, security, breach intimation, erasure and retention, contact details, children, Significant Data Fiduciaries, Data Principal rights, cross-border transfer and penalties (Act ss.3-17 and 27-34, 36-37; Rules 3, 5-16, 22 and 23) | A proposal in January 2026 to shorten the transition was not adopted as of this writing. Check the current position before relying on these dates. ## Territorial scope (s.3) The Act applies to the processing of digital personal data within India, whether the data was collected in digital form or collected otherwise and digitised. It also applies to processing outside India if it is in connection with offering goods or services to Data Principals within India. It does not apply to processing by an individual for a personal or domestic purpose, or to personal data the Data Principal made publicly available, or that another person made publicly available under a legal obligation. Whether it applies to your deployment depends on these facts, not on where your servers are. ## Section and rule mapping "Grantex provides" describes shipped behaviour of the auth service's `/v1/dpdp` routes and core grants. Everything else is yours. | Provision | Requirement (summary) | What Grantex provides | What remains your responsibility | | - | - | - | - | | **s.4** Grounds | Process only for a lawful purpose, with consent or for a legitimate use (s.7) | Consent records store the purposes agreed; grants bound agent authority by scope and can carry a [purpose](/concepts/purpose-bound-grants) | Choosing the lawful basis and purposes; Grantex does not check that agent actions stay within the consented purposes | | **s.5 + r.3** Notice | A notice that is understandable on its own: itemised personal data, specific purposes and the goods or services they enable, how to withdraw consent, exercise rights and complain to the Board; English or an Eighth Schedule language | Versioned, per-language [consent notices](/api-reference/dpdp/consent-notice-content) with a content hash, structured r.3 fields and a `validation` block; `DPDP_NOTICE_REQUIRE_RULE3=true` refuses incomplete notices | Writing the notice, showing it before consent, and judging that it is clear and accurate (the validation checks presence only) | | **s.6(1)** Consent | Free, specific, informed, unconditional, unambiguous, by clear affirmative action, limited to necessary data | Consent records bound to an active grant and the notice version shown | Designing the consent step so that it meets these conditions | | **s.6(4)** Withdrawal | Withdraw at any time, as easily as consent was given | [`POST .../withdraw`](/api-reference/dpdp/withdraw-consent) with a required reason | The user-facing control that makes withdrawal as easy as consent | | **s.6(6)** Cease processing | After withdrawal the fiduciary and its processors cease processing within a reasonable time | Optional revocation of the record's grant (`revokeGrant`, or `DPDP_WITHDRAWAL_REVOKES_GRANT=true`; the full grant cascade with `DPDP_REVOCATION_CASCADE=true`), and a `dpdp.data_deletion.requested` webhook | Stopping processing in your systems and your processors' | | **s.6(10)** Proof of consent | The fiduciary bears the burden of proving notice and consent | A signed consent proof (compact JWS, EdDSA) binding the record, grant, principal, notice version and hash, and purposes; records are retained after withdrawal and erasure | Keeping the key (`ED25519_PRIVATE_KEY`) stable and the database retained | | **s.7** Legitimate uses | Grounds other than consent, (a) to (i) | Nothing specific | Deciding and documenting any legitimate use | | **s.8(5) + r.6** Security safeguards | Reasonable safeguards including encryption or masking, access control, logs and monitoring, backups, security terms in processor contracts; keep logs and personal data for **one year** to detect and remediate unauthorised access (r.6(1)(e)) | A hash-chained audit log that Grantex never rewrites or deletes; scoped, revocable grants | Your security programme, processor contracts, and database retention of at least one year | | **s.8(6) + r.7** Breach intimation | Inform each affected Data Principal and the Board **without delay**; send the Board a detailed report **within 72 hours** of becoming aware (extendable on written request); no risk threshold | A [breach register](/api-reference/dpdp/record-breach) with `boardDetailedReportDueAt`, overdue flags, principal intimation records and deadline webhooks | Sending the intimations and the report; Grantex files nothing. CERT-In reporting (6 hours) is separate | | **s.8(7), (8) + r.8** Erasure and retention | Erase when consent is withdrawn or the purpose is no longer served, and have processors erase. The three-year inactivity erasure and **48-hour** advance notice (r.8(1)-(2)) apply only to Third Schedule platforms (large e-commerce, online gaming and social media). r.8(3): keep personal data, traffic data and processing logs for **at least one year** from processing | [Erasure](/api-reference/dpdp/request-erasure) revokes grants, marks records erased and reports what is retained and why; with `DPDP_ERASURE_EXPANDED=true` it also redacts grievances and deletes stored exports | Erasing your own and your processors' data; inactivity tracking and the 48-hour notice where they apply | | **s.8(9) + r.9** Contact details | Publish the contact of a DPO or a person able to answer questions, and include it in every response to a rights request | A `contact` field on notices | Publishing it and including it in responses | | **s.8(10), s.13 + r.14(3)** Grievance redressal | An effective grievance mechanism; publish a response period of **no more than 90 days**; the principal must use it before approaching the Board | [Grievances](/api-reference/dpdp/file-grievance) with a reference, `responsePeriodDays` (1 to 90), `expectedResolutionBy`, and a `submitted` / `in_review` / `resolved` / `rejected` workflow | Publishing the period, handling and answering grievances within it | | **s.9 + r.10-12** Children | Under 18; verifiable parental consent; no tracking, behavioural monitoring or targeted advertising directed at children; Fourth Schedule exemptions | Nothing specific | Age and parental consent checks, and restricting agent behaviour for children | | **s.10 + r.13** Significant Data Fiduciaries | DPO in India, independent data auditor, DPIA and audit every 12 months; localisation only where the Government specifies (r.13(4)) | Exports and the audit log as inputs to an audit | The designation-driven duties | | **s.11 + r.14** Access | A summary of the personal data and processing, and the fiduciaries and processors it was shared with | [Principal records](/api-reference/dpdp/list-principal-records) and principal-filtered exports of what Grantex holds | Assembling the full summary, including data in your systems | | **s.12** Correction and erasure | Correct, complete, update and erase on request, unless retention is required by law | Erasure as above | Correction and completion in your systems | | **s.14** Nominee | Nominate a person to exercise rights on death or incapacity | Nothing specific | Handling nominations | | **s.16 + r.15** Cross-border transfer | The Government may restrict transfer to countries it notifies (a negative list), and may set requirements by order; not a general localisation or adequacy regime | Self-hosting lets you choose where Grantex data sits (see [Data Residency](/compliance/data-residency)) | Checking transfers against any notified restriction | | **ss.6(7)-(9) + r.4** Consent Managers | A Board-registered, interoperable platform through which principals give, manage and withdraw consent | Not provided (see below) | Integrating with a registered Consent Manager if you use one | | **s.33 + Schedule** Penalties | Up to Rs 250 crore (security safeguards), Rs 200 crore (breach intimation; children), Rs 150 crore (SDF duties), Rs 50 crore (other provisions); Rs 10,000 for Data Principal duties | Not applicable | Not enforceable before 13 May 2027 | ## Consent Managers, and why Grantex is not one A Consent Manager is a person registered with the Board through which Data Principals give, manage, review and withdraw consent (ss.6(7)-(9)). Under Rule 4 and the First Schedule, registration opens on 13 November 2026 and requires, among other things, an Indian company with a net worth of at least Rs 2 crore, an interoperable platform independently certified, data that the Consent Manager itself cannot read, a seven-year machine-readable record of consents, and no sub-contracting of its obligations. Grantex is not registered as a Consent Manager and is not built as one: it acts for the developer (the Data Fiduciary), not for the Data Principal, and it keeps consent records the developer can read. Use it to keep the fiduciary's own evidence. ## Retention floors and erasure Two retention floors from 13 May 2027 shape what erasure can remove: * **r.6(1)(e)**: logs and personal data kept for one year to detect and remediate unauthorised access. * **r.8(3)**: personal data, associated traffic data and processing logs kept for at least one year from the date of processing, for the purposes in the Seventh Schedule, even after an erasure request. Grantex's erasure therefore marks consent records `erased` and keeps them, and never modifies audit entries; its response lists what was retained and why. Deleting retained rows after the floor passes is your database retention decision. ## Examples Record consent against an active grant: ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const record = await grantex.dpdp.createConsentRecord({ grantId: 'grnt_01HXYZ...', dataPrincipalId: 'user_abc123', purposes: [{ code: 'order_placement', description: 'Place orders you approve' }], consentNoticeId: 'shopping-assistant', processingExpiresAt: '2027-12-31T23:59:59Z', }); ``` Produce a DPDP audit export for a period: ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/exports \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "dpdp-audit", "dateFrom": "2027-05-13T00:00:00Z", "dateTo": "2027-06-30T23:59:59Z" }' ``` The export's `data` holds `exportType`, `dateRange`, `generatedAt`, `developerId`, `consentRecords`, `auditLog` (at most 1,000 entries; see `truncated`) and `grievances`. It is not a format prescribed by the Board. See [DPDP Compliance](/features/dpdp-compliance) for every feature. ## FAQ ### Do the DPDP obligations apply to us today? As of 30 September 2026 the notice, consent, security, breach, erasure, rights and penalty provisions are not yet in force; they apply from 13 May 2027. Building the controls now gives time to test them. ### Does Grantex handle grievances for us? No. Grantex records grievances, the response period you publish and the due date, and a review workflow. Receiving, answering and meeting the period is your process, run by your DPO or designated person. ### Does withdrawal revoke the agent's access immediately? Only if you ask: pass `revokeGrant: true`, or set `DPDP_WITHDRAWAL_REVOKES_GRANT=true`. The record's own grant is revoked; grants delegated from it are revoked too only with `DPDP_REVOCATION_CASCADE=true`. The revocation is visible to services that check revocation state. Processing in your own systems must also stop. ### Is Grantex a Consent Manager? No. See [above](#consent-managers-and-why-grantex-is-not-one). ### How does this relate to GDPR? The same records support a GDPR Art. 15 access export (`gdpr-article-15` with `dataPrincipalId`). GDPR has its own rules, for example a one-month response time for access requests (Art. 12(3)) and breach notification to the supervisory authority within 72 hours unless the breach is unlikely to result in a risk (Art. 33). ### What are the penalties? The Schedule to the Act sets maximum penalties per breach of a duty, up to Rs 250 crore for failing to take reasonable security safeguards. They are not a percentage of turnover, and they cannot be imposed for conduct before the relevant provisions commence. ## Sources * [Digital Personal Data Protection Act, 2023](https://www.meity.gov.in/static/uploads/2024/06/2bf1f0e9f04e6fb4f8fef35e82c42aa5.pdf) * [Digital Personal Data Protection Rules, 2025 (G.S.R. 846(E))](https://www.meity.gov.in/static/uploads/2025/11/53450e6e5dc0bfa85ebd78686cadad39.pdf) ## Related Resources * [DPDP Compliance Module](/features/dpdp-compliance) — what each feature does * [EU AI Act](/compliance/eu-ai-act) — EU AI Act mapping * [Compliance Evidence Pack API](/api-reference/compliance/generate-compliance-evidence-pack) — audit-chain evidence * [Compliance Matrix](/guides/compliance-matrix) — cross-framework mapping * [Blog: DPDP Act and AI Agents](/blog/dpdp-act-ai-agents) # EU AI Act Source: https://docs.grantex.dev/compliance/eu-ai-act The EU AI Act timeline as amended by Regulation (EU) 2026/1744, provider and deployer obligations, and how Grantex records can help evidence them, with their limits. ## Overview The **EU AI Act** is Regulation (EU) 2024/1689. It entered into force on 1 August 2024 and applies in stages. It was amended by the AI Omnibus, **Regulation (EU) 2026/1744**, which entered into force on 27 July 2026 and moved the high-risk dates. The dates below are those of the amended text, as of 30 September 2026. Most of the Act's detailed duties attach to **high-risk AI systems** and fall on their **providers** (who develop a system and place it on the market or put it into service under their name) and **deployers** (who use it under their authority). Grantex is an authorisation layer: it records which agent was allowed to do what, on whose authority, and what happened. Those records can help evidence some of these duties. They do not make a system compliant. This page maps Grantex features to EU AI Act provisions. It is not legal advice, not a conformity assessment and not a certification: only the conformity assessment procedures of the Act (and CE marking where they require it) establish conformity. Whether a duty applies depends on your role and on how your AI system is classified. Consult qualified counsel. Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership or IP questions may be sent to [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). ## Timeline | Date | What applies | | - | - | | **1 August 2024** | The Regulation enters into force | | **2 February 2025** | Art. 4 AI literacy and the Art. 5 prohibited practices | | **2 August 2025** | Obligations for general-purpose AI (GPAI) models (Arts. 53-55), governance, and penalties (except Art. 101) | | **2 August 2026** | General application, including the **Art. 50** transparency obligations; Commission enforcement of GPAI obligations and Art. 101 fines | | **2 December 2026** | End of the Art. 50(2) marking grace period for generative systems placed on the market before 2 August 2026; the new Art. 5 prohibition of AI systems that generate non-consensual intimate imagery or child sexual abuse material applies | | **2 August 2027** | GPAI models placed on the market before 2 August 2025 must comply | | **2 December 2027** | High-risk obligations for **Annex III** (stand-alone) systems (previously 2 August 2026) | | **2 August 2028** | High-risk obligations for **Annex I** (product-embedded) systems (previously 2 August 2027) | The separate "Digital Omnibus" on data protection (COM(2025) 837) is still a proposal; the GDPR is unchanged. ## Roles and classification * A team that builds an agent on a third-party model is usually the **provider** of an AI system, a **deployer** of it, or both. It is not a GPAI model provider unless it places a general-purpose model on the market. * An authorisation layer such as Grantex is not itself a high-risk AI system. An agent is high-risk if it falls under Art. 6: a safety component of an Annex I product, or a use listed in Annex III (for example credit scoring, recruitment or access to essential services). * Art. 6(3) lets a provider conclude that an Annex III system is not high-risk when it does not pose a significant risk, but never when it profiles natural persons; the provider documents that assessment and registers the system (Art. 6(4)). The Commission's classification guidelines were published as a draft on 19 May 2026. ## Obligations and what Grantex provides | Article | Who, from when | Requirement (summary) | What Grantex provides | What remains yours | | - | - | - | - | - | | **Art. 4** AI literacy | Providers and deployers, 2 Feb 2025 | As amended, take measures to support the development of AI literacy of staff and others operating AI systems on their behalf | Nothing specific | Training and literacy measures | | **Art. 5** Prohibited practices | All operators, 2 Feb 2025; the new NCII and CSAM prohibition from 2 Dec 2026 | Do not place on the market or use the listed practices | Nothing specific | Not building or using them | | **Art. 9** Risk management | High-risk providers | A documented, lifecycle risk management system | Controls you can cite as risk measures: scoped, time-limited, revocable grants; delegation that can only narrow authority; budgets and caps; policies | The risk management system itself | | **Art. 10** Data governance | High-risk providers | Training, validation and test data governance (bias-related special-category processing now in Art. 4a) | Nothing specific | Your data governance | | **Art. 11 + Annex IV** Technical documentation | High-risk providers | Technical documentation, kept for 10 years (Art. 18) | Exports can be annexed as records | Writing and keeping the documentation | | **Art. 12** Record-keeping | High-risk systems (provider designs, deployer keeps) | Automatic recording of events over the system's lifetime | The developer's hash-chained audit log of authorisation, delegation, revocation and DPDP events; the [evidence export](/api-reference/dpdp/eu-ai-act-evidence) reports its integrity and time span | Logging inside the AI system itself (inputs, outputs, model events); Grantex records authorisation events only | | **Art. 13** Transparency to deployers | High-risk providers | Instructions for use | Nothing specific | Writing the instructions | | **Art. 14** Human oversight | High-risk systems | People able to understand, not over-rely on, interpret, override, intervene in or stop the system | Human consent before a grant; [decision grants](/concepts/decision-grants) for approval of individual actions; payment approvals; revocation with cascade; the operator override (`POST /v1/emergency-stop`, when enabled); the evidence export counts each | Assigning competent people and designing the oversight | | **Art. 15** Accuracy, robustness, cybersecurity | High-risk providers | Appropriate levels throughout the lifecycle | Token binding, audience and revocation checks contribute to cybersecurity | The system's accuracy and robustness | | **Art. 17** Quality management | High-risk providers | A documented quality management system | Configuration as code (Terraform provider), versioned policies, and exports as records | The QMS | | **Art. 19(1)** Logs kept by providers | High-risk providers | Keep automatically generated logs under their control for **at least six months** | Grantex never deletes or rewrites audit entries; retention is your database's | Keeping logs at least that long (a minimum, not a maximum) | | **Art. 26** Deployers | High-risk deployers | Use per instructions; human oversight; monitor and inform the provider; keep logs **at least six months** (26(6)); inform workers; inform affected persons of Annex III decisions (26(11)) | Per-agent grants and scopes in the evidence export (`art26Deployer`); event streams and webhooks for monitoring | The deployer duties themselves | | **Art. 27** Fundamental rights impact assessment | Only public bodies, private providers of public services, and deployers of Annex III 5(b) credit scoring and 5(c) life and health insurance | A FRIA before first use | Nothing specific | The FRIA, where it applies | | **Art. 50** Transparency | Providers and deployers of certain AI systems, 2 Aug 2026 | Tell people they are interacting with an AI system unless obvious (50(1)); mark generated content in a machine-readable way (50(2)); disclose emotion recognition and biometric categorisation (50(3)); disclose deepfakes and AI-generated public-interest text (50(4)); at the latest at first interaction (50(5)) | **Not recorded.** The evidence export states `art50Transparency.recorded: false` | The disclosures and their evidence, from your own systems. The Code of Practice on marking and labelling AI-generated content (10 June 2026) is voluntary | | **Art. 72** Post-market monitoring | High-risk providers | A post-market monitoring system and plan | Audit and event data as inputs | The system and plan | | **Art. 73** Serious incidents | High-risk providers (deployers inform them) | Report not later than **15 days** after awareness; **2 days** for a widespread infringement or critical infrastructure; **10 days** in the event of a death; an incomplete initial report is allowed | The DPDP breach register lists personal data breaches; the evidence export includes them under `art73Incidents` | Deciding whether an incident is serious and reporting it; Grantex classifies and reports nothing | GPAI model obligations are in Arts. 53-55, not Art. 50. ## The EU AI Act evidence export `POST /v1/dpdp/exports` with `"type": "eu-ai-act-evidence"` returns sections mapped to Arts. 12, 14, 26, 50 and 73, each naming its data source, with an `applicability` block of the dates above and a disclaimer that it is evidence for the operator's own assessment, not a conformity assessment: ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/exports \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "eu-ai-act-evidence", "dateFrom": "2026-08-01T00:00:00Z", "dateTo": "2026-08-31T23:59:59Z" }' ``` See [EU AI Act Evidence Pack](/api-reference/dpdp/eu-ai-act-evidence) for every field. The older `eu-ai-act-conformance` type is kept for compatibility; despite its name, it is not a conformity assessment. ## Examples Register an agent with narrow scopes and ask a person for a grant (the grant bounds what the agent may do, and the person's approval is recorded): ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const agent = await grantex.agents.register({ name: 'shopper-01', description: 'Places orders the user approves on merchant.example', scopes: ['catalog:read', 'orders:create'], }); const request = await grantex.authorize({ agentId: agent.id, userId: 'user_abc123', scopes: ['catalog:read', 'orders:create'], expiresIn: '24h', }); // Send the person to request.consentUrl to approve or deny. ``` Stop an agent's authority, including every grant delegated from it (Art. 14 intervention), and collect audit-chain evidence for a period: ```typescript theme={null} await grantex.grants.revoke('grnt_01HXYZ...'); const pack = await grantex.compliance.evidencePack({ since: '2026-08-01T00:00:00Z', until: '2026-08-31T23:59:59Z', }); console.log(pack.chainIntegrity.valid); ``` ## Penalties (Art. 99) * Prohibited practices (Art. 5): up to EUR 35 million or 7% of worldwide annual turnover, whichever is higher. * Most operator obligations, including those of providers (Art. 16), deployers (Art. 26) and Art. 50: up to EUR 15 million or 3%. * Supplying incorrect or misleading information to authorities: up to EUR 7.5 million or 1%. * For SMEs, including start-ups, and small mid-caps, each cap is the **lower** of the two amounts. * GPAI model providers (Art. 101): up to EUR 15 million or 3%, enforceable by the Commission from 2 August 2026. ## Cross-framework mapping These are technical mappings, not a statement that any framework is met. | Grantex feature | EU AI Act | DPDP Act 2023 | OWASP ASI | | - | - | - | - | | Scoped, time-limited grants | Art. 9 (a risk control) | s.4 (purposes recorded; scopes bound authority) | ASI-01 (goal hijacking) | | Per-agent DID | Art. 12 (attribution in logs) | — | ASI-03 (identity abuse) | | Delegation that can only narrow | Art. 9 (a risk control) | — | ASI-05 (privilege escalation) | | Revocation with cascade | Art. 14 (intervene or stop) | s.6(6) (cease processing after withdrawal) | ASI-10 (rogue agents) | | Human consent and decision grants | Art. 14 (human oversight) | s.6 (consent) | — | | Hash-chained audit log | Art. 12 and Arts. 19(1), 26(6) (logs) | r.6(1)(e), r.8(3) (one-year logs) | — | | Breach register | Art. 73 (input to the incident decision) | s.8(6), r.7 (breach intimation) | — | ## Sources * [European Commission: AI Act regulatory framework](https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai) * [European Commission: the AI Omnibus enters into force](https://digital-strategy.ec.europa.eu/en/news/ai-omnibus-enters-force) * [AI Act Service Desk: article texts](https://ai-act-service-desk.ec.europa.eu/en/ai-act) (some article pages may still show the text before Regulation (EU) 2026/1744) ## Related Resources * [DPDP Act 2023](/compliance/dpdp-act-2023) — India's data protection mapping * [DPDP Compliance Module](/features/dpdp-compliance) — features and examples * [EU AI Act Evidence Pack](/api-reference/dpdp/eu-ai-act-evidence) — export reference * [Compliance Evidence Pack API](/api-reference/compliance/generate-compliance-evidence-pack) — audit-chain evidence * [Compliance Matrix](/guides/compliance-matrix) — cross-framework mapping * [OWASP Agentic Top 10 Blog](/blog/owasp-agentic-top-10-compliance) — threat taxonomy mapping # Privacy policy Source: https://docs.grantex.dev/compliance/privacy-policy # Privacy Policy (Draft) **Status:** Draft. Subject to legal review. **Not a legally executed privacy notice and not legal advice.** **Last reviewed:** 2026-05-24 **Applies to:** the hosted Grantex services at `grantex.dev`, `api.grantex.dev`, and `docs.grantex.dev`. This draft is published so customers and end users can review Grantex's privacy practices before a counsel-reviewed version is countersigned and published. ## 1. Who we are Grantex is an open protocol and a hosted developer service owned and operated by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. For privacy questions or to exercise the rights below, contact `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`; ownership or IP questions may also be sent to `sanjeev@orchestrum.in` or `mishra.sanjeev@gmail.com`. ## 2. What we collect and why ### As a developer using the hosted service * **Account data** — name, email, organization, password hash, hashed API key. Used to authenticate you and to operate the service. * **Usage and billing data** — request counts, error counts, plan tier, Stripe customer ID if billing is enabled. Used for billing and capacity planning. * **Operational logs** — request ID, timestamp, source IP, route, status code, latency. Used for security monitoring and debugging. Retained per the schedule below. ### As an end user (data principal) whose agent uses Grantex * **Principal identifier** — the opaque identifier your developer assigned you (typically pseudonymous). * **Authorization events** — what scopes you granted, to which agent, when, and when you revoked them. This is the core audit trail Grantex exists to provide. * **Optional WebAuthn credential references** — only if your developer enabled FIDO/WebAuthn. * **DPDP consent records, grievance references, erasure request IDs** — only if your developer is using the DPDP compliance features. We do **not** ask you for payment-card data, government identifiers, biometrics, location, or contacts. ## 3. Legal bases (GDPR-equivalent) * **Contract** — to deliver the service the developer agreed to. * **Legitimate interest** — to keep the service secure (rate limiting, anomaly detection, audit). * **Consent** — for any optional cookies that go beyond strictly necessary (see [Cookie Policy](./cookie-policy.md)). * **Legal obligation** — to respond to lawful requests from regulators. ## 4. How long we keep it | Category | Retention | | - | - | | Account data | For the life of the account; deleted within 30 days of account closure. | | Authorization audit trail | Operator-configurable; default 365 days on the hosted service. | | Operational logs | 30 days. | | Backups | 30 days, rolling. | | DPDP / GDPR erasure records | Indefinitely — required as proof the erasure occurred. The record keeps the data principal identifier and counts, not the erased content. | ## 5. Sub-processors and international transfers We use a small number of third-party services to operate the hosted offering. The full list and locations are in [sub-processors.md](./sub-processors.md). Hosted processing occurs in the US (`us-central1`); see [data-residency.md](./data-residency.md). For EEA / UK / Swiss transfers we rely on Standard Contractual Clauses; see [dpa.md](./dpa.md). ## 6. Your rights Depending on where you live, you have some or all of the following rights: * **Access** — request a copy of the data we hold about you. * **Rectification** — correct inaccurate data. * **Erasure** — ask us to delete your data. For agent-authorization records this is implemented programmatically via `POST /v1/dpdp/data-principals/:principalId/erasure` (see `apps/auth-service/src/routes/dpdp.ts`); your developer can also trigger this on your behalf. Records that must be kept as evidence, such as consent records marked erased and the tamper-evident audit log, are retained and not deleted; the erasure response lists them. * **Portability** — receive your data in JSON. * **Restriction** and **objection** to certain processing. * **Withdrawal of consent** at any time, with no effect on lawfulness of earlier processing. * **Lodge a complaint** with your local supervisory authority (e.g., EU DPA, the UK ICO, the Indian DPB). Exercise any of these by emailing `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`. We aim to respond within 30 days. ## 7. Children Grantex is a developer infrastructure service. We do not knowingly collect personal data from children under 13 (or the equivalent age of digital consent in your jurisdiction). If you believe we have, please contact us and we will delete it. ## 8. Cookies and tracking The marketing site (`grantex.dev`) uses Google Analytics for aggregate traffic statistics. The developer portal and API use only strictly necessary cookies (session, CSRF). Full details in the [Cookie Policy](./cookie-policy.md). ## 9. Security How we protect data is described in `SECURITY.md` and the [DPA template](./dpa.md), Section 7. To report a vulnerability, see `SECURITY.md`. ## 10. Changes Material changes to this policy will be announced via the public CHANGELOG and the developer portal at least 30 days before they take effect. The `Last reviewed` date at the top of this file is the authoritative version marker. ## 11. Contact Privacy and security: `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com` Ownership and IP: `sanjeev@orchestrum.in`, `mishra.sanjeev@gmail.com` # Sub processors Source: https://docs.grantex.dev/compliance/sub-processors # Sub-processor Disclosure **Status:** Current public disclosure. Subject to legal review. Not a certification document. **Last reviewed:** 2026-05-24 This list enumerates the third-party services Orchestrum Technologies LLP uses to operate the public hosted Grantex endpoints (`api.grantex.dev`, `grantex.dev`, `docs.grantex.dev`) and the SaaS tooling that supports the project. Grantex is owned by Orchestrum Technologies LLP; inventor and owner: Sanjeev Kumar. **Self-hosted Grantex deployments do not transit any of these sub-processors unless the operator opts in** — the open-source release runs entirely on infrastructure of the operator's choosing. Every entry below is derived from artifacts inside this repository (deploy scripts, Helm charts, Docker Compose files, GitHub workflows, package dependencies). Items the repository does not evidence are marked **TBD**. ## Production runtime sub-processors (hosted Grantex) | Sub-processor | Service | Purpose | Data categories | Location | Evidence | | - | - | - | - | - | - | | Google LLC | Google Cloud Run | Stateless auth-service compute | All API request/response payloads | `us-central1` (US) | `deploy/gcp/setup.sh` (`run.googleapis.com`, `CR_SERVICE="grantex-auth"`) | | Google LLC | Google Cloud SQL for PostgreSQL 16 | Primary datastore — agents, grants, audit, commerce state | All persisted application data | `us-central1` (US) | `deploy/gcp/setup.sh` (`sqladmin.googleapis.com`, `SQL_INSTANCE="grantex-pg16"`) | | Google LLC | Google Memorystore for Redis | Rate-limit counters, idempotency keys, short-lived caches | Request metadata; no PII at rest beyond TTL | `us-central1` (US) | `deploy/gcp/setup.sh` (`redis.googleapis.com`, `REDIS_INSTANCE="grantex-redis"`) | | Google LLC | Google Artifact Registry | Container image hosting for auth-service builds | Build artifacts, no customer data | `us-central1` (US) | `deploy/gcp/setup.sh` (`artifactregistry.googleapis.com`, `AR_REPO="grantex-images"`) | | Google LLC | Google Secret Manager | Runtime secrets for `api.grantex.dev` | Vault encryption key, signing keys, third-party API keys | `us-central1` (US) | `deploy/gcp/setup.sh` (`secretmanager.googleapis.com`) | | Google LLC | Google Cloud DNS | DNS hosting for `grantex.dev` zone | DNS records only | Global (Google anycast) | `deploy/gcp/dns.sh`, `deploy/gcp/setup.sh` (`dns.googleapis.com`) | | Google LLC | Firebase Hosting | Static marketing site (`grantex.dev`, `web/`) | No PII; CDN access logs only | Google CDN, global | `firebase.json` | | GitHub, Inc. (Microsoft) | GitHub | Source control, issue tracking, GitHub Actions CI/CD, GitHub Security Advisories | Code, build artifacts, vulnerability reports | US (GitHub-managed) | `.github/workflows/*.yml`, project hosted at `github.com/mishrasanjeev/grantex` | ## Optional sub-processors (only when feature-flagged on) The following providers are integrated in code but **disabled by default**; operators must explicitly enable them with the listed environment variables. | Sub-processor | Service | Purpose | Enabled by | Evidence | | - | - | - | - | - | | Stripe, Inc. | Stripe | Subscription billing for paid plans | `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `STRIPE_PRICE_*` | `docker-compose.yml:59-63`, `apps/auth-service/src/config.ts` | | Anthropic, PBC | Datadog (when configured as anomaly destination) | Anomaly alerting and metric forwarding | `ANOMALY_DESTINATIONS` env containing `datadog` (operator-supplied API key) | README anomaly section; `packages/destinations` Datadog adapter | | Amazon Web Services, Inc. | AWS S3 | Audit log export destination (immutable archive) | `AUDIT_DESTINATIONS` containing `s3` | `packages/destinations/package-lock.json` (`@aws-sdk/client-s3`) | | Google LLC | Google BigQuery | Audit log export destination (analytics) | `AUDIT_DESTINATIONS` containing `bigquery` | `packages/destinations/package-lock.json` (`@google-cloud/bigquery`) | | Apache Kafka operators | Kafka (operator-managed) | Audit/event streaming | `AUDIT_DESTINATIONS` containing `kafka` | `packages/destinations/package-lock.json` (`kafkajs`) | | Cloud Native Computing Foundation | OpenTelemetry collector (operator-managed sink) | Traces/metrics export | `OTEL_EXPORTER_OTLP_ENDPOINT` | `apps/auth-service/src/config.ts:51`, `deploy/helm/grantex/values.yaml:114-119` | ## Notably *not* in use today * **No transactional email provider configured.** TBD if SendGrid / Postmark / AWS SES is wired in for verification emails before public signup launch. * **No SMS provider configured.** * **No CRM, analytics, or session-replay tooling** (e.g., Segment, Mixpanel, FullStory) is referenced in the runtime code paths. * **No third-party customer-support tool** is integrated into the hosted product. * **No live payment processor.** Plural has a gated sandbox hosted-checkout adapter, but live Plural processing is fail-closed in code (`COMMERCE_LIVE_MODE_ENABLED` / `PLURAL_LIVE_ENABLED` default to off — see `apps/auth-service/src/lib/commerce/live-mode-guard.ts`). ## Change process Adding a new sub-processor requires: 1. A PR that updates this file with the row, evidence path, and data categories. 2. A corresponding entry in the GitHub release notes for the version that introduces the dependency. 3. Existing customers receive at least 30 days' notice via the public CHANGELOG and a banner in the developer portal before the new processor begins receiving production data. ## Contact Procurement and DPA questions: `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com` (see `SECURITY.md` for the canonical contact). Ownership and IP: `sanjeev@orchestrum.in`, `mishra.sanjeev@gmail.com`. # Terms of service Source: https://docs.grantex.dev/compliance/terms-of-service # Terms of Service (Draft) **Status:** Draft. Subject to legal review. **Not a legally executed agreement and not legal advice.** **Last reviewed:** 2026-05-24 **Applies to:** the hosted Grantex services at `grantex.dev` and `api.grantex.dev`. Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. The open-source Grantex software is licensed separately under the Apache License 2.0 (see `LICENSE`); these Terms govern only your use of the hosted service operated by Orchestrum Technologies LLP. ## 1. Acceptance By creating an account, calling the hosted API with credentials issued by the service, or otherwise using the hosted service, you agree to these Terms. If you are agreeing on behalf of an organization, you represent that you have authority to bind that organization. ## 2. The service Grantex provides a delegated authorization service for AI agents: agent registration, scoped authorization, grant-token issuance and verification, audit logging, and the supporting consent UI. Detailed behavior is described in the protocol specification (`SPEC.md`) and the public API reference (`docs.grantex.dev/api-reference`). ## 3. Your account * You must keep your API keys, signing keys, and admin credentials confidential. * You are responsible for activity that occurs under your account, including activity by any agent or end user you have authorized. * You must not use the service to violate any applicable law or to enable a third party to do so. ## 4. Acceptable use You may not, and may not permit your agents or end users to: * Reverse engineer, decompile, or attempt to derive the signing keys of the hosted service. * Use the service to facilitate fraud, theft, harassment, or other unlawful activity. * Probe, scan, or stress-test the service except via published load-test endpoints with prior coordination. * Send personal data the service is not designed to handle (see [Privacy Policy](./privacy-policy.md), Section 2 — no payment-card data, no special-category personal data). * Circumvent rate limits, billing meters, or the central live-mode guard. We may suspend or terminate access for material breach, with notice where reasonable. ## 5. Fees If your plan is paid, fees are described in your order form or at `grantex.dev/pricing` (pricing page **TBD**). Fees are billed through Stripe (see [Sub-processors](./sub-processors.md)). Unpaid invoices may result in service suspension after notice. ## 6. Beta and early-access features Features labelled "beta", "preview", "closed beta", or "experimental" are provided **as-is** with no service-level commitments. Today this includes Commerce V1 (`README.md` section "Agentic Commerce V1 (closed beta)"), the Trust Registry, and the SOC 2 readiness mapping. ## 7. Service level Target availability for the production hosted service is **99.5%** monthly, measured at the auth-service `/health` endpoint, excluding scheduled maintenance announced at least 48 hours in advance. A formal SLA with credits is **TBD**. ## 8. Data ownership and processing You retain ownership of all data you submit. The service processes that data only as described in the [Privacy Policy](./privacy-policy.md) and the [DPA template](./dpa.md). For self-hosted Grantex you control all data; these Terms do not apply. ## 9. Confidentiality Each party will protect the other's non-public information with at least the same degree of care it uses for its own (and no less than a reasonable degree of care). Public artifacts — the spec, the open-source code, documentation, and published metrics — are not confidential. ## 10. Intellectual property * The open-source software is yours to use under the Apache 2.0 license. * The "Grantex" name, logo, hosted-service branding, and product ownership remain with Orchestrum Technologies LLP. Sanjeev Kumar is listed as inventor and owner. * Feedback you provide may be used to improve the service without obligation, but Grantex will not publicly attribute negative feedback to you without consent. ## 11. Warranties and disclaimers The hosted service is provided **as-is** and **as-available**, without warranties of any kind, express or implied, including merchantability, fitness for a particular purpose, and non-infringement. Some jurisdictions do not allow the exclusion of implied warranties; in those jurisdictions the exclusions apply to the maximum extent permitted by law. ## 12. Limitation of liability To the maximum extent permitted by law, neither party will be liable to the other for indirect, incidental, special, consequential, or punitive damages, or for loss of profits, revenue, goodwill, or data, arising out of these Terms. Each party's total liability is capped at the fees you paid in the 12 months preceding the event giving rise to the claim. These limits do not apply to either party's indemnity obligations or to your payment obligations. ## 13. Termination Either party may terminate for material breach uncured 30 days after written notice. Sections 8 (Data), 11 (Disclaimers), 12 (Liability), 14 (Governing law) survive termination. On termination, the data return-or-delete process in the [DPA](./dpa.md), Section 10 applies. ## 14. Governing law and disputes The governing law and venue are those specified in your executed order form. The template default is Delaware, United States. Either party may seek injunctive relief in any court of competent jurisdiction. ## 15. Changes We may revise these Terms; material changes will be announced at least 30 days in advance via the public CHANGELOG and the developer portal. Continued use after the effective date constitutes acceptance. ## 16. Contact Security and service contact: `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com` Ownership and IP: `sanjeev@orchestrum.in`, `mishra.sanjeev@gmail.com` # Caps and Metering Source: https://docs.grantex.dev/concepts/caps-and-metering Per-tool call caps over rolling windows and per case, cost-unit budgets, and a meter that cannot be raced past a cap. ## What caps do A grant says which tools an agent may call. Caps say **how often**. They bound the damage of a looping agent, protect a paid provider account and make spend predictable. Two places declare caps. **The manifest** declares tenant-wide caps per tool, and the cost of a call in cost units: ```json theme={null} { "connector": "acme_kyb", "tools": { "resolve_business": {"permission": "read", "caps": {"per_hour": 200}}, "verify_business": {"permission": "read", "caps": {"per_hour": 50, "per_case": 3}, "cost_units": {"base": 5, "ownership": 10, "web_insights": 3}}, "screen_person": {"permission": "read", "caps": {"per_hour": 0}} } } ``` **The grant** declares caps for that grant only, in its `urn:grantex:tools:v1` authorization details: per tool, and a cost-unit budget for the connector under the reserved key `cost_units`: ```json theme={null} { "type": "urn:grantex:tools:v1", "connector": "acme_kyb", "purpose": "aml.cdd.onboarding", "caps": { "verify_business": {"per_hour": 50, "per_case": 3}, "cost_units": {"per_day": 5000} } } ``` | Window | Meaning | | - | - | | `per_hour` | Rolling hour: a call counts until exactly 3,600 seconds after it was reserved | | `per_day` | Rolling 24 hours | | `per_case` | All calls made for one case, with no time limit | Every declared cap is a separate counter, and a call must fit all of them. Manifest caps are shared by every grant in the tenant; grant caps count only that grant's calls. A cap of `0` disables the tool. ## Metering a call Configure a meter on the client. `enforce()` then reserves the call's units as its **last** step, after the token, scope, purpose and decision checks have passed, so a denied call never uses up a cap. ```python theme={null} import redis from grantex import Grantex from grantex.caps import CapsMeter, RedisCapsBackend meter = CapsMeter(RedisCapsBackend(redis.Redis.from_url("redis://localhost:6379/0"))) grantex = Grantex(api_key=api_key, caps_meter=meter) result = grantex.enforce( grant_token=token, connector="acme_kyb", tool="verify_business", case_id="case_0001", # required when a per_case cap applies cost_components=["base", "ownership"], # default: every unit the tool declares ) if not result.allowed: raise PermissionError(result.reason) ``` ```typescript theme={null} import { Redis } from 'ioredis'; import { Grantex, CapsMeter, RedisCapsBackend, ioredisRunner } from '@grantex/sdk'; const meter = new CapsMeter(new RedisCapsBackend(ioredisRunner(new Redis('redis://localhost:6379/0')))); const grantex = new Grantex({ apiKey, capsMeter: meter }); const result = await grantex.enforce({ grantToken: token, connector: 'acme_kyb', tool: 'verify_business', caseId: 'case_0001', costComponents: ['base', 'ownership'], }); ``` The tenant is the grant's developer (`dev` claim) unless you pass `caps_tenant_id` / `capsTenantId`, which applies to every counter of that call. A call's cost is the sum of the manifest `cost_units` for the components it incurs. Metering attaches to the call that incurs the cost, in the code that makes that call, and never to parsing its result afterwards. ### Case and cost components come from the gateway `case_id` and `cost_components` decide which counters a call is charged to, so the tool gateway sets them from its own context: the case being worked, and the provider request it is about to send. Never take them from the agent's or model's tool arguments, or an agent could name a fresh case to escape a per-case cap or claim a cheaper component. The same applies to `wrap_tool(case_id=..., cost_components=...)` / `wrapTool({ caseId, costComponents })` and to `enforceMiddleware({ extractCaseId, extractCostComponents })`, which should read trusted request context. An empty `cost_components` list for a tool that declares cost units is denied (`invalid_cost_component`); omit the argument to charge every declared unit. ### Check early, reserve once An agent platform often checks a call twice, once when the plan is validated and again at the tool gateway. Only the second check should consume a unit: ```python theme={null} # while validating the plan: decision only, nothing consumed check = grantex.enforce(grant_token=token, connector="acme_kyb", tool="verify_business", case_id=case_id, reserve=False) # at the gateway, immediately before the provider call result = grantex.enforce(grant_token=token, connector="acme_kyb", tool="verify_business", case_id=case_id, cost_components=["base"]) ``` `reserve=False` (`reserve: false`) compares current usage with the caps and returns `cap_limits` / `capLimits` and `caps_tenant_id` / `capsTenantId`. The check is point in time: another call can take the last unit before you reserve, so the reserving `enforce()` (or `meter.reserve(result.caps_tenant_id, result.cap_limits)`) is the decision that counts. ### Rolling caps out: `caps_mode` | Mode | Behaviour | | - | - | | `enforce` (default) | Calls a cap would deny are denied | | `warn` | Calls a cap, a missing meter or an unavailable backend would deny are **allowed**; `result.would_deny` / `wouldDeny` carries the `reason_code`, `sub_reason`, `reason` and `details` they would have got. Calls that fit are reserved as in `enforce`; calls over a cap reserve nothing, so counters show what enforcement would have allowed. From version TypeScript 0.8.0 / Python 0.7.0, a call with no amount under a `capped:N` scope is also allowed and reported here (`amount_missing`, or `malformed_cap` when the cap cannot be read) | | `off` | Caps are not evaluated and no meter is needed. From version TypeScript 0.8.0 / Python 0.7.0, a missing amount under a `capped:N` scope is not evaluated either | Set it on the client (`Grantex(caps_mode="warn")`, `new Grantex({ capsMode: 'warn' })`) or per call. Malformed grant caps are a token problem and are denied in every mode. Log `would_deny_all` / `wouldDenyAll` while in `warn`, review it, then switch to `enforce`. `would_deny` / `wouldDeny` is the first denial warn mode let through on the call. When several steps would deny the same call (for example a missing decision grant under `decisions_mode="warn"`, then a missing amount, then an exhausted call cap), `would_deny_all` / `wouldDenyAll` lists every one of them, in the order the steps run: decision, amount cap, call caps, decision consumption. Read the full list: a later warning never replaces an earlier one in `would_deny`, so reading only that field hides it. The client's separate `enforce_mode="permissive"` (development only) turns **every** denial into an allow, including `cap_exceeded` and `meter_unavailable`; the result keeps its `reason_code`, but nothing is reserved for such a call. ### When a cap is exceeded `enforce()` denies with `reason_code` `cap_exceeded`, `sub_reason` `limit_reached` and details that include error code **E1008**, the limit and the window: ```python theme={null} result.details == { "code": "E1008", "limit": 50, "window": "per_hour", "used": 50, "requested": 1, "scope": "manifest", "kind": "calls", } ``` Other `cap_exceeded` sub-reasons: | `sub_reason` | Cause | | - | - | | `case_required` | A `per_case` cap applies and no `case_id` was passed | | `invalid_case_id` | `case_id` is empty or longer than 256 characters | | `invalid_cost_component` | `cost_components` names a unit the tool does not declare, is empty for a tool that declares units, or the call's cost exceeds 2147483647 | | `meter_unavailable` | No meter is configured, or its backend failed | Malformed grant caps deny as `token_invalid` / `malformed_authorization_details`. ### Amount caps A scope such as `tool:merchant:write:*:capped:50` caps the amount of every call on that connector (the tightest `capped:N` on the connector wins), whatever permission the scope names: with `tool:merchant:read:*` and `tool:merchant:write:*:capped:50`, a read tool such as `get_order` is capped too. The call's amount is passed as `amount`; the SDK does not read it from the tool's arguments on its own. | `sub_reason` | Cause | Denied in `warn` / `off` | | - | - | - | | `amount_cap` | `amount` is above the cap; `details` carries `limit` and `amount` | Yes | | `invalid_amount` | `amount` is not a finite number, or a wrapper's amount extractor raised | Yes | | `malformed_cap` | A `capped:N` scope on the connector cannot be read | Yes when an amount is given. From version TypeScript 0.8.0 / Python 0.7.0, a call with no amount is denied too, and `warn` allows it and reports it in `would_deny_all` / `wouldDenyAll`; `off` skips it (the current release allows it) | | `amount_missing` (from version TypeScript 0.8.0 / Python 0.7.0) | A `capped:N` scope covers the connector and the call gave no amount; `details` carries `limit` | No: `warn` allows the call and reports it in `would_deny_all` / `wouldDenyAll`; `off` skips it | **From version TypeScript 0.8.0 / Python 0.7.0 (breaking):** a call with no amount under a `capped:N` scope is denied with `cap_exceeded` / `amount_missing`, and a malformed cap is denied whether or not an amount is given. In the current release such a call is allowed and the cap is never checked. Because the cap is connector-wide, this includes read-only tools with no monetary amount on a connector that carries a capped scope: give them an amount (an extractor may return `0`). To keep the old behaviour while you add amounts, set `caps_mode="warn"` / `capsMode: 'warn'` and log `would_deny_all` / `wouldDenyAll`; it covers both `amount_missing` and a malformed cap on a call without an amount, including calls that also report a decision warning. From version TypeScript 0.8.0 / Python 0.7.0, the wrappers take an amount extractor, a function from the call to its amount: ```python theme={null} protected = grantex.wrap_tool( place_order_tool, connector="merchant", tool_name="place_order", grant_token=lambda: state["grant_token"], extract_amount=lambda arguments: arguments["total"], # the tool's keyword arguments ) ``` ```typescript theme={null} const protectedTool = grantex.wrapTool(placeOrderTool, { connector: 'merchant', tool: 'place_order', grantToken: () => state.grantToken, extractAmount: (input) => (input as { total: number }).total, // the tool's input }); app.use('/api/tools/:connector/:tool', grantex.enforceMiddleware({ extractToken: (req) => (req.headers as Record).authorization?.replace('Bearer ', ''), extractConnector: (req) => (req.params as Record).connector!, extractTool: (req) => (req.params as Record).tool!, extractAmount: (req) => (req.body as { total: number }).total, // the request })); ``` Without an extractor, or when it returns `None` / `undefined` / `null`, a capped scope denies the call with `amount_missing`. An extractor that raises refuses the call before `enforce()` runs, whatever the grant says: `wrap_tool` raises `PermissionError`, `wrapTool` throws, and `enforceMiddleware` answers 403 with `cap_exceeded` / `invalid_amount`. A value that is not a finite number (a string, a boolean, `NaN`) is passed on and denied with `invalid_amount`. Read the amount the tool will actually spend, from the same arguments the tool receives, so the cap bounds what the call does. ### Failed calls are not refunded The reservation is made before the provider call and stays counted if the call then fails or times out. A timeout does not prove the provider did no work or did not bill for it, and refunding on error would let a flaky or hostile upstream reset the cap. Refund only when you know the request never left your process, for example when it failed validation locally or a connection could not be opened: ```python theme={null} try: response = provider.verify_business(request) except ConnectionNotOpened: meter.refund_unsent(result.reservation) raise ``` ## Backends | Backend | Use | How it stays atomic | | - | - | - | | `RedisCapsBackend` | Preferred | One Lua script checks and records every counter of a call | | `PostgresCapsBackend` | Deployments without Redis | One transaction upserts and locks each counter row (in a fixed order), then sums and inserts | | `InMemoryCapsBackend` | **Tests only** | A process-local lock; not shared between workers | Both SDKs derive the same counter keys and run the same Lua script and SQL, so Python and TypeScript workers can share one Redis or Postgres. Fifty parallel calls against a cap of ten reserve exactly ten, on both backends, in CI. **Redis** (6.0 or later; the scripts use `SET ... KEEPTTL`). Keys look like `grantex:caps:{}::z|s`. Everything a reservation touches shares one hash tag, so it works on Redis Cluster. Time comes from the Redis server. Per-hour and per-day keys expire one window plus 60 seconds after the **last** reservation on the counter (each reservation resets the TTL); entries older than the window are dropped whenever the counter is used. Per-case keys never expire unless you set `case_ttl_seconds` / `caseTtlSeconds`, because an expired per-case counter would reset the cap. Run Redis with `maxmemory-policy noeviction`: an evicted counter forgets reservations. **Postgres.** Create the tables with `grantex.caps.SCHEMA_SQL` or `CAPS_SCHEMA_SQL` in your migrations, or call `ensure_schema()` / `ensureSchema()`. The Python backend takes a factory for DB-API connections with `%s` placeholders, for example `pg8000`. The TypeScript backend takes a `pg` `Pool`. Reservations run in READ COMMITTED transactions and time comes from the database. Rows carry a hash of the tenant id rather than the id, so row-level security policies keyed on tenant ids do not apply to these tables. Expired rows are removed when their counter is next used; run `prune()` periodically (for example hourly, from a scheduled job) to delete expired reservations and empty counters from all tenants. Per-case reservations are kept unless the backend was given `case_ttl_seconds` / `caseTtlSeconds`. `prune()` skips counters that are being reserved; if a race makes it fail, run it again. ### No automatic failover The meter does not fall back from Redis to Postgres when Redis fails. Two stores would hold two sets of counters, and calls spread across them could exceed every cap. Choose one backend per deployment. If it is unavailable, `enforce()` denies with `meter_unavailable` until it recovers. ## Remaining budget `meter.usage(tenant_id, limits)` returns what each counter holds and what remains. Build the limits for a tool with `grantex.caps.build_cap_limits` (`buildCapLimits`), or use `result.cap_limits` from `enforce()`. ## Not yet covered This is the metering library. Showing caps and remaining budget on the consent page and in a per-case view, and a per-tenant `caps.enforce` rollout flag in the platform that drives `caps_mode`, are separate work. # Decision Grants Source: https://docs.grantex.dev/concepts/decision-grants Require a named person's approval, bound to one exact action and made outside the platform's reach, before an agent can decide, file, close or pay. ## Why a second credential A grant lets an agent call tools. Some tools *decide*: approve a customer, decline an application, delete a monitoring enrolment, release a payout. For those, "the agent had permission" is not an acceptable answer. A prompt that says "never approve without a human" is not a control, and neither is a button in a console that an API call can bypass. A **decision grant** is a token that only a named, step-up authenticated person can cause to be minted, bound to one exact action on one case, and usable once. The platform that runs the agent cannot mint one: it has no API for approving, for signing an approver in, or for choosing which identity provider approvers use. ## Declaring that a tool needs a decision In the tool manifest (schema 0.6): ```json theme={null} { "connector": "acme_kyb", "tools": { "monitor_delete": {"permission": "delete", "requires_decision": true}, "case_decision": {"permission": "write", "requires_decision": true, "four_eyes_on": ["decline"]}, "payout_release": {"permission": "write", "requires_decision": true, "decision_fields": ["currency"]} } } ``` `requires_decision` is not allowed on a `read` tool. `four_eyes_on` lists the decisions that need two approvers. `decision_fields` lists arguments, beyond the core action, that the decision must also bind. A grant can also require decisions: a tool listed in the grant token's `urn:grantex:decision:v1` entry needs a decision grant even when the manifest does not declare `requires_decision`, and a decision in either `four_eyes_on` list needs two approvers. ## What a decision grant approves Not the bytes of a tool call, but its **semantic action**: ```json theme={null} {"case_id": "case_8841", "action": "case_decision", "decision": "approve", "subject": "gb:00000001"} ``` plus `amount` when there is one and the declared `decision_fields` (for example `"extra": {"currency": "GBP"}`). The token carries the action and its RFC 8785 hash. When the agent calls the tool, the enforcer derives the action from the call's arguments and compares hashes. The agent may re-plan its payload, add a timestamp or reorder fields and the decision still holds; it may not change the case, the decision, the subject, the amount or a declared field. Anything else the call means is **not** approved. If a tool's effect depends on an argument that is not bound, declare it in `decision_fields`. ## Who can approve, and how 1. **The service administrator** allow-lists, per developer, the OpenID Connect identity providers whose users may approve. The developer API key cannot do this. 2. **The platform** creates a decision request with its API key: the action, the connector, the case version, the memo text and the policy score the approver must review, the manifest's `four_eyes_on`, and the agent the decision is for (`agentId`, its Grantex agent id; `grantId`, its grant). The response contains `approvalPage`, a link to the auth service. 3. **The approver** opens that page in their browser and signs in with an allow-listed identity provider, with a strong method (for example a passkey). The auth service runs the sign-in itself (authorization code with PKCE, state and nonce) and checks step-up from `acr` / `amr` and `auth_time`. The session lives in an HttpOnly cookie on the auth service and lasts for a configured window (one hour by default); no API ever returns it. 4. **One click per decision.** The page shows the memo, the policy score and the exact action. The approver clicks approve; the auth service measures how long the decision was on screen, refuses approvals faster than a minimum, and mints the decision grant. 5. **The agent's grant token fetches the minted grant.** The platform fetches the grants with the grant token of the agent the request names (`getGrants` / `get_grants`) and passes them with that agent's tool call. `enforce()` (or an MCP server) verifies them and **consumes** them at the auth service with that agent's grant token; only then does the call proceed. With the agent binding on (`DECISION_GRANT_AGENT_BINDING=true`, see [Binding decisions to the requesting agent](#binding-decisions-to-the-requesting-agent)), this is the only way to get them: the developer API key alone never receives a decision grant, `GET /v1/decisions/requests/{id}` reports `decisionGrantsReady` and the approvals but not the tokens, and another agent presenting them is refused (`wrong_agent`). 6. **Everything is in the audit chain**: who configured the identity provider, each sign-in, the approval (approver, identity provider, authentication method, dwell time, action, memo and policy score hashes), each release of the grants to an agent, consumption, and every refusal. Strong authentication happens once per session and each decision is a deliberate, recorded click: a passkey prompt for every action produces exactly the rubber-stamping this feature exists to prevent. Asking for a four-eyes decline from a platform: ```ts theme={null} import type { Grantex } from '@grantex/sdk'; /** Asks for a four-eyes decline for one agent and returns the page to send approvers to. */ export async function requestDecline( grantex: Grantex, caseId: string, caseVersion: string, memo: string, policyScore: Record, agent: { id: string; grantId: string }, ): Promise { const request = await grantex.decisions.createRequest({ action: { case_id: caseId, action: 'case_decision', decision: 'decline', subject: 'gb:00000001' }, connector: 'acme_kyb', caseVersion, fourEyesOn: ['decline'], // the manifest's four_eyes_on for this tool memo: { content: memo }, // shown to the approver and bound into the grant by hash policyScore: { content: policyScore }, agentId: agent.id, // the Grantex agent id: only its grant token fetches the grants, grantId: agent.grantId, // and they are consumed only for this agent and grant }); // Approvers sign in on this page and approve there; the platform cannot approve. return String(request['approvalPage']); } ``` Fetching the grants once approved, with the agent's grant token: ```ts theme={null} import type { Grantex } from '@grantex/sdk'; /** The decision grants of a request made for an agent, fetched with that agent's grant token. */ export async function grantsForAgent(grantex: Grantex, requestId: string, agentGrantToken: string): Promise { // With the binding on, the API key alone never receives them: getRequest // reports decisionGrantsReady and no decisionGrants. const released = await grantex.decisions.getGrants(requestId, agentGrantToken); // Present once fully approved, and only while unspent, unrevoked and unexpired. const grants = released['decisionGrants']; return Array.isArray(grants) ? grants.map(String) : []; } ``` A decision the platform takes for itself, naming no agent, is never released to an agent: the platform consumes it by request id (`grantex.decisions.consumeRequest(requestId, { action, caseVersion })`, or `consume_request` in Python; `POST /v1/decisions/requests/{id}/consume`), with the same checks as any consumption, and the grant never leaves the auth service. A request that names an agent cannot be consumed that way. Fetching with the agent's grant token and consuming by request id work whether or not the agent binding is on, so a platform can move to them first and turn the binding on afterwards. ## The four-eyes model For a decision listed in `four_eyes_on`, two different people must approve. * The first approval mints a grant marked `four_eyes: {approvals_required: 2, position: 1}`. Presented alone it is refused (`four_eyes_incomplete`). * The second approval is minted only after the first, by a **different approver**, and names the first (`first_jti`, `first_sub`). Approvers are identified by identity provider and subject; the auth service also refuses a second approver with the same verified email (`same_approver`). * The call presents both grants. Both are consumed together, or neither is. * Four eyes is enforced when approving, in offline verification (a grant that says two approvals are needed is never accepted alone, whatever the manifest says) and when consuming. Four eyes compares identities. It cannot tell that two accounts without a shared verified email belong to one person; configure identity providers to require verified emails where that matters. ## Validity A decision grant is valid until the first of: * it is **consumed**: one grant authorises one call; * the **case changes**: the platform registers a new case version (for example a hash of the evidence behind the decision) and unconsumed grants for the old version are revoked; * **24 hours** pass, or its request expires; * the request is **cancelled**. Consumption spends the grant even if the response is lost or the tool call fails afterwards; a person then has to approve again. Make the tool call idempotent per decision request. ## Enforcing Python: ```python theme={null} from typing import Any, Dict, List from grantex import Grantex def call_case_decision( grantex: Grantex, grant_token: str, decision_grants: List[str], tool_call_arguments: Dict[str, Any], current_case_version: str, ) -> None: """Calls a tool that needs a decision, with the decision grants a person approved.""" result = grantex.enforce( grant_token, "acme_kyb", "case_decision", decision_grants=decision_grants, # two for a decision listed in four_eyes_on arguments=tool_call_arguments, # the approved action is derived from these case_version=current_case_version, # from your own case state, never from the agent ) if not result.allowed: # reason_code is decision_required or decision_invalid; sub_reason says why raise PermissionError(f"{result.reason_code}/{result.sub_reason}: {result.reason}") # The grants are now spent: result.decision.jtis ``` TypeScript: ```ts theme={null} import type { Grantex } from '@grantex/sdk'; /** Calls a tool that needs a decision, with the decision grants a person approved. */ export async function callCaseDecision( grantex: Grantex, grantToken: string, decisionGrants: string[], toolCallArguments: Record, currentCaseVersion: string, ): Promise { const result = await grantex.enforce({ grantToken, connector: 'acme_kyb', tool: 'case_decision', decisionGrants, // two for a decision listed in four_eyes_on arguments: toolCallArguments, // the approved action is derived from these caseVersion: currentCaseVersion, // from your own case state, never from the agent }); if (!result.allowed) { // reasonCode is decision_required or decision_invalid; subReason says why throw new Error(`${result.reasonCode ?? 'denied'}/${result.subReason ?? ''}: ${result.reason}`); } // The grants are now spent: result.decision?.jtis } ``` `wrap_tool` / `wrapTool`, `enforceMiddleware` and the FastAPI `GrantexEnforcer` accept the decision grants and case version too (the FastAPI dependency reads grants from the `grantex-decision-grant` header and needs a `case_version` callback that reads your own case state). MCP servers use `grantexDecisionVerifier` from `@grantex/mcp-auth` (see [mcp-auth](/mcp-auth)); clients send grants in the `grantex-decision-grant` header and receive a `decision_required` challenge without one. ### Refusals | Result | Sub-reason | Meaning | | - | - | - | | `decision_required` | | No decision grant was presented. | | `decision_invalid` | `action_mismatch` | Approves another action, field value or connector. | | | `wrong_case` | Approves an action on another case. | | | `wrong_agent` | With the agent binding on: requested for another agent or grant, or consumed without that agent's live grant token. `enforce()` sends the caller's grant token, from which the auth service establishes the agent and grant. | | | `case_changed` | The case changed since the approval. | | | `expired` | Past its expiry. | | | `consumed` | Already used. | | | `same_approver` | Four eyes with one approver, or one grant twice. | | | `four_eyes_incomplete` | Needs a second approval. | | | `revoked`, `unknown_grant`, `malformed` | Cancelled, unknown to the issuer, or unreadable. | | | `consume_unavailable` | The auth service could not confirm consumption. The call is refused. | ### Rolling out: `decisions.required` `enforce()` denies by default (`decisions_mode="enforce"`). A platform rolling decision grants out per tenant maps its `decisions.required` flag to the mode: on means `enforce`; off means `warn`. **`warn` is not a control**: it does not deny a call to a decision tool that lacks a valid decision grant; it lets the call through and reports the denial that would have happened in `result.would_deny` (with any caps warn-mode warnings on the same call listed after it in `result.would_deny_all` / `wouldDenyAll`), so you can measure the effect before turning enforcement on. Valid grants presented in warn mode are still consumed. ## Binding decisions to the requesting agent A decision grant is a bearer credential: whoever holds it can present it. With `DECISION_GRANT_AGENT_BINDING=true` on the auth service, a decision an agent asked for belongs to that agent: * `GET /v1/decisions/requests/{id}` never returns the grants; it answers `decisionGrantsReady` and the approvals by `jti`. * `POST /v1/decisions/requests/{id}/grants` (`getGrants` / `get_grants`) releases them only against a live grant token of the agent and grant the request names. Every hand-out and every refusal is recorded in the audit chain; if the record cannot be written, nothing is released. * `POST /v1/decisions/consume` spends a request's grants only when a live grant token of that agent and grant accompanies them (`grantToken`, as for `getGrants`). The auth service verifies the token itself (signature, expiry, revocation, grant status, developer) and takes the agent and grant from it, never from the body: `agentDid`, `agentId` and `grantId`, when sent, must be that token's. A missing token, one that is not live, or another agent's or grant's, is refused (`wrong_agent`, 403), and the refusal is recorded with why (`token_check`) and what the body claimed. A request that names no agent is consumed as before, without a token; a token sent with it must still be live, and its agent is recorded. * Asking again for the same action and case version while a request is open for another agent or grant is refused (`wrong_agent`, 409) instead of answering with that request. The setting is off by default. Off, request creation, `GET` and consumption answer as they did before the binding existed: the developer API key reads the grants once approved, and consumption records the agent and grant without comparing them. Only `true` and `false` are accepted; any other value makes the decision endpoints answer 503. `enforce()` in both SDKs, and `grantexDecisionVerifier` given the SDK's `consume`, send the grant token they verified as `grantToken`, with its DID as `agentDid` (never as `agentId`) and its grant as `grantId`. An auth service from before the binding accepts only a Grantex agent id in `agentId` and ignores members it does not know, and one with the binding off reads neither `grantToken` nor `agentDid`, so the SDKs consume against every version. An SDK from before this change sends no grant token: against a service with the binding on, it cannot consume a decision that names an agent, and reports the refusal as `consume_unavailable`, which still denies the call. Before turning the binding on, move every platform that reads `decisionGrants` from `GET /v1/decisions/requests/{id}`: 1. A decision the platform takes for itself (its request names no agent): consume it by request id, `POST /v1/decisions/requests/{id}/consume` with the action and case version, in place of reading the grants and presenting them. Use `status` and the approvals (or `decisionGrantsReady` once the binding is on) to tell whether it is ready; a consumption that is not ready is refused with `unknown_grant` or `four_eyes_incomplete`. 2. A decision for an agent: name the agent (`agentId`, `grantId`) when asking for it, fetch the grants with that agent's grant token (`getGrants` / `get_grants`), and consume them with the same grant token (`grantToken`), as `enforce()` does. Naming the agent in the body is not enough. 3. Read `subReason` before the HTTP status when mapping refusals: `wrong_agent` arrives with 403, which is not an authentication failure. 4. Use an SDK that sends `grantToken` (this release or later) wherever `enforce()` or `grantexDecisionVerifier` consumes decision grants. Both endpoints in steps 1 and 2 work with the binding off, so each platform can move and be verified in production before the binding is turned on. What [AgenticOrg governed cases](/guides/agenticorg-governed-cases) must change is listed with that integration. ## Operating * The auth service's decision endpoints and pages are off until `DECISION_GRANTS_ENABLED=true`. `PUBLIC_BASE_URL` must be https and `VAULT_ENCRYPTION_KEY` set. Configure step-up with `DECISION_STEP_UP_AMR` / `DECISION_STEP_UP_ACR` and `DECISION_STEP_UP_MAX_AGE_SECONDS`, and the minimum dwell time with `DECISION_MIN_DWELL_MS`. Bind decisions to the requesting agent with `DECISION_GRANT_AGENT_BINDING=true` (default off; see above). * Register `https:///decisions/callback` as the redirect URI of each approver identity provider. * Metrics: `grantex_decision_grants_minted_total`, `grantex_decision_grants_consumed_total`, `grantex_decision_grants_rejected_total{stage,reason}` (stages `sign_in`, `request`, `approve`, `release`, `consume`, `case`) and the `grantex_decision_dwell_seconds` histogram. Alert when dwell time collapses towards the minimum. ## What this does and does not protect against Decision grants stop the platform, an agent, a prompt injection or a re-planned payload from producing or using an approval nobody gave; with the agent binding on, stop another agent, or the developer API key alone, from using a decision an agent asked for; stop replay (used, other case, changed case, expired, other tenant); stop an approval being submitted from another site or with a forged dwell time; bind what the approver was shown; and stop one identity from satisfying four eyes. They do not protect against a compromised service administrator or identity provider, one person holding two identities without a shared verified email, misleading memo content written by the platform, rubber-stamping (made visible, not impossible), script injection on the auth service's own origin, arguments the manifest leaves out of `decision_fields`, tools that act without calling `enforce()`, or a compromised auth service. When the auth service is unreachable, decisions cannot be consumed and calls are refused. The full profile, including every claim and the threat model, is the [decision grant specification](https://github.com/mishrasanjeev/grantex/blob/main/spec/decision-grant.md). # Multi-Agent Delegation Source: https://docs.grantex.dev/concepts/delegation How Grantex handles authorization chains across multi-agent pipelines. ## Overview Grantex supports multi-agent pipelines where a root agent spawns sub-agents with narrower scopes. Sub-agent tokens carry a full delegation chain that any service can inspect. ## How It Works A root agent that holds a grant for `['calendar:read', 'calendar:write', 'email:send']` can delegate a subset of those scopes to a sub-agent: ```typescript TypeScript theme={null} const delegated = await grantex.grants.delegate({ parentGrantToken: rootGrantToken, subAgentId: subAgent.id, scopes: ['calendar:read'], // must be a subset of parent scopes expiresIn: '1h', // capped at parent token's expiry }); console.log(delegated.grantToken); // new JWT for the sub-agent console.log(delegated.grantId); ``` ```python Python theme={null} delegated = client.grants.delegate( parent_grant_token=root_grant_token, sub_agent_id=sub_agent.id, scopes=["calendar:read"], expires_in="1h", ) print(delegated["grantToken"]) print(delegated["grantId"]) ``` ## Delegation Claims The delegated token includes additional JWT claims that trace the delegation chain: ```json theme={null} { "iss": "https://grantex.dev", "sub": "user_abc123", "client_id": "ag_SUB_AGENT", "scope": "calendar:read", "act": {"sub": "did:grantex:ag_ROOT_AGENT"}, "urn:grantex:grant": { "agent_did": "did:grantex:ag_SUB_AGENT", "parent_grant_id": "grnt_01ROOT...", "delegation_depth": 1 }, ... } ``` | Claim | Description | | - | - | | `act` | RFC 8693 actor chain. `act.sub` is the parent agent that created this delegation (not the current actor, which is the token's `client_id`, unlike the usual RFC 8693 reading); a deeper delegation nests the parent token's `act` inside, so the chain reads from the most recent delegator to the first | | `urn:grantex:grant.parent_grant_id` | Grant ID of the parent grant | | `urn:grantex:grant.delegation_depth` | Number of hops from the root grant (root = 0, first child = 1) | While legacy claims are issued (the 0.6 default), the token also carries `parentAgt`, `parentGrnt` and `delegationDepth` with the same values. Refreshing a delegated token keeps its `act` chain. ## Protocol Constraints The Grantex protocol enforces three constraints on delegation: ### 1. Scope subset Sub-agent scopes must be a strict subset of the parent's scopes. Scope escalation is rejected with a `400` error. ### 2. Expiry cap Sub-agent token expiry is `min(parent expiry, requested expiry)`. Sub-agents can never outlive their parent grant. ### 3. Cascade revocation Revoking a root grant **cascades to all descendant grants atomically**. There is no window during which a child grant remains valid after its parent has been revoked. This is implemented as a single recursive CTE in the database. ## Depth Limit The maximum delegation depth is **10 hops**, enforced at both the application level and the database level (via a `CHECK` constraint). This prevents unbounded delegation chains. ## Inspecting Delegation Chains When verifying a delegated token offline, the `VerifiedGrant` object includes the delegation metadata: ```typescript TypeScript theme={null} const grant = await verifyGrantToken(delegatedToken, { jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', }); if (grant.delegationDepth !== undefined) { console.log('This is a delegated token'); console.log('Parent agent:', grant.parentAgentDid); console.log('Parent grant:', grant.parentGrantId); console.log('Delegation depth:', grant.delegationDepth); } ``` ```python Python theme={null} grant = verify_grant_token(delegated_token, VerifyGrantTokenOptions( jwks_uri="https://api.grantex.dev/.well-known/jwks.json", )) if grant.delegation_depth is not None: print("This is a delegated token") print("Parent agent:", grant.parent_agent_did) print("Parent grant:", grant.parent_grant_id) print("Delegation depth:", grant.delegation_depth) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Event Bridge and Revocation Source: https://docs.grantex.dev/concepts/event-bridge-and-revocation Ingest signed provider events (SSF/CAEP Security Event Tokens or signed webhooks) and turn them into grant actions, never failing open. ## Why an event bridge A grant is issued against the facts at the time: the business was active, the case was open, the principal was employed. Facts change after issuance. A provider that monitors a business learns it was dissolved; an identity provider learns a session was revoked. The event bridge lets those systems tell the auth service, so the grants that relied on the old facts stop working. The bridge has three rules: 1. **Only verified events count.** A delivery that does not verify is refused with `401`, counted, and never acted on. 2. **An event is acted on at most once.** Every event id is claimed in a replay store before anything happens. 3. **Nothing is inferred.** A verified event that no mapping rule matches is logged, counted and ignored. It never widens or narrows a grant by default. The bridge is off unless `EVENT_BRIDGE_ENABLED=true`. With it off every event bridge route answers `404` (ingestion) or `403 FEATURE_DISABLED` (registration), and nothing else in the auth service changes. ## Registering a source Sources belong to one developer and are managed with the developer API key. ### SSF/CAEP transmitter A transmitter pushes Security Event Tokens (RFC 8417) to the receiver, as in RFC 8935 push delivery. Register its issuer and its public keys, inline or by URL: ```http theme={null} POST /v1/event-sources Authorization: Bearer Content-Type: application/json { "kind": "ssf", "name": "acme_kyb monitoring", "issuer": "https://transmitter.example.com", "jwksUri": "https://transmitter.example.com/jwks.json", "algorithms": ["ES256"], "maxAgeSeconds": 300 } ``` The response carries `ingestUrl` (`/v1/event-bridge/ssf/`). `audience` defaults to that URL; set it to what the transmitter puts in `aud` if it differs. `jwksUri` is fetched through the same outbound guard as webhook URLs (no private hosts in production), cached for five minutes and refetched at most every 30 seconds when a token names an unknown `kid`. Inline `jwks` must contain public keys only. A SET is accepted only when all of these hold, and each failure has its own reason code: | Check | Reason when it fails | | - | - | | Body is a compact JWS sent as `application/secevent+jwt` | `malformed`, `unsupported_media_type` (415) | | Header `typ` is `secevent+jwt` | `unsupported_typ` | | `alg` is on the source's allow list (`none` and HMAC never are) | `unsupported_alg` | | A transmitter key verifies the signature | `key_unavailable`, `signature_invalid` | | `iss` equals the registered issuer | `issuer_mismatch` | | `aud` includes the registered audience | `audience_mismatch` | | `iat` is present, not in the future, not older than `maxAgeSeconds` (60 s skew) | `iat_missing`, `iat_in_future`, `stale` | | `exp`, if present, has not passed | `expired` | | `jti` is present | `jti_missing` | | `events` names 1 to 20 events | `events_missing` | The subject is read from the SSF `sub_id` claim, or from an event's `subject` member for transmitters on earlier CAEP drafts. ### Generic signed webhook For systems that do not speak SSF, register a webhook source: ```http theme={null} POST /v1/event-sources Content-Type: application/json { "kind": "webhook", "name": "provider events", "toleranceSeconds": 300 } ``` The response includes `secret` **once**. It is stored encrypted with `VAULT_ENCRYPTION_KEY`, bound to the source id. The sender signs the timestamp and the exact body bytes: ```text theme={null} X-Grantex-Timestamp: X-Grantex-Signature: sha256=HEX(HMAC-SHA256(secret, ".")) ``` This is the scheme the auth service uses for its own outbound webhooks (`X-Grantex-Signature-V2`), so one signer serves both directions. The body is JSON: ```json theme={null} { "id": "evt_000001", "type": "business.dissolved", "occurred_at": "2026-09-15T12:00:00Z", "subject": { "business_ref": "gb:00000001" }, "data": { "status": "dissolved" } } ``` The signature is checked before the body is parsed; then the timestamp must be within `toleranceSeconds` of the receiver's clock, in either direction (`timestamp_out_of_window`). Several `sha256=` values may be sent, comma separated, while the sender changes secrets. **Rotation.** `POST /v1/event-sources//rotate-secret` returns a new secret. The previous one keeps verifying for `previousSecretTtlSeconds` (default one day, at most seven). Pass `0` for a leaked secret so it stops immediately. ## Mapping an event to an action A verified event does nothing until a rule says what it means. A rule is data: ```http theme={null} POST /v1/event-mapping-rules Authorization: Bearer Content-Type: application/json { "name": "dissolution revokes onboarding grants", "sourceId": null, "eventType": "business.*", "conditions": [{ "path": "data.status", "equals": "dissolved" }], "target": { "by": "subject_ref", "path": "subject.business_ref", "kind": "business_ref" }, "action": "revoke", "mode": "enforce" } ``` | Member | Meaning | | - | - | | `sourceId` | Only events from this source, or `null` for any source of this developer | | `eventType` | Exact type, or a prefix ending in `*` (`business.*` matches `business.dissolved`, not `businessx.dissolved`) | | `conditions` | Up to 10 tests over paths in the event; **all** must hold. Each is one of `equals` (a string, number, boolean or null — no type coercion), `in` (up to 50 values) or `exists` | | `target` | Which grants the event is about (below) | | `action` | `suspend`, `revoke` or `re_evaluate` | | `mode` | `enforce` acts; `observe` records what it would have done and changes nothing | | `status` | `active` or `disabled` | Paths start at `type`, `subject` or `data` and address members and array elements: `subject.business_ref`, `data.filing.parties.0`. Inherited JavaScript members are not event data and never match. ### Targets | `by` | Resolves to | | - | - | | `grant_id` | The grants with those ids | | `principal_id` | Every live grant of those principals | | `agent_id` | Every live grant of those agents | | `subject_ref` | Every grant bound to that `kind`/value pair (below) | The path may hold one identifier or a list of up to 50. Anything else — a number, an object, an absent path — records `target_invalid`: the rule acts on nothing. **Every resolution is scoped to the rule's developer.** A rule that names a grant, principal or agent of another developer resolves to nothing and records `no_target`; no grant of theirs is ever touched, and a rule may only name an event source of its own developer. ### Binding a subject to a grant Provider events talk about businesses and cases, not grant ids. Bind the identifiers a grant was issued for: ```http theme={null} PUT /v1/grants/grnt_01.../subject-refs { "refs": [{ "kind": "business_ref", "value": "gb:00000001" }, { "kind": "case_id", "value": "case_0001" }] } ``` `kind` is the developer's own vocabulary (lower case, up to 64 characters); values are opaque, up to 50 per grant. Bindings on a parent grant are enough: actions cascade to everything delegated beneath it. ## What the actions do ### `revoke` — cascade revocation The grant and **every grant delegated beneath it, to any depth** are revoked in one transaction: their status changes, wallet reservations are released, credentials issued for them are revoked, the developer's audit hash chain gets one entry per grant, and a `grant.revoked` event is emitted. The Redis revocation key is written after the commit as an accelerator; the database stays authoritative, so a cache outage cannot undo a committed revocation. Revocation is irreversible and reaches suspended grants too, so a suspended subtree can never be resumed under a revoked ancestor. Cascade revocation takes the same per-developer lock as delegation, so a child being delegated while its parent is revoked either loses the race (the parent is gone when it commits) or is included in the cascade. No active grant is ever left under a revoked one. ### `suspend` — reversible revocation The grant and its subtree move to `suspended`. Every authorisation check requires `status = 'active'`, so a suspended grant authorises nothing, and its tokens stop verifying. Undo it with: ```http theme={null} POST /v1/grants//resume ``` This restores exactly the grants that suspension suspended. It is refused with `409 ANCESTOR_INACTIVE` while any grant above the root is revoked or suspended, and it keeps working when the event bridge is turned off, so a suspension can always be undone. A grant is suspended once, under the first root that reached it. So suspending an ancestor of an already-suspended subtree reports **zero affected**: everything below it is already suspended, and each grant keeps the root it was suspended under, so resuming that original root still restores exactly what it suspended. Nothing is lost — the second call simply has nothing left to do — but do not read "0 affected" as "the suspension did not work". ### `re_evaluate` — hand the decision back Nothing about the grant changes. One audit entry per grant is written and a `grant.re_evaluation_requested` event is emitted to the developer's webhooks and event stream, carrying the grant ids, the event id and type, the rule id and the event's subject. The relying platform decides what to do. and a bounded copy of the event's subject (scalar members, short values, and `subject_truncated` when anything was dropped — the subject is provider-supplied). Revoking and suspending are idempotent, so a retried delivery costs nothing. Asking the platform to look again is not, so it is claimed per source, event and rule and happens at most once however often the delivery is retried. ## Audit records Every action writes an entry to the developer's audit hash chain: | Action | Audit action | Metadata | | - | - | - | | Revoke | `grantex.grant.revoked` | `grant_id`, `root_grant_id`, `depth`, `cascade`, `cause`, `trigger`, `reason?`, `event_id?`, `rule_id?`, `source_id?` | | Suspend | `grantex.grant.suspended` | as above | | — | — | A set carrying several events labels each action with the event that matched its rule, not the first one | | Resume | `grantex.grant.resumed` | `grant_id`, `root_grant_id` | | Re-evaluate | `grantex.grant.re_evaluation_requested` | `grant_id`, `event_id`, `rule_id`, `source_id` | `trigger` uses the evidence-package vocabulary (`api`, `event`, `admin`, `cascade`), so an evidence export names why a grant stopped. The `grantex.` prefix and the `grantex:platform` marker are reserved: `POST /v1/audit/log` refuses them, so a tenant cannot forge a revocation record. These entries are written whatever the plan's audit limit — a security record a full plan could suppress would be worthless. ## Delivery outcomes The receipt for each delivery records what the rules did, and the response carries the same status: | Status | Meaning | | - | - | | `unmapped` | No rule matched. Logged, counted, ignored | | `observed` | Only observe-mode rules matched. Nothing changed | | `applied` | At least one enforce-mode rule matched; the receipt records per rule whether it was `applied`, `no_target` or `target_invalid` | | `duplicate` | This event id was already processed | ## Replay protection Each verified delivery claims `(source, event id)` — the SET `jti` or the webhook `id` — before it is processed: | Situation | Response | Acted on? | | - | - | - | | First delivery | `202 {"status": …}` | Yes, once | | Same bytes again (a retransmission, or a replay inside the window) | `202 {"status": "duplicate"}` | No | | Same id, different payload | `401 {"err": "unverifiable"}` (reason `event_id_reused` in the log) | No | | Earlier delivery failed while processing | processed again | Yes (actions are idempotent) | A replay outside the webhook window, or of a SET older than `maxAgeSeconds`, is refused before it reaches the replay store. Receipts are pruned hourly, but only once they are older than the window in which their own source would still accept the delivery, and never sooner than `EVENT_BRIDGE_RECEIPT_RETENTION_HOURS`. Removing one earlier would make an old delivery acceptable again. That window is **twice** the tolerance, measured from when the delivery arrived. A webhook's timestamp check is two-sided, so a delivery may arrive timestamped up to `toleranceSeconds` in the *future* and stays acceptable until `received_at + 2 × toleranceSeconds`. A SET is bounded by `maxAgeSeconds` plus twice the 60 s clock skew, because its `iat` may also be ahead of the receiver's clock. Raising a source's tolerance widens this for future deliveries only. Receipts already pruned under the old, narrower window cannot come back, so for the length of the new window there are old deliveries that would verify again and have no receipt to refuse them. If you raise a tolerance materially, rotate the source's secret at the same time: that invalidates every old signature and closes the gap immediately. ## Responses * `202 {"status": "unmapped" | "applied" | "observed" | "duplicate"}` * `401 {"err": "unverifiable", "code": "EVENT_UNVERIFIABLE"}` for every verification failure: an unknown source, a disabled one, a source whose developer is outside `EVENT_BRIDGE_DEVELOPER_IDS`, a bad signature, a stale timestamp, a reused event id. One code for all of them, on purpose: a sender that could tell them apart could probe for source ids and for how far a guess had got. The precise reason is in the structured log (`alert: event_bridge_verification_failure`) and in the `reason` label of `grantex_event_bridge_verification_failures_total`. The **response body** is what is indistinguishable, not the work behind it. A real source id proceeds to secret decryption and HMAC comparison, so it takes measurably longer than an unknown one — around 1.4 ms in the reviewer's measurement, over 400 samples each. For an SSF source with a `jwksUri` the difference can be far larger, because a real id can trigger an outbound key fetch. Treat the endpoint as resistant to *reading* which ids exist, not to a patient attacker timing it * `415` for the wrong media type * `404` when the bridge is off entirely * `5xx` when processing failed; the receipt is left `failed` so the sender's retry is processed again ## How an agent finds out: the revocation feed `enforce()` verifies a grant token offline against the issuer's JWK Set. That is what makes it fast and what makes revocation invisible to it: a revoked grant's token stays cryptographically valid until it expires. Revoking a grant stops the auth service issuing anything new; it does not, on its own, stop an SDK that already holds a token. The revocation feed closes that gap. Three modes, chosen per client, and tightened (never loosened) per call: | `revocationCheck` / `revocation_check` | What `enforce()` does | Cost | | - | - | - | | `online` (default from the next SDK release) | asks the auth service about this grant (`GET /v1/revocations/status`) | one request per call | | `feed` | consults an in-memory set kept current by the feed | one long-lived connection per process | | `offline` (the opt-out; published versions up to `@grantex/sdk` 0.7.0 and `grantex` 0.6.0 have no revocation check at all and behave like this) | nothing: a revoked grant's token is accepted until it expires | none | From the next SDK release a client created without `revocationCheck` / `revocation_check` checks `online`, and denies with `grant_revoked` / `status_unavailable` when the auth service cannot answer, including when a deployment has turned the feed endpoints off. To keep the previous behaviour, pass `offline` explicitly: ```ts theme={null} const grantex = new Grantex({ apiKey, revocationCheck: 'offline' }); ``` ```python theme={null} grantex = Grantex(api_key=api_key, revocation_check="offline") ``` `online` costs one round trip to the auth service per `enforce()` call, and those calls draw on the developer's revocation-status budget: 6,000 a minute (100 a second) across all of the developer's instances, whatever the plan (see [Rate limits](#rate-limits)). The status endpoint's per-address limit is the same 6,000 a minute, so a server running many tools behind one egress address can use the whole budget; past it the SDK is answered `429`, retries, and then denies with `status_unavailable`. A client making more checked calls than that, or one on a hot path, should use `feed`, which costs one connection per process however many calls it makes. ### Per-call overrides only tighten The modes are ordered by how soon a revocation is seen: `offline` never, `feed` within its staleness bound, `online` on the next call (`offline` \< `feed` \< `online`; exported as `REVOCATION_CHECK_STRENGTH`). The per-call `revocationCheck` / `revocation_check` option of `enforce()` may choose the client's mode or a stricter one. A weaker one is refused before anything is checked: `enforce()` rejects (TypeScript) or raises `ValueError` (Python), so code that reaches `enforce()` cannot switch off what the deployment configured. A client configured `offline` can still ask for `feed` or `online` on a sensitive call. ```ts theme={null} const grantex = new Grantex({ apiKey, revocationCheck: 'feed', revocationFeed: { staleAfterMs: 5_000 }, }); ``` ```python theme={null} grantex = Grantex( api_key=api_key, revocation_check="feed", revocation_feed_stale_after=5.0, ) ``` A denial carries `grant_revoked` with a sub-reason: `revoked`, `suspended`, `parent_revoked`, `feed_stale`, `feed_unavailable` or `status_unavailable`. ### Failing closed The last three matter most. An agent whose feed has gone quiet does not know what has been revoked, so it stops authorising calls: * the feed records when it last heard from the auth service; * the stream sends a heartbeat every second, and only while the server has read the database successfully; * if the client has heard nothing for longer than its staleness bound (default 5 seconds), `enforce()` denies with `feed_stale`; * if the deployment does not serve the feed, or the feed is not ready, every call is denied with `feed_unavailable`; * in `online` mode, a check that cannot be completed denies with `status_unavailable`, and a grant the auth service does not recognise is refused rather than assumed live. This is the opposite of a cache: it is a claim about freshness that expires. ### The endpoints ```http theme={null} GET /v1/revocations # snapshot, plus the cursor to stream from GET /v1/revocations?since= # changes since a cursor (ETag, 304, optional wait=) GET /v1/revocations/stream?since= # Server-Sent Events: revocation, heartbeat GET /v1/revocations/status?grantId=&jti= # one credential, for online checks ``` A client starting cold reads the cursor and then pages the snapshot — every grant currently revoked or suspended and not yet expired, and every individually revoked token — then streams from the cursor. Because the cursor is read first, nothing that happens while it pages can fall between the two. Entries are `{seq, action, grantId, jti, expiresAt, at}` with `action` one of `revoked`, `suspended`, `resumed` or `token_revoked`. They are a set of identifiers, so a duplicate delivery changes nothing. **The cursor never advances past an entry that could still be overtaken.** A transaction that took its sequence number before another but committed after it would otherwise be skipped, so entries younger than the settle window (`REVOCATION_FEED_SETTLE_SECONDS`, default 15 s) are delivered but do not move the cursor. That costs a few repeated entries and removes a way to miss one. **And never past the page it returned.** A page is bounded by `limit` (1000 by default) while the settled maximum is not; a cursor taken from the larger number would skip everything in between while the client believed itself up to date. The cursor a response carries is therefore the lowest of the two — which matters exactly when there is a lot to deliver: a large cascade, an emergency stop, a sweep. Delivered entries are kept for `REVOCATION_FEED_RETENTION_HOURS` past the expiry of the credential they are about, and an hourly worker prunes the rest, so the table the snapshot reads does not grow without bound. The triggers fill the table even while the feed is off, so the first prune after it turns on can face a large backlog: the worker deletes in batches of 1000 rows, at most 50 batches or 60 seconds a run, and leaves the rest to the next run. Only one instance prunes at a time (it takes a Postgres advisory lock and the others skip that run), and each instance's first run waits a random delay of up to `REVOCATION_FEED_PRUNE_JITTER_SECONDS` (default 300), so the instances of one deploy do not start together. ### Where the entries come from Database triggers on `grants` and `grant_tokens`, not from each revocation path. Every way a grant stops — `DELETE /v1/grants/:id`, a cascade from a provider event, an emergency stop, a consent withdrawal, an anomaly, a DPDP erasure, OAuth revocation, and the hard delete behind `DELETE /v1/agents/:id` — writes a feed entry in the same transaction as the revocation itself. Four triggers cover it: two on status changes and two on deletion, since a deleted row can appear in no snapshot. `pg_notify` wakes the receivers on commit; each instance also polls (`REVOCATION_FEED_POLL_MS`, default 500 ms), so a lost notification costs latency and never correctness. If those triggers are missing (the migration could not take the lock at startup), the feed endpoints answer `503 FEED_UNAVAILABLE` rather than an empty feed, and clients fail closed. ### Measured `scripts/revocation-release-test.sh` starts the auth service against real Postgres and Redis, builds a delegation tree, revokes each parent and measures how long the child kept being authorised, through both SDKs. G-6 requires two seconds at the ninety-fifth percentile. Two things make that number mean something. The TypeScript measurement runs in a plain Node process loading the build from the checkout, and the Python one refuses to start unless `grantex` was imported from the checkout — an ambient install would otherwise "prove" the criterion against code nobody reviewed. And the clock starts when the revocation is committed (when the API call returns), not when the call was made: the revoke call is rate limited, and the SDK waiting out a `Retry-After` is not propagation. That wait is reported separately as `revoke_call_max_ms` — and it has a budget of its own (10 s, `REVOCATION_REVOKE_CALL_BUDGET_MS`), because a release that prints a minute-long wait on the containment path and passes anyway is not telling you the truth. Revoking used to share the plan's rate-limit bucket with ordinary traffic (FINDINGS G-23); it now has a containment bucket of its own (see [Rate limits](#rate-limits) below), so the wait no longer depends on the tenant's plan or its other traffic. Any figure quoted from a run is **environment-specific**. The numbers depend on the machine, the container runtime, whether Postgres and Redis are local, and what else is running: an independent reviewer measured p95 100–506 ms and max 515–673 ms where this checkout's machine measured tens of milliseconds. What the release test asserts is the requirement — p95 within two seconds, no failed trial — not a particular number. ## The emergency stop Cascade revocation is the documented emergency stop for the whole platform. One authenticated call halts every agent under a grant, an agent, a principal or a whole developer: ```http theme={null} POST /v1/emergency-stop Authorization: Bearer { "scope": { "type": "agent", "id": "ag_01..." }, "reason": "incident 4102: provider credentials leaked", "confirm": "stop agent:ag_01...", "dryRun": false, "lockout": false } ``` * `confirm` must be exactly `stop :`; anything else is refused with `412 CONFIRMATION_REQUIRED`, and the expected phrase is not echoed back. Nothing is revoked before that check passes. * `dryRun: true` reports how many grants the scope covers and revokes nothing. * A developer API key can only stop its own grants. The platform operator uses `POST /v1/admin/emergency-stop` with `ADMIN_API_KEY` and a `developerId`. * `GET /v1/emergency-stops` lists what has been stopped, when, by whom and why, and the lockouts still in force, a page at a time (`page`, `pageSize`) with `freezesTotal` giving how many there are in all. * Underneath it is an ordinary cascade revocation per matched grant, so the stop appears in the audit hash chain (one `grantex.grant.revoked` per grant plus one `grantex.emergency_stop` summary) and on the revocation feed, and agents following the feed are denied within seconds. * Off unless `EMERGENCY_STOP_ENABLED=true`. The revocations are irreversible: principals have to authorise again. * **A sweep, and a lockout only when asked for.** Without `lockout`, it revokes what exists, re-reading the scope until it comes back empty so a grant delegated mid-stop is caught, and then it is done: the same API key can mint a new grant immediately afterwards, and the response says `"lockout": false`. `status` is `completed`, `incomplete` (grants kept appearing) or `failed` (a batch did not finish — the record says what was revoked, and the call can be repeated). * **`lockout: true` freezes issuance under the scope** until the freeze is lifted. The freeze is recorded before the first sweep, in the same transaction as the stop's record. Every issuance path then refuses with `403 ISSUANCE_FROZEN` (`access_denied` on the OAuth endpoints): authorize, code exchange, refresh, delegation, the OAuth profile's pushed request and token endpoint, consent bundles and agent passports (`POST /v1/passport/issue`). Commerce passports and decision grants are not issued from grants and are outside any lockout; the runbook says how to contain commerce passports. A freeze covers what a stop over the same scope would revoke, so a refresh, delegation or passport is checked against every grant above it. If the freeze state cannot be read, issuance fails closed with `503 FREEZE_STATE_UNAVAILABLE`. The response says `"lockout": true` with a `freezeId`, and `grantex.issuance_frozen` goes on the audit chain. * `POST /v1/emergency-stop/unfreeze` lifts a lockout; `confirm` must be exactly `unfreeze :`. The operator lifts any lockout with `POST /v1/admin/emergency-stop/unfreeze`. A lockout the operator placed can only be lifted by the operator, because the tenant's key may be the leaked credential. Lifting it writes `grantex.issuance_unfrozen` on the audit chain. The runbook — rehearsing it, working out the blast radius, what to do when an agent keeps running, and what to do if the API itself is unreachable — is section 11 of `docs/self-hosting.md`. ## Rate limits Revoking and stopping are rate limited in a **containment** bucket of their own, and the feed in a **status** bucket of its own, rather than in the developer's plan budget. A tenant that has spent its plan quota on ordinary calls can still revoke, and its SDKs can still learn about revocations. | Bucket | Routes | Per developer, every plan | Redis unavailable | | - | - | - | - | | containment | `DELETE /v1/grants/:id`, `POST /v1/tokens/revoke`, `POST /v1/emergency-stop`, `POST /v1/passport/:id/revoke`, `POST /v1/consent-bundles/:id/revoke` | 2,000 a minute | served, counted per instance against the same ceiling | | status | `GET /v1/revocations`, `/v1/revocations/status`, `/v1/revocations/stream`, `GET /v1/consent-bundles/:id/revocation-status` | 6,000 a minute | `503 RATE_LIMIT_UNAVAILABLE`; clients fail closed | The per-address limits on each route still apply first (20 a minute for the emergency stop; 600, 6,000 and 120 for the feed, status and stream routes). The status route's per-address limit is the developer's status budget, because an SDK checking `online` calls it once per `enforce()`: a lower one would refuse a server running many tools behind one address before the developer reached its budget. The feed and stream are called once per SDK process (a long poll or a reconnect), so their per-address limits scale with processes, not calls, and stay lower. A revocation fails open when the limiter cannot count it because it is written to Postgres, which is authoritative, and refusing it would prolong an incident over an outage of a cache. `RATE_LIMIT_ROUTE_CLASSES_ENABLED=false` puts these routes back in the plan budget. See `docs/guides/rate-limits.mdx`. ## Observability | Metric | Labels | | - | - | | `grantex_event_bridge_events_received_total` | `source_type` | | `grantex_event_bridge_events_verified_total` | `source_type` | | `grantex_event_bridge_verification_failures_total` | `source_type`, `reason` | | `grantex_event_bridge_events_unmapped_total` | `source_type` | | `grantex_event_bridge_events_duplicate_total` | `source_type` | | `grantex_event_bridge_rule_matches_total` | `action`, `mode` | | `grantex_event_bridge_actions_total` | `action`, `outcome` (`applied`, `observed`, `no_target`, `target_invalid`) | | `grantex_grant_revocations_total` | `action` (`revoked`, `suspended`, `resumed`), `cause` (`api`, `event`, `emergency_stop`) | | `grantex_revocation_propagation_seconds` | `stage` (`event_to_commit`) | | `grantex_revocation_feed_delivery_seconds` | — (commit to delivery) | | `grantex_revocation_feed_entries_total` | `action` | | `grantex_revocation_feed_polls_total` | `outcome` | | `grantex_revocation_feed_subscribers` | — (live streams on this instance) | | `grantex_revocation_feed_stale_seconds` | — (since the last successful read) | | `grantex_revocation_feed_pruned_total` | — (entries deleted past retention) | | `grantex_revocation_feed_prune_runs_total` | `outcome` (`complete`, `capped`, `skipped_locked`, `failed`) | | `grantex_emergency_stops_total` | `scope`, `outcome` (`applied`, `dry_run`, `refused`) | | `grantex_issuance_freeze_changes_total` | `action` (`placed`, `reaffirmed`, `lifted`), `scope` | | `grantex_issuance_refusals_total` | `path` (the issuance route), `reason` (`frozen`, `freeze_state_unavailable`) | | `grantex_rate_limit_decisions_total` | `bucket` (`plan`, `containment`, `status`), `outcome` (`allowed`, `limited`, `unavailable`, `local_allowed`, `local_limited`) | Every refused delivery also logs `alert: "event_bridge_verification_failure"` with the source id and reason, never the payload or signature. Alert rules are in `deploy/prometheus/event-bridge-alerts.yml`. ## Settings | Variable | Default | Meaning | | - | - | - | | `EVENT_BRIDGE_ENABLED` | `false` | Turns on registration and ingestion | | `EVENT_BRIDGE_DEVELOPER_IDS` | (all) | Comma-separated developers the bridge is limited to, for a staged rollout | | `EVENT_BRIDGE_RATE_LIMIT_PER_MINUTE` | `30000` | Ingestion requests per client address, read per request | | `EVENT_BRIDGE_RECEIPT_RETENTION_HOURS` | `48` | Floor for how long a delivery receipt is kept; never shorter than the window in which its source would still accept the delivery | | `VAULT_ENCRYPTION_KEY` | — | Required to register webhook sources | | `REVOCATION_FEED_ENABLED` | `true` (from the next release; `false` before) | Serves the revocation feed and status endpoints. `false` is the opt-out; any other value leaves them on. SDK clients checking `online` (the default) or `feed` deny every call while they are off | | `REVOCATION_FEED_DEVELOPER_IDS` | (all) | Developers the feed is limited to; every other developer gets `404`, so their default SDK clients deny | | `REVOCATION_FEED_POLL_MS` | `500` | How often an instance looks for new revocations | | `REVOCATION_FEED_SETTLE_SECONDS` | `15` | How long an entry may still be uncommitted | | `REVOCATION_FEED_HEARTBEAT_MS` | `1000` | How often a stream confirms it is up to date | | `REVOCATION_FEED_MAX_CONNECTIONS` | `200` | Streams one developer may hold on one instance | | `REVOCATION_FEED_RETENTION_HOURS` | `48` | How long delivered entries are kept after they expire | ## What this does not defend against * A transmitter whose signing key is stolen can send events that verify. Scope what its events can do with mapping rules, and disable the source (`PATCH /v1/event-sources/` with `{"status": "disabled"}`) if its key leaks. * Events are only as timely as the sender. The bridge bounds how **old** an accepted event may be, not how late the sender is. * A disabled source's events are refused, not queued: re-enable it and have the sender retransmit. * A rule is only as good as the bindings behind it. A grant with no `subject_ref` binding is not reached by a rule that targets one; the delivery records `no_target` and says so in the metrics. * Revocation stops new authorisation decisions. An SDK holding a verified token still needs to learn about it; see the revocation feed for how quickly, and what happens when it cannot. * The feed tells an SDK what the auth service knows. An agent that opts out (`revocationCheck: 'offline'`, and any client of `@grantex/sdk` 0.7.0 or `grantex` 0.6.0 and earlier, which do not check revocation at all) keeps calling until its token expires, which is why short grant lifetimes still matter. * A revocation is bounded by the client's staleness bound, not by zero: an agent can make calls in the window between the revocation and the entry arriving. Lower `staleAfterMs` and the poll interval to narrow it; the measured propagation on a local stack is well under 100 ms. # Evidence and Verification Source: https://docs.grantex.dev/concepts/evidence-and-verification A per-case evidence package - grants, tool calls, upstream records, policy scores, human decisions - in a hash chain the auth service anchors and signs, verifiable without trusting the system that produced it. ## Why a package An agent that works a regulated case - onboarding a business, dispositioning a screening hit - leaves its trail across several systems: the grant that authorised it, the provider calls it made, the policy that scored the result, the person who approved the decision. An auditor asking "why was this business referred, and who approved the decline?" should not have to query all of them, or trust any of them. An **evidence package** is one JSON document per case that answers those questions on its own: | The auditor asks | The package holds | | - | - | | Under what authority did the agent act? | The grant chain, root to leaf, with purpose, tools and caps. | | What did it look at? | Every tool call: provider, tool, keyed digests of input and output, and a reference to each upstream record returned. | | What was it running? | Model, prompt, policy and schema versions. | | Why this recommendation? | Policy score, tier and fired rules; every input with the records it came from, or marked unsourced; the records each memo section cites; screening dispositions. | | Who decided, and how carefully? | Every decision from the decision-grant store: approver, authentication method, dwell time, the action and its four-eyes pairing. | | Was anything stopped or withdrawn? | Revocations, and voids of records made in error. | | Who said so, and when? | For every entry, whether the platform or the tenant asserted it, and when the service recorded it. | | Was any of it edited? | A hash chain whose root the auth service records in its audit chain and signs. | The format is specified in `spec/evidence-package.md`, with a JSON Schema in `spec/evidence-package-1.0.schema.json`. ## How tamper evidence works 1. **Canonical bytes.** A package is only valid as its own RFC 8785 canonical JSON, so every byte matters. 2. **A hash chain.** Each entry carries the hash of the entry before it and of itself. Change an entry and verification names that entry and shows the expected and actual hash. 3. **An anchor.** On export the auth service writes the package root into the tenant's audit chain as a platform-only entry and embeds that entry in the package. Tenants cannot write such entries through `POST /v1/audit/log`. 4. **A signature.** The service signs the root and the anchor with its platform key, so anyone with the service JWKS can check the package came from the service. ## What verification proves | You verify with | The result says | It proves | | - | - | - | | the root only | `anchor: internal-consistency-only` | Nothing changed since someone computed that root. | | the root and an anchor hash from the audit log | `anchor: pinned` | The package matches that audit entry. | | the root and the service JWKS | `signature: verified`, `anchor: covered by the service signature` | The auth service exported and recorded this package. **Use this when the package crosses an organisational boundary.** | Entries marked tenant-asserted are statements the tenant's platform made to the service when they were recorded; the service's signature proves it received them then, not that they are true. Grants, decisions, consumptions and revocations come from the service's own records. ## Privacy by default A default package does not let a reader recover or confirm who was involved, what the case is about, which provider records were fetched or what the tool inputs were, and two packages cannot be joined. Five classes are keyed per case with HMAC unless disclosed: * `principal` and `approver` identifiers, and the case `subject`: pseudonyms (`pz:...`); * upstream `record` identifiers: pseudonyms, stable within the case so citations still line up; * tool input and output `content` digests: keyed digests (`hmac-sha256:...`), so equal inputs in two cases do not match. A decision grant's own `action_hash` is an unkeyed hash over the clear subject; putting it next to a pseudonymised subject would let anyone confirm a guess, so such packages carry a keyed `action_ref` instead. Memos, rationales and rule reasons appear only as digests. Disclosure (for example `"disclose": ["record"]` so an auditor can look up the provider records) is allowed only for developers an operator lists in `EVIDENCE_DISCLOSURE_DEVELOPER_IDS`. The case owner, holding the tenant key, can always recompute a keyed value to confirm it. ## Verifying a package Get the root and anchor from somewhere other than the package - the export response headers you stored, or the tenant audit log (`GET /v1/audit/entries?action=evidence.package_exported`) - and the service JWKS from `/.well-known/jwks.json`. Then: ```bash theme={null} grantex evidence verify package.json --root sha256:fbc98bfa7f0c59cd400504ec838b159ba20fbbca31a64c3e50e7a848463deade --jwks jwks.json ``` (`grantex-evidence verify ...` with the Python SDK.) It exits 0 only if every check passes and prints the trust basis: ```text theme={null} verified: 18 entries, root sha256:fbc98bfa... root: matches --root anchor: covered by the verified service signature signature: verified (kid evidence-example-es256) unsourced policy inputs: 1 entries recorded late: 1 tenant-asserted entries: 12 ``` On any break it prints the failing link and exits 1 (usage errors, such as a missing `--root`, exit 2): ```text theme={null} FAILED entry_hash_mismatch: entry 13 content does not match its hash entry: 13 field: entries[13].hash expected: sha256:23c2... actual: sha256:1017... ``` In code: ```python theme={null} import json from pathlib import Path from grantex.evidence import upstream_records_for, verify_package data = Path("spec/examples/evidence/evidence-package.json").read_bytes() root = "sha256:fbc98bfa7f0c59cd400504ec838b159ba20fbbca31a64c3e50e7a848463deade" anchor = "8f6af1cdf6fc68b4acdaef31e3bcf5b4ef0a4c41ebba6f7c559852df4b0ad04f" result = verify_package(data, expected_root=root, expected_anchor_hash=anchor) assert result.ok and result.anchor_status == "pinned", (result.code, result.field_path) # With record identifiers disclosed, every upstream record behind the recommendation, from the package alone. disclosed = Path("spec/examples/evidence/evidence-package-disclosed.json").read_bytes() assert verify_package(disclosed, expected_root="sha256:b79d4c3bd0633b77615abe1153e5d982da63ee6afa07e16ddd6c75c5222d5a99").ok for record in upstream_records_for(json.loads(disclosed), "rec_0001"): print(record["provider"], record["tool"], record["record_id"], record["retrieved_at"]) ``` ```typescript theme={null} import { readFileSync } from 'node:fs'; import { evidence } from '@grantex/sdk'; const result = evidence.verifyPackage(readFileSync('package.json'), { expectedRoot: 'sha256:fbc98bfa7f0c59cd400504ec838b159ba20fbbca31a64c3e50e7a848463deade', jwks: JSON.parse(readFileSync('jwks.json', 'utf8')), requireSignature: true, }); if (!result.ok) throw new Error(`${result.code} at ${result.fieldPath}`); ``` ## Producing evidence A platform records evidence as the case runs with `POST /v1/evidence/cases/{caseId}/records`: run context, tool calls with their upstream record identifiers, policy evaluations, dispositions and recommendations. Each record is checked when it is written - including its references to earlier records and its grant - so an accepted case can always be exported. Records are idempotent on their own ids, and mistakes are voided with `POST /v1/evidence/cases/{caseId}/void`, never deleted. Decisions come from the auth service's decision grants. When the case is decided, `POST /v1/evidence/cases/{caseId}/export` returns the package. Everything is behind the `EVIDENCE_EXPORT_ENABLED` flag, off by default. ## What it does not prove The package proves what was recorded, by whom and when, and that nothing changed since. It does not prove the platform recorded every call it made; that rests on recording at the enforcement point and reconciling with the audit log. Keyed values are not anonymous to whoever holds the tenant key. # Grant Token Source: https://docs.grantex.dev/concepts/grant-token Grantex grant tokens are RS256- or ES256-signed JWTs with agent-specific claims. ## Overview Grantex tokens are standard JWTs (RS256 by default, or ES256) extended with agent-specific claims. Any service can verify them locally using the published JWKS, without a per-token call to the Grantex verification API. Verifiers still need the public keys, fetched from the issuer and cached according to their SDK's behavior. ## Decoded Example ```json theme={null} { "iss": "https://grantex.dev", "sub": "user_abc123", "aud": "https://api.targetservice.com", "iat": 1709000000, "exp": 1709086400, "jti": "tok_01HXYZ987xyz", "client_id": "ag_01HXYZ123abc", "scope": "calendar:read payments:initiate:max_500", "urn:grantex:grant": { "grant_id": "grnt_01HXYZ456def", "agent_did": "did:grantex:ag_01HXYZ123abc", "developer_id": "org_yourcompany" } } ``` Grant tokens are RFC 9068 JWT access tokens (`typ: at+jwt`). A stock OAuth or JOSE library validates them with standard semantics; the full profile is `spec/grant-token-0.6.md`. ## Standard Claims | Claim | Type | Description | | - | - | - | | `iss` | `string` | Issuer — the Grantex server that signed the token | | `sub` | `string` | The end-user (principal) who authorized this agent | | `aud` | `string?` | Intended audience, when the grant is bound to a resource — services should reject tokens with a mismatched `aud` | | `iat` | `number` | Issued-at timestamp (seconds since epoch) | | `exp` | `number` | Expiry timestamp (seconds since epoch) | | `jti` | `string` | Unique token ID — used for real-time revocation | | `client_id` | `string` | The agent's client identifier | | `scope` | `string` | Exact scopes granted, space-delimited — services should check these | | `cnf` | `object?` | `jkt`: thumbprint of the agent key that must prove possession (DPoP), when key-bound | | `act` | `object?` | Delegation chain (RFC 8693), on delegated grants | | `authorization_details` | `array?` | Purpose, tools, caps, budget and decision references (RFC 9396) | ## Grantex Grant Claim `urn:grantex:grant` carries the grant record under a collision-resistant name: | Member | Type | Description | | - | - | - | | `grant_id` | `string` | Grant record ID — links token to the persisted grant | | `agent_did` | `string` | The agent's DID — cryptographically verifiable identity | | `developer_id` | `string` | The developer org that built the agent | | `parent_grant_id` | `string?` | Parent grant, on delegated grants | | `delegation_depth` | `number?` | Hops from the root grant, on delegated grants | ## Legacy Claim Aliases `agt`, `dev`, `grnt`, `scp`, `parentAgt`, `parentGrnt`, `delegationDepth` and `bdg` are the pre-0.6 names. They are still issued, with the same values, while the auth service's `GRANT_TOKEN_LEGACY_CLAIMS` is on — the default in 0.6; it defaults off in 0.7. The SDK verifiers read the standard claim first, fall back to an alias with a deprecation warning, and reject a token whose claim and alias disagree. See `docs/migration-0.6.md`. The hosted service publishes keys at `https://api.grantex.dev/.well-known/jwks.json`, but its canonical `iss` value is `https://grantex.dev`. Current SDKs recognize that stable JWKS alias automatically. Custom verifiers must validate the canonical issuer explicitly. ## Delegation Claims Present on tokens issued to sub-agents via `grants.delegate()`: | Claim | Type | Description | | - | - | - | | `act` | `object` | RFC 8693 actor chain: `act.sub` is the parent agent's DID; nested `act` members are earlier delegators | | `urn:grantex:grant.parent_grant_id` | `string` | Grant ID of the parent grant — full delegation chain is traceable | | `urn:grantex:grant.delegation_depth` | `number` | How many hops from the root grant (root = 0) | The legacy aliases `parentAgt`, `parentGrnt` and `delegationDepth` carry the same values while legacy claims are issued. ## Verification Grant tokens can be verified two ways: ### Offline (recommended) Verify the RS256 or ES256 signature locally using the published JWKS. This avoids the online token-verification API, although the verifier may still need to retrieve the current public keys from the JWKS endpoint. ```typescript TypeScript theme={null} import { verifyGrantToken } from '@grantex/sdk'; const grant = await verifyGrantToken(token, { jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', requiredScopes: ['calendar:read'], }); ``` ```python Python theme={null} from grantex import verify_grant_token, VerifyGrantTokenOptions grant = verify_grant_token(token, VerifyGrantTokenOptions( jwks_uri="https://api.grantex.dev/.well-known/jwks.json", )) ``` ### Online Call the Grantex API for real-time revocation status: ```typescript TypeScript theme={null} const result = await grantex.tokens.verify(token); console.log(result.valid); // true or false console.log(result.scopes); ``` ```python Python theme={null} result = client.tokens.verify(token) print(result.valid) print(result.scopes) ``` ## Security * **Algorithm allowlist**: every verification layer (auth service, TypeScript, Python and Go SDKs) accepts only RS256 and ES256, and only with a key of the matching type named by `kid`. The `alg` header cannot select another algorithm or another key. * **Replay prevention**: Every token's `jti` is tracked. Presenting a previously-seen `jti` returns `valid: false`. * **Minimum key size**: The auth service enforces a minimum 2048-bit RSA modulus on RSA signing keys; EC keys must be on P-256. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # How It Works Source: https://docs.grantex.dev/concepts/how-it-works The three primitives that make up the Grantex protocol. ## Three Primitives Grantex introduces three primitives for agent authorization: ``` ┌───────────────────┬─────────────────────┬───────────────────────┐ │ Agent Identity │ Delegated Grant │ Audit Trail │ │ │ │ │ │ Cryptographic │ Human-approved, │ Append-only, │ │ DID / JWT for │ scoped, revocable │ hash-chained log │ │ every agent │ permission tokens │ of every action │ └───────────────────┴─────────────────────┴───────────────────────┘ ``` ### Agent Identity Every agent registered with Grantex receives a cryptographic DID (Decentralized Identifier). This identity is embedded in every grant token the agent receives, allowing any service to verify which agent is acting. ### Delegated Grant A delegated grant is a human-approved, scoped, time-limited permission token. The user sees a plain-language consent UI describing exactly what the agent can do. Once approved, Grantex issues an RS256-signed JWT — the **grant token** — that the agent presents to services. ### Audit Trail Every action taken by an authorized agent is logged to an append-only, hash-chained audit trail. Each entry references the previous entry's hash, creating a tamper-evident chain that can be verified independently. ## The Authorization Flow ``` Developer registers agent → declares required scopes │ ▼ Agent requests authorization from end-user │ ▼ User approves via Grantex consent UI (scoped, time-limited) │ ▼ Grantex issues signed Grant Token (RS256 JWT + custom claims) │ ▼ Agent presents token to any service → service verifies locally via JWKS │ ▼ Every action logged to immutable audit trail │ ▼ User can revoke any grant at any time → effective in < 1 second ``` ### Step by step 1. **Register** — The developer registers their agent with Grantex, declaring a name, description, and the scopes (permissions) the agent needs. 2. **Authorize** — When the agent needs to act on behalf of a user, it initiates an authorization request. The user is redirected to a consent UI showing exactly what permissions are being requested. 3. **Approve** — The user reviews the scopes in plain language and approves (or denies). On approval, Grantex returns an authorization code to your redirect URI. 4. **Exchange** — Your application exchanges the authorization code for a signed grant token (RS256 JWT). This token contains the user's identity, the agent's DID, granted scopes, and expiry. 5. **Verify** — Any service receiving the grant token can verify it offline using the published JWKS endpoint. No Grantex account or API call needed — just fetch the public keys. 6. **Audit** — As the agent takes actions, each one is logged to the audit trail with the agent ID, grant ID, action name, and outcome. 7. **Revoke** — The user can revoke any grant at any time. Revocation is effective immediately — the token is blocklisted in Redis and subsequent verification calls reject it. ## Why an Open Standard? * **Model-neutral** — Works with OpenAI, Anthropic, Google, Llama, Mistral. No single AI provider can credibly own the authorization layer for their competitors' agents. * **Framework-native** — First-class integrations for LangChain, AutoGen, CrewAI, Vercel AI, and more. * **Locally verifiable** — Services verify signatures and claims with published JWKS keys, without a per-token call to the Grantex verification API. The JWKS endpoint must remain reachable whenever a verifier needs current keys. * **Compliance-supporting controls** — Audit, scope, consent, and revocation features can provide evidence for applicable frameworks. They do not by themselves determine legal compliance or a system's risk classification. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Agent Passport and Grant Source: https://docs.grantex.dev/concepts/passport-vs-grant An Agent Passport says who an agent is; a grant says what a person lets it do. How a grant is bound to a passport. Passport binding ships behind `PASSPORT_BOUND_GRANTS_ENABLED`, off by default. It is part of the Agent Trust Registry, Phase 1, and is not yet enabled on the hosted service. The normative text is [spec/passport-binding.md](https://github.com/mishrasanjeev/grantex/blob/main/spec/passport-binding.md). ## Two credentials, two questions An **Agent Passport** answers *who is this agent?* An accredited issuer gives it to the agent after checking who provides it, what software it is and which key it holds. It is an SD-JWT VC signed by the issuer, and the registry keeps the issuer's attestation of it. A **grant** answers *what may this agent do, for whom?* A Principal gives it to the agent, for some scopes, a purpose and a time. It is issued by Grantex after the Principal's consent, as a grant token. | | Agent Passport | Grant | | - | - | - | | Issued by | An accredited issuer | Grantex, on a Principal's consent | | Says | Who the agent and its provider are, which key it holds | What it may do, for whom, until when | | Lifetime | Up to a year | Hours to days | | Revoked by | The issuer (its status list) or the registry (its acceptance list) | The Principal, the developer, an emergency stop | A passport never authorizes anything. A grant may be **bound** to one. ## A bound grant With the flag on, the developer passes the passport when requesting the grant, with the rail or verifier the grant is for as `audience`: ```json theme={null} { "agentId": "ag_01J8Z3K4M5N6P7Q8R9S0T1V2W3", "principalId": "user_shopper", "scopes": ["read"], "audience": "https://merchant.example/checkout", "passport": "" } ``` Before the request is recorded, the registry checks that the passport is genuine and current, that its issuer is accredited, that the issuer's attestation of it is registered and still accepted by both the issuer and the registry, that its key is a proven key of the agent, and that the request stays within the limits the issuer checked. The consent page then shows the agent's trust level, the issuers and the declared limits before the Principal decides. The grant token carries a reference to the passport, never the passport: ```json theme={null} { "type": "urn:grantex:commerce:v1", "passport": { "issuer": "https://issuer.example", "id": "att-01", "hash": "sha-256:vDGKR0eipzfsrEgKMqXzI0NWGjZnjYVkBXW4Vf6PjcE", "key_thumbprint": "gzr6dlS40bV-rn_SVrIuqv36jX1W6ZnTeGkVbXr-SgY" }, "acceptance_status": { "uri": "https://registry.example/status/attestations/racl_01J8Z3K4M5N6P7Q8R9S0T1V2W3", "idx": 4127 } } ``` and `cnf.jkt` equal to the same key thumbprint, so only the holder of the passport's key can use the grant. A relying party that trusts the registry resolves `acceptance_status` to learn whether the registry still stands behind the passport, and the key rule tells it that the key presenting the grant is the key the issuer checked. ## When the passport goes away A bound grant never outlives its passport: it ends at the passport's `exp`, or its attestation's if that is earlier, whatever lifetime was requested. A bound grant's token is not issued or refreshed once the passport or its attestation has expired, the issuer revokes or suspends the passport, the registry withdraws its acceptance, the issuer is suspended, or the key is compromised or rotated out. The issuer's status is read again when the registry's last read of it is no longer fresh; if the issuer's list cannot be read, no token is issued (`status_stale`). Revoking grants that were already issued when that happens is the registry cascade, a later milestone; it finds them through the binding recorded for each grant. ## One merchant at a time: child grants A Principal may let an agent shop at more than one merchant. The developer names them, as exact origins, in the authorization request's `authorization_details`, next to the passport: ```json theme={null} [ { "type": "urn:grantex:commerce:v1", "allowed_merchants": ["https://merchant.example", "https://shop.merchant.example"], "amount_range": { "currency": "EUR", "max": "250.00" }, "budget": { "amount": "500.00", "currency": "EUR" } } ] ``` The grant token carries them in its commerce entry. It is not what the agent shows a merchant. Before it checks out at one, the agent's developer exchanges the grant token (RFC 8693 token exchange on `POST /v1/token`) for a **child grant** for that merchant alone. The request carries a `DPoP` proof (RFC 9449) signed with the passport's key, the parent's `cnf.jkt`: the developer's API key and a copy of the parent token are not enough. ```json theme={null} { "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", "subject_token": "", "subject_token_type": "urn:ietf:params:oauth:token-type:access_token", "resource": "https://merchant.example" } ``` The child's `aud` is the merchant, which must be one of `allowed_merchants` (else `audience_mismatch`). It lives at most 15 minutes, and never longer than its parent, the passport or the attestation. It keeps the parent's key (`cnf.jkt`), passport reference and acceptance status, and its limits are the parent's or narrower, never wider: its `allowed_merchants` is that one merchant. Each exchange checks the passport again, so none is issued once the passport, its attestation or its issuer is revoked or suspended. A child is a token of its parent grant: revoking the grant revokes every child, and a budget debit made with the child's grant id is taken from the parent's budget. A passport-bound grant is not delegated to a sub-agent yet; a sub-agent will bind its own passport. # Purpose-Bound Grants Source: https://docs.grantex.dev/concepts/purpose-bound-grants Bind a grant to the purpose a person approved, and let tools refuse calls made for any other purpose. The examples on this page are available in published Python `grantex==0.6.1`. The last verified AgenticOrg governed-case integration uses an exact local purpose allowlist; installing the newer SDK does not make that integration enforce token-level purpose or per-case caps without case-context wiring. See [the integration boundary](/guides/agenticorg-governed-cases) and [Release Status](/release-status) before using these examples in production. ## What a purpose is A scope says *what* an agent may do. A purpose says *why*. A grant issued for customer due diligence at onboarding should not be usable to enrich a marketing list, even though both call the same verification tool. A grant carries one purpose from a controlled vocabulary: | Purpose | Meaning | | - | - | | `aml.cdd.onboarding` | Customer due diligence at onboarding | | `aml.cdd.ongoing` | Ongoing customer due diligence | | `aml.screening` | Screening against watch lists | | `procurement.vendor_onboarding` | Vendor onboarding | | `payments.payout` | Payouts | Organisations add private terms under their own namespace: `x-.`, for example `x-acme-bank.kyb_refresh`. Any other value is rejected when the grant is requested, and denied by `enforce()` if it ever appears in a token. ## Requesting a purpose-bound grant Pass `purpose` when creating the authorization request. It needs at least one `tool::` scope, because the purpose binds tool calls on those connectors. ```python theme={null} request = grantex.authorize(AuthorizeParams( agent_id=agent.id, user_id="user_01", scopes=["tool:acme_kyb:read"], purpose="aml.cdd.onboarding", )) ``` ```typescript theme={null} const request = await grantex.authorize({ agentId: agent.id, userId: 'user_01', scopes: ['tool:acme_kyb:read'], purpose: 'aml.cdd.onboarding', }); ``` The consent page shows the purpose above the requested permissions. The purpose the person approved is stored on the grant, recorded on every audit entry logged for the grant (`purpose` on audit entries and on `GET /v1/grants/:id`), inherited by delegated grants, and kept when the grant token is refreshed. ## How the purpose travels in the token Grant tokens carry the purpose in `authorization_details` (RFC 9396), one entry per connector named by the grant's scopes: ```json theme={null} { "scp": ["tool:acme_kyb:read"], "authorization_details": [ {"type": "urn:grantex:tools:v1", "connector": "acme_kyb", "purpose": "aml.cdd.onboarding"} ] } ``` An entry of this type may also carry `tools` (tool names, or prefixes ending in `*`), `caps` and `data_region`. `enforce()` applies an entry's `tools` list in addition to the scopes. `data_region` names the region the grant's data may be processed in (`POST /v1/authorize` takes it as `dataRegion` when `DATA_REGION_ISSUANCE_ENABLED=true`, lowercased and bound to every connector the scopes name, with or without a purpose): a relying party with `dataRegionCheck: 'on'` (`data_region_check="on"` in Python, `dataRegionCheck: on` in the gateway) denies a call for a connector whose entry names another region with `region_mismatch` / `region_mismatch`, and one whose entry names a region when the relying party has no `dataRegion` with `region_mismatch` / `region_unconfigured`; an entry without a region is unrestricted, and the check is off by default in this release. Entries of other types are ignored. A claim that cannot be read unambiguously (not an array, an entry without `type`, an unknown key in a `urn:grantex:tools:v1` entry, or two entries for the same connector) denies every call with `token_invalid` / `malformed_authorization_details`. ## Restricting a tool to purposes A manifest declares which purposes a tool may be called for with `allowed_purposes` (see the manifest 0.6 schema in `spec/manifest-0.6.md`): ```json theme={null} { "connector": "acme_kyb", "tools": { "get_case": "read", "resolve_business": {"permission": "read", "allowed_purposes": ["aml.cdd.*"]}, "screen_person": {"permission": "read", "allowed_purposes": ["aml.*"]}, "monitor_enroll": {"permission": "write", "allowed_purposes": ["aml.cdd.ongoing"]} } } ``` ### Matching rules Patterns match whole dot-separated segments. * A pattern without a wildcard matches only the identical purpose. `aml.cdd.ongoing` matches `aml.cdd.ongoing` and nothing else. * `prefix.*` matches a purpose that starts with all of `prefix`'s segments and has at least one more. `aml.cdd.*` matches `aml.cdd.onboarding` and `aml.cdd.ongoing`; it does **not** match `aml.cddx` (a different segment) or `aml.cdd` itself. * **`aml.*` does not match `aml`.** A wildcard stands for at least one further segment, so a pattern never matches its own prefix. `aml` alone is not a purpose in the vocabulary, and treating `aml.*` as covering it would let a vague purpose satisfy every specific restriction below it. * A wildcard is only allowed as the whole last segment: `*`, `aml.*.onboarding` and `aml.cdd*` are rejected when the manifest loads. * Matching is case-sensitive and purposes are lower case, so `AML.cdd.onboarding` never matches. ### What `enforce()` does For a tool that declares `allowed_purposes`, after the scope and permission checks: | Grant | Result | | - | - | | No `urn:grantex:tools:v1` entry for the connector, or an entry without purpose | denied, `purpose_not_allowed` / `missing` | | Purpose outside the vocabulary and not a private term | denied, `purpose_not_allowed` / `unknown_purpose` | | Purpose matches none of the patterns | denied, `purpose_not_allowed` / `not_matched` | | Purpose matches a pattern | continues to the remaining checks | ```python theme={null} result = grantex.enforce(grant_token=token, connector="acme_kyb", tool="resolve_business") if not result.allowed: # result.reason_code == "purpose_not_allowed" # result.sub_reason == "not_matched" # result.details == {"allowed_purposes": ["aml.cdd.*"], "purpose": "marketing.enrichment"} raise PermissionError(result.reason) ``` `result.purpose` (`result.purpose` in TypeScript as well) reports the grant's purpose for the connector on allowed and denied results, so it can be written to your own audit records. ## Compatibility * Tools without `allowed_purposes` behave exactly as before, whatever purpose the grant carries or lacks. * Requests without `purpose` issue the same tokens as before (no `authorization_details` entry is added). * The purpose columns added to `auth_requests`, `grants` and `audit_entries` are nullable; existing rows have no purpose. * Purpose is not part of the audit entry hash. It is stamped from the grant record, which the entry already references by `grantId`. * Offline consent bundles and the OAuth agent-grants profile do not accept a purpose yet. # Scope Registry Source: https://docs.grantex.dev/concepts/scope-registry Standard scope definitions for common domains, plus guidelines for custom scopes. The Grantex scope registry provides standard scope definitions organized by domain. Use these as a starting point when registering agents, or define your own custom scopes following the conventions below. ## Standard Scopes All scopes follow the `resource:action[:constraint]` format defined in [Scopes](/concepts/scopes). ### Calendar | Scope | Description | Constraint Examples | | - | - | - | | `calendar:read` | Read calendar events and availability | `since_2026-01-01`, `limit_100` | | `calendar:write` | Create and modify calendar events | `max_duration_8h` | | `calendar:delete` | Delete calendar events | — | | `calendar:share` | Share calendars with other users | — | ### Email | Scope | Description | Constraint Examples | | - | - | - | | `email:read` | Read email messages and metadata | `folder_inbox`, `since_2026-01-01` | | `email:send` | Send emails on the user's behalf | `limit_50` (per day) | | `email:delete` | Delete email messages | `folder_trash` | | `email:draft` | Create and manage draft messages | — | ### Payments | Scope | Description | Constraint Examples | | - | - | - | | `payments:read` | View payment history and balances | `since_2026-01-01` | | `payments:initiate` | Initiate payments and transfers | `max_500`, `max_5000` | | `payments:approve` | Approve pending payments | `max_1000` | | `payments:refund` | Process refunds | `max_500` | ### Files | Scope | Description | Constraint Examples | | - | - | - | | `files:read` | Read file contents and metadata | `folder_documents`, `limit_1000` | | `files:write` | Upload and modify files | `max_size_50mb` | | `files:delete` | Delete files | `folder_temp` | | `files:share` | Share files with other users | — | ### Contacts | Scope | Description | Constraint Examples | | - | - | - | | `contacts:read` | Read contact information | `limit_500` | | `contacts:write` | Create and update contacts | — | | `contacts:delete` | Delete contacts | — | ### Profile | Scope | Description | Constraint Examples | | - | - | - | | `profile:read` | Read user profile information | — | | `profile:write` | Update user profile | — | ### Notifications | Scope | Description | Constraint Examples | | - | - | - | | `notifications:read` | Read notification history | `since_2026-01-01` | | `notifications:send` | Send notifications to the user | `limit_100` | | `notifications:manage` | Manage notification preferences | — | ### Database | Scope | Description | Constraint Examples | | - | - | - | | `database:read` | Query database records | `limit_10000` | | `database:write` | Insert and update records | — | | `database:delete` | Delete records | `limit_100` | | `database:schema` | Read or modify database schema | — | ### API | Scope | Description | Constraint Examples | | - | - | - | | `api:read` | Read API resources | `limit_1000` | | `api:write` | Create and update API resources | — | | `api:delete` | Delete API resources | — | | `api:admin` | Administrative API operations | — | ### Admin | Scope | Description | Constraint Examples | | - | - | - | | `admin:read` | Read administrative data | — | | `admin:write` | Modify system configuration | — | | `admin:users` | Manage user accounts | — | | `admin:audit` | Access audit logs | `since_2026-01-01` | ## Constraint Patterns Constraints are the optional third segment of a scope string. They narrow the permission granted: | Pattern | Description | Example Scope | | - | - | - | | `max_` | Maximum monetary amount | `payments:initiate:max_500` | | `folder_` | Restrict to a specific folder | `files:read:folder_documents` | | `since_` | Only access data after a date | `email:read:since_2026-01-01` | | `limit_` | Maximum number of items/operations | `contacts:read:limit_500` | | `max_size_` | Maximum file/payload size | `files:write:max_size_50mb` | | `max_duration_` | Maximum time duration | `calendar:write:max_duration_8h` | Constraints are validated by the consuming service, not by Grantex itself. Grantex stores and delivers them in the `scp` claim — your application enforces the constraint logic. ## Custom Scope Guidelines When your application needs scopes beyond the standard registry: ### Naming Convention Follow the `resource:action[:constraint]` format: ``` # Good inventory:read orders:create:max_10 reports:generate:since_2026-01-01 # Bad — don't use dots, slashes, or camelCase inventory.read orders/create readInventory ``` ### Recommendations 1. **Use lowercase** — scope strings should be entirely lowercase with colons as separators 2. **Keep resources singular or use a clear noun** — `order:read`, not `reading-orders` 3. **Use standard actions** — prefer `read`, `write`, `create`, `delete`, `send`, `approve`, `manage`, `admin` 4. **Add constraints only when needed** — constraints should narrow a broad permission, not replace fine-grained scopes 5. **Document scopes** — include descriptions when registering agents so users see clear consent prompts ### Registering Custom Scopes Declare your custom scopes when registering an agent: ```typescript theme={null} const agent = await grantex.agents.register({ name: 'inventory-bot', scopes: [ 'inventory:read', 'inventory:write', 'orders:create:max_10', ], }); ``` ## Scope Compatibility Grantex supports wildcard matching for scope checking. A broader scope satisfies a narrower requirement: | Token Scope | Required Scope | Match? | | - | - | - | | `files:read` | `files:read` | Yes — exact match | | `files:*` | `files:read` | Yes — wildcard matches any action | | `files:*` | `files:delete` | Yes — wildcard matches any action | | `files:read` | `files:write` | No — different action | | `files:read` | `files:*` | No — specific scope doesn't satisfy wildcard requirement | | `payments:initiate:max_500` | `payments:initiate` | Yes — constrained scope satisfies unconstrained | | `payments:initiate` | `payments:initiate:max_500` | No — unconstrained doesn't imply specific constraint | Wildcard scopes (`resource:*`) are useful for trusted internal agents that need full access to a resource domain. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Scopes Source: https://docs.grantex.dev/concepts/scopes Grantex scopes use the resource:action[:constraint] naming convention. ## Scope Format Grantex defines a standard scope format: ``` resource:action[:constraint] ``` | Part | Required | Description | | - | - | - | | `resource` | Yes | The resource being accessed (e.g. `calendar`, `email`, `payments`) | | `action` | Yes | The action being performed (e.g. `read`, `write`, `send`, `initiate`) | | `constraint` | No | An optional constraint on the action (e.g. `max_500` for a payment cap) | ## Examples | Scope | Meaning | | - | - | | `calendar:read` | Read calendar events | | `calendar:write` | Create and modify events | | `email:send` | Send emails on user's behalf | | `payments:initiate:max_500` | Initiate payments up to \$500 | | `files:read` | Read user files | | `profile:read` | Read user profile | ## How Scopes Work ### At registration When a developer registers an agent, they declare the full set of scopes the agent may ever need: ```typescript theme={null} const agent = await grantex.agents.register({ name: 'travel-booker', scopes: ['calendar:read', 'payments:initiate:max_500', 'email:send'], }); ``` ### At authorization When requesting user consent, the agent specifies which scopes it needs for this particular session. These must be a subset of the registered scopes: ```typescript theme={null} const auth = await grantex.authorize({ agentId: agent.id, userId: 'user_abc123', scopes: ['calendar:read', 'payments:initiate:max_500'], }); ``` ### In the grant token Approved scopes are embedded in the JWT's `scp` claim: ```json theme={null} { "scp": ["calendar:read", "payments:initiate:max_500"] } ``` ### At verification Services check the `scp` claim to decide whether to allow a request: ```typescript theme={null} const grant = await verifyGrantToken(token, { jwksUri: 'https://api.grantex.dev/.well-known/jwks.json', requiredScopes: ['payments:initiate'], }); // Throws if 'payments:initiate' is not in the token's scopes ``` ## Scope Enforcement in Integrations Protected framework integrations (LangChain, CrewAI, Vercel AI, etc.) first verify the token signature and claims with the configured JWKS endpoint, then enforce the verified `scp` claim. Current standalone verifiers may fetch JWKS on each invocation; they do not merely trust an unverified decode. ```typescript theme={null} // LangChain example const tool = createGrantexTool({ name: 'read_calendar', grantToken, requiredScope: 'calendar:read', // checked only after JWKS verification func: async (input) => getCalendarEvents(input), }); ``` If the token doesn't include the required scope, the tool throws immediately — before your function runs. ## User Experience Users see plain-language descriptions of scopes during the consent flow, not raw scope strings. Service providers define these descriptions when they register their scope definitions with Grantex. ## Scope Registry For a complete list of standard scope definitions organized by domain (calendar, email, payments, files, and more), along with constraint patterns and custom scope guidelines, see the [Scope Registry](/concepts/scope-registry). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). ## Tool-qualified scopes A scope may name one tool: `tool:::` (an optional `:capped:` may follow either form; a `*` in the tool position is connector-wide, like no tool segment). By default `enforce()` reads such a scope as the connector permission, so `tool:acme_kyb:read:get_case` covers every read tool on `acme_kyb`. With `toolQualifiedScopes: true` (`tool_qualified_scopes=True` in Python) the tool segment is honoured: a tool's permission comes from the connector-level scopes and the scopes naming that tool, and a connector whose scopes all name other tools denies the call with `tool_not_granted` / `tool_scope_missing`, `details.tool_scopes` listing the tools they name. The option is off in this release and becomes the default in the next major. # Tool Manifests Source: https://docs.grantex.dev/concepts/tool-manifests How tool manifests map every tool to a permission level, enabling precise scope enforcement for AI agent tool calls. ## What Is a Tool Manifest? A tool manifest is a declarative mapping from tool names to permission levels for a specific connector. It tells Grantex exactly which permission is required to call each tool, so enforcement does not have to guess from tool name keywords. ```json theme={null} { "connector": "salesforce", "version": "1.0.0", "description": "Salesforce CRM connector", "tools": { "create_lead": "write", "update_opportunity": "write", "query": "read", "create_task": "write", "get_account": "read", "list_opportunities": "read" } } ``` When an agent calls `create_lead`, Grantex looks up the manifest, sees that `create_lead` requires `write` permission, and checks whether the agent's grant token includes a scope that covers `write` on the `salesforce` connector. *** ## The Permission Hierarchy Grantex defines four permission levels in a strict hierarchy. Higher levels subsume all lower levels: ``` admin (level 3) └── delete (level 2) └── write (level 1) └── read (level 0) ``` | Level | Permission | Typical Operations | | - | - | - | | 0 | `read` | Query, list, get, search, fetch, check, download | | 1 | `write` | Create, update, send, post, upload, apply | | 2 | `delete` | Delete, remove, void, terminate, revoke, cancel | | 3 | `admin` | Run payroll, period close, force reset, purge | ### Coverage Rules A scope grants access to all tools at or below its permission level: | Granted Scope | READ Tools | WRITE Tools | DELETE Tools | ADMIN Tools | | :-: | :-: | :-: | :-: | :-: | | `tool:X:read` | Allowed | Denied | Denied | Denied | | `tool:X:write` | Allowed | Allowed | Denied | Denied | | `tool:X:delete` | Allowed | Allowed | Allowed | Denied | | `tool:X:admin` | Allowed | Allowed | Allowed | Allowed | **Example:** An agent with `tool:salesforce:write:*` can call `query` (read) and `create_lead` (write) but cannot call `delete_contact` (delete) or `run_period_close` (admin). *** ## Scope Format Tool enforcement scopes follow the format: ``` tool:{connector}:{permission}:{resource}[:capped:{N}] ``` | Part | Required | Description | | - | - | - | | `tool` | Yes | Fixed prefix identifying this as a tool scope | | `connector` | Yes | The connector name (e.g., `salesforce`, `s3`, `stripe`) | | `permission` | Yes | The maximum permission level granted (`read`, `write`, `delete`, `admin`) | | `resource` | Yes | The resource pattern (`*` for all tools on this connector) | | `capped:N` | No | Optional spending cap per operation (e.g., `capped:500`) | ### Examples | Scope | Meaning | | - | - | | `tool:salesforce:write:*` | Write access to all Salesforce tools (covers read + write) | | `tool:s3:read:*` | Read-only access to all S3 tools | | `tool:stripe:write:*:capped:500` | Write access to Stripe tools, capped at 500 per operation | | `tool:jira:admin:*` | Full admin access to all Jira tools | | `tool:okta:delete:*` | Delete access to all Okta tools (covers read + write + delete) | *** ## How enforce() Uses Manifests When you call `enforce()`, Grantex performs these steps: 1. **Decode the JWT** — extract `scp` (scopes), `grnt`/`jti` (grant ID), `agt` (agent DID) 2. **Look up the manifest** — find the loaded manifest for the given connector 3. **Resolve the permission** — look up the tool name in the manifest to get its required permission level 4. **Check coverage** — determine if any scope in the token covers the required permission on this connector 5. **Check caps** — if the scope is capped, verify the call's amount is within the cap. From the next release a capped scope denies a call that gives no amount (`amount_missing`); see [Amount caps](/concepts/caps-and-metering#amount-caps) 6. **Return the result** — `allowed: true` or `allowed: false` with a reason ```python theme={null} result = grantex.enforce( grant_token=token, # JWT with scp: ["tool:salesforce:write:*"] connector="salesforce", # Look up salesforce manifest tool="delete_contact", # Manifest says: delete permission required ) # Token grants write, tool requires delete -> DENIED # result.allowed = False # result.reason = "write scope does not permit delete operations on salesforce" ``` *** ## Defining Manifests Grantex enforces permissions on **any connector you define** — not just the ones we ship. You own the manifest, you own the permission mapping. ### Custom Manifests Define a manifest for any tool, API, or internal service. No dependency on Grantex to add support: * **Inline** — define in code with `ToolManifest(...)`: ```python theme={null} from grantex import ToolManifest, Permission grantex.load_manifest(ToolManifest( connector="my-internal-crm", description="Internal CRM API", tools={ "search_contacts": Permission.READ, "create_deal": Permission.WRITE, "delete_account": Permission.DELETE, "manage_roles": Permission.ADMIN, }, )) ``` * **JSON file** — load from disk with `ToolManifest.from_file("./manifests/my-service.json")` * **Directory** — load all manifests from a folder with `grantex.load_manifests_from_dir("./manifests/")` * **Extend** — add tools to any manifest with `manifest.add_tool("new_tool", Permission.WRITE)` * **CLI generate** — auto-generate manifests from source code with `grantex manifest generate agent_tools.py` The `enforce()` engine, permission hierarchy, and JWT scope resolution work identically for custom and pre-built manifests. There is no difference at runtime. ### Pre-Built Manifests As a convenience, Grantex ships 53 pre-built manifests covering 339 tools across five categories: | Category | Connectors | Tools | Examples | | - | - | - | - | | Finance | 11 | 78 | Stripe, SAP, QuickBooks, NetSuite | | HR | 8 | 56 | Darwinbox, Okta, DocuSign, Greenhouse | | Marketing | 16 | 94 | Salesforce, HubSpot, Mailchimp, Google Ads | | Ops | 7 | 46 | Jira, Confluence, ServiceNow, Zendesk | | Comms | 11 | 65 | Gmail, Slack, GitHub, S3, Twilio | Import and load them directly: ```python theme={null} from grantex.manifests.salesforce import manifest as sf from grantex.manifests.stripe import manifest as stripe grantex.load_manifests([sf, stripe]) ``` These are a starting point, not a boundary. Mix pre-built and custom manifests freely — most production deployments use both. *** ## Fail-Closed Default If `enforce()` is called with a connector or tool that has no manifest loaded, the call is **denied by default**: ```python theme={null} result = grantex.enforce( grant_token=token, connector="unknown-service", tool="do_something", ) # result.allowed = False # result.reason = "No manifest loaded for connector 'unknown-service'. Load a manifest first." ``` This fail-closed behavior ensures that agents cannot bypass enforcement by calling tools that were never declared. Every tool must have an explicit permission entry in a loaded manifest. For development and testing, you can switch to permissive mode: ```python theme={null} grantex = Grantex( api_key=key, enforce_mode="strict", # default -- deny unknown tools # enforce_mode="permissive", # dev only -- allow unknown tools with warning ) ``` Never use `enforce_mode="permissive"` in production. It defeats the purpose of scope enforcement. *** ## JSON Manifest File Format When storing manifests as files (for git, CI, or team sharing), use this JSON format: ```json theme={null} { "connector": "inventory-service", "version": "1.0.0", "description": "Internal warehouse inventory API", "tools": { "get_stock_level": "read", "reserve_inventory": "write", "release_reservation": "write", "adjust_stock": "write", "force_stock_reset": "admin" } } ``` | Field | Type | Required | Description | | - | - | - | - | | `connector` | `string` | Yes | Unique connector identifier | | `version` | `string` | No | Semantic version of the manifest (default `"1.0.0"`) | | `description` | `string` | No | Human-readable description | | `tools` | `object` | Yes | Map of tool name to a permission level (`"read"`, `"write"`, `"delete"`, `"admin"`) or, from schema 0.6, a tool object | ### Tool objects (schema 0.6) A tool can also be declared as an object that adds constraints to its permission. String and object forms can be mixed in one file: ```json theme={null} { "connector": "acme_kyb", "tools": { "get_case": "read", "verify_business": { "permission": "read", "allowed_purposes": ["aml.cdd.*"], "caps": {"per_hour": 50, "per_case": 3}, "cost_units": {"base": 5, "ownership": 10} }, "case_decision": {"permission": "write", "requires_decision": true, "four_eyes_on": ["decline"]} } } ``` | Key | Meaning | | - | - | | `permission` | Required permission level | | `allowed_purposes` | Purpose patterns the grant's purpose must match (`aml.cdd.*`) | | `caps` | `per_hour`, `per_day` (rolling windows) and `per_case` call caps; `0` disables the tool | | `cost_units` | Units charged per call against a cost-unit budget | | `requires_decision` | The call also needs a [decision grant](/concepts/decision-grants) (not allowed on `read` tools) | | `risk_tier` | `low`, `medium` or `high`; a `high` tool needs a decision grant on every call whether or not `requires_decision` is set (not allowed on `read` tools) | | `four_eyes_on` | Decisions that need two different approvers | Manifests that use the object form are validated against the published JSON Schema, `spec/manifest-0.6.schema.json`: an unknown key is rejected with an error naming its path. Denied `enforce()` results carry a stable `reason_code` (`reasonCode` in TypeScript) such as `purpose_not_allowed`, `decision_required` or `cap_exceeded`. See the [manifest 0.6 specification](https://github.com/mishrasanjeev/grantex/blob/main/spec/manifest-0.6.md) for every rule and denial code. *** ## Related * [Custom Manifests guide](/guides/custom-manifests) — define manifests for any connector * [Scope Enforcement guide](/guides/scope-enforcement) — end-to-end walkthrough * [Scopes concept](/concepts/scopes) — the `resource:action[:constraint]` scope format * [TypeScript SDK enforce()](/sdks/typescript/enforce) — API reference * [Python SDK enforce()](/sdks/python/enforce) — API reference * [CLI manifest commands](/cli/manifest) — browse and validate manifests * [AgenticOrg case study](/case-studies/agenticorg) — 53 pre-built + custom manifests enforced in production ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Grantex vs OAuth 2.0 Source: https://docs.grantex.dev/concepts/vs-oauth How Grantex compares to OAuth 2.0 and when to use each. Grantex is built **on top of** OAuth 2.0 — not as a replacement, but as a purpose-built authorization layer for AI agents. This page explains the key differences and when each approach is the right fit. ## Side-by-Side Comparison | Dimension | OAuth 2.0 | Grantex | | - | - | - | | **Identity model** | Human users and client applications | Agents, principals (humans), and developers — first-class entities | | **Delegation** | Limited — no standard for sub-delegation | Agent-to-agent delegation with depth tracking and parent grant chaining (SPEC §9) | | **Revocation** | Revoke individual tokens | Revoke grants (all tokens under a grant cascade-revoke), plus principal self-service revocation | | **Audit** | Not built in — requires external logging | Tamper-evident, hash-chained audit log with compliance exports (SOC 2, HIPAA, GDPR) | | **Scope model** | Flat string scopes | Hierarchical scopes with a registry, scope constraints in policies, and time-bound restrictions | | **Agent-to-agent auth** | Not addressed | Sub-agent delegation with `parentAgt`, `parentGrnt`, and `delegationDepth` JWT claims | | **Compliance** | Bolted on | Built in — anomaly detection, policy engine, evidence packs | | **Local verification** | Often uses an introspection endpoint for opaque tokens | JWT-based signature and claim verification after obtaining JWKS keys; standalone SDK helpers may fetch keys on each call | ## When to Use OAuth 2.0 OAuth 2.0 is the right choice when: * You are building **human-facing** login flows (social login, SSO) * Your application follows the traditional **client → resource server** model * You need broad ecosystem compatibility with existing OAuth providers (Google, GitHub, Okta) * Agents are **not** part of your authorization model Grantex does not replace your identity provider. Use OAuth 2.0 / OIDC for authenticating humans, and Grantex for authorizing what agents can do on their behalf. ## When to Use Grantex Grantex is the right choice when: * **AI agents** act on behalf of users and need verifiable, revocable permissions * Agents **delegate** to sub-agents and you need to track the delegation chain * You need a **tamper-evident audit trail** of every action agents take * **Compliance** requirements (SOC 2, HIPAA, GDPR) demand structured evidence of agent activity * Users need to **review and revoke** agent permissions through a self-service dashboard * You want **local signature and claim verification** without hitting a token introspection endpoint; JWKS retrieval and caching remain deployment concerns ## Using Both Together A common architecture is: 1. **OAuth 2.0 / OIDC** handles user authentication (login with Google, Okta, etc.) 2. **Grantex** handles agent authorization (what the agent can do, audit trail, revocation) ``` User ──[OIDC login]──▶ Your App ──[Grantex authorize]──▶ Agent │ ▼ Sub-Agent (delegated) ``` The Grantex `principalId` maps to your user's identity from the OAuth provider. Your app initiates a Grantex authorization request, the user consents, and the agent receives a grant token scoped to exactly what it needs. ## Further Reading * [How It Works](/concepts/how-it-works) — Grantex authorization flow walkthrough * [Grant Tokens](/concepts/grant-token) — JWT structure and claims * [Delegation](/concepts/delegation) — Agent-to-agent delegation model * [Protocol Specification](/protocol/specification) — Full SPEC.md ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Anthropic SDK Tool Use Source: https://docs.grantex.dev/examples/anthropic-tool-use Scope-enforced tool use with audit logging using the Anthropic SDK. ## What it does This example demonstrates the `@grantex/anthropic` integration by building scope-enforced tools for Claude: 1. **Register an agent** and obtain a grant token via the sandbox flow 2. **Create scoped tools** using `createGrantexTool` with JSON Schema (`calendar:read`, `email:send`) 3. **Use `GrantexToolRegistry`** to manage tools and dispatch `tool_use` blocks 4. **Invoke tools** via `client.messages.create()` (if `ANTHROPIC_API_KEY` is set) or directly 5. **Demonstrate `GrantexScopeError`** -- attempting to execute a tool with `storage:delete` (a scope not in the grant) throws a `GrantexScopeError` with `requiredScope` and `grantedScopes` properties 6. **Inspect scopes offline** using `getGrantScopes()` to decode the JWT without a network call 7. **Inspect the audit trail** to see the logged actions ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/anthropic-tool-use npm install npm start ``` To use Claude for real tool use instead of direct invocation, set `ANTHROPIC_API_KEY`: ```bash theme={null} ANTHROPIC_API_KEY=sk-ant-... npm start ``` ## Expected output Without `ANTHROPIC_API_KEY` (direct invocation): ```text theme={null} Agent registered: ag_01HXYZ... Grant token received, grantId: grnt_01HXYZ... Tools created: read_calendar, send_email (registry has 2 tools) --- Invoking tools directly (no ANTHROPIC_API_KEY set) --- Calendar result: {"date":"today","events":[{"title":"Team standup","time":"9:00 AM"},{"title":"Design review","time":"2:00 PM"}]} Email result: {"sent":true,"to":"alice@example.com","subject":"Today's events","bodyLength":49} --- Testing scope enforcement --- GrantexScopeError caught: Required scope: storage:delete Granted scopes: calendar:read, email:send --- Inspecting grant scopes --- Scopes in token: calendar:read, email:send --- Audit trail --- [success] tool:read_calendar — 2026-03-30T12:00:00.000Z [success] tool:send_email — 2026-03-30T12:00:01.000Z Done! Anthropic SDK integration demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | | `ANTHROPIC_API_KEY` | *(none)* | Optional. Set to use Claude for real tool use | ## Source code The full source is in [`examples/anthropic-tool-use/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/anthropic-tool-use). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Audit Dashboard Source: https://docs.grantex.dev/examples/audit-dashboard Query, filter, and analyze the Grantex audit trail with metrics and hash chain verification. ## What it does This example demonstrates how to use the Grantex audit trail as a rich data source: 1. **Set up two agents** with different scopes and generate grant tokens 2. **Generate audit entries** — a mix of success, failure, and blocked statuses across agents 3. **Display full audit timeline** — formatted table with status icons, agent names, and timestamps 4. **Filter by agent** — show entries for each agent separately 5. **Filter by action** — show only specific actions (e.g. `email:send`) with metadata 6. **Compute metrics** — success rate, failure rate, entries per agent, top actions by frequency 7. **Verify hash chain integrity** — walk the cryptographic chain to confirm tamper evidence ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/audit-dashboard npm install npm start ``` ## Expected output ```text theme={null} === Audit Dashboard Demo === Agent A (data-reader) registered: ag_01HXYZ... Agent B (notifier) registered: ag_01HXYZ... Both agents authorized. --- Generating audit entries --- Generated 9 audit entries. --- Full Audit Timeline --- # Status Agent Action Time 1 [+] success data-reader data:read 12:00:01 2 [+] success data-reader calendar:read 12:00:01 ... 8 [!] blocked notifier email:send 12:00:02 9 [x] failure data-reader calendar:write 12:00:02 --- Metrics Summary --- Total entries: 9 Success: 6 (67%) Failure: 2 (22%) Blocked: 1 (11%) --- Hash Chain Integrity Check --- All hash chains verified: integrity PASSED Done! Audit dashboard demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/audit-dashboard/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/audit-dashboard). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # CrewAI Agent Source: https://docs.grantex.dev/examples/crewai-agent Scoped tools with audit logging using CrewAI. ## What it does This example demonstrates the `grantex-crewai` integration by building CrewAI tools with Grantex scope enforcement: 1. **Register an agent** and obtain a grant token via the sandbox flow 2. **Create scoped tools** using `create_grantex_tool` -- each tool is bound to a specific scope (`calendar:read`, `email:send`) 3. **Wrap tools with `with_audit_logging`** to automatically log every invocation to the Grantex audit trail 4. **Invoke tools** directly (in a full CrewAI crew, the LLM would select and call these tools) 5. **Demonstrate scope enforcement** -- attempting to create a tool with `account:delete` (a scope not in the grant) raises a `PermissionError` 6. **Inspect the audit trail** to see the logged actions ## Prerequisites * **Python 3.9+** * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/crewai-agent pip install -r requirements.txt python main.py ``` ## Expected output ```text theme={null} Agent registered: ag_01HXYZ... Grant token received, grantId: grnt_01HXYZ... Tools created: read_calendar, send_email Audit logging attached --- Invoking read_calendar --- Result: {"events": [{"title": "Team standup", "time": "9:00 AM", "query": "today"}, {"title": "Design review", "time": "2:00 PM", "query": "today"}]} --- Invoking send_email --- Result: Email sent successfully: "Meeting summary: standup at 9 AM, design review at 2 PM" --- Testing scope enforcement --- Scope check blocked unauthorized tool: Grant token does not include required scope "account:delete". Granted scopes: calendar:read, email:send --- Audit trail --- [success] read_calendar — 2026-02-28T12:00:00.000Z [success] send_email — 2026-02-28T12:00:01.000Z Done! CrewAI integration demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/crewai-agent/main.py`](https://github.com/mishrasanjeev/grantex/tree/main/examples/crewai-agent). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Google ADK Source: https://docs.grantex.dev/examples/google-adk Scope-enforced tools using Google Agent Development Kit. ## What it does This example demonstrates the `grantex-adk` integration by building scope-enforced tools for Google Agent Development Kit (ADK): 1. **Register an agent** and obtain a grant token via the sandbox flow 2. **Create scoped tools** using `create_grantex_tool` -- each tool returns a plain function that can be used directly with Google ADK agents (`calendar:read`, `email:send`) 3. **Invoke tools** directly as plain functions (in a full ADK agent, Gemini would select and call these tools) 4. **Demonstrate scope enforcement** -- attempting to create a tool with `account:delete` (a scope not in the grant) raises a `PermissionError` 5. **Inspect the audit trail** to see the logged actions ## Prerequisites * **Python 3.9+** * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/google-adk pip install -r requirements.txt python main.py ``` ## Expected output ```text theme={null} Agent registered: ag_01HXYZ... Grant token received, grantId: grnt_01HXYZ... Tools created: read_calendar, send_email --- Invoking read_calendar --- Result: {"events": [{"title": "Team standup", "time": "9:00 AM", "query": "today"}, {"title": "Design review", "time": "2:00 PM", "query": "today"}]} --- Invoking send_email --- Result: Email sent successfully: "Meeting summary: standup at 9 AM, design review at 2 PM" --- Testing scope enforcement --- Scope check blocked unauthorized tool: Grant token does not include required scope "account:delete". Granted scopes: calendar:read, email:send --- Audit trail --- [success] read_calendar — 2026-02-28T12:00:00.000Z [success] send_email — 2026-02-28T12:00:01.000Z Done! Google ADK integration demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/google-adk/main.py`](https://github.com/mishrasanjeev/grantex/tree/main/examples/google-adk). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # LangChain Agent Source: https://docs.grantex.dev/examples/langchain-agent Scoped tools with automatic audit logging using LangChain. ## What it does This example demonstrates the `@grantex/langchain` integration by building a LangChain agent with Grantex-scoped tools: 1. **Register an agent** and obtain a grant token via the sandbox flow 2. **Create scoped tools** using `createGrantexTool` -- each tool is bound to a specific scope (`calendar:read`, `email:send`) 3. **Attach a `GrantexAuditHandler`** that automatically logs every tool invocation to the Grantex audit trail 4. **Invoke tools** directly (or via an LLM if `OPENAI_API_KEY` is set) 5. **Demonstrate scope enforcement** -- attempting to create a tool with `account:delete` (a scope not in the grant) throws immediately 6. **Inspect the audit trail** to see the logged actions ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/langchain-agent npm install npm start ``` To use a real LLM for tool selection instead of direct invocation, set `OPENAI_API_KEY`: ```bash theme={null} OPENAI_API_KEY=sk-... npm start ``` ## Expected output ```text theme={null} Agent registered: ag_01HXYZ... Grant token received, grantId: grnt_01HXYZ... Tools created: read_calendar, send_email Audit handler configured --- Invoking read_calendar --- Result: {"events":[{"title":"Team standup","time":"9:00 AM","query":"today"},{"title":"Design review","time":"2:00 PM","query":"today"}]} --- Invoking send_email --- Result: Email sent successfully: "Meeting summary: standup at 9 AM, design review at 2 PM" --- Testing scope enforcement --- Scope check blocked unauthorized tool: Grant token does not include required scope "account:delete". Granted scopes: calendar:read, email:send --- Audit trail --- [success] read_calendar — 2026-02-28T12:00:00.000Z [success] send_email — 2026-02-28T12:00:01.000Z Done! LangChain integration demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | | `OPENAI_API_KEY` | *(none)* | Optional. Set to use a real LLM for tool selection | ## Source code The full source is in [`examples/langchain-agent/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/langchain-agent). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Multi-Agent Email Flow Source: https://docs.grantex.dev/examples/multi-agent-email-flow Multi-agent delegation with failure handling, cascade revocation, and audit trail. ## What it does This example demonstrates a realistic two-agent email automation workflow with comprehensive failure handling: 1. **Register two agents** — a planner (Agent A) and an executor (Agent B) 2. **Agent A reads calendar events** after offline scope verification 3. **Agent A delegates `email:send`** to Agent B (subset of its own scopes) 4. **Agent B sends an email** using the delegated, scoped permission 5. **Failure: unauthorized scope** — Agent B tries `calendar:read` (not delegated) and is rejected immediately 6. **Failure: cascade revocation** — Agent A's grant is revoked, which automatically invalidates Agent B's delegated token 7. **Recovery pattern** — Agent B detects the revoked token and reports that a new delegation is needed 8. **Audit trail inspection** — shows a timeline of all success and failure events across both agents ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/multi-agent-email-flow npm install npm start ``` ## Expected output ```text theme={null} === Multi-Agent Email Flow === Agent A (planner) registered: ag_01HXYZ... Agent B (executor) registered: ag_01HXYZ... Agent A grant token: grantId: grnt_01HXYZ... scopes: calendar:read, email:send --- Agent A: Reading calendar --- Scope verified offline: calendar:read Calendar events found: 3 --- Agent A: Delegating email:send to Agent B --- Delegated token issued: scopes: email:send delegationDepth: 1 --- Agent B: Sending email --- Email sent: msg_1234567890 --- Failure: Agent B tries calendar:read (not delegated) --- Blocked! Agent B cannot read calendar. --- Failure: Revoking Agent A's grant (cascade to Agent B) --- After revocation: Agent A token valid: false (revoked) Agent B token valid: false (cascade revoked) --- Audit trail --- [+] Agent A calendar:read — success [+] Agent B email:send — success [x] Agent B calendar:read — failure [x] Agent B email:send — failure Done! Multi-agent email flow with failure handling complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/multi-agent-email-flow/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/multi-agent-email-flow). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Next.js Starter Source: https://docs.grantex.dev/examples/nextjs-starter Interactive Next.js app showing the full Grantex consent flow. A full-stack Next.js application that walks through the complete Grantex authorization lifecycle — agent registration, consent UI, token exchange, and audit logging. Unlike the CLI examples, this demo runs a real web server so you can see the consent redirect flow visually. ## What it demonstrates * Registering an agent from a server-side API route * Creating an authorization request with a redirect URI * Redirecting to the Grantex consent UI * Exchanging the authorization code for a grant token * Logging an audit entry after token exchange * Displaying grant details and audit trail in a callback page ## Quick start ```bash theme={null} cd examples/nextjs-starter npm install cp .env.example .env # Edit .env with your GRANTEX_API_KEY npm run dev ``` Open [http://localhost:3000](http://localhost:3000) and click **Start Demo**. ## Flow overview ``` Browser Next.js Server Grantex API │ │ │ │── Click "Start Demo" ──────▶│ │ │ │── POST /v1/agents ────────▶│ │ │◀── agent { id, did } ──────│ │ │── POST /v1/authorize ─────▶│ │ │◀── { consentUrl } ─────────│ │◀── redirect to consentUrl ──│ │ │ │ │ │── Approve on consent UI ────────────────────────────────▶│ │◀── redirect /callback?code=...&state=... ────────────────│ │ │ │ │── POST /api/exchange ──────▶│ │ │ │── POST /v1/token ─────────▶│ │ │◀── { grantToken } ─────────│ │ │── POST /v1/audit/log ─────▶│ │ │◀── { entryId } ────────────│ │◀── grant details + audit ───│ │ ``` ## Project structure ``` examples/nextjs-starter/ app/ page.tsx # Landing page with "Start Demo" button callback/page.tsx # Receives code, displays grant details api/ authorize/route.ts # Registers agent + starts auth request exchange/route.ts # Exchanges code for grant token audit/route.ts # Lists audit entries for display .env.example # Required environment variables ``` ## Environment variables | Variable | Description | Default | | - | - | - | | `GRANTEX_API_KEY` | Your Grantex API key | (required) | | `GRANTEX_URL` | Auth service base URL | `https://api.grantex.dev` | | `NEXT_PUBLIC_APP_URL` | Public URL of this app (for redirect URI) | `http://localhost:3000` | ## Key design decisions * **Server-side SDK only** — All Grantex SDK calls happen in API routes. The API key never reaches the browser. * **Cookie-based state** — `agentId` and a random `state` are stored in cookies before redirect, then verified on the callback to prevent CSRF. * **Fresh agent per demo** — Each click registers a new agent. No database required. * **No UI libraries** — Minimal CSS matching the grantex.dev branding. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OpenAI Agents SDK Source: https://docs.grantex.dev/examples/openai-agents Scope-enforced tools using the OpenAI Agents SDK. ## What it does This example demonstrates the `grantex-openai-agents` integration by building scope-enforced tools for the OpenAI Agents SDK: 1. **Register an agent** and obtain a grant token via the sandbox flow 2. **Create scoped tools** using `create_grantex_tool` -- each tool is bound to a specific scope (`calendar:read`, `email:send`) 3. **Invoke tools** directly (in a full Agents workflow, the LLM would select and call these tools) 4. **Demonstrate scope enforcement** -- attempting to create a tool with `account:delete` (a scope not in the grant) raises a `PermissionError` 5. **Inspect the audit trail** to see the logged actions ## Prerequisites * **Python 3.9+** * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/openai-agents pip install -r requirements.txt python main.py ``` ## Expected output ```text theme={null} Agent registered: ag_01HXYZ... Grant token received, grantId: grnt_01HXYZ... Tools created: read_calendar, send_email --- Invoking read_calendar --- Result: {"events": [{"title": "Team standup", "time": "9:00 AM", "query": "today"}, {"title": "Design review", "time": "2:00 PM", "query": "today"}]} --- Invoking send_email --- Result: Email sent successfully: "Meeting summary: standup at 9 AM, design review at 2 PM" --- Testing scope enforcement --- Scope check blocked unauthorized tool: Grant token does not include required scope "account:delete". Granted scopes: calendar:read, email:send --- Audit trail --- [success] read_calendar — 2026-02-28T12:00:00.000Z [success] send_email — 2026-02-28T12:00:01.000Z Done! OpenAI Agents SDK integration demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/openai-agents/main.py`](https://github.com/mishrasanjeev/grantex/tree/main/examples/openai-agents). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Quickstart: Gemma 4 Offline Auth Source: https://docs.grantex.dev/examples/quickstart-gemma Get offline authorization working with Gemma 4 agents in under 5 minutes. Install @grantex/gemma, create a consent bundle, and verify tokens offline. ## What You'll Build This quickstart walks you through the full offline authorization lifecycle for a Gemma 4 on-device agent: 1. **Create a consent bundle** while online (one API call) 2. **Verify grant tokens offline** using the JWKS snapshot in the bundle 3. **Log actions to a tamper-evident audit log** (hash-chained, Ed25519-signed) 4. **Sync the audit log** back to the Grantex cloud when connectivity returns The entire verification path runs locally with zero network calls. ## Prerequisites * **Node.js 24 LTS** (TypeScript source tooling) or **Python 3.10+** (Python) * A Grantex developer account and API key ([sign up free](https://grantex.dev)) * A registered agent with the scopes your Gemma 4 model needs ## Step 1: Install ```bash TypeScript theme={null} npm install @grantex/gemma ``` ```bash Python theme={null} pip install grantex-gemma ``` ## Step 2: Create a Consent Bundle (Online) The consent bundle packages everything the device needs to operate offline: a signed grant token, a JWKS snapshot for signature verification, and an Ed25519 key pair for audit signing. ```typescript TypeScript theme={null} import { createConsentBundle, storeBundle, } from '@grantex/gemma'; const bundle = await createConsentBundle({ apiKey: process.env.GRANTEX_API_KEY!, agentId: 'ag_01HXYZ...', userId: 'user_abc123', scopes: ['calendar:read', 'email:send'], offlineTTL: '72h', }); // Encrypt and persist to disk for offline use await storeBundle(bundle, './consent-bundle.enc', process.env.BUNDLE_KEY!); console.log('Bundle created:', bundle.bundleId); console.log('Offline until:', bundle.offlineExpiresAt); ``` ```python Python theme={null} import os from grantex_gemma import create_consent_bundle, store_bundle bundle = create_consent_bundle( api_key=os.environ["GRANTEX_API_KEY"], agent_id="ag_01HXYZ...", user_id="user_abc123", scopes=["calendar:read", "email:send"], offline_ttl="72h", ) # Encrypt and persist to disk for offline use store_bundle(bundle, "./consent-bundle.enc", os.environ["BUNDLE_KEY"]) print(f"Bundle created: {bundle.bundle_id}") print(f"Offline until: {bundle.offline_expires_at}") ``` This is the only step that requires network connectivity. Everything after this runs entirely on-device. ## Step 3: Create an Offline Verifier Load the bundle and create a verifier that uses the embedded JWKS snapshot. ```typescript TypeScript theme={null} import { loadBundle, createOfflineVerifier, } from '@grantex/gemma'; const bundle = await loadBundle('./consent-bundle.enc', process.env.BUNDLE_KEY!); const verifier = createOfflineVerifier({ jwksSnapshot: bundle.jwksSnapshot, requireScopes: ['calendar:read'], clockSkewSeconds: 30, }); ``` ```python Python theme={null} from grantex_gemma import load_bundle, create_offline_verifier bundle = load_bundle("./consent-bundle.enc", os.environ["BUNDLE_KEY"]) verifier = create_offline_verifier( jwks_snapshot=bundle.jwks_snapshot, require_scopes=["calendar:read"], clock_skew_seconds=30, ) ``` ## Step 4: Verify a Token Offline Every time the Gemma 4 agent attempts an action, verify the grant token before allowing it. No network call is made. ```typescript TypeScript theme={null} try { const grant = await verifier.verify(bundle.grantToken); console.log('Agent DID:', grant.agentDID); console.log('Principal:', grant.principalDID); console.log('Scopes:', grant.scopes.join(', ')); console.log('Expires:', grant.expiresAt.toISOString()); } catch (err) { console.error('Verification failed:', err); } ``` ```python Python theme={null} try: grant = verifier.verify(bundle.grant_token) print(f"Agent DID: {grant.agent_did}") print(f"Principal: {grant.principal_did}") print(f"Scopes: {', '.join(grant.scopes)}") print(f"Expires: {grant.expires_at.isoformat()}") except Exception as err: print(f"Verification failed: {err}") ``` ## Step 5: Create an Offline Audit Log Every authorized action should be recorded. The audit log is append-only, Ed25519-signed, and hash-chained so tampering is detectable at sync time. ```typescript TypeScript theme={null} import { createOfflineAuditLog } from '@grantex/gemma'; const auditLog = createOfflineAuditLog({ signingKey: bundle.offlineAuditKey, logPath: './audit-log.jsonl', }); // Log an action const entry = await auditLog.append({ action: 'calendar.read', agentDID: grant.agentDID, grantId: grant.grantId, scopes: grant.scopes, result: 'success', metadata: { eventCount: 12 }, }); console.log(`Audit entry #${entry.seq} — hash: ${entry.hash.slice(0, 12)}...`); ``` ```python Python theme={null} from grantex_gemma import create_offline_audit_log audit_log = create_offline_audit_log( signing_key=bundle.offline_audit_key, log_path="./audit-log.jsonl", ) # Log an action entry = audit_log.append( action="calendar.read", agent_did=grant.agent_did, grant_id=grant.grant_id, scopes=grant.scopes, result="success", metadata={"event_count": 12}, ) print(f"Audit entry #{entry.seq} - hash: {entry.hash[:12]}...") ``` ## Step 6: Sync When Back Online When connectivity returns, upload the offline audit entries to the Grantex cloud. The server verifies the hash chain and Ed25519 signatures, then reconciles the entries with the cloud audit log. ```typescript TypeScript theme={null} import { syncAuditLog } from '@grantex/gemma'; const result = await syncAuditLog(auditLog, { endpoint: 'https://api.grantex.dev', apiKey: process.env.GRANTEX_API_KEY!, bundleId: bundle.bundleId, batchSize: 100, }); console.log(`Synced ${result.syncedCount} entries`); if (result.hasErrors) { console.warn('Sync errors:', result.errors); } ``` ```python Python theme={null} result = audit_log.sync( api_key=os.environ["GRANTEX_API_KEY"], bundle_id=bundle.bundle_id, ) print(f"Synced: {result.accepted} accepted, {result.rejected} rejected") print(f"Revocation status: {result.revocation_status}") ``` ## Expected Output ```text theme={null} Bundle created: cb_01HXYZ... Offline until: 2026-04-06T12:00:00.000Z Agent DID: did:grantex:ag_01HXYZ... Principal: user_abc123 Scopes: calendar:read, email:send Expires: 2026-04-04T00:00:00.000Z Audit entry #1 — hash: a3f29c81b4e2... Synced 1 entries ``` ## Environment Variables | Variable | Default | Description | | - | - | - | | `GRANTEX_API_KEY` | -- | Developer API key from the Grantex portal | | `BUNDLE_KEY` | -- | Passphrase for AES-256-GCM bundle encryption | ## Next Steps * [Offline Authorization](/features/offline-authorization) -- architecture deep-dive and security model * [Consent Bundles](/features/consent-bundles) -- full ConsentBundle reference and lifecycle * [Gemma 4 SDK Reference](/sdks/gemma) -- complete API docs for every export * [Raspberry Pi Guide](/guides/raspberry-pi) -- run a Gemma 4 agent on a Pi 5 with offline auth * [Android Guide](/guides/android-gemma4) -- integrate into an Android app * [iOS Guide](/guides/ios-gemma4) -- integrate into an iOS app with CryptoKit ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Quickstart (Python) Source: https://docs.grantex.dev/examples/quickstart-py End-to-end authorization lifecycle with the Python SDK. ## What it does This example walks through the complete Grantex authorization lifecycle using the Python SDK: 1. **Register an agent** with `calendar:read` and `email:send` scopes 2. **Authorize in sandbox mode** -- the sandbox API key auto-approves and returns a `code` immediately (no redirect required) 3. **Exchange the code** for a signed RS256 grant token 4. **Verify the token offline** against the local JWKS endpoint 5. **Log an audit entry** recording a `calendar.read` action 6. **Revoke the token** and confirm revocation via an online verify check ## Prerequisites * **Python 3.9+** * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/quickstart-py pip install -r requirements.txt python main.py ``` ## Expected output ```text theme={null} Agent registered: ag_01HXYZ... did:grantex:ag_01HXYZ... Auth request: areq_01HXYZ... Sandbox auto-approved, code: 01JXYZ... Grant token received, grantId: grnt_01HXYZ... Scopes: calendar:read, email:send Token verified offline: principalId: test-user-001 agentDid: did:grantex:ag_01HXYZ... scopes: calendar:read, email:send Audit entry logged: alog_01HXYZ... Token revoked. Post-revocation verify: revoked Done! Full authorization lifecycle complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/quickstart-py/main.py`](https://github.com/mishrasanjeev/grantex/tree/main/examples/quickstart-py). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Quickstart (TypeScript) Source: https://docs.grantex.dev/examples/quickstart-ts End-to-end authorization lifecycle with the TypeScript SDK. ## What it does This example walks through the complete Grantex authorization lifecycle using the TypeScript SDK: 1. **Register an agent** with `calendar:read` and `email:send` scopes 2. **Authorize in sandbox mode** -- the sandbox API key auto-approves and returns a `code` immediately (no redirect required) 3. **Exchange the code** for a signed RS256 grant token 4. **Verify the token offline** against the local JWKS endpoint 5. **Log an audit entry** recording a `calendar.read` action 6. **Revoke the token** and confirm revocation via an online verify check ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/quickstart-ts npm install npm start ``` ## Expected output ```text theme={null} Agent registered: ag_01HXYZ... did:grantex:ag_01HXYZ... Auth request: areq_01HXYZ... Sandbox auto-approved, code: 01JXYZ... Grant token received, grantId: grnt_01HXYZ... Scopes: calendar:read, email:send Token verified offline: principalId: test-user-001 agentDid: did:grantex:ag_01HXYZ... scopes: calendar:read, email:send Audit entry logged: alog_01HXYZ... Token revoked. Post-revocation verify: revoked Done! Full authorization lifecycle complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | ## Source code The full source is in [`examples/quickstart-ts/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/quickstart-ts). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Token Refresh & Rotation Source: https://docs.grantex.dev/examples/token-expiry-refresh Active-grant refresh rotation, lost-response recovery, and the expired-grant re-authorization boundary. ## What it does This example demonstrates the complete token refresh lifecycle: 1. **Authorize an active grant** using the `expiresIn` parameter 2. **Use the token** successfully while the grant remains active (local JWKS-based + online verification) 3. **Refresh the token** — get a new JWT and rotated refresh token with the same `grantId` and `expiresAt` 4. **Use the refreshed token** — verify it works with full scope access 5. **Refresh response recovery** — retry the previous refresh token immediately to recover the already-rotated token 6. **Refresh token rotation** — demonstrate that old refresh tokens cannot keep being reused 7. **Expired grant boundary** — demonstrate that an expired grant must be re-authorized rather than refreshed ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/token-expiry-refresh npm install npm start ``` ## Expected output ```text theme={null} === Token Refresh & Rotation Demo === Agent registered: ag_01HXYZ... --- Authorizing an active grant (5m) --- Grant token received: grantId: grnt_01... scopes: calendar:read, email:send expiresAt: 2026-03-30T12:05:00.000Z refreshToken: ref_01HXYZ... --- Using token before expiry --- Offline verification: PASSED Online verification: valid = true --- Refreshing token --- Token refreshed successfully! grantId: grnt_01... (same as original) expiresAt: 2026-03-30T12:05:00.000Z (grant lifetime unchanged) scopes: calendar:read, email:send --- Using refreshed token --- Offline verification: PASSED Online verification: valid = true --- Refresh response recovery window --- Retrying the original refresh token immediately (simulates a lost HTTP response)... Recovered refresh token matches first rotation: true --- Refresh token rotation (single-use enforcement) --- Advancing the rotation chain with the current refresh token... Attempting to reuse the original refresh token after the chain moved forward... Blocked! Original refresh token rejected. Reason: Refresh tokens are single-use; recovery only works before the rotated child is used. --- Expired grant boundary --- Creating a short-lived grant (3s) to show the re-authorization boundary... Waiting 5s for the grant to expire... Online verification after expiry: valid = false (expected: false) Refresh after grant expiry blocked. Reason: Refresh rotates credentials for an active grant; expired grants require re-authorization. Done! Token refresh lifecycle complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | | `GRANT_TTL` | `5m` | Active grant lifetime used for refresh rotation | | `EXPIRED_GRANT_TTL` | `3s` | Short grant lifetime used only for the expired-grant boundary check | | `TOKEN_TTL` | unset | Backward-compatible alias for `EXPIRED_GRANT_TTL` | ## Refresh boundary `expiresIn` controls the underlying grant lifetime. Refresh rotates credentials for that active grant and does not extend the grant's `expiresAt`. If the refresh response is lost after the server commits rotation, retry the previous refresh token immediately; Grantex can return the already-rotated refresh token during the five-minute recovery window. After the grant expires, the caller must re-authorize. ## Source code The full source is in [`examples/token-expiry-refresh/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/token-expiry-refresh). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Vercel AI Chatbot Source: https://docs.grantex.dev/examples/vercel-ai-chatbot Scope-enforced tools with audit logging using Vercel AI SDK. ## What it does This example demonstrates the `@grantex/vercel-ai` integration by building scope-enforced tools with the Vercel AI SDK: 1. **Register an agent** and obtain a grant token via the sandbox flow 2. **Create scoped tools** using `createGrantexTool` with Zod schemas for typed parameters (`calendar:read`, `email:send`) 3. **Wrap tools with `withAuditLogging`** to automatically log every execution to the Grantex audit trail 4. **Invoke tools** via `generateText` (if `OPENAI_API_KEY` is set) or directly 5. **Demonstrate `GrantexScopeError`** -- attempting to create a tool with `storage:delete` (a scope not in the grant) throws a `GrantexScopeError` with `requiredScope` and `grantedScopes` properties 6. **Inspect the audit trail** to see the logged actions ## Prerequisites * **Node.js 24 LTS** (the tested repository toolchain) * **Docker** (Docker Desktop or Docker Engine with Compose) ## Run Start the local Grantex stack from the repository root: ```bash theme={null} docker compose up --build ``` In a separate terminal, run the example: ```bash theme={null} cd examples/vercel-ai-chatbot npm install npm start ``` To use a real LLM with `generateText` instead of direct tool invocation, set `OPENAI_API_KEY`: ```bash theme={null} OPENAI_API_KEY=sk-... npm start ``` ## Expected output Without `OPENAI_API_KEY` (direct invocation): ```text theme={null} Agent registered: ag_01HXYZ... Grant token received, grantId: grnt_01HXYZ... Tools created: read_calendar, send_email Audit logging attached --- Invoking tools directly (no OPENAI_API_KEY set) --- Calendar result: {"date":"today","events":[{"title":"Team standup","time":"9:00 AM"},{"title":"Design review","time":"2:00 PM"}]} Email result: {"sent":true,"to":"alice@example.com","subject":"Today's events","bodyLength":49} --- Testing scope enforcement --- GrantexScopeError caught: Required scope: storage:delete Granted scopes: calendar:read, email:send --- Audit trail --- [success] read_calendar — 2026-02-28T12:00:00.000Z [success] send_email — 2026-02-28T12:00:01.000Z Done! Vercel AI integration demo complete. ``` ## Environment variables | Variable | Default | Description | | - | - | - | | `GRANTEX_URL` | `http://localhost:3001` | Base URL of the Grantex auth service | | `GRANTEX_API_KEY` | `sandbox-api-key-local` | API key. Use a sandbox key for auto-approval | | `OPENAI_API_KEY` | *(none)* | Optional. Set to use `generateText` with a real LLM | ## Source code The full source is in [`examples/vercel-ai-chatbot/src/index.ts`](https://github.com/mishrasanjeev/grantex/tree/main/examples/vercel-ai-chatbot). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Irregularity Detection Source: https://docs.grantex.dev/features/anomaly-detection What Grantex detects, when it runs, and how an account chooses alert-only or revocation. ## What runs today `POST /v1/anomalies/detect` is an authenticated, **explicit** detection run. It is not an always-on background monitor. A caller must schedule or invoke it. It evaluates the requesting account's data for: | Finding | Current threshold | Severity | | - | - | - | | `rate_spike` | More than 50 audit actions by one agent in the last hour | High | | `high_failure_rate` | More than 20% failed or blocked among at least five audit entries in 24 hours | Medium | | `new_principal` | A newly issued grant for an agent/principal pair with no earlier grant | Low | | `off_hours_activity` | More than 10 audit entries by one agent between 22:00 and 06:00 UTC in 24 hours | Low | The endpoint persists findings and replaces that account's unacknowledged findings from the previous run. Acknowledged findings remain. The separate `/v1/anomaly/rules` catalog lists additional built-in definitions, but those definitions are **not** all evaluated by this detector. Custom rules and `/v1/anomaly/channels` are configuration records; the legacy detector does not execute custom rule conditions or dispatch to channel records. Do not rely on either as an active security control. ## Account response policy With `IRREGULARITY_RESPONSE_POLICY_ENABLED=true`, the account can choose one response for this detector: | Mode | Effect when a high or critical finding identifies an agent | | - | - | | `revoke_agent_grants` (default) | Revoke that agent's active, unexpired grants in this account. This may affect many principals when they share an agent. | | `alert_only` | Persist findings and emit `anomaly.detected` events, but do not revoke grants through this detector. | The mode applies to the **whole account**, not an individual agent or grant. It does not turn off manual revocation, independent policy checks, or other security controls. A policy change is recorded in account history and emits an `irregularity.policy.updated` event on a best-effort basis. If the flag is off, the policy endpoints return 404 and the detector retains its previous automatic-revocation behavior. If the flag is on but policy state is unavailable, detection fails closed with 503. ```bash theme={null} curl -H "Authorization: Bearer $GRANTEX_API_KEY" \ https://api.grantex.dev/v1/irregularities/response-policy curl -X PATCH https://api.grantex.dev/v1/irregularities/response-policy \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode":"alert_only"}' ``` Read the policy back before relying on alert-only mode. The portal's **Irregularities** page provides the same account-level control when the operator has enabled it. ## Running and reviewing detection ```bash theme={null} curl -X POST https://api.grantex.dev/v1/anomalies/detect \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` The response includes `responseMode`, `total`, `autoRevokedGrants`, and `anomalies`. Use `GET /v1/anomalies` to read persisted findings and `PATCH /v1/anomalies/{id}/acknowledge` to acknowledge one. The separate `/v1/anomaly/alerts` routes provide the alert lifecycle (`open`, `acknowledged`, `resolved`). When the response-policy flag is enabled, each persisted finding from a detection run emits an `anomaly.detected` event. Subscribe through the existing event stream or a standard Grantex webhook subscription for that event type. Delivery is best-effort: query stored findings if delivery is critical to your workflow. Creating a record under `/v1/anomaly/channels` does **not** send Slack, email, or webhook notifications. PagerDuty and Datadog are not accepted channel types there. `GET /v1/anomaly/metrics?window=24h` returns aggregate stored-alert counts. Supported windows are `1h`, `6h`, and `24h`; this endpoint is distinct from the Prometheus `/metrics` endpoint. For rollout and a runnable example, see [Irregularity Detection Setup](/guides/anomaly-detection-setup). Response-policy helpers are published in TypeScript `@grantex/sdk@0.8.2`, Python `grantex==0.7.2`, and Go `v0.4.3`. See [Release Status](/release-status). ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Consent Bundles Source: https://docs.grantex.dev/features/consent-bundles Consent bundles package a grant token, JWKS snapshot, and audit signing key for offline-capable authorization. ## What is a Consent Bundle? A consent bundle is a self-contained package that enables offline authorization. It bundles everything a device needs to verify grant tokens, enforce scopes, and produce tamper-evident audit logs without any network connectivity. Consent bundles are issued by the Grantex API after a standard authorization flow completes. The end-user must have already consented to the requested scopes before a bundle can be created. ## ConsentBundle Fields | Field | Type | Description | | - | - | - | | `bundleId` | `string` | Unique identifier for this bundle (e.g. `cb_01HXYZ...`) | | `grantToken` | `string` | RS256-signed Grantex grant token (JWT) | | `jwksSnapshot` | `JWKSSnapshot` | Server's public keys for offline verification | | `offlineAuditKey` | `OfflineAuditKey` | Ed25519 key pair for signing audit entries | | `checkpointAt` | `number` | Unix timestamp (ms) of last successful cloud sync | | `syncEndpoint` | `string` | URL for uploading audit entries when reconnected | | `offlineExpiresAt` | `string` | ISO-8601 timestamp after which offline operation is disallowed | ### JWKSSnapshot | Field | Type | Description | | - | - | - | | `keys` | `JWK[]` | Array of JSON Web Keys from the server's JWKS endpoint | | `fetchedAt` | `string` | ISO-8601 timestamp when keys were fetched | | `validUntil` | `string` | ISO-8601 timestamp after which the snapshot should be refreshed | ### OfflineAuditKey | Field | Type | Description | | - | - | - | | `publicKey` | `string` | PEM-encoded Ed25519 public key (also stored server-side) | | `privateKey` | `string` | PEM-encoded Ed25519 private key (device-only) | | `algorithm` | `string` | Algorithm identifier (default `'Ed25519'`) | ## Lifecycle A consent bundle follows a four-stage lifecycle: ``` CREATE ──► USE ──► SYNC ──► REFRESH/EXPIRE │ │ │ │ online offline online online ``` ### 1. Create (Online) The developer calls `POST /v1/consent-bundles` (or `createConsentBundle()` in the SDK) while the device is online. The server: 1. Validates the API key and agent registration 2. Verifies the end-user has consented to the requested scopes 3. Issues a fresh grant token 4. Snapshots the current JWKS 5. Generates an Ed25519 key pair (public key stored server-side, both keys returned) 6. Returns the complete `ConsentBundle` ```typescript TypeScript theme={null} import { createConsentBundle, storeBundle } from '@grantex/gemma'; const bundle = await createConsentBundle({ apiKey: 'gx_...', agentId: 'ag_01HXYZ...', userId: 'user_abc123', scopes: ['calendar:read', 'email:send'], offlineTTL: '72h', }); await storeBundle(bundle, '/data/bundle.enc', process.env.BUNDLE_KEY!); ``` ```python Python theme={null} from grantex_gemma import create_consent_bundle, store_bundle bundle = create_consent_bundle( api_key="gx_...", agent_id="ag_01HXYZ...", user_id="user_abc123", scopes=["calendar:read", "email:send"], offline_ttl="72h", ) store_bundle(bundle, "/data/bundle.enc", os.environ["BUNDLE_KEY"]) ``` ### 2. Use (Offline) While offline, the device loads the bundle and uses its contents: * **`grantToken`** is passed to `createOfflineVerifier` for JWT verification * **`jwksSnapshot`** provides the signing keys for signature validation * **`offlineAuditKey`** signs each audit entry for tamper evidence * **`offlineExpiresAt`** is checked before each operation; the device must stop operating after this time ### 3. Sync (Online) When connectivity returns, the device uploads its offline audit entries via `POST /v1/audit/offline-sync`. The server: 1. Validates the Ed25519 signatures using the stored public key 2. Verifies the hash chain integrity 3. Checks grant revocation status 4. Stores accepted entries in the cloud audit log 5. Returns a `SyncResult` with acceptance/rejection counts and revocation status ### 4. Refresh or Expire Bundles have a finite lifetime controlled by `offlineTTL`. When the TTL is running low: * **`shouldRefresh(bundle)`** returns `true` when less than 20% of the TTL remains * **`refreshBundle(bundle, apiKey)`** calls the server to get a fresh bundle with extended `offlineExpiresAt`, a new JWKS snapshot, and rotated audit keys If the device cannot refresh before the bundle expires, it must stop offline operations and wait for connectivity to create a new bundle. ```typescript theme={null} import { shouldRefresh, refreshBundle, storeBundle } from '@grantex/gemma'; // Check periodically when connectivity is available if (shouldRefresh(bundle)) { const fresh = await refreshBundle(bundle, apiKey); await storeBundle(fresh, '/data/bundle.enc', encryptionKey); bundle = fresh; } ``` ## Storage Options ### Encrypted File (Default) AES-256-GCM encryption with a passphrase. The encryption key is derived from the passphrase via SHA-256. File format: ``` [12-byte IV][16-byte GCM auth tag][ciphertext] ``` Best for: servers, Raspberry Pi, Linux devices, CI environments. ### Keychain (macOS / iOS) On Apple platforms, use the system Keychain for hardware-backed storage. The `storage: 'keychain'` option (when supported) stores the bundle in the Keychain with `kSecAttrAccessibleWhenUnlocked` protection. Best for: macOS and iOS applications. ### Secure Enclave On devices with hardware security modules, the `storage: 'secure-enclave'` option stores the bundle encryption key in the Secure Enclave and only the ciphertext on disk. Requires platform-specific setup. Best for: iOS devices with Secure Enclave, Android devices with StrongBox. ### Android EncryptedSharedPreferences Android applications should use `EncryptedSharedPreferences` from the Jetpack Security library. See the [Android guide](/guides/android-gemma4) for details. ## Encryption The bundle contains sensitive material (grant token, Ed25519 private key). It must always be encrypted at rest. **What is encrypted:** * The entire `ConsentBundle` JSON is serialized and encrypted as a single unit * AES-256-GCM provides both confidentiality and integrity * The GCM auth tag detects any modification to the ciphertext **What is NOT encrypted:** * The file path on disk is not hidden * The file size is visible (reveals approximate bundle size) * The encryption key derivation uses SHA-256 of the passphrase -- use a strong passphrase or a hardware-generated key **`BundleTamperedError`** is thrown if: * The file is too short (missing IV or auth tag) * The GCM auth tag does not match (wrong key or modified file) * The decrypted JSON is malformed ## Refresh Logic The `shouldRefresh()` function uses a simple heuristic: if less than 20% of the total TTL remains, it is time to refresh. ``` Total TTL = offlineExpiresAt - checkpointAt Remaining = offlineExpiresAt - now shouldRefresh = (remaining / totalTTL) < 0.2 ``` For a 72-hour TTL, this triggers a refresh at approximately the 57.6-hour mark (14.4 hours remaining). **Refresh strategy recommendations:** | Scenario | `offlineTTL` | Refresh strategy | | - | - | - | | Mobile app with intermittent connectivity | `24h` | Check `shouldRefresh` on each app foreground | | Raspberry Pi with daily Wi-Fi window | `72h` | Cron job during expected connectivity window | | Laptop with frequent connectivity | `8h` | Background timer every 30 minutes | | Air-gapped with weekly data transfer | `168h` | Include refresh in data transfer protocol | ## Bundle Revocation Bundles can be revoked server-side via `POST /v1/consent-bundles/:id/revoke`. Revocation is checked at: 1. **Bundle creation** -- a revoked grant cannot be bundled 2. **Sync time** -- the sync response includes `revocation_status` 3. **Status check** -- `GET /v1/consent-bundles/:id/revocation-status` returns the current status A revoked bundle's grant token remains cryptographically valid (the signature still verifies), but the device should stop using it once it learns about the revocation at sync time. ## Listing Bundles Developers can list all bundles for their account: ```bash theme={null} curl -H "Authorization: Bearer gx_..." \ https://api.grantex.dev/v1/consent-bundles ``` The response includes bundle metadata (ID, agent, scopes, expiry, revocation status) but does not include the grant token or private keys. ## Next Steps * [Offline Authorization](/features/offline-authorization) -- architecture and security model * [Quickstart: Gemma 4](/examples/quickstart-gemma) -- hands-on tutorial * [Consent Bundles API](/api-reference/consent-bundles) -- REST API endpoints * [Gemma 4 SDK Reference](/sdks/gemma) -- full API reference ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # DID Infrastructure Source: https://docs.grantex.dev/features/did-infrastructure W3C Decentralized Identifier (DID) infrastructure for independent credential verification. Resolve did:web:grantex.dev to verify any Grantex-issued credential. ## Overview Grantex publishes a W3C Decentralized Identifier (DID) document that enables any party to verify Grantex-issued Verifiable Credentials without contacting the Grantex API. The DID document contains the public keys used to sign VCs and is hosted at a well-known URL following the `did:web` method. ## did:web:grantex.dev The Grantex DID follows the [did:web method specification](https://w3c-ccg.github.io/did-method-web/). The DID `did:web:grantex.dev` resolves to: ``` https://grantex.dev/.well-known/did.json ``` On the auth service, the DID document is served at: ``` https://api.grantex.dev/.well-known/did.json ``` Both URLs return the same document. The endpoint is public and requires no authentication. ## DID Document Structure ```json theme={null} { "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1" ], "id": "did:web:grantex.dev", "verificationMethod": [ { "id": "did:web:grantex.dev#key-1", "type": "JsonWebKey2020", "controller": "did:web:grantex.dev", "publicKeyJwk": { "kty": "RSA", "alg": "RS256", "n": "...", "e": "AQAB", "kid": "key-1", "use": "sig" } }, { "id": "did:web:grantex.dev#key-2", "type": "JsonWebKey2020", "controller": "did:web:grantex.dev", "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "...", "kid": "key-2", "use": "sig" } } ], "authentication": [ "did:web:grantex.dev#key-1" ], "assertionMethod": [ "did:web:grantex.dev#key-1", "did:web:grantex.dev#key-2" ], "service": [ { "id": "did:web:grantex.dev#grantex-api", "type": "GrantexAuthService", "serviceEndpoint": "https://api.grantex.dev" }, { "id": "did:web:grantex.dev#status-list", "type": "StatusList2021Service", "serviceEndpoint": "https://api.grantex.dev/v1/credentials/status" } ] } ``` ## Verification Methods The DID document contains two verification methods: | Key | Algorithm | Purpose | | - | - | - | | `key-1` | RS256 (RSA) | Primary signing key for grant tokens and VCs. Same key published in the JWKS endpoint. | | `key-2` | Ed25519 | Secondary signing key for future use with EdDSA-based VCs. | ### Relationship to JWKS The RS256 key in the DID document is the same key available via the JWKS endpoint (`/.well-known/jwks.json`). The two endpoints serve different ecosystems: | Endpoint | Format | Used by | | - | - | - | | `/.well-known/jwks.json` | JSON Web Key Set | JWT libraries verifying grant tokens | | `/.well-known/did.json` | DID Document | VC libraries verifying Verifiable Credentials | Both resolve to the same underlying RSA key pair. If you are only verifying grant tokens (JWTs), use the JWKS endpoint. If you are verifying Verifiable Credentials, use the DID document. ## How Verification Works When a verifier receives a Grantex-issued Verifiable Credential (VC-JWT), the verification process is: ``` Verifier Public Internet │ │ │── 1. Decode VC-JWT header ──────────────────────│ │ (extract "iss": "did:web:grantex.dev") │ │ │ │── 2. Resolve DID ──────────────────────────────►│ │ GET https://grantex.dev/.well-known/did.json │ │◄── DID document with public keys ───────────────│ │ │ │── 3. Extract matching key (by kid) ─────────────│ │ │ │── 4. Verify JWT signature ──────────────────────│ │ (RS256 or Ed25519) │ │ │ │── 5. Check credentialStatus ───────────────────►│ │ GET .../v1/credentials/status/:id │ │◄── StatusList2021 bitstring ────────────────────│ │ │ │── 6. Check bit at statusListIndex ──────────────│ │ (0 = active, 1 = revoked) │ │ │ │── Verification complete ────────────────────────│ ``` ### Example: Verifying Without the Grantex SDK Any JWT library can verify Grantex VCs. Here is a minimal example using `jose` (TypeScript) and `PyJWT` (Python): ```typescript TypeScript theme={null} import * as jose from 'jose'; // 1. Fetch the DID document const didDoc = await fetch('https://api.grantex.dev/.well-known/did.json') .then(r => r.json()); // 2. Extract the RS256 key const keyEntry = didDoc.verificationMethod.find( (vm: any) => vm.publicKeyJwk.alg === 'RS256' ); const publicKey = await jose.importJWK(keyEntry.publicKeyJwk, 'RS256'); // 3. Verify the VC-JWT const { payload } = await jose.jwtVerify(vcJwt, publicKey, { issuer: 'did:web:grantex.dev', }); console.log(payload.vc.credentialSubject); // { id: 'did:grantex:ag_01...', scopes: ['calendar:read'], ... } // 4. Check revocation status const statusUrl = payload.vc.credentialStatus.statusListCredential; const statusList = await fetch(statusUrl).then(r => r.json()); // Decode the bitstring and check the index ``` ```python Python theme={null} import jwt import requests # 1. Fetch the DID document did_doc = requests.get("https://api.grantex.dev/.well-known/did.json").json() # 2. Extract the RS256 key key_entry = next( vm for vm in did_doc["verificationMethod"] if vm["publicKeyJwk"].get("alg") == "RS256" ) public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key_entry["publicKeyJwk"]) # 3. Verify the VC-JWT payload = jwt.decode( vc_jwt, public_key, algorithms=["RS256"], issuer="did:web:grantex.dev", ) print(payload["vc"]["credentialSubject"]) # 4. Check revocation status status_url = payload["vc"]["credentialStatus"]["statusListCredential"] status_list = requests.get(status_url).json() # Decode the bitstring and check the index ``` ## Agent DIDs Every agent registered in Grantex receives a DID in the format `did:grantex:ag_XXXX`. This DID serves as the agent's cryptographic identity and appears in: * The `agt` claim of grant tokens * The `credentialSubject.id` field of Verifiable Credentials * Audit log entries Agent DIDs are issued by Grantex and are not self-sovereign. They identify the agent within the Grantex ecosystem and are resolvable via the Grantex API. ## Service Endpoints The DID document declares two service endpoints: ### GrantexAuthService The primary Grantex API endpoint. Verifiers can use this to access the token verification API, JWKS, and other endpoints. ```json theme={null} { "id": "did:web:grantex.dev#grantex-api", "type": "GrantexAuthService", "serviceEndpoint": "https://api.grantex.dev" } ``` ### StatusList2021Service The base URL for StatusList2021 credentials. Verifiers append the status list ID to check revocation. ```json theme={null} { "id": "did:web:grantex.dev#status-list", "type": "StatusList2021Service", "serviceEndpoint": "https://api.grantex.dev/v1/credentials/status" } ``` ## Self-Hosting When self-hosting Grantex, the DID document is served from your own domain. The `did:web` method resolves based on the domain in the DID string: | DID | Resolves to | | - | - | | `did:web:grantex.dev` | `https://grantex.dev/.well-known/did.json` | | `did:web:auth.example.com` | `https://auth.example.com/.well-known/did.json` | Configure the `ISSUER_DID` environment variable in your auth service deployment to set the DID that appears in issued VCs. The auth service automatically generates the DID document from the signing key. The SDKs' `issuerDid` / `issuer_did` verification option reads grant-token keys from the JWK Set next to that location (`did:web:issuer.example` reads `https://issuer.example/.well-known/jwks.json`). From the releases after `grantex` 0.6.0 and `@grantex/sdk` 0.7.0, setting `boundedJwksFetch: true` / `bounded_jwks_fetch=True` also checks the DID as the did:web method requires: it accepts only a fully qualified domain name written in ASCII (an internationalized name in its `xn--` form), never an IP address, and it also refuses `localhost` and names under `.local`, `.home.arpa` and `.internal`. The option is off by default in these releases; a later major release turns it on by default, with `false` / `False` as the opt-out. With it on, verifiers of a deployment on a private network set `jwksUri` and `issuer` instead. See [Offline verification](/sdks/typescript/offline-verification#did-issuers). ## W3C Standards Alignment | Standard | Grantex Implementation | | - | - | | [DID Core v1.0](https://www.w3.org/TR/did-core/) | Full compliance for `did:web` method | | [did:web Method](https://w3c-ccg.github.io/did-method-web/) | `/.well-known/did.json` hosting | | [JsonWebKey2020](https://w3c-ccg.github.io/lds-jws2020/) | Verification method format | | [VC Data Model v2.0](https://www.w3.org/TR/vc-data-model-2.0/) | Credential issuance and verification | | [StatusList2021](https://www.w3.org/TR/vc-status-list/) | Revocation checking | ## API Reference | Method | Endpoint | Auth | Description | | - | - | - | - | | `GET` | `/.well-known/did.json` | None (public) | W3C DID document | | `GET` | `/.well-known/jwks.json` | None (public) | JSON Web Key Set (same RSA key) | | `GET` | `/v1/credentials/status/:id` | None (public) | StatusList2021 credential | ## Next Steps * [Verifiable Credentials](/features/verifiable-credentials) -- issue and verify W3C VCs * [FIDO2 / WebAuthn](/features/fido-webauthn) -- human presence verification * [Grant Token](/concepts/grant-token) -- the standard JWT-based grant token * [Token Verification](/guides/token-verification) -- offline JWT verification via JWKS ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # DPDP & GDPR Evidence Module Source: https://docs.grantex.dev/features/dpdp-compliance Consent records bound to grants and versioned notices, withdrawal, erasure, grievances, a breach register and exports that help a Data Fiduciary evidence its DPDP Act, GDPR and EU AI Act obligations. ## Overview Grantex's DPDP routes keep the records a Data Fiduciary needs to evidence its obligations under India's Digital Personal Data Protection Act 2023 (DPDP Act) and the Digital Personal Data Protection Rules 2025 (DPDP Rules), and to answer GDPR access requests, for AI agent deployments. Each consent record is tied to the Grantex grant that authorised the agent, and every DPDP state change is written to the developer's tamper-evident audit chain. **What Grantex is, and is not.** Grantex is a technical control that helps a Data Fiduciary keep and produce evidence. It is **not** a Consent Manager registered with the Data Protection Board of India, it does **not** notify the Board or Data Principals on your behalf, it does not decide whether your processing is lawful, and this page is not legal advice. The obligations stay with the Data Fiduciary. Consult qualified counsel. As of 30 September 2026 most DPDP obligations are **not yet in force**. The [DPDP Rules 2025](https://www.meity.gov.in/static/uploads/2025/11/53450e6e5dc0bfa85ebd78686cadad39.pdf) (G.S.R. 846(E), notified 13 November 2025) bring the notice, consent, security, breach, erasure, rights and penalty provisions into force on **13 May 2027**, and Consent Manager registration on 13 November 2026. See [DPDP Act 2023](/compliance/dpdp-act-2023) for the section-by-section mapping and [EU AI Act](/compliance/eu-ai-act) for the EU timeline. ## Where the features live | Surface | How to reach it | Notes | | - | - | - | | REST API | `https://api.grantex.dev/v1/dpdp/*`, or your self-hosted auth service | Every feature on this page. See the [API reference](/api-reference/dpdp/create-consent-record) | | TypeScript SDK | `import { Grantex } from '@grantex/sdk'`, then `grantex.dpdp.*` | Consent records, notices, withdrawal, erasure, grievances, exports | | Python SDK | `from grantex import Grantex`, then `client.dpdp.*` | The same set; create calls take a params dataclass | | Go SDK | `client.DPDP.*` | The same set | | CLI | `grantex dpdp ...` | See [CLI](/integrations/cli) | | `@grantex/dpdp` | Standalone functions and local helpers | See the package README | The SDKs and CLI cover the original routes. The newer routes and fields (notice lists and reads, grievance lists and updates, erasure request reads, the breach register, `responsePeriodDays`, `consentNoticeVersion`, the structured notice fields and the `eu-ai-act-evidence` export) are shown below as REST calls; call them that way unless your SDK version lists them. [Release Status](/release-status) says which versions are published. ## Consent notices A consent notice is registered once per `noticeId`, `version` and `language`. Grantex stores its content and a SHA-256 `contentHash`. A consent record names the version shown and carries that hash in its signed proof, so a later change to the notice text is detectable. A notice can carry structured fields for what DPDP Rules r.3 asks a notice under DPDP Act s.5 to contain: `itemisedPersonalData`, `purposeDetails` (with the goods or services each purpose enables), `withdrawalUrl`, `rightsUrl`, `boardComplaintUrl`, and a `contact` for the person able to answer questions (s.8(9), r.9). Every response includes a `validation` block listing which r.3 elements are present or missing, and whether the language is English or an Eighth Schedule language. The check is for presence, not quality: it is evidence for your own review. With `DPDP_NOTICE_REQUIRE_RULE3=true` an incomplete notice is refused with `400 NOTICE_INCOMPLETE`. Details: [Consent Notice Content](/api-reference/dpdp/consent-notice-content). ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/consent-notices \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "noticeId": "shopping-assistant", "version": "3", "language": "hi-IN", "title": "Shopping assistant: how we use your data", "content": "...the full notice text shown to the user...", "purposes": [{ "code": "order_placement", "description": "Place orders you approve" }], "itemisedPersonalData": [ { "category": "contact", "description": "Email address for order confirmations" }, { "category": "order_history", "description": "Items you ordered through the assistant" } ], "purposeDetails": [ { "code": "order_placement", "description": "Place orders you approve", "goodsOrServices": "Ordering from merchant.example" } ], "withdrawalUrl": "https://app.example.com/privacy/withdraw", "rightsUrl": "https://app.example.com/privacy/rights", "boardComplaintUrl": "https://app.example.com/privacy/complain-to-the-board", "contact": { "designation": "Data Protection Officer", "email": "dpo@example.com" } }' ``` Grantex does not render the notice to the user. Showing it, in the language the user chose, before asking for consent is your application's job. ## Consent records A consent record binds a Data Principal's consent to an **active grant** and to the **notice version** shown. Create the grant first, through the normal Grantex authorisation flow, then record the consent: ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const record = await grantex.dpdp.createConsentRecord({ grantId: 'grnt_01HXYZ...', dataPrincipalId: 'user_abc123', purposes: [{ code: 'order_placement', description: 'Place orders you approve' }], consentNoticeId: 'shopping-assistant', processingExpiresAt: '2027-12-31T23:59:59Z', }); console.log(record.recordId, record.consentProof?.proofJwt); ``` ```python Python theme={null} from grantex import CreateConsentRecordParams, Grantex client = Grantex(api_key="gx_...") record = client.dpdp.create_consent_record( CreateConsentRecordParams( grant_id="grnt_01HXYZ...", data_principal_id="user_abc123", purposes=[{"code": "order_placement", "description": "Place orders you approve"}], consent_notice_id="shopping-assistant", processing_expires_at="2027-12-31T23:59:59Z", ) ) ``` ```bash CLI theme={null} grantex dpdp consent create \ --grant-id grnt_01HXYZ... \ --principal-id user_abc123 \ --notice-id shopping-assistant \ --processing-expires-at 2027-12-31T23:59:59Z \ --purposes '[{"code":"order_placement","description":"Place orders you approve"}]' ``` What the server does: * Refuses a grant that is not the developer's, or is revoked, suspended or expired (`400 INVALID_GRANT`). With `DPDP_ENFORCE_GRANT_PRINCIPAL=true` it also refuses a `dataPrincipalId` that is not the grant's principal. * Uses the latest version of the notice unless `consentNoticeVersion` pins one; `consentNoticeLanguage` picks the language when that version exists in several. * Signs a **consent proof**: a compact JWS (EdDSA over Ed25519) covering the record id, grant, principal, notice id, version and hash, the purpose codes and the consent time. It has no `exp`, because it is evidence the fiduciary may need for as long as it keeps the record (DPDP Act s.6(10) puts the burden of proving consent on the fiduciary). Verify it with the key its `kid` names in the JWKS at `jwksUri`. If it cannot be signed, no record is created (`503 CONSENT_PROOF_UNAVAILABLE`). * Sets `retentionUntil` to 30 days after `processingExpiresAt`. This is a product default, not a legal retention period, and Grantex does not delete anything when it passes. Record statuses are `active`, `withdrawn`, `expired` and `erased`. A record becomes `expired` only when the consent expiry worker is on (`DPDP_CONSENT_EXPIRY_ENABLED=true`); with `DPDP_CONSENT_EXPIRY_REVOKES_GRANT=true` the worker also revokes the grant. **Purpose limitation.** The record stores the purposes the principal agreed to and the grant's scopes. Grantex does not compare an agent's later actions with those purposes. What an agent can do is bounded by the grant's scopes, and a grant can carry a purpose that tools check (see [Purpose-Bound Grants](/concepts/purpose-bound-grants)). Choosing scopes that match the consented purposes is your design decision. Reads (`GET /v1/dpdp/consent-records`, `/v1/dpdp/consent-records/{recordId}` and `/v1/dpdp/data-principals/{principalId}/records`) have no side effects. Record lists return what they always did (up to 100 records unfiltered, every match for a data principal) unless you send `limit` (up to 200) or `cursor`, in which case they page and return `nextCursor`; `totalRecords` is always the full count. ## Withdrawal Under DPDP Act s.6(4) a Data Principal may withdraw consent at any time, as easily as it was given. Offering that means, for example a control next to the one that gave consent, is your application's job; it then calls: ```typescript TypeScript theme={null} const result = await grantex.dpdp.withdrawConsent('crec_01HXYZ...', { reason: 'Principal withdrew consent in account settings', revokeGrant: true, deleteProcessedData: true, }); // result.status === 'withdrawn'; result.dataDeleted === false ``` ```python Python theme={null} result = client.dpdp.withdraw_consent( "crec_01HXYZ...", reason="Principal withdrew consent in account settings", revoke_grant=True, ) ``` ```bash CLI theme={null} grantex dpdp consent withdraw crec_01HXYZ... \ --reason "Principal withdrew consent in account settings" --revoke-grant ``` * `reason` is required. Only an `active` record can be withdrawn: a second withdrawal is `409 ALREADY_WITHDRAWN`, and erased or expired records answer `409 CONSENT_ERASED` or `409 CONSENT_EXPIRED`. * `revokeGrant: true` revokes the record's own grant (`revokedAt`, the revocation cache and a `grant.revoked` event); grants delegated from it keep working. An operator who sets `DPDP_REVOCATION_CASCADE=true` gets the same cascade as `DELETE /v1/grants/{id}` instead: delegated grants, credentials and wallet reservations. Without `revokeGrant` the grant stays active, unless the operator sets `DPDP_WITHDRAWAL_REVOKES_GRANT=true`, which makes an omitted `revokeGrant` mean `true`. After a withdrawal the fiduciary and its processors must cease processing within a reasonable time (s.6(6)). Revoking the grant stops processing that goes through Grantex; a service that verifies tokens offline sees the revocation only once it checks revocation state. * `deleteProcessedData: true` emits a `dpdp.data_deletion.requested` webhook (`recordId`, `grantId`, `dataPrincipalId`, `requestedAt`). Grantex holds none of the data your application processed, so it deletes nothing itself (`dataDeleted` is always `false`); deleting that data in your systems and your processors' is your step (s.8(7)). ## Erasure `POST /v1/dpdp/data-principals/{principalId}/erasure` acts on a Data Principal's erasure request (DPDP Act s.12). In one transaction it revokes the principal's active grants (their delegated grants too only with `DPDP_REVOCATION_CASCADE=true`), marks the consent records `erased`, and stores an erasure request (`ER-YYYY-`) that says what was **retained** and why. With `DPDP_ERASURE_EXPANDED=true` it also replaces grievance descriptions and evidence with a fixed marker and deletes stored exports about the principal; without it, both are kept and listed as retained: | Retained | Why | | - | - | | Consent records, marked `erased` | The fiduciary must be able to prove consent (s.6(10)), and DPDP Rules r.8(3) require personal data, associated traffic data and processing logs to be kept for at least one year from processing, for the purposes in the Seventh Schedule | | Audit entries, never modified | The one-year log retention in r.6(1)(e) and r.8(3); the entries also form a hash chain that any rewrite would break | | Grievances (redacted with `DPDP_ERASURE_EXPANDED=true`) | The record of grievance handling; without the flag the text is kept too | | Stored exports (only without `DPDP_ERASURE_EXPANDED`) | Expanded erasure is not enabled on the deployment | | Your processed data | Not held by Grantex; erasing it is your step | A repeat call returns the completed request with `200`. The request can be read again from `GET /v1/dpdp/erasure-requests/{requestId}`. The DPDP Rules' three-year inactivity erasure and its 48-hour advance notice (r.8(1) and r.8(2), Third Schedule) apply only to large e-commerce, online gaming and social media platforms. Grantex does not track inactivity or send that notice. ## Grievances A Data Fiduciary must offer a grievance mechanism and publish its response period, which may not exceed 90 days (DPDP Act s.13; DPDP Rules r.14(3)). Grantex records each grievance with a non-sequential reference (`GRV-YYYY-`), the period you publish (`responsePeriodDays`, 1 to 90; the default of 7 is a product default, not a statutory period) and `expectedResolutionBy`: ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/grievances \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "dataPrincipalId": "user_abc123", "recordId": "crec_01HXYZ...", "type": "unauthorized-processing", "description": "Order history used for recommendations I did not agree to", "responsePeriodDays": 30 }' ``` Move it through review with `PATCH /v1/dpdp/grievances/{grievanceId}`: `submitted` to `in_review`, then `resolved` or `rejected` with a `resolution`; any other move is `409 INVALID_TRANSITION`. List grievances with `GET /v1/dpdp/grievances?status=in_review`. Each change emits `dpdp.grievance.updated`. Grantex stores the due date; it does not chase it, route the grievance to a person, or reply to the principal. ## Breach register Under DPDP Act s.8(6) and DPDP Rules r.7, a fiduciary that becomes aware of a personal data breach informs each affected Data Principal without delay, informs the Board without delay, and sends the Board a detailed report **within 72 hours** of becoming aware (extendable on written request). There is no risk threshold. Separately, the CERT-In directions of 28 April 2022 require cyber incidents to be reported to CERT-In within 6 hours. The breach register keeps your record of this: ```bash theme={null} curl -X POST https://api.grantex.dev/v1/dpdp/breaches \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": "Unauthorised read of an order history export", "nature": "confidentiality", "extent": "Order history of three customers", "awareAt": "2027-06-01T10:00:00Z", "affectedDataPrincipalIds": ["user_abc123", "user_def456", "user_ghi789"] }' ``` * The response carries `boardDetailedReportDueAt` (`awareAt` plus 72 hours, or a granted extension's date), `boardDetailedReportOverdue`, and `principalIntimation` (`intimatedCount`, `pendingCount`). * `PATCH /v1/dpdp/breaches/{breachId}` records the Board intimation times, the r.7(2)(b) detailed report fields, an extension, and the status (`open`, `initial_intimated`, `reported`, `closed`, in that order). * `POST /v1/dpdp/breaches/{breachId}/principal-intimations` records who was told, by which channel, when, and which r.7(1)(a)-(e) content the message carried. * Webhooks: `dpdp.breach.recorded`, `dpdp.breach.principal_intimation_due`, and, with `DPDP_BREACH_DEADLINE_ALERTS_ENABLED=true`, `dpdp.breach.board_report_due` before and after the 72-hour deadline. **Grantex does not notify Data Principals and files nothing with the Board.** It records what you tell it you sent. See [Record Breach](/api-reference/dpdp/record-breach). ## Exports `POST /v1/dpdp/exports` produces a JSON export, stored for 7 days (`410 GONE` after that). Audit entries are capped at 1,000 per export, and `truncated` says when the cap was hit. | `type` | What it contains | | - | - | | `dpdp-audit` | Consent records, audit entries and grievances for the period, optionally for one principal | | `gdpr-article-15` | The same records; with `dataPrincipalId`, an `article15` block with the purposes, recipients (agents and grant audiences), retention and source Grantex holds for that person, and `automatedDecisionMaking: { recorded: false }` | | `eu-ai-act-evidence` | Sections mapped to Arts. 12, 14, 26, 50 and 73 of the EU AI Act, with an audit-chain integrity check, an `applicability` block and a disclaimer; see [EU AI Act Evidence Pack](/api-reference/dpdp/eu-ai-act-evidence) | | `eu-ai-act-conformance` | Kept for compatibility; prefer `eu-ai-act-evidence`. Despite its name it is not a conformity assessment | ```typescript TypeScript theme={null} const access = await grantex.dpdp.createExport({ type: 'gdpr-article-15', dateFrom: '2027-01-01T00:00:00Z', dateTo: '2027-06-30T23:59:59Z', dataPrincipalId: 'user_abc123', }); ``` ```python Python theme={null} from grantex import CreateExportParams access = client.dpdp.create_export( CreateExportParams( type="gdpr-article-15", date_from="2027-01-01T00:00:00Z", date_to="2027-06-30T23:59:59Z", data_principal_id="user_abc123", ) ) ``` An export contains only what Grantex holds. A GDPR Art. 15 response, or a summary under DPDP Act s.11, also needs the data your own systems process. ## Webhook events | Event | When | | - | - | | `dpdp.consent.created` | A consent record was created | | `dpdp.consent.withdrawn` | A record was withdrawn | | `dpdp.consent.expired` | The expiry worker expired a record | | `dpdp.data_deletion.requested` | A withdrawal asked you to delete processed data | | `dpdp.erasure.completed` | An erasure request completed | | `dpdp.grievance.filed`, `dpdp.grievance.updated` | A grievance was filed or changed status | | `dpdp.breach.recorded`, `dpdp.breach.principal_intimation_due` | A breach was recorded | | `dpdp.breach.board_report_due` | The Board report deadline is near or has passed (worker flag) | Subscribe with [webhooks](/guides/webhooks). Breach events carry no principal ids or breach text. ## Audit trail Every DPDP state change appends an entry to the developer's hash-chained audit log: `grantex.dpdp.consent_created`, `consent_withdrawn`, `consent_expired`, `erasure_completed`, `grievance_filed`, `grievance_updated`, `notice_created`, `export_created`, `breach_recorded`, `breach_updated`, `breach_principals_intimated` and `breach_deadline_alerted`. Grantex never rewrites or deletes audit entries; how long they are kept is the retention of your database (the DPDP Rules ask for at least one year). ## Configuration flags All default off and take the exact value `true`. | Flag | Effect | | - | - | | `DPDP_WITHDRAWAL_REVOKES_GRANT` | An omitted `revokeGrant` on withdrawal means `true` | | `DPDP_ENFORCE_GRANT_PRINCIPAL` | Refuse a consent record whose `dataPrincipalId` is not the grant's principal | | `DPDP_CONSENT_EXPIRY_ENABLED` | Run the worker that marks records past `processingExpiresAt` as `expired` | | `DPDP_CONSENT_EXPIRY_REVOKES_GRANT` | With the expiry worker, also revoke the grant | | `DPDP_NOTICE_REQUIRE_RULE3` | Refuse notices missing an r.3 element or in another language | | `DPDP_EXPORT_GDPR_REQUIRES_PRINCIPAL` | Refuse a `gdpr-article-15` export without `dataPrincipalId` | | `DPDP_REVOCATION_CASCADE` | Withdrawal, erasure and the expiry worker revoke through the full grant cascade instead of only the record's own grant | | `DPDP_ERASURE_EXPANDED` | Erasure also redacts grievances and deletes stored exports | | `DPDP_REQUIRE_PERSISTENT_PROOF_KEY` | Refuse consent records (`503`) while the proof key is ephemeral (`ED25519_PRIVATE_KEY` unset) | | `DPDP_REQUIRE_NOTICE_LANGUAGE` | Require `consentNoticeLanguage` when a notice version exists in several languages | | `DPDP_BREACH_DEADLINE_ALERTS_ENABLED` | Run the breach deadline worker (`DPDP_BREACH_ALERT_LEAD_MINUTES`, default 720) | Set `ED25519_PRIVATE_KEY` in production so consent proofs stay verifiable across restarts and instances, and `ED25519_STABLE_KID=true` so the key id does not change with the month the service restarts in. See [Self-Hosting](/guides/self-hosting). ## What remains your responsibility * Choosing the lawful basis (consent, or a legitimate use under s.7) and the purposes, and showing a notice that meets s.5 and r.3. * Giving users a way to withdraw that is as easy as consenting, and ceasing processing in your systems and your processors' after a withdrawal. * Erasing the data you and your processors hold, and giving the r.8(2) 48-hour notice where the Third Schedule applies. * Publishing your contact details and grievance response period, and answering grievances within it. * Notifying affected principals and the Board of breaches, and CERT-In of cyber incidents. * Verifiable parental consent for children (s.9, r.10), the duties of a Significant Data Fiduciary (s.10, r.13), and any cross-border restriction (s.16). ## Related Resources * [DPDP Act 2023](/compliance/dpdp-act-2023) — section and rule mapping * [EU AI Act](/compliance/eu-ai-act) — article mapping and timeline * [DPDP integration](/integrations/dpdp) — endpoint list * [Compliance Matrix](/guides/compliance-matrix) — cross-framework mapping * [Principal Sessions](/sdks/typescript/principal-sessions) — authenticated links for the core agent-permission dashboard; not a DPDP rights portal * [Blog: DPDP Act and AI Agents](/blog/dpdp-act-ai-agents) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # FIDO2 / WebAuthn Source: https://docs.grantex.dev/features/fido-webauthn Enroll customer passkeys, verify live consent, and export opt-in portable WebAuthn assertion evidence. ## Overview The Grantex-hosted service enabled portable WebAuthn evidence on September 27, 2026\. Production browser E2E verified enrollment, live approval, assertion evidence, refresh, delegation, account response policy, and public VC revocation status. Self-hosted deployments still default to off and must follow the rollout below. Grantex supports FIDO2/WebAuthn passkeys during consent. A valid assertion proves use of a credential bound to the principal and to Grantex's challenge. When hosted enrollment is enabled, the server also requires WebAuthn user verification during registration and assertion; this may be a device PIN, biometric, or other authenticator-supported method. The server does not learn which method the customer used. Live-mode consent requires an existing passkey for the principal. Sandbox-mode accounts can also require one with `fidoRequired: true`. A consent link alone cannot register a passkey because it does not authenticate the principal. The Grantex-hosted service runs with `PASSKEY_ENROLLMENT_ENABLED=true`, `FIDO_RP_ID=grantex.dev`, and `FIDO_ORIGIN=https://grantex.dev`; its production browser flow has passed enrollment and live-consent E2E. Self-hosted deployments must enable the flag and configure their own public HTTPS origin and RP ID. The feature is off by default there; do not enable live mode for customers until they can complete an enrollment test on that origin. The hosted enrollment client is published in `@grantex/sdk@0.8.0`, `grantex==0.7.0`, and `github.com/mishrasanjeev/grantex-go@v0.4.1`. These are independently versioned packages; installation does not turn on the server feature. Check [Release Status](/release-status) for current registry evidence and rollout limitations. Portable evidence is a separate, default-off source rollout: `PORTABLE_WEBAUTHN_EVIDENCE_ENABLED=true`. It requires `IRREGULARITY_CASCADE_REVOCATION_ENABLED=true` so automatic grant revocation also updates VCs and public status lists, and `PORTABLE_WEBAUTHN_EVIDENCE_STATUS_CHECK_ENABLED=true` so issuer verification checks stored grant and ancestor status. Keep both safety flags enabled if issuance is rolled back. The hosted service has all three enabled. TypeScript, Python, and Go SDKs expose the typed grant evidence reference and VC attestation in their newly verified releases; custom assertion UIs still use the REST endpoints directly. ## Why FIDO for Agents? Standard consent flows rely on a button click in a browser. A verified WebAuthn assertion establishes that: 1. The specific device registered by the user is present 2. User verification was performed when the hosted-enrollment policy requires it; the legacy developer registration path only requests it as preferred 3. The assertion is bound to the specific challenge issued by Grantex For high-value operations, use hosted enrollment with required user verification and your own customer identity-binding controls. Portable evidence is available only for new ceremonies with an opt-in VC; the consent gate alone is not payment-network certification. ## Developer Setup ### Sandbox And Live Parity | Account mode | `fidoRequired` | Authorization behavior | | - | - | - | | Sandbox | `false` | Auto-approval for integration testing; no proof of human consent | | Sandbox | `true` | Pending consent, enrollment and verified passkey assertion before approval or denial | | Live | Either value | Pending consent, enrollment and verified passkey assertion before approval or denial | For a realistic sandbox rehearsal, set `fidoRequired: true` before requesting authorization. Both `/v1/authorize` and OAuth PAR then redirect to consent instead of returning an automatic code. Developer-key approve/deny shortcuts cannot bypass that requirement. Live mode never inherits sandbox auto-approval. OAuth requests without a principal hint let the customer enter the principal identifier, then verify a passkey registered for that principal; typing an identifier is not itself authentication. Enable FIDO for your account using the SDK or REST API: ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); await grantex.updateSettings({ fidoRequired: true, fidoRpName: 'My Application', // displayed in browser passkey prompts }); ``` ```python Python theme={null} from grantex import Grantex, UpdateDeveloperSettingsParams client = Grantex(api_key="gx_...") client.update_settings(UpdateDeveloperSettingsParams( fido_required=True, fido_rp_name="My Application", )) ``` ```bash cURL theme={null} curl -X PATCH https://api.grantex.dev/v1/me \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"fidoRequired": true, "fidoRpName": "My Application"}' ``` When `true`, sandbox-mode authorization requests also require a WebAuthn assertion. Live mode already requires one regardless of this setting. The Relying Party name displayed in the browser's passkey prompt. Typically your application name. ## Hosted Enrollment For End Customers Your application must first authenticate the end customer and bind that session to the exact `principalId` it uses in Grantex. From your **server**, issue a short-lived, one-use enrollment link and deliver it to that customer through the authenticated session. Never expose your developer API key in browser code. The enrollment link itself is a bearer secret: do not log it, put it in analytics, or send it to another customer. ```typescript theme={null} // Run on your server with @grantex/sdk@0.8.0, after authenticating the customer. const { enrollmentUrl, expiresAt } = await grantex.webauthn.createEnrollmentSession({ principalId: authenticatedCustomer.grantexPrincipalId, // Optional: link back to a pending consent request for the same principal. authRequestId, }); // Show enrollmentUrl only to authenticatedCustomer. It expires after 10 minutes. ``` Python `grantex==0.7.0` uses `client.webauthn.create_enrollment_session(principal_id=principal_id, auth_request_id=auth_request_id)`; Go `v0.4.1` uses `client.WebAuthn.CreateEnrollmentSession(ctx, grantex.WebAuthnEnrollmentSessionParams{PrincipalID: principalID, AuthRequestID: authRequestID})`. Both calls belong on your authenticated application server, not in browser code. The customer opens the link at `/passkey-enroll`, uses their device's passkey prompt, and returns to consent when the link was bound to an authorization request. The browser registration challenge expires after five minutes and must be restarted if it expires. The registration requires device user verification. An enrollment ticket can register only one credential and cannot be reused. To add another device, create another link. The developer portal's **Passkeys** page can also issue a link for a principal. Operators must establish the customer's identity before sharing it. There is deliberately no unauthenticated "register" button on the consent page: anyone holding a consent URL could otherwise enroll their own passkey as the principal. If enrollment is disabled (404) or the customer cannot use a passkey-capable browser, live-mode approval remains blocked. Sandbox mode can be used for integration testing, but it is not an alternative live-mode approval path. For production rollout, deploy the auth service and the `/passkey-enroll` public-hosting rewrite first. Confirm `FIDO_ORIGIN` is the exact public HTTPS origin and `FIDO_RP_ID` is its valid relying-party domain, then enable `PASSKEY_ENROLLMENT_ENABLED=true`. Test registration and live approval on that origin before sending links to customers. The repository includes `apps/auth-service/tests/production/passkey-policy.test.ts` and the manual `Production Passkey and Irregularity E2E` GitHub Actions workflow. That test creates an isolated production account, passkey, grant and audit records; it must not use a customer account. ## Portable Evidence Rollout To enable portable evidence, first deploy migration `119_webauthn_evidence.sql` and confirm the enabled Docker/Chromium E2E suite and all CI checks are green. Test the deployed RP ID/origin with an isolated account. Enable `IRREGULARITY_CASCADE_REVOCATION_ENABLED=true` and `PORTABLE_WEBAUTHN_EVIDENCE_STATUS_CHECK_ENABLED=true`, then reconcile historical revoked grants and credentials using `node dist/scripts/reconcile-revoked-vcs.js` (dry run) followed by `--apply` on an authorized maintenance worker. Only then enable `PORTABLE_WEBAUTHN_EVIDENCE_ENABLED=true` on the auth service; startup rejects the portable flag without both safety flags. Run the `Production Passkey and Irregularity E2E` workflow after activation. New assertions will carry evidence; existing grants do not gain it retroactively. Approved live requests that predate activation have no captured assertion and fail token exchange with `PASSKEY_EVIDENCE_REQUIRED`; restart consent for those customers. To stop new issuance, disable only `PORTABLE_WEBAUTHN_EVIDENCE_ENABLED`; leave the two safety flags on for already-issued credentials. Before trusting old VCs' public status lists, reconcile active child grants and credentials left behind by historical grant-only irregularity revocations. From an auth-service build with database access, run `node dist/scripts/reconcile-revoked-vcs.js` for counts, then `node dist/scripts/reconcile-revoked-vcs.js --apply` to cascade the old revocations and update VC rows and status-list bits. This command is idempotent and prints counts, not credential data. Keep cascade revocation and evidence status checks enabled even if portable issuance is rolled back: turning off only `PORTABLE_WEBAUTHN_EVIDENCE_ENABLED` stops new capture and restores legacy exchange for old no-evidence requests, while previously captured evidence remains verifiable and propagates on refresh/delegation. Keep the opt-in VCs' stable public keys only as long as necessary. For local verification, start a disposable Postgres instance, set `AUDIT_INTEGRATION_DATABASE_URL`, install Chromium with `npx playwright install chromium`, and run `npm run test:e2e` from `apps/auth-service`. The browser suite uses a virtual authenticator and a real local Postgres database; it cannot by itself prove that a hosted production origin, routing rewrite, or customer device is configured correctly. ## Developer-Driven Registration Ceremony The hosted link above is the recommended customer flow. The older developer-authenticated `register/options` and `register/verify` endpoints can drive registration from your own application, but they request user verification as `preferred`, not `required`. Do not treat a credential created through that path as verified human presence at enrollment. Register a new passkey for each device. ``` Browser Your Server Grantex │ │ │ │ │─── registerOptions() ───────►│ │ │◄── challenge + options ──────│ │◄── publicKey options ────────────│ │ │ │ │ │── navigator.credentials.create() │ │ │── (user touches fingerprint) ────│ │ │ │ │ │── attestation response ─────────►│ │ │ │─── registerVerify() ────────►│ │ │◄── credential stored ────────│ │◄── success ──────────────────────│ │ ``` ### Step 1: Get Registration Options ```typescript TypeScript theme={null} const options = await grantex.webauthn.registerOptions({ principalId: 'user_abc123', }); // options contains: // - challengeId: string (server-side reference) // - publicKey: { rp, user, challenge, pubKeyCredParams, ... } // - publicKey.pubKeyCredParams: [{ type: 'public-key', alg: -7 }, ...] // - publicKey.authenticatorSelection: { userVerification: 'preferred' } on legacy API // - publicKey.timeout: browser timeout // - publicKey.challenge: base64url-encoded ``` ```python Python theme={null} options = client.webauthn.register_options(principal_id="user_abc123") # Returns challengeId and publicKey registration options. ``` ### Step 2: Create Credential in Browser Pass the options to the browser's WebAuthn API: ```javascript theme={null} // In the browser const credential = await navigator.credentials.create({ publicKey: { ...options.publicKey, challenge: base64urlToBuffer(options.publicKey.challenge), user: { ...options.publicKey.user, id: base64urlToBuffer(options.publicKey.user.id), }, excludeCredentials: (options.publicKey.excludeCredentials || []).map((item) => ({ ...item, id: base64urlToBuffer(item.id), })), }, }); ``` ### Step 3: Verify and Store Send the credential response back to your server and forward it to Grantex: ```typescript TypeScript theme={null} await grantex.webauthn.registerVerify({ challengeId: options.challengeId, response: { id: credential.id, rawId: bufferToBase64url(credential.rawId), type: credential.type, response: { clientDataJSON: bufferToBase64url(credential.response.clientDataJSON), attestationObject: bufferToBase64url(credential.response.attestationObject), }, clientExtensionResults: credential.getClientExtensionResults(), }, }); ``` ```python Python theme={null} from grantex import WebAuthnRegistrationVerifyParams client.webauthn.register_verify(WebAuthnRegistrationVerifyParams( challenge_id=options.challenge_id, response={ "id": credential_id, "rawId": raw_id_b64, "type": "public-key", "response": { "clientDataJSON": client_data_b64, "attestationObject": attestation_b64, }, }, )) ``` ## Assertion Ceremony (Consent Flow) When FIDO is enabled, the consent flow includes a WebAuthn assertion challenge. This happens automatically in the hosted consent UI, but you can also drive it programmatically. ``` Browser Grantex Consent UI Grantex │ │ │ │── user opens consent URL ───────►│ │ │ │── POST assert/options ──────►│ │ │◄── challenge ────────────────│ │◄── publicKey get options ────────│ │ │ │ │ │── navigator.credentials.get() ──►│ │ │── (user touches fingerprint) ────│ │ │ │ │ │── assertion response ───────────►│ │ │ │── POST assert/verify ───────►│ │ │── POST consent/approve ─────►│ │ │◄── authorization code ───────│ │◄── redirect to callback ─────────│ │ ``` ### Programmatic Assertion If you build a custom consent UI, call `POST /v1/webauthn/assert/options` with the pending `authRequestId` and bound `principalId`. The response contains `{ challengeId, publicKey }`; decode `publicKey.challenge` and each `publicKey.allowCredentials[].id` from base64url before calling `navigator.credentials.get({ publicKey })`. Serialize the returned assertion's `id`, `rawId`, `type`, `response.clientDataJSON`, `response.authenticatorData`, `response.signature`, optional `response.userHandle`, and `clientExtensionResults` as base64url where applicable. Send `{ challengeId, response }` to `POST /v1/webauthn/assert/verify`, then separately call `POST /v1/consent/{authRequestId}/approve` or `/deny`. Verification only marks the pending request as FIDO-verified; it does **not** approve the grant by itself. The hosted consent page implements this sequence. The published TypeScript, Python, and Go WebAuthn clients do not currently expose `assertOptions`/`assertVerify` helper methods; use these REST endpoints for a custom UI. ## Managing Credentials Users can have multiple passkeys registered (e.g., laptop fingerprint + phone + YubiKey). Use the credentials endpoints to list and delete them. Enroll each additional device with a new one-use link. An authenticator that already has a credential for this principal is excluded from duplicate registration. Deleting a credential stops future assertions with that key; it does not retroactively invalidate existing grants or historical assertion evidence. Revoke the affected grants separately when removing access. Agents with issued VCs retain their grant and credential history: attempting to hard-delete one returns `409 AGENT_HAS_CREDENTIAL_HISTORY`. Suspend the agent and revoke its grants instead; its existing public credential status must remain available. ```typescript TypeScript theme={null} // List all credentials for a user const { credentials } = await grantex.webauthn.listCredentials('user_abc123'); for (const cred of credentials) { console.log(cred.id, cred.createdAt, cred.lastUsedAt); } // Delete a credential await grantex.webauthn.deleteCredential('cred_01HXYZ...'); ``` ```python Python theme={null} # List all credentials for a user credentials = client.webauthn.list_credentials("user_abc123").credentials for cred in credentials: print(cred.id, cred.created_at, cred.last_used_at) # Delete a credential client.webauthn.delete_credential("cred_01HXYZ...") ``` ## Portable Assertion Evidence With portable evidence enabled, successful assertion verification stores the raw assertion, challenge, credential public key, prior counter, RP ID, origin, and assertion time on the pending authorization request. Approval or denial remains a separate operation. Token exchange rechecks the assertion, binds it to the grant, and places a compact `webauthnEvidence` digest reference in the signed grant token and grant API response. Refresh preserves this reference. An approved live request from before evidence capture cannot be exchanged without new passkey consent while the flag is enabled. Request `credentialFormat: "vc-jwt"` or `"both"` at token exchange to obtain a VC with the complete `GrantexWebAuthnAssertion` in `vc.evidence`. The public credential key in this opt-in export is stable and can correlate presentations; minimize sharing and retention. Independent verifiers must check the VC's issuer signature, expiry and revocation, pin a trusted RP ID and origin, reverify the assertion signature/challenge/counter, and compare its digest with the grant reference. The authenticator did not sign the scopes or independently prove the customer's legal identity: the issuer signature and your enrollment identity checks supply those bindings. See [Verifiable Credentials](/features/verifiable-credentials). No payment-network certification is claimed. ## API Reference | Method | Endpoint | Description | | - | - | - | | `POST` | `/v1/webauthn/register/options` | Generate passkey registration options | | `POST` | `/v1/webauthn/register/verify` | Verify registration and store credential | | `POST` | `/v1/webauthn/enrollment-sessions` | Issue an authenticated one-use enrollment link | | `GET` | `/passkey-enroll` | Hosted browser enrollment page | | `POST` | `/v1/webauthn/enroll/options` | Exchange a valid enrollment ticket for registration options | | `POST` | `/v1/webauthn/enroll/verify` | Verify the passkey and consume the ticket | | `GET` | `/v1/webauthn/credentials` | List WebAuthn credentials for a principal | | `DELETE` | `/v1/webauthn/credentials/:id` | Delete a credential | | `POST` | `/v1/webauthn/assert/options` | Generate assertion options for consent | | `POST` | `/v1/webauthn/assert/verify` | Verify assertion during consent | | `PATCH` | `/v1/me` | Update developer settings (FIDO config) | ## Security Considerations * **User verification**: Hosted enrollment requires `userVerification: "required"` and verifies it on the server. With enrollment enabled, consent assertions also require it. The legacy developer-authenticated registration API requests `"preferred"`; do not treat those older ceremonies as proof of user verification. * **Challenge expiry**: Registration and assertion challenges expire after 5 minutes. Replaying an expired challenge returns a 400 error. * **Credential binding**: Each credential is bound to a specific principal and developer. A credential registered for one developer's consent flow cannot be used for another. * **Attestation**: Grantex accepts `"none"`, `"indirect"`, and `"direct"` attestation conveyance. Attestation statements are stored but not currently used for trust decisions. * **Missing credential**: Consent cannot be approved until the application issues an authenticated enrollment link and the customer registers a passkey. There is no weaker live-mode fallback. ## Release Validation Boundary The repeatable browser checks are in `tests/e2e/passkey-policy-browser.e2e.test.ts` (real Postgres and Chromium), `tests/production/passkey-parity.test.ts` (sandbox/live parity, multiple devices, denial, approval, principal selection, OAuth callbacks and revocation), and `tests/production/passkey-policy.test.ts` (portable evidence, refresh, delegation, public VC status and both irregularity response modes), and `tests/production/passkey-portal.test.ts` (dashboard login, enrollment-link issuance, credential listing and confirmed removal). The manual **Production Passkey and Irregularity E2E** workflow runs all three production files against isolated accounts on the hosted HTTPS origin. Credential removal sends an authenticated, bodyless `DELETE` request. Do not attach `Content-Type: application/json` without a JSON body; the API rejects that malformed request with HTTP 400. The dashboard handles the successful HTTP 204 response without attempting to parse an empty JSON response. Successful automation is sign-off for these tested service/browser contracts, not a guarantee that every customer device, operating system or browser works. Chromium uses virtual authenticators. Before rollout to your customers, test your supported physical devices, user-verification prompts, cancellation, recovery and accessibility. Your application still owns customer authentication, principal binding and secure delivery of enrollment links. No unauthenticated enrollment, weaker live consent fallback, or payment-network certification is supplied by these tests. ## Next Steps * [Verifiable Credentials](/features/verifiable-credentials) -- assertion export and independent verification * [DID Infrastructure](/features/did-infrastructure) -- how verifiers resolve Grantex's signing keys * [End-User Permissions](/guides/end-user-permissions) -- user-facing permission dashboard ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # MCP Auth Server Source: https://docs.grantex.dev/features/mcp-auth-server MCP Auth 4 binds rendered consent to the authenticated human and checks current grant authority before protected tool execution. ## Release and installation Published release: `@grantex/mcp-auth@4.1.0`. Its npm archive matches the validated CI artifact. The 4.0 database/restart and Chromium evidence remains historical; 4.1 adds high-risk decision-grant handling. This is the MCP Auth **4.1.0** deployment profile. Check [Release Status](/release-status) for publication. Node.js 22.12+ and SDK 0.8.1+ are required. Version 2.0.2's missing rendered page is [historical](/legacy/mcp-auth-server-2); version 3 added the page, but used the OAuth client ID as the human principal. Version 4 closes that identity gap. ```bash theme={null} npm install @grantex/mcp-auth@4.1.0 @grantex/sdk@0.8.2 pg ``` ## Human-confirmed request flow 1. The MCP resource challenges unauthenticated calls and advertises resource metadata. 2. The client discovers authorization metadata and starts authorization with PKCE S256 and the intended resource. 3. The host authenticates the human. `resolvePrincipal(request)` derives a tenant-scoped external ID from verified host credentials, never client parameters. 4. The server renders purpose, tools, declared limits, redirect destination and duration, with Allow and Deny. No upstream authorization occurs before approval. 5. Approval rechecks the same human and browser/CSRF binding, then starts Grantex consent. Live Grantex consent still requires the principal's passkey. 6. The callback checks browser binding and the same authenticated principal before issuing a single-use code. 7. Exchange checks PKCE, resource and Grantex's returned principal subject. Refresh preserves that subject. 8. The resource checks signature, issuer, audience, scopes, local revocation and online current-grant authority before execution. Sensitive actions require consumed, action-bound human decisions. Logout or identity changes refuse approval/callback. A principal resolver or current-authority outage fails closed. The host owns login and session verification; the package cannot authenticate an unsigned identity assertion. See [passkey registration](/features/fido-webauthn). ## Required resource enforcement Configure both `revocations` and `currentGrant: grantexCurrentGrantVerifier(grantex)` in the resource guard or Express/Hono middleware. The latter calls the trusted issuer's `grantex.grants.verify` on every protected request, without positive caching. Inactive authority returns 401; an issuer outage returns 503. It checks issuer-side current authority, not just a locally valid signature. Mark sensitive tools `requires_decision` and configure `grantexDecisionVerifier` with a trusted action resolver to consume decisions. Delegation consent alone does not approve every business decision. Four-eyes rules require the configured independent approvers. Never accept an agent's claim that a human approved as decision evidence. `/introspect` requires confidential-client Basic authentication and checks current issuer authority by default. It reports inactive during an issuer outage. The warned `allowUnauthenticatedIntrospection` and `introspectionCurrentGrant: 'none'` options are evaluation-only, not recommended production configuration. ## Deployment responsibilities | Concern | Version 4 behavior | Operator responsibility | | - | - | - | | Human identity | Required resolver, repeated at approval/callback | Verify login/session and namespace IDs by tenant | | Consent | Rendered, single-use, browser/CSRF/principal bound | Configure truthful purpose, terms and labels | | State | Postgres/Redis adapters and atomic consumption | Provision durable shared state and migrations | | Tokens | PKCE/resource/principal binding through exchange/refresh | Protect credentials and shared state | | Revocation | Explicit local checker and current issuer verification | Connect both sources; deny outages | | Decisions | Decision-required tools fail closed without a verifier | Bind trusted semantic actions and consume approvals | | Limits | Caps are displayed as declared | Apply SDK enforcement and atomic service accounting before side effects | Revocation prevents subsequent requests; it cannot undo completed side effects or cancel a handler already executing. Data residency is not bound by this authorization endpoint. Gateway-wide rate limits, TLS and host session security remain deployment work. ## Migration and validation Read the [complete deployment guide](/mcp-auth) and [enforcement migration](/migration-enforcement). Drain pending requests, upgrade all replicas together and reauthorize old identity-unbound requests/refresh bindings. Do not mix v3/v4 against one state namespace. JSON binding fields do not need a new SQL schema; retain existing registered clients. `allowLegacyClientPrincipal: true`, `currentGrant: 'none'` and `revocations: 'none'` are explicit warned evaluation opt-outs, not production recommendations. Local memory storage is evaluation-only. The permanent suite covers identity switches, logout, denial, replay, concurrency, expiry, token subject substitution, issuer outage/revocation, Postgres/Redis restarts, Chromium layout and accessibility. Validate your actual MCP client, host sessions, TLS origin and live passkey ceremony before production. Independent cross-vendor or physical-authenticator certification is not claimed. # MPP Agent Passport Source: https://docs.grantex.dev/features/mpp-agent-passport W3C Verifiable Credential for agent identity in machine-to-machine payments via the Machine Payments Protocol (MPP). ## Overview The **Machine Payments Protocol (MPP)** defines how AI agents pay for services via HTTP 402 flows and streaming sessions. MPP solves payment mechanics but has a structural gap: **no identity layer**. When an agent makes an MPP payment, the `source` field is a wallet address — no human name, no organization, no authorization chain. For low-value API calls this is fine. For B2B procurement and regulated transactions, it's a compliance blocker. Grantex fills this gap with the `AgentPassportCredential` — a W3C VC 2.0 credential that binds agent identity, human delegation, spending limits, and payment categories into a single offline-verifiable document. The `@grantex/mpp` package provides both agent-side middleware (attach passports to outgoing requests) and merchant-side verification (validate passports on incoming requests) in a single package. ## How It Works ``` ┌──────────────────────────────────────────────────────────────────┐ │ 1. Human issues AgentPassportCredential via Grantex │ │ → W3C VC 2.0 with Ed25519 proof, StatusList2021 revocation │ │ → Contains: agentDID, humanDID, orgDID, categories, limits │ └─────────────────────────┬────────────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 2. Agent makes MPP request to merchant │ │ → Authorization: Payment │ │ → X-Grantex-Passport: │ └─────────────────────────┬────────────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 3. Merchant verifies passport in <50ms (offline-capable) │ │ → Checks signature, expiry, categories, amount limits │ │ → Returns VerifiedPassport with full identity chain │ └─────────────────────────┬────────────────────────────────────────┘ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ 4. Merchant fulfills request — knows exactly who authorized it │ │ → Audit entry: passportId, agentDID, humanDID, amount │ └──────────────────────────────────────────────────────────────────┘ ``` ## AgentPassportCredential Structure The credential follows the W3C Verifiable Credentials Data Model v2.0: | Field | Type | Description | | - | - | - | | `@context` | `string[]` | `["https://www.w3.org/ns/credentials/v2", "https://grantex.dev/contexts/mpp/v1"]` | | `type` | `string[]` | `["VerifiableCredential", "AgentPassportCredential"]` | | `id` | `string` | `urn:grantex:passport:` | | `issuer` | `string` | `did:web:grantex.dev` | | `validFrom` | `string` | ISO 8601 issuance timestamp | | `validUntil` | `string` | ISO 8601 expiry (max 30 days) | | `credentialSubject.id` | `string` | Agent DID (`did:grantex:ag_...`) | | `credentialSubject.humanPrincipal` | `string` | DID of the authorizing human | | `credentialSubject.organizationDID` | `string` | Org DID (`did:web:`) | | `credentialSubject.grantId` | `string` | Underlying Grantex grant ID | | `credentialSubject.allowedMPPCategories` | `string[]` | Permitted MPP service categories | | `credentialSubject.maxTransactionAmount` | `object` | `{ amount: number, currency: string }` | | `credentialSubject.paymentRails` | `string[]` | Payment networks (e.g., `["tempo"]`) | | `credentialSubject.delegationDepth` | `number` | From grant delegation chain | | `credentialStatus` | `object` | StatusList2021 revocation entry | | `proof` | `object` | Ed25519Signature2020 | ## MPP Category Scopes Each MPP category maps to a Grantex scope. The agent's grant must include the corresponding scope: | MPP Category | Grantex Scope | | - | - | | `inference` | `payments:mpp:inference` | | `compute` | `payments:mpp:compute` | | `data` | `payments:mpp:data` | | `storage` | `payments:mpp:storage` | | `search` | `payments:mpp:search` | | `media` | `payments:mpp:media` | | `delivery` | `payments:mpp:delivery` | | `browser` | `payments:mpp:browser` | | `general` | `payments:mpp:general` | ## Trust Registry The trust registry is a public endpoint that maps organization DIDs to verified trust records: ```bash theme={null} curl https://api.grantex.dev/v1/trust-registry/did:web:grantex.dev ``` ```json theme={null} { "organizationDID": "did:web:grantex.dev", "verifiedAt": "2026-03-20T03:11:31.447Z", "verificationMethod": "soc2", "trustLevel": "soc2", "domains": ["grantex.dev"] } ``` Trust levels: `basic` (self-declared), `verified` (DNS-TXT proof), `soc2` (SOC 2 audit verified). ## Verification Error Codes | Code | Description | | - | - | | `PASSPORT_EXPIRED` | Credential `validUntil` has passed | | `PASSPORT_REVOKED` | StatusList2021 bit is set | | `INVALID_SIGNATURE` | Ed25519/RS256 signature verification failed | | `UNTRUSTED_ISSUER` | Issuer DID not in the trusted issuers list | | `CATEGORY_MISMATCH` | Passport categories don't cover the required service | | `AMOUNT_EXCEEDED` | Passport max amount below required threshold | | `MISSING_PASSPORT` | No `X-Grantex-Passport` header provided | | `MALFORMED_CREDENTIAL` | Invalid base64url encoding or missing VC fields | ## API Endpoints | Method | Endpoint | Auth | Description | | - | - | - | - | | `POST` | `/v1/passport/issue` | API key | Issue an AgentPassportCredential | | `GET` | `/v1/passport/:id` | API key | Retrieve passport by ID | | `POST` | `/v1/passport/:id/revoke` | API key | Revoke passport (flips StatusList2021 bit) | | `GET` | `/v1/trust-registry/:orgDID` | None | Look up org trust record (public) | | `GET` | `/v1/trust-registry` | API key; admin key (`ADMIN_API_KEY`) when `TRUST_REGISTRY_ADMIN_LISTING_ENFORCED=true` | List all trust records, across developers (operator) | ## SDK Support The PassportsClient / PassportsService is available across all three SDKs: | SDK | Access | Since | | - | - | - | | **TypeScript** (`@grantex/sdk`) | `client.passports.issue(...)` | 0.3.3 | | **Python** (`grantex`) | `client.passports.issue(...)` | 0.3.3 | | **Go** (`grantex-go`) | `client.Passports.Issue(ctx, ...)` | 0.1.3 | ## Next Steps * **[MPP Integration Guide](/guides/mpp-integration)** — step-by-step setup for agents and merchants * **[Verifiable Credentials](/features/verifiable-credentials)** — the underlying VC infrastructure * **[DID Infrastructure](/features/did-infrastructure)** — how agent DIDs work * **[Delegation](/concepts/delegation)** — multi-agent delegation chains ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Offline Authorization Source: https://docs.grantex.dev/features/offline-authorization How Grantex enables offline authorization for on-device AI agents. Architecture, security model, and limitations. ## Why Offline Auth Matters On-device AI models like Gemma 4 run locally -- on phones, Raspberry Pis, laptops, and edge servers. These devices are frequently disconnected: airplane mode, poor cellular coverage, air-gapped environments, or simply cost-conscious users who limit data usage. Without offline authorization, agents on these devices face two bad options: 1. **No auth at all** -- the agent runs unchecked while offline, then syncs results later with no accountability trail 2. **Block until online** -- the agent refuses to operate without connectivity, defeating the purpose of on-device inference Grantex offline authorization provides a third option: **pre-authorized operation with cryptographic accountability**. The agent receives a consent bundle while online, then verifies tokens, enforces scopes, and logs actions locally until connectivity returns. ## Architecture Offline authorization operates in three phases: ``` Phase 1: PROVISIONING (online) ┌─────────┐ ┌──────────┐ ┌──────────────┐ │ Device │──POST──►│ Grantex │──issue──►│ Consent │ │ │◄────────│ API │ │ Bundle │ │ │ bundle │ │ │ │ └─────────┘ └──────────┘ │ • grantToken │ │ • JWKS snap │ │ • audit keys │ │ • sync URL │ │ • expiry │ └──────────────┘ Phase 2: OFFLINE OPERATION (no network) ┌─────────────────────────────────────────────────────┐ │ Device │ │ │ │ Agent action │ │ │ │ │ ├── verify(grantToken) ← JWKS snapshot (local) │ │ ├── enforceScopes(['calendar:read']) │ │ ├── execute action │ │ └── auditLog.append({action, result, ...}) │ │ │ │ │ └── hash-chain + Ed25519 signature │ │ │ └─────────────────────────────────────────────────────┘ Phase 3: SYNC (reconnected) ┌─────────┐ ┌──────────┐ │ Device │──POST──►│ Grantex │ verify hash chain │ │ entries │ API │ verify signatures │ │◄────────│ │ check revocation │ │ status │ │ reconcile audit log └─────────┘ └──────────┘ ``` ### Phase 1: Provisioning While the device has connectivity, the developer's application calls `POST /v1/consent-bundles` to create a consent bundle. This is a standard Grantex authorization flow -- the end-user must have already consented to the requested scopes. The bundle contains: * **Grant token** (RS256 JWT) -- the same token format used online * **JWKS snapshot** -- the server's public keys at the time of issuance, used to verify the token signature offline * **Ed25519 audit key pair** -- used to sign offline audit entries so the server can verify they originated from this device * **Sync endpoint** -- the URL for uploading audit entries when reconnected * **Offline expiry** -- the point after which the bundle is no longer valid The bundle is encrypted with AES-256-GCM and stored locally. ### Phase 2: Offline Operation Every time the agent performs an action, the offline verifier: 1. Decodes the JWT header and resolves the signing key from the JWKS snapshot by `kid` 2. Verifies the RS256 signature -- blocks `none` and `HS256` algorithms 3. Checks `exp` with configurable clock skew tolerance 4. Checks `iat` is not in the future 5. Extracts Grantex claims (`agt`, `sub`, `scp`, `grnt`, `delegationDepth`) 6. Enforces required scopes (configurable: throw or log) 7. Enforces maximum delegation depth If all checks pass, the action proceeds and an audit entry is appended to the local log. ### Phase 3: Sync When connectivity returns, the device uploads its audit entries via `POST /v1/audit/offline-sync`. The server: 1. Verifies the hash chain -- each entry's `prevHash` must match the previous entry's `hash` 2. Verifies Ed25519 signatures using the public key stored when the bundle was created 3. Checks whether the grant has been revoked since the device went offline 4. Reconciles entries with the cloud audit log If the grant was revoked while offline, the sync response indicates `revocation_status: 'revoked'` and includes the revocation timestamp so the device can determine which actions were authorized and which occurred after revocation. ## ConsentBundle Reference See [Consent Bundles](/features/consent-bundles) for the complete type reference and lifecycle documentation. ## Security Model ### Guarantees | Property | Guarantee | | - | - | | **Token authenticity** | RS256 signature verified against JWKS snapshot. Forged tokens are rejected. | | **Scope enforcement** | The verifier checks `scp` claims against required scopes before every action. | | **Audit integrity** | Hash chain + Ed25519 signatures make tampering detectable at sync time. | | **Time-bounded** | Bundles expire after a configurable TTL (default 72 hours). | | **Delegation control** | `maxDelegationDepth` prevents unbounded sub-delegation chains. | | **Encryption at rest** | Bundles are stored with AES-256-GCM encryption. | ### Limitations | Limitation | Explanation | | - | - | | **No real-time revocation** | If a grant is revoked while the device is offline, the device will not learn about it until sync. Actions between revocation and sync are logged but the agent continues operating. | | **JWKS key rotation** | If the server rotates its signing keys while the device is offline, the JWKS snapshot becomes stale. The `validUntil` field on the snapshot helps detect this. | | **Clock drift** | The verifier uses a clock-skew tolerance (default 30 seconds), but devices with significantly drifted clocks may incorrectly accept or reject tokens. | | **No online budget enforcement** | Budget debits require a network call. Offline agents cannot enforce budgets in real time. | | **Bundle theft** | If an attacker obtains both the encrypted bundle file and the encryption key, they can use the grant token until it expires. Use hardware-backed storage (Keychain, Secure Enclave) on supported platforms. | ### Threat Model The offline authorization system is designed to defend against: * **Token forgery** -- RS256 signatures cannot be forged without the server's private key * **Audit log tampering** -- breaking the hash chain or forging Ed25519 signatures is detectable at sync * **Scope escalation** -- the verifier enforces the scopes embedded in the token at issuance * **Replay attacks** -- each token has a unique `jti` and expiry; each audit entry has a monotonic sequence number It does **not** defend against an attacker with root access to the device who can modify the verification logic itself. This is a fundamental limitation of client-side security. ## Sync Behavior ### Batch Upload Audit entries are uploaded in configurable batches (default 100 entries). Each batch is retried up to 3 times with exponential back-off (200ms, 400ms, 800ms). Failed batches are reported in the `SyncResult.errors` array but do not block subsequent batches. ### Idempotency Each entry has a monotonic `seq` number. The device tracks `syncedUpTo` in a local marker file. Re-syncing the same entries is safe -- the server deduplicates by sequence number within a bundle. ### Conflict Resolution The server is the authority. If the server already has entries for a given bundle and sequence range, it compares hashes. Matching hashes are accepted; mismatched hashes are flagged as conflicts and included in the audit trail. ## Revocation While Offline Grant revocation is a server-side operation. When a grant is revoked: 1. The device continues operating with the cached bundle until sync 2. At sync time, the server returns `revocation_status: 'revoked'` with the revocation timestamp 3. The device should stop using the bundle and delete the local copy 4. Actions that occurred after the revocation timestamp are flagged in the audit log To minimize the window of exposure, use short `offlineTTL` values for sensitive operations. For example, a medical device agent might use `1h` while a home automation agent could use `72h`. ## Audit Log Integrity The offline audit log uses two complementary integrity mechanisms: ### Hash Chain Each entry includes a SHA-256 hash computed from: ``` seq|timestamp|action|agentDID|grantId|scopes|result|metadata|prevHash ``` The `prevHash` field links to the previous entry's hash, forming a chain. The first entry uses the sentinel value `GENESIS_HASH` (`0000000000000000`). Breaking any entry invalidates all subsequent hashes. ### Ed25519 Signatures Each entry's hash is signed with the Ed25519 private key from the consent bundle. The server holds the corresponding public key and verifies signatures at sync time. This proves that entries were produced by the device that received the original bundle. ### Verification Use `verifyChain()` to check the integrity of a log locally: ```typescript theme={null} import { verifyChain } from '@grantex/gemma'; const entries = await auditLog.entries(); const result = verifyChain(entries); if (!result.valid) { console.error(`Integrity breach at entry ${result.brokenAt}`); } ``` ## Next Steps * [Consent Bundles](/features/consent-bundles) -- full ConsentBundle reference and lifecycle * [Quickstart: Gemma 4](/examples/quickstart-gemma) -- hands-on tutorial * [Gemma 4 SDK Reference](/sdks/gemma) -- complete API reference * [Consent Bundles API](/api-reference/consent-bundles) -- REST API endpoints * [Offline Sync API](/api-reference/offline-sync) -- sync endpoint reference ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Agent Prepaid Wallets Source: https://docs.grantex.dev/features/prepaid-wallets Principal-controlled prepaid balances, multi-wallet assignment, spend policy, reload approval, and emergency blocking for AI agents. Agent prepaid wallets give a human principal a durable value and policy boundary for autonomous spending. A wallet can be assigned to multiple agents, and an agent can hold multiple wallet assignments. Assignment safeguards combine with layered wallet, agent, budget-group, principal, and developer policy. ## Lifecycle 1. A developer registers an agent for the `wallet:read`, `wallet:spend`, the merchant action scope (for example `travel:book`), and optional `wallet:reload:request` scopes. 2. The principal authorizes those scopes through OAuth Agent Grants. 3. The application creates a short-lived principal session. 4. The principal creates and funds one or more wallets. 5. The principal assigns wallets with transaction, rolling, recipient, resource, scope, validity, and optional shared budget-group policy. 6. The DPoP-authenticated agent reserves value before presenting a payment. 7. A layered policy may deny, allow, or require an exact principal approval. 8. The resource server verifies and settles the one-time authorization. 9. The principal reviews decisions, approves payments or reloads, or blocks spending. ## Wallet states | State | New authorizations | Existing reservations | Reload | | - | - | - | - | | `active` | Allowed by assignment policy | Verifiable and settleable | Allowed by workflow | | `blocked` | Denied | Released when the block is applied | Denied to the agent | | `closed` | Denied permanently | Released when the close is applied | Denied | Assignments independently use `active`, `blocked`, or `revoked`. Revocation is terminal. A principal can also set `allWalletsBlocked` for one agent, scoped to that principal and developer. ## Balance accounting Balances are `NUMERIC(78,0)` atomic-unit integers: ```text theme={null} funding: available += amount reservation: available -= amount; reserved += amount settlement: reserved -= amount release: reserved -= amount; available += amount ``` Every transition and the resulting balance is recorded in the wallet ledger. The application role receives no update or delete permission on ledger rows. The database checks that available and reserved values never become negative. ## Assignment and layered policy evaluation An authorization succeeds only if all of these remain true while the wallet row is locked: * wallet, assignment, grant, and OAuth access token are active; * the agent is not globally blocked by the principal; * assignment validity dates include the current time; * asset and network match the wallet; * recipient, HTTPS resource origin, and scope are explicitly allowed or the corresponding allow-any choice was made; * the payment scope is present in the human-approved OAuth grant; * amount does not exceed the per-transaction cap; * reserved plus settled value in the rolling window, plus the new amount, does not exceed the cumulative cap; * enough available value exists. After the assignment checks, every matching policy is evaluated. Policies can scope to an assignment, wallet, agent, shared `budgetGroup`, principal, or developer. They can deny, always require approval, or enforce amount and count limits over per-authorization, rolling, day, week, month, or lifetime windows. Filters cover recipient, resource origin, action scope, asset, network, merchant ID, purpose, project ID, cost center, and verified merchant state. All matching layers compose; a narrow policy cannot override a broader deny or increase a stricter limit. Reserved and settled amounts both count toward usage. Cross-wallet and cross-agent policy evaluation uses a shared advisory lock, while balance movement uses the wallet row lock. Concurrent requests serialize on the wallet row. The database update also requires `available_amount >= amount`, so a race cannot overdraw the wallet even if callers arrive together. ## Exact payment approval A policy can return `approval_required` instead of creating a reservation. The approval is durable, short-lived, and bound to the exact agent, wallet, assignment, amount, asset, network, recipient, resource, scope, merchant, purpose, project, cost center, matched policies, and idempotency key. The principal approves or rejects it through the dashboard or principal-session API. An approved retry must repeat the original terms and is consumed when the reservation succeeds. It cannot be reused for a different transaction. The authorization JWT also binds the semantic context, so changing it at x402 verification or settlement fails closed. ## Reload separation of duties An agent may ask for a reload only when available value reaches the configured low-balance threshold. It cannot approve or fund the request. The principal can: * approve, then fund; * reject; * leave the request pending; * fund directly without an agent request in sandbox mode. Wallet-level reload controls can cap maximum balance, a single reload, cumulative reload amount and period, and reload count and period. The server rechecks these limits when funding, after the principal decision, so an old approval cannot bypass newer balance or velocity state. Only one pending request per agent and wallet exists. The agent persists an idempotency key before the first call and reuses it for the exact request. That replay returns the same record through pending, approval/rejection, and funding; changed terms or a second key while another request is pending conflict. Approval/rejection and funding are response-loss safe: repeating the same decision or funding call returns the existing result without crediting twice. A contradictory decision or external funding reference returns a conflict. Agent wallet listings expose policy and balances, but not custody-provider identifiers, wallet addresses, principal identifiers, or wallet metadata. ## Custody boundary `sandbox_ledger` is the internal-ledger implementation. It is suitable for local development, deterministic integration tests, and off-chain prepaid accounting where the Grantex ledger is explicitly the system of record. `external` with operator-configured `base_usdc` supports native Base USDC EIP-3009 payments. It verifies finalized funding receipts and settlement evidence and persists signatures with balance reservations. See [Base USDC custody](/guides/base-usdc-custody) for provisioning, SDK availability and the in-process signing-key risk. Other or unconfigured providers fail with `CUSTODY_ADAPTER_UNAVAILABLE`. Arbitrary provider references never become spendable value. For Base, blocking prevents new signatures but does not cancel already signed payments. Those reservations remain held and count against limits until finalized settlement or unused expiry, including across server restarts and RPC outages. Reconciliation never accepts caller-invented settlement evidence. ## API families Principal-session endpoints are under `/v1/principal/prepaid-wallets*` and cover wallets, assignments, direct reloads, reload decisions, activity, and release or block actions. Principal policies and exact approvals use `/v1/principal/prepaid-wallet-spend-policies*` and `/v1/principal/prepaid-wallet-payment-approvals*`. Developer-level policies use `/v1/prepaid-wallet-spend-policies*` under API-key authentication. DPoP agent endpoints are under `/v1/prepaid-wallets*` and cover listing, payment authorization, and reload requests. The official x402 v2 facilitator endpoints are `/v1/x402/supported`, `/v1/x402/verify`, and `/v1/x402/settle`. See the [x402 integration guide](/integrations/x402) for code and failure handling and [Agent Wallet Governance](/guides/agent-wallet-governance) for the Grantex, issuer, runtime, principal, and merchant boundaries. Self-hosting operators must also complete [Prepaid Wallet Production Readiness](/guides/prepaid-wallet-production) for public routing, migration, notification, merchant-recovery, custody, and external-review dependencies. # SD-JWT (Selective Disclosure) Source: https://docs.grantex.dev/features/sd-jwt Selective Disclosure JWTs let agents present only the claims needed for a given transaction, enabling privacy-preserving authorization and Verifiable Intent compatibility. ## Overview SD-JWT (Selective Disclosure JWT) extends Grantex's Verifiable Credential support with privacy-preserving selective disclosure. Instead of revealing all credential claims to every verifier, agents can choose which fields to disclose per interaction. SD-JWT builds on the existing VC-JWT infrastructure. Pass `credentialFormat: "sd-jwt"` during token exchange to receive an SD-JWT instead of a standard VC-JWT. ## Why Selective Disclosure? In agentic commerce, an agent may interact with many services, each needing different information: * A payment processor needs to verify budget authorization, but doesn't need the principal's identity * A content API needs to verify scopes, but doesn't need to know the developer * A compliance auditor needs full disclosure of all claims SD-JWT lets the agent present a tailored credential for each interaction, revealing only what's necessary. This follows the principle of minimal disclosure and aligns with privacy regulations like GDPR. ## How It Works ### SD-JWT Format An SD-JWT consists of three parts separated by `~`: ``` ~~~...~ ``` * **Issuer JWT**: A standard JWT containing visible claims and SHA-256 hashes of selective claims in a `_sd` array * **Disclosures**: Base64url-encoded JSON arrays of `[salt, claim_name, claim_value]` * **Trailing `~`**: Indicates the end of disclosures ### Issuance Flow ```typescript theme={null} // Request an SD-JWT during token exchange const result = await grantex.tokens.exchange({ code: authCode, agentId: 'ag_01ABC', credentialFormat: 'sd-jwt', }); // result.sdJwtCredential contains the full SD-JWT ``` ### Presentation Flow When presenting to a verifier, the agent selects which fields to reveal: ```typescript theme={null} // Present only the scopes and grantId (omitting principalId, developerId) const result = await grantex.credentials.present({ sdJwt: sdJwtCredential, nonce: 'verifier-nonce-123', audience: 'https://api.merchant.com', }); // result.disclosedClaims contains only the revealed fields ``` ### Verification Any party can verify an SD-JWT presentation: ```typescript theme={null} const verification = await grantex.credentials.present({ sdJwt: presentedSdJwt, }); if (verification.valid) { console.log('Disclosed claims:', verification.disclosedClaims); // Only contains the fields the holder chose to reveal } ``` ## Default Selective Fields By default, the following credential subject fields are selectively disclosable: | Field | Description | Always visible? | | - | - | - | | `id` (agentDid) | Agent's DID identifier | Yes | | `type` | Credential type (`AIAgent`) | Yes | | `grantId` | Grant record ID | Yes | | `principalId` | End-user who authorized | No (selective) | | `developerId` | Developer organization | No (selective) | | `scopes` | Authorized scopes | No (selective) | | `delegationDepth` | Delegation chain depth | No (selective) | ## Verifiable Intent Compatibility SD-JWT enables Grantex credentials to be compatible with Verifiable Intent frameworks like the Mastercard model. By selectively disclosing only the fields relevant to a specific transaction, agents can: 1. **Prove authorization** without revealing identity 2. **Demonstrate scope** without exposing the full grant 3. **Verify delegation chains** without leaking organizational structure ## SDK Support ### TypeScript ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_live_...' }); // Issue SD-JWT const exchange = await grantex.tokens.exchange({ code: authCode, agentId: agentId, credentialFormat: 'sd-jwt', }); // Verify presentation const result = await grantex.credentials.present({ sdJwt: exchange.sdJwtCredential!, nonce: 'verifier-nonce', }); ``` ### Python ```python theme={null} from grantex import Grantex, SDJWTPresentParams grantex = Grantex(api_key="gx_live_...") # Verify an SD-JWT presentation result = grantex.credentials.present( SDJWTPresentParams( sd_jwt=sd_jwt_string, nonce="verifier-nonce", ) ) if result.valid: print(result.disclosed_claims) ``` ## API Reference ### Token Exchange with SD-JWT ```http theme={null} POST /v1/token Content-Type: application/json Authorization: Bearer gx_live_... { "code": "auth-code", "agentId": "ag_01ABC", "credentialFormat": "sd-jwt" } ``` Response includes `sdJwtCredential` alongside the standard `grantToken`. ### Verify SD-JWT Presentation ```http theme={null} POST /v1/credentials/present Content-Type: application/json { "sdJwt": "~~~", "nonce": "optional-verifier-nonce", "audience": "optional-audience" } ``` **Response:** ```json theme={null} { "valid": true, "vcId": "vc_01ABC...", "disclosedClaims": { "id": "did:grantex:ag_01ABC", "type": "AIAgent", "grantId": "grnt_01ABC", "scopes": ["read", "write"] } } ``` ## Security Considerations * Disclosures are salted with 128-bit cryptographic random values to prevent correlation attacks * The `_sd` array contains SHA-256 hashes that bind disclosures to the issuer JWT * Tampered or fabricated disclosures are detected by hash mismatch * The issuer JWT signature covers the `_sd` array, preventing hash substitution * SD-JWT verification requires the same public key infrastructure as VC-JWT (Grantex JWKS/DID) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Trust Registry Source: https://docs.grantex.dev/features/trust-registry Public organization directory with DID identity and DNS ownership verification. ## Overview The Grantex Trust Registry is a public, searchable directory of organizations that publish AI agents. Organization records use DIDs as stable identifiers and can prove domain ownership through a DNS TXT challenge. Search and organization-detail endpoints are public. Registration and DNS verification require a developer API key. The full listing of registry records, across developers, can be restricted to the service administrator credential, and operators should turn that on. DNS ownership is the only self-service verification method implemented by the current public API. There is no public document-upload, SOC 2/ISO review, manual-review SLA, compliance-badge application, CDN badge widget, or automatic agent-linking workflow. Fields such as `badges`, `compliance`, and agent statistics may appear in responses, but their presence must not be treated as an independent certification unless an authoritative process is published. ## Public Search ```bash theme={null} curl "https://api.grantex.dev/v1/registry/orgs?q=acme&verified=true&limit=20" ``` Supported filters are `q`, `verified`, `badge`, `category`, `limit`, and `cursor`. Results include an organization DID, name, description, verification level, stored badges, basic statistics, website, and logo URL when present. ```json theme={null} { "data": [ { "did": "did:web:acme.example", "name": "Acme AI", "verificationLevel": "verified", "badges": ["dns-verified"], "stats": { "totalAgents": 0, "weeklyActiveGrants": 0, "averageRating": 0 } } ], "nextCursor": null } ``` ## Organization Detail ```bash theme={null} curl "https://api.grantex.dev/v1/registry/orgs/did%3Aweb%3Aacme.example" ``` The detail response can include domain, public keys, stored compliance flags, contacts, verification method/time, and registered registry-agent records. The organization's JWK Set is also public: ```bash theme={null} curl "https://api.grantex.dev/v1/registry/orgs/did%3Aweb%3Aacme.example/jwks" ``` ## Registration and Verification Registration returns a one-time plaintext verification token and DNS instructions. The service stores only a hash afterward. ```bash theme={null} curl -X POST https://api.grantex.dev/v1/registry/orgs \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "did": "did:web:acme.example", "name": "Acme AI", "website": "https://acme.example", "contact": { "security": "security@acme.example" }, "requestVerification": true, "verificationMethod": "dns-txt" }' ``` After publishing the returned token at `_grantex-verify.acme.example`, trigger verification with the returned `orgId` (the DID is also accepted by the API): ```bash theme={null} curl -X POST "https://api.grantex.dev/v1/registry/orgs/$ORG_ID/verify-dns" \ -H "Authorization: Bearer $GRANTEX_API_KEY" ``` Successful verification sets the organization to `verified`, records `dns-txt`, clears the stored token hash, and adds the `dns-verified` marker. ## Operator Listing `GET /v1/trust-registry` returns the 100 newest registry records of every developer, verified or not. Because it crosses tenants, it is meant for the platform operator, but by default any developer API key can still read it, under that developer's plan budget. Set `TRUST_REGISTRY_ADMIN_LISTING_ENFORCED=true` (read when the service starts) to restrict it. Operators should turn this on. With it on, the listing takes the service administrator credential (`ADMIN_API_KEY`, as `Authorization: Bearer `), never a developer API key, and is limited to 20 calls a minute per address: * A developer API key, a wrong key or no key is refused with `401 UNAUTHORIZED`, and nothing is read. * While `ADMIN_API_KEY` is not configured, every call is refused with `503 SERVICE_UNAVAILABLE`. Off, the default, and any value other than exactly `true`, keep the existing developer-API-key behaviour unchanged. To look up one organization, use the public detail endpoints above. ## Endpoint Summary | Method | Path | Authentication | Purpose | | - | - | - | - | | `GET` | `/v1/registry/orgs` | None | Search organizations | | `GET` | `/v1/registry/orgs/:did` | None | Read organization detail | | `GET` | `/v1/registry/orgs/:did/jwks` | None | Read organization public keys | | `POST` | `/v1/registry/orgs` | Developer API key | Register an organization | | `POST` | `/v1/registry/orgs/:orgId/verify-dns` | Developer API key | Verify its DNS challenge | | `GET` | `/v1/trust-registry` | Developer API key; admin key (`ADMIN_API_KEY`) when `TRUST_REGISTRY_ADMIN_LISTING_ENFORCED=true` | List every developer's records (operator) | ## Accredited Issuers and Agent Attestations The organization registry above is self-service, and its strongest check is DNS ownership. A separate layer of the registry lets accredited issuers vouch for agents: * **Accredited issuers.** The registry operator accredits each issuer for specific trust-mark types, with a static JWKS and a `status_list_base`. * **Attestations.** An issuer posts an attestation, a compact JWS, only for an agent key whose possession has been proven. * **Trust levels.** The registry computes a level (`basic`, `verified`, `attested` or `attested_verified`) and flags, publishes its acceptance of each attestation on its own status lists, and fails closed: anything suspended reads `basic`. * **Reads.** Relying parties read the result through the minimised agent lookup or the signed manifest `/.well-known/agent-registry.json`. * **Passport-bound grants.** A grant can be bound to an Agent Passport, an SD-JWT VC from an accredited issuer, so it stops working when the registry no longer stands behind the passport. Unauthenticated reads are behind `REGISTRY_PUBLIC_ENDPOINTS_ENABLED`, and passport-bound grants are behind `PASSPORT_BOUND_GRANTS_ENABLED`. Both are off by default; see [Self-Hosting](/self-hosting). | Audience | Start here | | - | - | | Issuers | [Becoming an Accredited Issuer](/issuers/becoming-an-accredited-issuer), then [Running the Mock Issuer](/issuers/running-the-mock-issuer) | | Providers | [Registering Agents](/providers/registering-agents) | | Relying parties | [Verifying Agents](/relying-parties/verifying-agents) | | Everyone | [Passport vs Grant](/concepts/passport-vs-grant) | ## Related * [Trust Registry Setup](/guides/trust-registry-setup) * [MPP Agent Passports](/features/mpp-agent-passport) * [Security Best Practices](/guides/security-best-practices) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Verifiable Credentials Source: https://docs.grantex.dev/features/verifiable-credentials W3C Verifiable Credentials issued alongside grant tokens. Portable, tamper-proof proof of agent authorization for any verifier. ## Overview The Grantex-hosted service enabled opt-in portable WebAuthn assertion evidence on September 27, 2026. Self-hosted deployments must enable it explicitly. The current TypeScript, Python, and Go SDKs listed in [Release Status](/release-status) expose typed grant-evidence references and VC attestations. Grantex can issue W3C Verifiable Credentials (VCs) alongside standard RS256 JWTs. While grant tokens are optimized for real-time authorization (short-lived, revocation-checked, scope-enforced), Verifiable Credentials provide a portable, standards-compliant proof of authorization that any party can verify independently using the Grantex DID document. Verifiable Credentials are opt-in. Existing token exchange flows continue to work unchanged. Pass `credentialFormat: "vc-jwt"` during token exchange to receive a VC alongside the standard grant token. Portable WebAuthn evidence additionally requires `PORTABLE_WEBAUTHN_EVIDENCE_ENABLED=true`, `IRREGULARITY_CASCADE_REVOCATION_ENABLED=true`, and `PORTABLE_WEBAUTHN_EVIDENCE_STATUS_CHECK_ENABLED=true` on the auth service (all source defaults are off). Keep the latter two safety flags on if issuance is rolled back. The current primary SDKs expose the typed reference; independent verification still requires checking the VC, assertion, trusted origin, and current status. ## Why Verifiable Credentials? Grant tokens work well within systems that integrate with Grantex. But in agentic commerce, agents interact with third-party services that may have no relationship with Grantex: * A payment processor needs proof that the agent is authorized to spend on the user's behalf * A contract-signing service needs proof of human consent * An insurance API needs proof of delegated authority from a specific principal Verifiable Credentials provide a signed document that a verifier can check using published keys without a Grantex account. The verifier must trust the issuer's enrollment and authorization process, and revocation checks require current status-list data. ## W3C Compliance Grantex VCs conform to: | Standard | Version | Description | | - | - | - | | [VC Data Model](https://www.w3.org/TR/vc-data-model-2.0/) | v2.0 | Credential structure and semantics | | [VC-JWT](https://www.w3.org/TR/vc-data-model-2.0/#json-web-token) | -- | JWT encoding of VCs (compact, URL-safe) | | [StatusList2021](https://www.w3.org/TR/vc-status-list/) | -- | Bitstring-based revocation mechanism | | [DID Core](https://www.w3.org/TR/did-core/) | v1.0 | Issuer identification via `did:web` | ## Issuing a Verifiable Credential Request a VC during token exchange by setting the `credentialFormat` parameter: ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const result = await grantex.tokens.exchange({ code, agentId: agent.id, credentialFormat: 'vc-jwt', }); console.log(result.grantToken); // standard RS256 JWT (always present) console.log(result.verifiableCredential); // W3C VC-JWT (present when requested) ``` ```python Python theme={null} from grantex import Grantex, ExchangeTokenParams client = Grantex(api_key="gx_...") result = client.tokens.exchange(ExchangeTokenParams( code=code, agent_id=agent.id, credential_format="vc-jwt", )) print(result.verifiable_credential) # W3C VC-JWT ``` ```bash cURL theme={null} curl -X POST https://api.grantex.dev/v1/token \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"code": "01J...", "agentId": "ag_01...", "credentialFormat": "vc-jwt"}' ``` ## Credential Types ### AgentGrantCredential Issued for direct grants (user authorizes an agent directly). The credential subject attests that a specific principal authorized a specific agent with specific scopes. ```json theme={null} { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://grantex.dev/ns/credentials/v1" ], "type": ["VerifiableCredential", "AgentGrantCredential"], "issuer": "did:web:grantex.dev", "issuanceDate": "2026-03-08T12:00:00Z", "expirationDate": "2026-03-09T12:00:00Z", "credentialSubject": { "id": "did:grantex:ag_01HXYZ123abc", "type": "Agent", "grantId": "grnt_01HXYZ...", "principalId": "user_abc123", "developerId": "org_yourcompany", "scopes": ["calendar:read", "payments:initiate:max_500"], "authorizedAt": "2026-03-08T12:00:00Z" }, "credentialStatus": { "id": "https://api.grantex.dev/v1/credentials/status/1#42", "type": "StatusList2021Entry", "statusPurpose": "revocation", "statusListIndex": "42", "statusListCredential": "https://api.grantex.dev/v1/credentials/status/1" } } ``` ### DelegatedGrantCredential Issued for delegated grants (agent delegates to a sub-agent). Includes the full delegation chain for traceability. ```json theme={null} { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://grantex.dev/ns/credentials/v1" ], "type": ["VerifiableCredential", "DelegatedGrantCredential"], "issuer": "did:web:grantex.dev", "credentialSubject": { "id": "did:grantex:ag_SUB_AGENT", "type": "DelegatedAgent", "grantId": "grnt_01CHILD...", "parentGrantId": "grnt_01ROOT...", "parentAgentDid": "did:grantex:ag_ROOT_AGENT", "delegationDepth": 1, "principalId": "user_abc123", "scopes": ["calendar:read"] } } ``` ### Portable WebAuthn Evidence With portable evidence enabled, for a new passkey-approved grant, Grantex stores the verified assertion and includes a signed `webauthnEvidence` reference in the grant token. When `credentialFormat` is `vc-jwt` or `both`, the VC's `evidence` array contains a `GrantexWebAuthnAssertion` with `version: 1`, `authRequestId`, `credentialId`, base64url `credentialPublicKey`, `previousCounter`, `rpId`, `origin`, `challenge`, base64url `clientDataJSON`, `authenticatorData`, `signature`, `userVerified`, `assertedAt`, and a SHA-256 hex `digest`. With the rollout flag enabled (or for already-evidenced grants), the VC and grant commit together and failed requested VC issuance fails the exchange. Existing grants cannot acquire past assertion evidence retroactively. When an agent delegates a grant, the child token and opt-in VC inherit the original assertion reference/evidence only after Grantex checks it against the signed parent reference. This proves the original principal ceremony in the chain, not a new human approval of the delegation. Requested delegated VCs also commit atomically with their child grant. The digest is SHA-256 of the UTF-8 bytes of compact JavaScript `JSON.stringify` output (no added whitespace) for this ordered array: `[authRequestId, credentialId, credentialPublicKey, previousCounter, rpId, origin, challenge, clientDataJSON, authenticatorData, signature, userVerified, assertedAt]`. It binds the compact grant reference to the exact exported assertion. Verify the issuer signature and status, pin the expected RP ID and origin, recalculate the digest, then verify the WebAuthn signature and challenge using the included public key and prior counter. The `webauthnVerified` field from `/v1/credentials/verify` reports successful assertion verification, not independent customer-identity verification. The authenticator signs the challenge, not the grant/scopes; the Grantex issuer signature binds them and attests enrollment. Trust in the issuer's enrollment and identity checks remains essential. Raw public keys are correlatable, so request and distribute these VCs only when needed. ## Verifying a VC ### Using the Grantex SDK ```typescript TypeScript theme={null} const verification = await grantex.credentials.verify(vcJwt); if (verification.valid) { console.log('Issuer:', verification.payload?.['iss']); console.log('Assertion verified:', verification.webauthnVerified === true); } else { console.log('Invalid:', verification.error); } ``` ```python Python theme={null} verification = client.credentials.verify(vc_jwt) if verification.valid: print("Issuer:", verification.payload["iss"] if verification.payload else None) print("Assertion verified:", verification.webauthn_verified is True) else: print("Invalid:", verification.error) ``` ### Independent Verification Any party can verify a Grantex VC without the SDK by: 1. Decoding the VC-JWT (standard JWT decode) 2. Resolving the issuer DID (`did:web:grantex.dev` resolves to `https://grantex.dev/.well-known/did.json`) 3. Extracting the public key from the DID document 4. Verifying the JWT signature against the public key 5. Checking the `credentialStatus` endpoint for revocation 6. If WebAuthn evidence is required, checking the trusted RP ID/origin, assertion signature/challenge/counter, and grant-reference digest ```bash theme={null} # Resolve the DID document curl https://api.grantex.dev/.well-known/did.json # Check revocation status curl https://api.grantex.dev/v1/credentials/status/1 ``` No Grantex account or API key is required for verification. The DID document and status list endpoints are public, but the verifier still needs a trusted issuer policy and fresh status data. ## Revocation via StatusList2021 Grantex uses the W3C StatusList2021 standard for credential revocation. Each VC references a position in a bitstring-based status list. When a grant is revoked, the corresponding bit is flipped. ### How It Works 1. Each credential is assigned a `statusListIndex` (a position in the bitstring) 2. The status list credential is published at a public URL (`/v1/credentials/status/:id`) 3. When a grant is revoked, Grantex sets the bit at that index 4. Verifiers fetch the status list and check the bit to determine revocation ### Checking Status ```typescript TypeScript theme={null} // Via SDK const cred = await grantex.credentials.get('vc_01HXYZ...'); console.log(cred.status); // "active" or "revoked" // The SDK also checks status during verify() const result = await grantex.credentials.verify(vcJwt); console.log(result.valid, result.revoked === true); ``` ```python Python theme={null} cred = client.credentials.get("vc_01HXYZ...") print(cred.status) # "active" or "revoked" result = client.credentials.verify(vc_jwt) print(result.valid, result.revoked is True) ``` ### Revocation Timing When you revoke a grant via `DELETE /v1/grants/:id` or an enabled irregularity cascade, the following database changes happen atomically: 1. The grant record is marked as revoked 2. The StatusList2021 bit is flipped (VC shows as revoked) 3. All delegated sub-grants are cascade-revoked (and their VCs) The Redis revocation cache is updated after commit; the database remains authoritative. An issuer-side check for evidence-bearing VCs also consults their stored grant status. Independent verifiers must check a fresh status list; a signature and WebAuthn assertion alone do not establish current authorization. Operators should run the [historical VC reconciliation](/features/fido-webauthn#portable-evidence-rollout) when upgrading a deployment that previously used grant-only irregularity revocation. ## Listing Credentials ```typescript TypeScript theme={null} // List all credentials const { credentials } = await grantex.credentials.list(); // Filter by grant const { credentials: grantCreds } = await grantex.credentials.list({ grantId: 'grnt_01HXYZ...', }); // Filter by principal const { credentials: userCreds } = await grantex.credentials.list({ principalId: 'user_abc123', status: 'active', }); ``` ```python Python theme={null} # List all credentials result = client.credentials.list() # Filter by grant result = client.credentials.list(grant_id="grnt_01HXYZ...") # Filter by principal and status result = client.credentials.list( principal_id="user_abc123", status="active", ) ``` ## Mastercard Verifiable Intent Grantex VCs have some building blocks relevant to agentic commerce, but no Mastercard certification or full Verifiable Intent interoperability is claimed: | Requirement | Grantex Implementation | | - | - | | Cryptographic human presence proof | Verified assertion evidence in opt-in VCs for new passkey-approved grants; issuer/enrollment trust still required | | Verifiable agent identity | Agent DID (`did:grantex:ag_...`) as credential subject | | Scoped authorization | `scopes` array in credential subject | | Revocable credentials | StatusList2021 with real-time revocation | | Standard-compliant format | W3C VC Data Model v2.0 + VC-JWT encoding | | Independent verification | Public DID document + public status list endpoints | ## API Reference | Method | Endpoint | Description | | - | - | - | | `GET` | `/v1/credentials/:id` | Retrieve a specific Verifiable Credential | | `GET` | `/v1/credentials` | List credentials with optional filters | | `POST` | `/v1/credentials/verify` | Verify a VC-JWT (signature + status + expiry) | | `GET` | `/v1/credentials/status/:id` | StatusList2021 credential (public, no auth) | ## Next Steps * [FIDO2 / WebAuthn](/features/fido-webauthn) -- passkey-based human presence verification * [DID Infrastructure](/features/did-infrastructure) -- how the issuer DID works * [Grant Token](/concepts/grant-token) -- the standard JWT-based grant token * [Multi-Agent Delegation](/concepts/delegation) -- delegation chains in VCs ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # x402 Architecture Source: https://docs.grantex.dev/features/x402-architecture Official x402 v2 payment messages backed by principal-controlled Grantex prepaid-wallet reservations. Grantex uses x402 as the payment-message transport and the prepaid-wallet service as the authorization and accounting authority. Payment requirements alone do not prove that an AI agent has delegated authority or available principal-owned value; the server-issued reservation connects those facts. ## Components | Component | Responsibility | | - | - | | Principal | Funds wallets, sets policies, approves reloads, and blocks use | | OAuth authorization server | Issues DPoP-bound agent access tokens for the wallet resource | | Wallet policy service | Atomically evaluates assignments, limits, allowlists, and balance | | `@grantex/x402` client | Converts official `PAYMENT-REQUIRED` into a reservation-backed `PAYMENT-SIGNATURE` retry | | Resource server | Defines amount, asset, recipient, resource, scope, and timeout | | Facilitator endpoints | Verify and settle the exact signed authorization | | PostgreSQL | Serializes spend, stores reservations, and records the wallet ledger | ## Protocol flow ```text theme={null} Agent Resource server Grantex wallet/facilitator Principal | GET /resource | | | |---------------------->| | | | 402 PAYMENT-REQUIRED | | | |<----------------------| | | | DPoP authorize(amount, recipient, resource, scope, idempotency key) | |------------------------------------------------->| | | lock wallet -> check policy -> reserve | | signed one-time authorization | | |<-------------------------------------------------| | | retry PAYMENT-SIGNATURE | | |---------------------->| POST /v1/x402/verify | | | |------------------------->| | | | valid | | | |<-------------------------| | | | POST /v1/x402/settle | | | |------------------------->| | | | settled + transaction ID | | | 200 PAYMENT-RESPONSE |<-------------------------| | |<----------------------| | | ``` ## Exact binding The authorization JWT and database reservation bind: * authorization and reservation identifiers; * developer, principal, agent, grant, wallet, and assignment; * amount in atomic units, asset, and network; * recipient and exact resource URL; * delegated action scope; * canonical request hash and expiry. The authorization lifetime is at most 300 seconds. The accepted x402 requirements must exactly equal the requirements supplied to the facilitator. The facilitator rejects malformed envelopes, unsupported versions/schemes, expired signatures, released reservations, and any binding mismatch. Verification is provisional. A resource server must settle successfully before starting irreversible protected work or returning the paid result; treating verification alone as payment would reintroduce a verify/use race. Remote resource URLs must use HTTPS so `PAYMENT-SIGNATURE` cannot disclose the one-time authorization over plaintext. HTTP is accepted only for explicit loopback development addresses. ## Idempotency and replay The agent supplies a high-entropy idempotency key when reserving. Its hash is unique across the developer, principal, and agent, not merely within one wallet. This is important for automatic wallet selection: a lost response can be retried, but the retry cannot select a different wallet or change its terms. Settlement is idempotent and returns the original transaction identifier. Calling authorization again with a key whose reservation settled returns `PAYMENT_ALREADY_SETTLED`. Verification accepts only a currently reserved authorization; a settled authorization cannot verify again and therefore cannot re-run protected work. Released and expired reservations cannot be revived. The fetch adapter accepts a caller-provided `idempotencyKey`. Callers should derive it from a durable logical order/request identifier and reuse it after a timeout or a lost wallet-authorization response. The generated default protects a single fetch flow, but cannot correlate two independently restarted caller invocations. That option is deliberately not an HTTP header and cannot recover merchant work after settlement. A side-effecting merchant must also accept a normal `Idempotency-Key` request header, atomically persist its business result under that key, and return the cached result on a caller retry. Without that merchant record, a response lost after settlement fails closed with `PAYMENT_ALREADY_SETTLED`; Grantex does not replay protected work. ## Blocking semantics For `grantex:prepaid`, applying an assignment, wallet, or all-wallet agent block happens in the same database transaction that releases affected outstanding reservations. New authorizations fail, old authorizations stop verifying, and reserved value returns to available value. Historical settlements remain immutable. For opt-in Base USDC, blocks prevent new signatures but cannot recall signatures already issued. These EVM reservations remain held until finalized settlement or confirmed unused expiry. Manual release, local timeout, policy mutation and RPC failure do not return that exposure to the available balance. ## Legacy GDT boundary The package still includes standalone GDT issuance and verification for signed authorization context. That path is not the managed wallet protocol. A GDT is stateless unless the host supplies durable state, so its `spendLimit` claim must not be described as an atomically enforced cumulative balance. New prepaid x402 integrations should use `PrepaidWalletAgentClient.x402Authorizer`. ## External payment rails `grantex:prepaid` identifies the repository's managed prepaid settlement scheme. It does not claim an on-chain transfer. The opt-in [Base USDC adapter](/guides/base-usdc-custody) uses standard x402 v2 `exact` on `eip155:8453`, a policy-gated EIP-3009 signer and the merchant's EVM facilitator. Grantex checks finalized USDC funding and reconciles on-chain nonce/transfer evidence independently of the merchant's HTTP response. It does not send an internal Grantex JWT to an EVM merchant. Other rails require separate adapters. Base `agent.fetch` binds the original request URL, sends the stable logical request key as HTTP `Idempotency-Key`, rejects redirects and reuses the stored signature after response loss. Merchant-side result persistence is still required. Unconfigured custody, stale RPC or ambiguous evidence fails closed. # GDT Specification Source: https://docs.grantex.dev/features/x402-gdt-spec Specification of the Grantex Delegation Token (GDT) — a W3C Verifiable Credential 2.0 for agent spend authorization. ## Grantex Delegation Token (GDT) A **Grantex Delegation Token** is a W3C Verifiable Credential 2.0 that carries signed authorization context for an AI agent acting on behalf of a human principal. It is encoded as a compact JWT signed with Ed25519 (EdDSA algorithm). A relying service decides whether and how to enforce that context. This document specifies the legacy standalone authorization-context token. A GDT is not a prepaid balance, payment proof, or shared cumulative-spend counter. Use [Agent Prepaid Wallets](/features/prepaid-wallets) and the official x402 v2 flow when spend must be atomically reserved, accumulated, reloaded, and blocked across callers. ## Token Format ### JWT Header ```json theme={null} { "alg": "EdDSA", "typ": "JWT" } ``` ### JWT Payload ```json theme={null} { "iss": "did:key:z6Mk...principal...", "sub": "did:key:z6Mk...agent...", "vc": { "@context": [ "https://www.w3.org/ns/credentials/v2", "https://grantex.dev/v1/x402" ], "type": [ "VerifiableCredential", "GrantexDelegationToken" ], "credentialSubject": { "id": "did:key:z6Mk...agent...", "scope": ["weather:read", "news:read"], "spendLimit": { "amount": 10, "currency": "USDC", "period": "24h" }, "paymentChain": "base", "delegationChain": ["did:key:...principal..."] } }, "iat": 1711036800, "exp": 1711123200, "jti": "550e8400-e29b-41d4-a716-446655440000" } ``` ## Claim Definitions ### Standard JWT Claims | Claim | Type | Description | | - | - | - | | `iss` | `string` | Issuer — the principal's `did:key` who authorized the delegation | | `sub` | `string` | Subject — the agent's `did:key` being delegated to | | `iat` | `number` | Issued-at timestamp (Unix epoch seconds) | | `exp` | `number` | Expiration timestamp (Unix epoch seconds) | | `jti` | `string` | Unique token ID (UUID v4) for revocation and audit | ### VC Context The `vc` claim follows the W3C Verifiable Credentials Data Model 2.0: | Field | Value | Description | | - | - | - | | `@context` | `["https://www.w3.org/ns/credentials/v2", "https://grantex.dev/v1/x402"]` | W3C VC 2.0 context + Grantex x402 context | | `type` | `["VerifiableCredential", "GrantexDelegationToken"]` | Credential types | ### Credential Subject | Field | Type | Description | | - | - | - | | `id` | `string` | Agent DID (matches JWT `sub`) | | `scope` | `string[]` | Array of authorized `resource:action` patterns | | `spendLimit.amount` | `number` | Maximum spend in the given period | | `spendLimit.currency` | `string` | `"USDC"` or `"USDT"` | | `spendLimit.period` | `string` | Rolling period: `"1h"`, `"24h"`, `"7d"`, `"30d"` | | `paymentChain` | `string` | Target blockchain (default: `"base"`) | | `delegationChain` | `string[]` | Ordered list of DIDs in the delegation chain | ## Scope Format Scopes follow the `resource:action` pattern: ``` weather:read — Read weather data weather:write — Write/update weather data weather:* — All weather actions (wildcard) * — All resources and actions (full access) ``` ### Scope Matching Rules 1. **Exact match**: `weather:read` matches `weather:read` 2. **Action wildcard**: `weather:*` matches `weather:read`, `weather:write`, etc. 3. **Global wildcard**: `*` matches everything 4. **No implicit escalation**: `weather:read` does NOT match `weather:write` ## Spend Limit Periods | Period | Duration | Use Case | | - | - | - | | `1h` | 1 hour | High-frequency, low-value APIs | | `24h` | 24 hours | Standard daily delegation | | `7d` | 7 days | Weekly batch operations | | `30d` | 30 days | Monthly budgets | ## Expiry Formats The `expiry` parameter accepts: | Format | Example | Description | | - | - | - | | Shorthand hours | `24h` | 24 hours from now | | Shorthand days | `7d` | 7 days from now | | ISO 8601 duration | `PT24H` | 24 hours from now | | ISO 8601 duration | `P7D` | 7 days from now | | ISO 8601 datetime | `2026-03-22T00:00:00Z` | Absolute expiry | ## Delegation Chain The `delegationChain` field records the full chain of delegation: ``` Organization (did:key:z6Mk...org...) └── Manager (did:key:z6Mk...mgr...) └── Agent (did:key:z6Mk...agent...) ``` The delegation chain enables audit of the full authorization path. ## Cryptographic Details | Property | Value | | - | - | | Algorithm | EdDSA (Ed25519) | | Key type | OKP (Octet Key Pair) | | Curve | Ed25519 | | DID method | `did:key` with multicodec prefix `0xed01` | | Multibase encoding | Base58btc (`z` prefix) | | Token encoding | Compact JWT serialization | ## Verification Checks A GDT is considered valid only if ALL of the following pass: 1. The JWT signature is valid for the issuer's Ed25519 public key 2. The `exp` claim is in the future 3. The `jti` is not in the revocation registry 4. The requested resource matches at least one granted scope 5. The request amount does not exceed the spend limit 6. The `vc.type` array includes both `VerifiableCredential` and `GrantexDelegationToken` 7. The `vc.credentialSubject` contains valid scope and spend limit data ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Agent Wallet Governance Source: https://docs.grantex.dev/guides/agent-wallet-governance Responsibility boundaries, layered spend controls, exact payment approvals, reload governance, and deployment gaps for AI-agent prepaid wallets. Grantex is the authorization and governance layer above a wallet, card, bank, or on-chain issuer. It does not replace custody, payment-network, or merchant controls. A production design combines all of these layers and fails closed if any required layer is unavailable. ## Responsibility matrix | Layer | Owns | Does not delegate to Grantex | | - | - | - | | Human principal | Assign wallets, approve authority, set budgets, approve exact exceptions, request reload funding, and block an assignment, wallet, or agent | Final consent and exceptional-spend decisions | | AgenticOrg or another agent runtime | Agent orchestration, task planning, connector execution, and the human interaction used to request approval or reload | Custody, OAuth consent, durable spend accounting, or payment-network authorization | | Grantex | Agent identity, OAuth delegation, DPoP sender binding, multi-wallet assignment, layered semantic policy, exact-bound approvals, reload separation of duties, atomic reservation, stop controls, and audit evidence | KYC/KYB, custody, settlement-network acceptance, chargebacks, or sanctions screening | | Wallet issuer or custodian | KYC/KYB, AML and sanctions controls, custody and safeguarding, card/account issuance, network authorization, MCC/geography/card controls, clearing, settlement, FX, refunds, disputes, fraud signals, and reconciliation | The principal's semantic agent authority or cross-wallet project budgets unless explicitly integrated | | Merchant or resource server | Server-trusted amount, payee, resource, order identity, HTTP idempotency, x402 verification/settlement, and delivery of the paid result | Trusting agent-supplied price, recipient, purpose, or settlement evidence | This division is deliberate. Issuer controls remain authoritative for movement of money. Grantex can make the authorization more precise by proving which agent may spend, for which principal, wallet, recipient, resource, action, project, purpose, and cost center. `purpose`, `projectId`, and `costCenter` are integrity-bound into the Grantex authorization and x402 exchange, but they originate with the caller. Grantex prevents an in-flight substitution; it does not independently prove that the caller chose the correct accounting label. High-assurance deployments must map these values from a trusted workflow, merchant order, or independently attested runtime context instead of accepting arbitrary model output. ## Layered policy model Every payment first passes the assignment baseline and then every matching layered policy. Grantex supports policy scopes for: * one wallet assignment; * one wallet across assigned agents; * one agent across assigned wallets; * a shared `budgetGroup` across agents and wallets; * one principal; * the developer tenant. Policies can deny a matching payment, require principal approval, or limit amount and count. Limit windows can be per authorization, rolling seconds, calendar day, week, month, or lifetime. Optional filters match recipients, resource origins, action scopes, assets, networks, merchant IDs, purposes, project IDs, cost centers, and verified merchant status. The most restrictive applicable result wins: 1. A deny policy rejects the payment. 2. A limit breach rejects or requires approval according to `onExceed`. 3. A matching approval policy requires approval. 4. Only a fully allowed decision can reserve value. Reserved and settled amounts count toward limits. Policy evaluation, usage calculation, and wallet reservation run under database locks, so parallel agents cannot race a shared group or principal budget. ## Safe assignment defaults New assignments do not silently allow every recipient, action scope, or HTTPS resource. The principal must provide an allowlist or explicitly opt into the corresponding `allowAnyRecipient`, `allowAnyScope`, or `allowAnyResource` setting. Existing assignments created before migration `092` retain their prior meaning during migration. An authorization can never add OAuth authority. The agent's DPoP access token must contain `wallet:spend` and the exact merchant action scope even when wallet policy would otherwise allow the payment. ## Exact principal approval A `require_approval` result creates a short-lived durable request bound to the exact agent, principal, wallet, assignment, amount, asset, network, recipient, resource, action scope, merchant, purpose, project, cost center, policy set, and idempotency key. ```ts theme={null} import { PrepaidPaymentApprovalRequiredError, createX402Agent, } from '@grantex/x402'; try { await x402.fetch(resourceUrl, { idempotencyKey }); } catch (error) { if (!(error instanceof PrepaidPaymentApprovalRequiredError)) throw error; await notifyPrincipal(error.approval.approvalRequestId); // After the principal approves the request: await x402.fetch(resourceUrl, { walletId: error.approval.walletId, idempotencyKey: error.idempotencyKey, approvalRequestId: error.approval.approvalRequestId, }); } ``` Approval does not create a reusable spending token. A changed amount, recipient, resource, semantic context, wallet, or idempotency key is rejected. Successful reservation consumes the approval once. ## Reload governance Agents may request reloads but cannot approve or fund them. Wallet controls can set a maximum balance, maximum single reload, cumulative reload amount and window, and reload count and window. These controls are rechecked when the principal funds an approved request, not only when the agent asks for it. Reload and payment events are emitted to the Grantex event stream. Operators must bridge those events to email, SMS, chat, or another approved human channel when the principal is not actively using the dashboard. ## Audit and emergency controls Every policy decision has a durable decision ID, matched policy IDs, outcome, usage snapshot, and normalized payment context. Policy decisions are append-only. Wallet ledger entries are also append-only. The principal dashboard exposes pending approvals, policy status, wallet activity, reload requests, and stop controls. For the internal `grantex:prepaid` rail, blocking an assignment, a wallet, or all of one agent's wallets releases active reservations in that boundary. Disabling or revoking a policy also releases active reservations. Policy mutation, release, authorization, and settlement share a developer-scoped database lock, so they have a single serial order and a released authorization cannot settle afterward. Base USDC signatures have a different boundary: new signatures stop, but issued signatures remain valid on-chain until used or expired. Their reservations stay held and count against limits until finalized-chain reconciliation. See [Base USDC custody](/guides/base-usdc-custody) before enabling real funds. ## Current implementation and external gaps | Capability | Status | Production action | | - | - | - | | Sandbox ledger, layered policy, exact approval, multi-wallet assignment, reload controls, reservation, settlement, and audit | Implemented and Docker E2E tested | Run migrations `091` and `092`, PostgreSQL backup/restore, and the published route probes | | TypeScript, x402, Python, and Go clients | Implemented in the release identified by each package manifest | Pin a registry-verified version; repository source alone does not prove publication | | External issuer/custody adapter | Opt-in native Base USDC adapter included in source; other/unconfigured rails fail closed | Provision protected custody and RPC using the Base guide, or integrate the chosen provider; refunds/disputes are not added by this adapter | | KYC/KYB, AML, sanctions, fraud, MCC, geography, card state, FX, and safeguarding | Issuer/operator responsibility | Configure and independently test the regulated provider controls | | Principal email/SMS/chat delivery | Event source only | Operate a durable notification bridge with retry, dead-letter handling, and escalation | | Merchant business-result recovery | Merchant responsibility | Atomically store the result under HTTP `Idempotency-Key` before returning success | | Multi-currency aggregate budgets | Not implemented | Use one atomic asset per policy or integrate a trusted, timestamped FX normalization service before aggregation | | Independent attestation of purpose, project, and cost-center labels | Not implemented; caller values are policy-evaluated and proof-bound | Derive labels from a trusted workflow/merchant mapping or require a signed upstream attestation before using them for accounting or compliance | | Provider refunds, disputes, and chargebacks in Grantex ledger | Not implemented | Reconcile provider lifecycle events and extend the append-only ledger before claiming end-to-end external accounting | | Policy simulation before activation | Not implemented | Create policies disabled, test in sandbox, inspect decisions, then activate; do not treat this as a formal dry-run API | | Fine-grained developer API-key roles for policy administration | Not implemented | Treat developer API keys as tenant administrators and isolate them accordingly | See [Agent Prepaid Wallets](/features/prepaid-wallets), the [x402 integration guide](/integrations/x402), and [Prepaid Wallet Production Readiness](/guides/prepaid-wallet-production) for protocol and hosting details. # Grantex and AgenticOrg Governed Cases Source: https://docs.grantex.dev/guides/agenticorg-governed-cases What Grantex verifies for AgenticOrg business onboarding cases, what AgenticOrg enforces locally, and which purpose and cap controls are not yet wired into the integration. ## Direct answer Grantex supplies verifiable, delegated tool authority for AgenticOrg's governed business cases. The AgenticOrg source runtime requires one active tenant role agent, a registered provider read-tool manifest, an exact local case-purpose allowlist, and a valid delegated grant before every provider call. A missing or denied check stops the provider call. Case decisions and analyst review remain human actions in AgenticOrg; an agent grant does not approve a case. This describes repository source, **not an assertion that a particular hosted tenant has the feature enabled or that the latest main commit is deployed**. Check the deployed revision and the tenant's `governed_cases.enabled` flag. ## Responsibility boundary | Control | Current owner | What to verify | | - | - | - | | Tenant, case state and human actor | AgenticOrg | Tenant isolation, enabled flag, state transition and a signed-in person for decisions or reviews | | Case-purpose allowlist | AgenticOrg | Exact `case_purposes` on the selected active role; this is local configuration, not a token claim | | Delegated tool authority | Grantex grant, checked by AgenticOrg through the published Python SDK | Token signature, expiry, registered agent identity, connector, tool and permission | | Provider response and evidence | Provider and AgenticOrg | Provider access, cited response, policy version, case record and hand-off | | Token-level purpose, case-bound cap | Not active in this integration | Do not represent the local allowlist or a general budget as either control | At the last verified AgenticOrg integration check on September 24, 2026, its Python dependency was `grantex==0.5.1`. That integration did not enforce token-level purpose or per-case caps for this flow, and a pooled run token was not bound to one case. Python `0.6.1` is now published with general purpose and cap APIs, but a package release alone does not wire AgenticOrg's case context into enforcement. Recheck AgenticOrg's deployed dependency and flow before claiming this as a production control. See [Release Status](/release-status). ## Operator setup 1. Review the selected provider's manifest and allow only the read tools the business-underwriter and screening-disposition roles need. 2. Register exactly one active, shared tenant agent for each role in AgenticOrg. Verify each stored Grantex agent ID and derived scope set. 3. Set each role's exact `case_purposes` list and provision a root grant that covers the registered tools. Keep credentials in a secret manager. 4. Configure a reviewed case policy and provider. The bundled mock provider is for local and test environments, not production verification. 5. Run denied-path tests for missing role, wrong purpose, missing grant, revoked grant, undeclared tool and provider failure before enabling the tenant flag. 6. Confirm the deployed SHA and operator logs before describing the flow as available to users. AgenticOrg's [case lifecycle](https://github.com/mishrasanjeev/agentic-org/blob/main/docs/governance/case-lifecycle.md) and [grant-enforcement runbook](https://github.com/mishrasanjeev/agentic-org/blob/main/docs/operations/grant-enforcement.md) are the implementation and operations sources of truth. ## SDK and MCP boundary Machine-safe submit, list, read and investigation-scheduling methods for the AgenticOrg Python and TypeScript SDKs are proposed in [AgenticOrg PR #1401](https://github.com/mishrasanjeev/agentic-org/pull/1401). At this guide's September 24, 2026 check, they are not in AgenticOrg `main` or its published `0.3.0` client packages. Evaluate them from that PR's source branch until it is merged, released and verified separately. Scheduling an investigation is not proof it succeeded; inspect the later case state. AgenticOrg's MCP server advertises general agents-as-tools, not the governed case roles. Grantex MCP transport authorization and tool authorization are separate from AgenticOrg's human case-decision path. Neither MCP discovery nor an agent token can stand in for a signed-in person or a verified decision grant. ## Decision grants and the agent binding The Grantex auth service setting `DECISION_GRANT_AGENT_BINDING` (off by default) binds decision grants to the agent a request names and stops `GET /v1/decisions/requests/{id}` returning them to the developer API key (see [Decision Grants](/concepts/decision-grants#binding-decisions-to-the-requesting-agent)). An integration that records a decision by reading `decisionGrants` from that `GET` and presenting them to `POST /v1/decisions/consume` stops working when the binding is on, because the list is no longer returned. Before the binding is turned on, such an integration: 1. **Consumes its own decisions by request id.** A request created without `agentId` or `grantId` is consumed with `POST /v1/decisions/requests/{id}/consume` and `{"action": ..., "caseVersion": ...}`, so its grants never leave Grantex. The answer has the same shape as `POST /v1/decisions/consume` (`requestId`, `jtis`, `actionHash`, and `approvers` each with `sub` and `jti`). 2. **Consumes at the same point as before.** If the decision is recorded under a lock today, the consumption by request id happens under that lock too. "Not approved yet" comes from the request's own state, or from an `unknown_grant` or `four_eyes_incomplete` refusal, rather than from an empty grant list. 3. **Reads `subReason` before the HTTP status.** Grantex refuses a grant presented for another agent with `403`, `"reason": "decision_invalid"` and `"subReason": "wrong_agent"`, which is not an authentication failure. 4. **Leaves agent decisions to the agent.** Grants of a request made for an agent (`agentId`, `grantId`) are released only to that agent's grant token (`POST /v1/decisions/requests/{id}/grants`) and consumed only when the same agent's grant token accompanies the consumption (`grantToken` on `POST /v1/decisions/consume`). Grantex establishes the agent from that token, never from agent or grant identifiers in the request body, so the developer API key alone cannot consume them. The SDKs' `enforce()` sends the token it verified, as [Decision Grants](/concepts/decision-grants#binding-decisions-to-the-requesting-agent) describes. Consumption by request id and the grants endpoint work with the binding off, so an integration can move to them and be verified first. Turn the binding on only after that, and confirm that a four-eyes decline is recorded end to end. ## Future integration gate Token-level purpose, decision and cap features should be enabled only after a published SDK is installed, its API is pinned, AgenticOrg passes the case context to enforcement, and cross-tenant, replay, expiry, revocation, and per-case budget tests pass. Do not infer those guarantees from a dependency version bump alone. # Android + Gemma 4 Guide Source: https://docs.grantex.dev/guides/android-gemma4 Integrate Grantex offline authorization into Android apps running Gemma 4 agents. ## Overview Android apps running Gemma 4 via Google AI Edge SDK (formerly MediaPipe LLM) can use Grantex consent bundles for offline authorization. This guide covers secure bundle storage with EncryptedSharedPreferences, JWT verification with Nimbus JOSE, and audit logging in Kotlin. ## Prerequisites * **Android Studio** Ladybug or later * **Android SDK 26+** (minSdk) -- required for Jetpack Security * **Kotlin 1.9+** * **A Grantex account** with API key and registered agent ## Step 1: Gradle Setup Add the dependencies to your app's `build.gradle.kts`: ```kotlin theme={null} dependencies { // Grantex offline auth implementation("com.nimbusds:nimbus-jose-jwt:9.37") // Secure storage implementation("androidx.security:security-crypto:1.1.0-alpha06") // JSON parsing implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") // HTTP client (for provisioning and sync only) implementation("com.squareup.okhttp3:okhttp:4.12.0") } ``` Add the serialization plugin to your project-level `build.gradle.kts`: ```kotlin theme={null} plugins { kotlin("plugin.serialization") version "1.9.22" } ``` ## Step 2: Define the ConsentBundle Model ```kotlin theme={null} import kotlinx.serialization.Serializable @Serializable data class JWKSSnapshot( val keys: List>, val fetchedAt: String, val validUntil: String, ) @Serializable data class OfflineAuditKey( val publicKey: String, val privateKey: String, val algorithm: String, ) @Serializable data class ConsentBundle( val bundleId: String, val grantToken: String, val jwksSnapshot: JWKSSnapshot, val offlineAuditKey: OfflineAuditKey, val checkpointAt: Long, val syncEndpoint: String, val offlineExpiresAt: String, ) ``` ## Step 3: Secure Storage with EncryptedSharedPreferences Use Jetpack Security to store the consent bundle. The encryption key is managed by Android Keystore, backed by hardware on supported devices. ```kotlin theme={null} import android.content.Context import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys import kotlinx.serialization.json.Json import kotlinx.serialization.encodeToString import kotlinx.serialization.decodeFromString class BundleStore(context: Context) { private val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) private val prefs = EncryptedSharedPreferences.create( "grantex_bundles", masterKeyAlias, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, ) fun store(bundle: ConsentBundle) { val json = Json.encodeToString(bundle) prefs.edit().putString("active_bundle", json).apply() } fun load(): ConsentBundle? { val json = prefs.getString("active_bundle", null) ?: return null return Json.decodeFromString(json) } fun clear() { prefs.edit().remove("active_bundle").apply() } } ``` ## Step 4: Offline JWT Verification Use Nimbus JOSE to verify the grant token against the JWKS snapshot. This runs entirely on-device. ```kotlin theme={null} import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSVerifier import com.nimbusds.jose.crypto.RSASSAVerifier import com.nimbusds.jose.jwk.JWK import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jwt.SignedJWT import java.security.interfaces.RSAPublicKey import java.util.Date data class VerifiedGrant( val agentDID: String, val principalDID: String, val scopes: List, val expiresAt: Date, val grantId: String, val depth: Int, ) class OfflineVerifier( private val jwksSnapshot: JWKSSnapshot, private val requiredScopes: List = emptyList(), private val clockSkewMs: Long = 30_000, private val maxDelegationDepth: Int? = null, ) { private val blockedAlgorithms = setOf("none", "HS256") fun verify(token: String): VerifiedGrant { val jwt = SignedJWT.parse(token) val header = jwt.header // Block insecure algorithms val alg = header.algorithm.name require(alg !in blockedAlgorithms) { "Blocked algorithm: $alg" } // Find the signing key by kid val kid = header.keyID ?: throw IllegalArgumentException("JWT header missing kid") val jwk = jwksSnapshot.keys.find { it["kid"] == kid } ?: throw IllegalArgumentException("No key found for kid=$kid") // Parse JWK and verify val rsaKey = JWK.parse(jwk).toRSAKey() val verifier: JWSVerifier = RSASSAVerifier(rsaKey.toRSAPublicKey()) require(jwt.verify(verifier)) { "JWT signature verification failed" } // Check expiry with clock skew val claims = jwt.jwtClaimsSet val now = Date() val adjustedNow = Date(now.time + clockSkewMs) val expiresAt = claims.expirationTime ?: throw IllegalArgumentException("JWT missing exp claim") require(expiresAt.after(Date(now.time - clockSkewMs))) { "Token expired at ${expiresAt}" } // Extract Grantex claims val scopes = (claims.getStringListClaim("scp") ?: emptyList()) val agentDID = claims.getStringClaim("agt") ?: "" val principalDID = claims.subject ?: "" val jti = claims.jwtid ?: "" val grantId = claims.getStringClaim("grnt") ?: jti val depth = claims.getIntegerClaim("delegationDepth") ?: 0 // Scope enforcement if (requiredScopes.isNotEmpty()) { val missing = requiredScopes.filter { it !in scopes } require(missing.isEmpty()) { "Scope violation: required $requiredScopes but grant has $scopes" } } // Delegation depth enforcement if (maxDelegationDepth != null) { require(depth <= maxDelegationDepth) { "Delegation depth $depth exceeds maximum $maxDelegationDepth" } } return VerifiedGrant( agentDID = agentDID, principalDID = principalDID, scopes = scopes, expiresAt = expiresAt, grantId = grantId, depth = depth, ) } } ``` ## Step 5: Audit Logging Implement a simplified audit log with hash chaining and Ed25519 signing. ```kotlin theme={null} import java.io.File import java.security.KeyFactory import java.security.MessageDigest import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec import java.time.Instant import java.util.Base64 data class AuditEntry( val seq: Int, val timestamp: String, val action: String, val agentDID: String, val grantId: String, val scopes: List, val result: String, val prevHash: String, val hash: String, val signature: String, ) class OfflineAuditLog( private val signingKeyPem: String, private val logFile: File, ) { private var seq = 0 private var prevHash = "0000000000000000" // GENESIS_HASH init { // Resume from existing log if (logFile.exists()) { val lines = logFile.readLines().filter { it.isNotBlank() } if (lines.isNotEmpty()) { val last = Json.decodeFromString(lines.last()) seq = last.seq prevHash = last.hash } } } fun append( action: String, agentDID: String, grantId: String, scopes: List, result: String, ): AuditEntry { val nextSeq = ++seq val timestamp = Instant.now().toString() // Compute hash val hashInput = "$nextSeq|$timestamp|$action|$agentDID|$grantId|${scopes.joinToString(",")}|$result||$prevHash" val hash = sha256Hex(hashInput) // Sign the hash with Ed25519 val signature = signEd25519(hash, signingKeyPem) val entry = AuditEntry( seq = nextSeq, timestamp = timestamp, action = action, agentDID = agentDID, grantId = grantId, scopes = scopes, result = result, prevHash = prevHash, hash = hash, signature = signature, ) logFile.appendText(Json.encodeToString(entry) + "\n") prevHash = hash return entry } private fun sha256Hex(input: String): String { val digest = MessageDigest.getInstance("SHA-256") return digest.digest(input.toByteArray()).joinToString("") { "%02x".format(it) } } private fun signEd25519(data: String, pem: String): String { val keyBytes = Base64.getDecoder().decode( pem.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replace("\\s".toRegex(), "") ) val keySpec = PKCS8EncodedKeySpec(keyBytes) val keyFactory = KeyFactory.getInstance("Ed25519") val privateKey = keyFactory.generatePrivate(keySpec) val sig = Signature.getInstance("Ed25519") sig.initSign(privateKey) sig.update(data.toByteArray()) return sig.sign().joinToString("") { "%02x".format(it) } } } ``` ## Step 6: Putting It Together ```kotlin theme={null} class GemmaAgentActivity : AppCompatActivity() { private lateinit var bundleStore: BundleStore private lateinit var verifier: OfflineVerifier private lateinit var auditLog: OfflineAuditLog override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bundleStore = BundleStore(this) val bundle = bundleStore.load() ?: throw IllegalStateException("No consent bundle. Run provisioning first.") verifier = OfflineVerifier( jwksSnapshot = bundle.jwksSnapshot, requiredScopes = listOf("camera:read", "location:read"), ) auditLog = OfflineAuditLog( signingKeyPem = bundle.offlineAuditKey.privateKey, logFile = File(filesDir, "audit.jsonl"), ) // Verify and run agent val grant = verifier.verify(bundle.grantToken) Log.i("Grantex", "Verified: ${grant.agentDID}, scopes: ${grant.scopes}") auditLog.append( action = "camera.read", agentDID = grant.agentDID, grantId = grant.grantId, scopes = grant.scopes, result = "success", ) } } ``` ## Provisioning the Bundle Call the Grantex API from your backend or a one-time setup activity. Do not embed your API key in the APK. ```kotlin theme={null} suspend fun provisionBundle(apiKey: String, agentId: String): ConsentBundle { val client = OkHttpClient() val body = """ { "agentId": "$agentId", "userId": "android-user-001", "scopes": ["camera:read", "location:read"], "offlineTTL": "72h" } """.trimIndent() val request = Request.Builder() .url("https://api.grantex.dev/v1/consent-bundles") .addHeader("Authorization", "Bearer $apiKey") .addHeader("Content-Type", "application/json") .post(body.toRequestBody("application/json".toMediaType())) .build() val response = client.newCall(request).execute() require(response.isSuccessful) { "HTTP ${response.code}: ${response.body?.string()}" } return Json.decodeFromString(response.body!!.string()) } ``` ## Security Considerations * **Never embed API keys in the APK**. Provision bundles from your backend. * **EncryptedSharedPreferences** uses AES-256-GCM with keys in Android Keystore (hardware-backed on most devices). * **ProGuard / R8** should keep Nimbus JOSE and kotlinx-serialization classes. Add keep rules if verification fails in release builds. * **Root detection** is recommended for high-security use cases. A rooted device can extract the bundle from SharedPreferences. * **Certificate pinning** on OkHttp for the provisioning and sync calls prevents MITM attacks. ## Next Steps * [iOS Guide](/guides/ios-gemma4) -- integrate on iOS with CryptoKit * [Raspberry Pi Guide](/guides/raspberry-pi) -- run on Pi 5 * [Offline Authorization](/features/offline-authorization) -- architecture and security model * [Gemma 4 SDK Reference](/sdks/gemma) -- complete API reference ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Irregularity Detection Setup Source: https://docs.grantex.dev/guides/anomaly-detection-setup Run Grantex's explicit detector and choose an account-wide alert-only or revocation response. ## 1. Confirm the rollout The server operator must set `IRREGULARITY_RESPONSE_POLICY_ENABLED=true` after deploying the schema migration. The switch is off by default. It enables the account response-policy API and `anomaly.detected` event emission for explicit detection runs; it does not start a scheduler. With your account's developer API key, check the current mode: ```bash theme={null} curl -H "Authorization: Bearer $GRANTEX_API_KEY" \ https://api.grantex.dev/v1/irregularities/response-policy ``` A 404 means this deployment has not enabled the policy feature. A 503 means the policy cannot be read and detection fails closed while the feature is enabled. The default mode for an enabled account is `revoke_agent_grants`. ## 2. Choose the account response If several principals' grants share one agent, a high-severity finding can revoke all that agent's active grants in the account. Choose `alert_only` when human review should precede a manual revocation: ```bash theme={null} curl -X PATCH https://api.grantex.dev/v1/irregularities/response-policy \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode":"alert_only"}' ``` Read it back with the GET request above. This setting is **account-wide** and affects only the legacy detector's automatic grant revocation. It does not bypass other policy enforcement. Use `revoke_agent_grants` to restore the original response. The portal's **Irregularities** page exposes the same setting. Policy changes are stored in account history. ## 3. Run and inspect the detector Call the detector explicitly, or schedule this call from your own service: ```bash theme={null} curl -X POST https://api.grantex.dev/v1/anomalies/detect \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` Check `responseMode` and `autoRevokedGrants` in the response. `alert_only` must return `autoRevokedGrants: 0`. Read stored findings separately: ```bash theme={null} curl -H "Authorization: Bearer $GRANTEX_API_KEY" \ 'https://api.grantex.dev/v1/anomalies?unacknowledged=true' ``` This detector currently evaluates rate spikes, high failure rates, new principals, and off-hours activity. Listing ten built-in definitions at `/v1/anomaly/rules` is not evidence that all ten run. Custom-rule and channel APIs store configuration but do not execute or dispatch for this detector. See [Irregularity Detection](/features/anomaly-detection) for exact thresholds. ## 4. Deliver findings to a human With the response-policy flag enabled, each persisted finding emits `anomaly.detected` through the existing event bus. Register a standard Grantex webhook subscription for that type, or consume the event stream. Filter by severity in your consumer and route the event to your own review queue. Event delivery is best-effort, so reconcile with `GET /v1/anomalies` after failures or outages. A record created under `/v1/anomaly/channels` alone does not send a notification. Response-policy methods are published in TypeScript `@grantex/sdk@0.8.2`, Python `grantex==0.7.2`, and Go `v0.4.3`. The REST calls above work with a deployment that has enabled the flag. See [Release Status](/release-status). # Base USDC Custody Source: https://docs.grantex.dev/guides/base-usdc-custody Governed x402 EIP-3009 signing, verified funding, safe retries and finalized-chain reconciliation. This adapter requires explicit custody/RPC provisioning even after the server code is deployed. Python `0.7.2` and Go `v0.4.3` publish the response and reconciliation APIs, not automatic HTTP payment wrappers. Published TypeScript `0.8.2` and x402 `0.4.1` provide the opt-in Base 402/sign/retry flow. All four versions are registry verified. Local tests prove local EVM execution, not a real-money third-party payment. Server deployment, SDK publication and a funded production compatibility call are separate milestones; none implies the others. ## Supported boundary * x402 v2 `exact`, Base mainnet `eip155:8453`. * Native USDC `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913`, six decimals. * EOA wallets signing EIP-3009 `TransferWithAuthorization`, domain `USD Coin`, version `2`. * Existing Grantex assignment/layered policies, DPoP-bound OAuth, approvals and blocks. * Other chains, assets, Permit2 and contract wallets remain unsupported. The merchant's EVM facilitator settles Base payments. Grantex's existing `/v1/x402/verify` and `/v1/x402/settle` remain for `grantex:prepaid`. Grantex independently reconciles chain evidence; a merchant HTTP 200 is not evidence for updating the Base wallet ledger. ## Provision custody Use a dedicated address for each wallet. Do not spend it through another app or share it across Grantex deployments: independent ledgers cannot coordinate the same balance. A unique database index prevents duplicate Base custody addresses within one deployment. Apply the complete migration set, including `094_base_usdc_custody.sql`. Configure these secrets on the authorization service, never in an agent, browser, repository or command-line log: | Setting | Purpose | | - | - | | `BASE_USDC_RPC_URL` | Trusted Base RPC; production requires HTTPS. Must support finalized blocks, historical reads, receipts and filtered logs | | `BASE_USDC_WALLETS` | Operator-controlled JSON mapping of wallet IDs to key and exact ownership | | `VAULT_ENCRYPTION_KEY` | Stable vault key for encrypted stored signatures; retain it with database backups | | `RSA_PRIVATE_KEY` | Stable OAuth signing key across service restarts | Mapping shape, with placeholders only: ```json theme={null} { "principal-base-wallet-1": { "privateKey": "0x<64 hex characters from your secret manager>", "developerId": "dev_your_developer", "principalId": "your-principal-id" } } ``` This initial adapter uses an in-process custody key, not an HSM, MPC or managed wallet provider. Restrict secret access, isolate the service, keep a limited float and review the custody threat model before real-money use. Compromise of the signing service can bypass application policy. Replacing the signer must preserve ownership, reservation, retry and reconciliation guarantees. ## Create and fund Using an authenticated principal-session client: The hosted principal API origin is `https://api.grantex.dev`. The OAuth issuer and agent wallet audience remain `https://grantex.dev` and `https://grantex.dev/v1/prepaid-wallets` respectively. Do not change a token's audience to the principal API host. Self-hosted deployments use their configured origins. Health and protected metrics are served by the API origin, not the marketing site's limited Firebase rewrites. ```ts theme={null} const principal = new PrincipalPrepaidWalletClient({ baseUrl: 'https://api.grantex.dev', sessionToken, }); const wallet = await principal.create({ name: 'Agent Base USDC float', custodyMode: 'external', provider: 'base_usdc', providerWalletId: 'principal-base-wallet-1', walletAddress: custodyAddress, network: 'eip155:8453', asset: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', decimals: 6, }); ``` The key must derive `walletAddress`; its provisioned owner must match the caller. Transfer native USDC through the operator's approved funding process. Grantex does not charge a card or convert fiat. Once the transfer is finalized, credit it using the lowercase transaction hash and decimal transfer log index: ```ts theme={null} await principal.reload(wallet.walletId, '1000000', reloadIdempotencyKey, `${transactionHash.toLowerCase()}:${transferLogIndex}`); ``` The receipt must be successful, canonical and finalized, with the exact USDC transfer amount and recipient. Self-transfers do not qualify. Each funding log is credited only once and chain balance must cover the total credited balance. Pending outgoing settlements can delay new credits until reconciliation catches up. Approved agent reload requests use the same proof through `fundReload`. Assign recipients, resource origins, scopes, per-transaction and cumulative limits through the usual principal API. Layered amount/count budgets and exact approval policies also apply. Hex address letter case cannot evade policy. ## Authorize and pay Build the updated SDK and x402 package from this checkout: ```ts theme={null} const walletAgent = new PrepaidWalletAgentClient({ oauthClient, accessToken }); const paid = createX402Agent({ walletId: wallet.walletId, authorizePayment: walletAgent.x402Authorizer, baseUsdc: { scope: 'licensing:preflight', purpose: 'licensing-check' }, }); const response = await paid.fetch(merchantUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input), idempotencyKey: durableLogicalRequestId, }); ``` The action scope comes from application configuration, must be granted by OAuth and must pass wallet policy. Merchants need no Grantex-specific challenge fields. The adapter rejects redirects, resource URL mismatches and incompatible terms. Use `agent.fetch`, not the exposed low-level `agent.client`, for request-bound Base handling. The server encrypts and commits the signature with its balance hold before returning it. The merchant gets no internal Grantex JWT or policy metadata. Timeouts are 10-300 seconds, further bounded by grant, token and assignment expiry. ## Retries and blocks Reuse the same idempotency key for the same logical request after response loss. Retries recover the same stored signature and nonce, including after a service restart. Changed terms conflict. After settlement/expiry, inspect reconciliation and the merchant's stored result instead of automatically paying with a new key. **Blocking stops new signatures, but cannot recall an existing one.** Previously signed payments may still settle before their on-chain expiry. Revocation, policy changes, blocks and manual release cannot return that exposure to the available balance. Outstanding holds count against budgets across window edges. The service sweeps pending Base reservations every 30 seconds in bounded batches and log ranges. You can also reconcile explicitly: ```ts theme={null} const state = await walletAgent.reconcileReservation(reservationId); // { reservationId, status: 'reserved' | 'settled' | 'expired', transaction } ``` Agent and principal clients expose `reconcileReservation` in TypeScript, `reconcile_reservation` in Python and `ReconcileReservation` in Go. Python preserves `evmPayment` as a dictionary; TypeScript/Go have typed response fields. Automatic x402 HTTP retries are provided by the TypeScript adapter, not newly claimed as a Python/Go feature. Settlement requires a finalized matching nonce event and transfer receipt. Unused expiry requires a finalized block past expiry and an unused nonce. RPC failure, stale chain time or missing evidence leaves funds reserved. A consumed nonce without matching settlement evidence also remains reserved. Finality can take longer than the payment timeout. Monitor old pending holds and provider incidents; never manually credit them just to unblock spending. ## Deployment and recovery runbook 1. Back up PostgreSQL and the stable vault/signing keys. Deploy the reviewed server and complete migration set with Base custody still unconfigured. 2. Confirm `/health` and the existing OAuth/prepaid regression suite. Deploying code alone does not activate a wallet or transfer funds. 3. After custody review, inject the protected RPC and exact owner-to-wallet mapping from your secret manager. Never use the Docker fixture keys or token contract in production. Fund and reconcile a dedicated limited-float wallet. 4. Run one approved low-value compatibility payment, retaining the HTTP status, finalized transaction hash and matching ledger entry, not the signature/key. 5. Operate reconciliation continuously. On request-throttled or scale-to-zero runtimes such as Cloud Run, the in-process timer is not a guaranteed scheduler. Use an always-allocated CPU instance with a minimum instance count, or an authenticated external worker calling the reconciliation API for pending IDs. Alert on stale pending holds and provider failures. During an incident, block new spending and keep reconciliation running. Do not roll back to server versions predating migration 094 handling while signed reservations remain: older release/block logic cannot preserve this exposure. Wait for finalized terminal outcomes before rollback, and retain encrypted signature records, vault keys and operator bindings until reconciliation and evidence retention requirements are satisfied. Never drop the additive columns as a rollback shortcut. An RPC outage delays availability rather than granting permission to release funds. ## External responsibilities Provision and protect a funded wallet, trusted RPC and custody process. Confirm facilitator support with the merchant. Merchant result recovery requires its HTTP `Idempotency-Key` result store; Grantex cannot add that to an unrelated API. Notification delivery, KYC/AML, refunds, disputes and legal/provider approval remain operator responsibilities. The local tests use PostgreSQL, Redis, the auth-service, official x402 facilitator code and an isolated Anvil chain with a test-only USDC contract. This verifies real signatures and transfers locally, not Base-mainnet/PayAI availability. The repository's `tests/base-usdc/README.md` contains repeatable test commands. # Budget & Spending Controls Source: https://docs.grantex.dev/guides/budget-controls Allocate budgets to grants and track spending with per-transaction debit, threshold alerts, and JWT budget claims. ## Overview Grantex budget controls let you allocate a monetary budget to any grant, debit amounts on each API call, and receive real-time alerts when spending thresholds are crossed. Key features: * **Per-grant budget allocation** with atomic debit operations * **Race-condition safe** — uses SQL `WHERE remaining_budget >= amount` * **Threshold alerts** at 50% and 80% via event bus (`budget.threshold`) * **Exhaustion events** when the budget hits zero (`budget.exhausted`) * **JWT `bdg` claim** — budget balance embedded in grant tokens for downstream enforcement ## Allocating a Budget ```typescript TypeScript theme={null} const grantex = new Grantex({ apiKey: 'gx_...' }); const allocation = await grantex.budgets.allocate({ grantId: 'grnt_01HXYZ...', initialBudget: 100, currency: 'USD', // optional, defaults to USD }); console.log(allocation.id); // "bdg_01HXYZ..." console.log(allocation.remainingBudget); // "100.0000" ``` ```python Python theme={null} from grantex import Grantex, AllocateBudgetParams grantex = Grantex(api_key="gx_...") allocation = grantex.budgets.allocate(AllocateBudgetParams( grant_id="grnt_01HXYZ...", initial_budget=100, currency="USD", )) print(allocation.remaining_budget) # "100.0000" ``` ```bash cURL theme={null} curl -X POST https://api.grantex.dev/v1/budget/allocate \ -H "Authorization: Bearer gx_..." \ -H "Content-Type: application/json" \ -d '{"grantId": "grnt_01HXYZ...", "initialBudget": 100}' ``` ## Debiting from a Budget Each debit atomically decrements the remaining balance. If the debit exceeds the remaining budget, the API returns **402 Payment Required** with code `INSUFFICIENT_BUDGET`. ```typescript TypeScript theme={null} const result = await grantex.budgets.debit({ grantId: 'grnt_01HXYZ...', amount: 0.50, description: 'GPT-4 API call', metadata: { model: 'gpt-4', tokens: 1200 }, }); console.log(result.remaining); // "99.5000" console.log(result.transactionId); // "btx_01HXYZ..." ``` ```python Python theme={null} from grantex import DebitBudgetParams result = grantex.budgets.debit(DebitBudgetParams( grant_id="grnt_01HXYZ...", amount=0.50, description="GPT-4 API call", )) print(result.remaining) # "99.5000" ``` ### Handling Insufficient Budget ```typescript theme={null} try { await grantex.budgets.debit({ grantId, amount: 9999 }); } catch (err) { if (err instanceof GrantexApiError && err.code === 'INSUFFICIENT_BUDGET') { // Balance too low — prompt user to top up } } ``` ## Checking Balance ```typescript theme={null} const balance = await grantex.budgets.balance('grnt_01HXYZ...'); console.log(balance.remainingBudget); // "99.5000" console.log(balance.currency); // "USD" ``` ## Transaction History ```typescript theme={null} const { transactions, total } = await grantex.budgets.transactions( 'grnt_01HXYZ...', { page: 1, pageSize: 50 }, ); for (const tx of transactions) { console.log(`${tx.description}: -$${tx.amount}`); } ``` ## Budget JWT Claim When a grant has a budget allocation, the issued grant token includes a `bdg` claim with the current remaining balance at the time of token issuance: ```json theme={null} { "sub": "user_123", "agt": "did:grantex:agent_abc", "scp": ["read:email", "send:email"], "grnt": "grnt_01HXYZ...", "bdg": 99.5, "exp": 1709312000 } ``` Service providers can use this claim to enforce budget limits without making additional API calls. ## Threshold Alerts Grantex automatically emits events when spending crosses thresholds: | Event | Trigger | | - | - | | `budget.threshold` | Remaining budget drops below 50% or 20% of initial | | `budget.exhausted` | Remaining budget reaches zero | These events are delivered via: * **Webhooks** — to all registered webhook endpoints * **SSE/WebSocket** — via the real-time event stream (`GET /v1/events/stream`) Example event payload: ```json theme={null} { "id": "evt_01HXYZ...", "type": "budget.threshold", "createdAt": "2026-03-01T12:00:00Z", "data": { "grantId": "grnt_01HXYZ...", "remainingBudget": "20.0000", "initialBudget": "100.0000", "thresholdPercent": 80 } } ``` ## Portal Dashboard Budget allocations are visible in the developer portal under **Budgets**: * Summary cards showing total allocated, spent, and remaining * Per-grant usage bars with status badges (Healthy / > 50% / > 80% / Exhausted) * Transaction drill-down with pagination ## API Reference | Method | Path | Description | | - | - | - | | `POST` | `/v1/budget/allocate` | Create a budget allocation for a grant | | `POST` | `/v1/budget/debit` | Debit an amount from a grant's budget | | `GET` | `/v1/budget/balance/:grantId` | Get current budget balance | | `GET` | `/v1/budget/transactions/:grantId` | List paginated transaction history | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Cedar Integration Source: https://docs.grantex.dev/guides/cedar-integration Use AWS Cedar as your Grantex policy backend ## Overview Grantex supports [AWS Cedar](https://www.cedarpolicy.com/) as a pluggable policy backend. Cedar provides fine-grained, type-safe policy evaluation with an entity-based model. ## Configuration ```bash theme={null} POLICY_BACKEND=cedar CEDAR_URL=http://cedar:8180 CEDAR_FALLBACK_TO_BUILTIN=true ``` ## Cedar Entity Model Grantex maps authorization requests to Cedar's entity model: | Cedar Concept | Grantex Mapping | | - | - | | Principal | `Grantex::Agent` with agent ID | | Action | `Grantex::Action::authorize` | | Resource | `Grantex::Grant` with grant ID | | Context | scopes, principal, developer, time | ## Example Cedar Policy ```cedar theme={null} // Allow any agent to authorize read-only grants permit( principal is Grantex::Agent, action == Grantex::Action::"authorize", resource is Grantex::Grant ) when { context.scopes == ["read"] }; // Deny delegation beyond depth 2 forbid( principal is Grantex::Agent, action == Grantex::Action::"authorize", resource is Grantex::Grant ) when { context.delegationDepth > 2 }; ``` ## Timeout and Fallback Same as OPA: 5-second timeout with configurable fallback to built-in. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Agentic Commerce Implementation PRD Source: https://docs.grantex.dev/guides/commerce-v1-agentic-commerce-implementation-prd Merchant-readable product requirements and gap plan for making Grantex Commerce and AgenticOrg ready for self-serve agentic commerce. # Agentic Commerce Implementation PRD This document explains what still needs to be built before merchants can self-serve into agentic commerce using Grantex and AgenticOrg. For the consolidated source of truth, read `docs/guides/commerce-v1-agentic-commerce-prd.md`. This implementation PRD is a supporting summary and must not diverge from that canonical document. It is written for founders, merchants, operators, product teams, and engineers. The short version is simple: > Merchants should connect the systems they already run today. Grantex turns > approved public-safe facts from those systems into signed OACP artifacts and > policy decisions. AgenticOrg runs the buyer and seller agents, connector > workflows, local artifact cache, and channel UX. Provider and fintech rails > own mandate and payment execution. Grantex must not become a toll booth for > every non-binding agent interaction. This document is not a rollout approval. It does not enable production Commerce V1, public discovery, checkout/payment creation, live payments, live Plural, or any production allowlist. ## Current OACP Runtime Closure Boundary The launch-closure implementation proves a non-executing OACP runtime vertical: AgenticOrg seller onboarding and read-only connector sync, Grantex internal artifact authority issuance, AgenticOrg durable cache consumption, buyer-safe answers, bridge adapters, and provider-owned capability metadata checks. It does not enable checkout, payment, order, mandate, refund, return, shipment, inventory hold, public discovery publication, live provider execution, or merchant-private API mutation. ## 1. Architecture In Plain English ```mermaid theme={null} flowchart TB merchant["Merchant existing systems"] store["Storefront: Shopify, WooCommerce, Magento, custom web"] erp["ERP, PIM, inventory, WMS, OMS, logistics, CRM"] provider["Provider and fintech mandate/payment rails"] selleragent["AgenticOrg Seller Commerce Agent"] grantex["Grantex trust, protocol, policy, and artifact authority"] artifacts["OACP artifacts and adapter previews"] agenticorg["AgenticOrg buyer/seller runtime and artifact cache"] buyer["Buyer using an AI agent"] merchant --> store merchant --> erp merchant --> provider merchant --> selleragent store --> selleragent erp --> selleragent selleragent --> grantex grantex --> artifacts artifacts --> agenticorg agenticorg --> buyer agenticorg -. "provider capability verification, when approved" .-> provider ``` Grantex is the trust, protocol, policy, and canonical-artifact authority. It owns artifact rules, public-safe fact validation, source/freshness policy, merchant approval state, consent/passport posture, protocol adapter mappings, and evidence requirements. AgenticOrg is the agent runtime. It owns seller-agent onboarding, connector sync initiation, buyer-agent sessions, local artifact cache across buyer agent, seller agent, tenant, and merchant, channel bridges, refusal UX, and seller/buyer education. It must not become the source of truth for merchants, products, inventory, price, policy, settlement, or refunds. Providers and fintech rails own mandate setup, mandate verification, payment execution, and payment/refund settlement. Grantex stores only non-sensitive evidence references when policy or artifact lineage requires them. ### End-To-End Operating Flow The detailed buyer and seller walkthrough is documented in `docs/guides/commerce-v1-end-to-end-agentic-commerce-flow.mdx`. Seller one-time setup: 1. Start in AgenticOrg Seller Commerce Agent and create a merchant onboarding packet for Grantex authority review. 2. Verify merchant identity and keep private artifacts outside repos. 3. Connect existing systems through AgenticOrg seller-agent connector workflows, merchant-owned connector platforms, approved external integration providers, or CSV/API. 4. Declare source-of-truth precedence for catalog, price, inventory, order, fulfillment, support, and payment data. 5. Configure category preset, required public fields, return/warranty policy, tax, shipping, payment, support, and rollback ownership. 6. Select allowed agent capabilities and buyer channels. 7. Run scans, readiness scoring, human review gates, sandbox rehearsal, and rollback checks. 8. Request the smallest approved rollout, usually read-only discovery first. Buyer one-time setup: 1. Choose an approved chat or agent surface, such as ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, or a future channel. 2. Link or sign in to create a buyer-agent session. 3. Set safe preferences such as locale, currency, delivery region, notification path, and accessibility needs. 4. See what actions the channel can support and what remains blocked. 5. Approve or deny each payment-affecting action through Grantex consent. Regular transaction: 1. Buyer asks the agent to discover, compare, or prepare. 2. AgenticOrg uses cached signed OACP artifacts when TTL, revocation, and risk rules allow; otherwise it asks Grantex to refresh or verify authority artifacts. 3. AgenticOrg reads product, price, inventory, delivery, return, policy, and capability facts from artifacts and approved source evidence, not model guesses. 4. Non-binding discovery can continue locally without routing every message through Grantex. 5. Commitment-bound actions require boundary, prepared envelope, reconciliation, eligibility, and dry-run checks before future handoff. 6. Merchant-system confirmation uses approved connector handoff or merchant-owned systems. 7. Provider-owned mandate/payment capability verification stays with the provider/fintech rail and may be verified directly by AgenticOrg when approved. 8. Grantex keeps policy, artifact lineage, and non-sensitive evidence references only where required. 9. AgenticOrg shows buyer-safe status and refuses unsupported claims. ## 2. Current Implementation Snapshot The repo already has important foundations: | Capability | Current implementation evidence | Current state | | - | - | - | | Merchant and tenant control plane | Commerce tenants, merchants, merchant API keys, operator and merchant caller resolution, emergency disable/re-enable, commerce dashboard pages. | Foundation exists; self-serve runtime onboarding is still incomplete. | | Category presets | Commerce category preset migration and policy defaults. | Foundation exists; merchant-facing preset workflow needs hardening. | | Catalog and variants | Product/variant create, list, search, item detail, bulk dry-run/upsert, patch, archive, CSV/JSON-oriented portal support. | Good V1 base; large async imports and real connector sync still pending. | | Inventory | Variant availability buckets and freshness fields: `in_stock`, `out_of_stock`, `pre_order`, `back_order`, `unknown`, `last_synced_at`. | Enough for read-only discovery; not enough for reservations, location stock, delivery promise, or quantity accounting. | | Consent and Commerce Passport | Consent request, exchange, verify, revoke, challenge flow, scoped passport checks. | Core trust layer exists; production delivery and AP2-style signed mandate evidence still pending. | | Cart and payment intent | Cart draft and provider-neutral payment intent endpoints with idempotency and amount-cap checks. | Mock/sandbox path foundation exists; live provider path remains gated. | | Provider evidence and webhooks | Provider capability evidence references, webhook intake metadata, replay metadata, reconciliation metadata, and ops-health posture. | Foundation exists; live provider rails still require approvals and stronger evidence, and provider/fintech rails remain payment authority. | | Merchant webhooks | Signed merchant webhook source and `catalog.product.updated` intake. | Good first sync event; price-only, inventory-only, order, fulfillment, return, and support webhooks are pending. | | MCP/native agent surface | `merchant.get_profile`, `catalog.search`, `catalog.get_item`, `inventory.check`, `cart.create`, `checkout.create`, `payment.create_intent`, `payment.get_status`. | Correct agent surface; public discovery remains fail-closed. | | Docs navigation | Commerce V1 docs group exists in `docs/docs.json`. | Updated by this PRD pass to include this implementation PRD. | | Deploy workflow guard | Auth-service and portal deploy workflows have path filters for runtime paths. | Docs-only changes should not trigger Grantex deploy workflows, but CI still runs broad validation. | | OACP artifact foundation | C6W3-C6Z internal helpers, tests, docs, authority route, verifier, and AgenticOrg runtime handoff foundations define artifact families, adapter mappings, commitment boundaries, prepared envelopes, evidence reconciliation, eligibility packets, dry-run verifier results, authority requests, cache use, buyer answers, MCP/OpenAPI/A2A/WhatsApp/Telegram bridges, and capability checks. | Runtime implemented for the Shopify read-only OACP vertical; still no checkout/payment/order/mandate execution, public protocol publication, external certification, or live provider claim. | ### 2.1 Current OACP Status Through C6Z The current OACP implementation is a non-executing runtime vertical, not a live checkout product. The 2026-06-18 production closure run was blocked because the then-mounted AgenticOrg Shopify token returned `401 Unauthorized` and the AgenticOrg-configured Grantex token returned `422 tenant_not_provisioned`. AgenticOrg now has merchant-scoped Shopify credential storage, adapter payload generation, bridge routes, and purchase-preparation blockers; a real merchant launch still requires valid external credentials and tenant allowlisting. * implemented: internal artifact schemas, public-safe fixtures, adapter previews, commitment boundary resolver, prepared envelopes, response reconciliation, eligibility packets, execution-controller dry-run verifier, internal C6Z authority request route, artifact verifier, and non-enablement flags; * implemented in both repos: Grantex authority helpers/tests/docs and AgenticOrg local consumer/runtime helpers/tests/docs, including seller-agent packet creation, Shopify sync initiation, artifact cache, buyer answer, MCP bridge, and Plural/Pine capability verifier paths; * blocked in production: successful Shopify read-only sync with the mounted token and successful AgenticOrg-to-Grantex tenant provisioning for the configured internal token; * not implemented or not enabled: public OACP spec site, public endpoint, public discovery, execution controller, order/fulfillment/refund runtime, or live checkout/payment; * still blocked: publication, certification, conformance, standards submission, production readiness claims, and any real merchant approval. ## 3. Target Merchant Journey The full self-serve journey should feel like this: 1. Merchant creates a Grantex Commerce account. 2. Merchant chooses a business category such as electronics, fashion, home, grocery, pharmacy, services, or B2B. 3. Merchant connects an existing system or uploads a catalog. 4. Grantex shows a preview of the merchant profile, catalog, prices, inventory, warranty, return policy, and public discovery text. 5. Merchant selects what AI agents may do: * browse only; * create cart drafts; * request checkout consent; * read order status; * request support or return handling. 6. Grantex runs automated checks for secrets, private data, stale inventory, missing policies, overclaims, and unsupported live paths. 7. Legal, product, security, ops, rollback, smoke, and evidence owners approve the launch packet. 8. Merchant rehearses the sandbox flow with synthetic or sandbox data. 9. Merchant requests production rollout. 10. Grantex enables the smallest approved surface first, usually read-only discovery, then expands only after smoke evidence and rollback readiness. ## 4. Existing Merchant Systems Merchants should not be asked to rebuild their businesses for agents. The merchant-facing connection experience should start in AgenticOrg Seller Commerce Agent. Grantex should validate canonical public-safe facts and evidence refs, not demand custody of every operational credential. | Existing merchant system | Data OACP needs | Correct custody and target model | Fast-track approach | | - | - | - | - | | Shopify, WooCommerce, Magento, custom storefront | Product, variant, media, price, availability, categories, collections. | AgenticOrg or merchant/external connector custody; Grantex validates public-safe catalog artifacts. | Start with CSV/manual plus one high-priority connector family. Add webhook freshness. | | ERP or PIM | Product master, brand, attributes, tax codes, SKU hierarchy, category truth. | Merchant/external connector custody; Grantex validates source precedence and category artifacts. | Let merchant declare source-of-truth precedence. Record sync source and timestamp. | | WMS or inventory system | Stock state, quantity, location, reservation, delivery feasibility. | Merchant/external connector custody; Grantex validates source/freshness and refusal policy. | Keep availability buckets for browse; require stronger model before checkout promises. | | OMS | Order creation, order ID, status, cancellation, fulfillment, return status. | Merchant OMS remains operational owner; Grantex validates public-safe status evidence when needed. | Required before live checkout at scale. Start with create/read/status evidence. | | Logistics provider | Delivery slots, pickup slots, tracking, failed delivery events. | Logistics/carrier owner remains source; Grantex validates delivery-promise artifacts only after approval. | Needed for order/fulfillment capability and buyer trust. | | Payment or mandate provider | Provider-owned mandate capability, payment intent/order, hosted checkout, status webhook, settlement, refunds. | Provider/fintech rail owns mandate and payment execution; Grantex stores only non-sensitive evidence refs when policy requires them. | Start with provider capability verification and mock/sandbox evidence; no live execution. | | CRM/support desk | Ticket ID, support conversation, refund request, return request. | Merchant support system remains owner; Grantex validates support/return evidence when needed. | Start with manual handoff/reference; automate only after policy gates. | Connector rule: > A connector imports data. It does not make a merchant live. Live agent access > requires readiness checks, merchant approval, policy activation, consent, audit, > and rollback ownership. ## 5. Standards And Protocol Fit Grantex should maintain one canonical OACP artifact model and produce protocol views from it. The system must not create one separate database per protocol and must not turn protocol adapters into transaction authority. | Surface | Relevant external idea | Grantex implementation requirement | | - | - | - | | OACP artifacts | Internal trust profile for seller agents, buyer agents, merchant systems, and provider rails. | C6W3-C6Z are internal only; artifacts preserve TTL, source/freshness, refusals, and non-enablement flags. | | Native Grantex API | First-party authority APIs for artifacts and policy. | Continue as authority surface, not as a mandatory toll booth for all non-binding cached interactions. | | MCP | Agent tool transport. UCP also lists MCP among supported transports. | Expose tools that consume artifacts and preserve refusal semantics. | | UCP-style capability profile | UCP discovery uses business/platform profiles, services, capabilities, negotiation, and transports such as REST, MCP, A2A, and embedded. See [UCP overview](https://ucp.dev/specification/overview/). | Publish profiles only from approved merchant capability state. Do not claim UCP certification until conformance tests exist. | | ACP-style checkout | Stripe ACP describes agentic checkout create, retrieve, update, complete, cancel, order, and refund concepts. See [Stripe ACP specification](https://docs.stripe.com/agentic-commerce/protocol/specification). | Map Grantex cart/payment/order state into ACP-compatible shapes where provider and merchant support it. | | AP2-style mandate evidence | AP2 secures agent-performed payments through authorization evidence and mandates. See [AP2 specification](https://ap2-protocol.org/ap2/specification/). | Map provider-owned mandate evidence references and Grantex policy/artifact lineage into future evidence without claiming AP2 conformance. | | schema.org | Product, Offer, shipping, and return policy metadata are represented by schema.org types. See [Product](https://schema.org/Product), [Offer](https://schema.org/Offer), [OfferShippingDetails](https://schema.org/OfferShippingDetails), and [MerchantReturnPolicy](https://schema.org/MerchantReturnPolicy). | Generate public-safe JSON-LD/feed output from approved catalog and policy fields only. | Do not claim certified UCP, ACP, AP2, MPP, A2A, live-provider, or schema.org production readiness until implementation, tests, approvals, and release evidence exist. ## 6. Comprehensive Gap Register | Gap | User impact | Required implementation | Owning repo | Release gate | | - | - | - | - | - | | Self-serve merchant signup | Merchant cannot join without operator work. | Signup, tenant creation, merchant profile, owner invite, category preset, sandbox/live environment split, onboarding checklist. | Grantex | Merchant can complete sandbox profile without engineering support. | | KYB/KYC and legal evidence | Real merchant identity cannot be trusted. | Private artifact references, legal/compliance decision, verification state machine, evidence retention. | Grantex plus external reviewers | No live mode without approved identity and evidence references. | | Merchant role model | Owners, developers, ops, support, and finance users need different powers. | RBAC for owner, developer, catalog manager, ops, support, finance, auditor, rollback owner. | Grantex | Sensitive actions require correct role and audit. | | Existing-system connector framework | Merchants need their existing stores to sync. | AgenticOrg seller-agent connector initiation, credential-custody selection, source-of-truth precedence, sync jobs, source health, retry, and Grantex artifact validation. | AgenticOrg + merchant/external connector owner + Grantex policy | Connector cannot expose agent capabilities by itself. | | Large catalog import | Real merchants have many SKUs. | Async import jobs, dry-run diff, row-level errors, duplicate handling, rollback, idempotency, import history. | Grantex | Import can be retried and audited without data loss. | | Catalog data quality | Agents need complete public-safe product facts. | Required field checks by category, image validation, brand/model/specs, warranty summary, return summary, tax/GST metadata. | Grantex | Readiness score blocks missing critical fields. | | Inventory depth | Agents must not promise unavailable stock. | Quantity, location, stock confidence, freshness TTL, source system, future reservation/stock hold. | Grantex | Checkout blocked or warned when stale/unknown. | | Delivery and pickup promise | Buyers need delivery confidence before checkout. | Delivery options, pickup slots, address serviceability, shipping fee/tax, delivery ETA, carrier/logistics evidence. | Merchant/logistics source + Grantex evidence policy | No delivery promise without fresh evidence. | | Pricing, tax, offers, EMI | Agents need correct totals. | Price freshness, tax inclusivity, GST slab, shipping/handling, coupons, discounts, EMI/affordability evidence. | Merchant/provider source + Grantex policy | Agent cannot invent discounts or affordability. | | Cart update lifecycle | ACP-style and real checkout need update/cancel flows. | Cart update, recalculate totals, fulfillment option selection, cancel/expire, buyer messages. | Future execution controller + Grantex policy | Idempotent update and audit coverage before any live path. | | Order lifecycle | Payment without order operations is incomplete. | Order create/read/list, order status, order permalink/reference, status webhooks, manual review, cancellation. | Merchant OMS + future execution controller + Grantex evidence policy | Paid checkout creates or references an order only after separate approval. | | Fulfillment lifecycle | Merchant ops need shipment and pickup tracking. | Fulfillment status, shipment tracking, pickup/delivery slots, partial fulfillment, failed delivery event. | Merchant/logistics source + Grantex evidence policy | Buyer-agent can read only verified status. | | Returns and refund requests | Post-purchase trust needs a safe path. | Return/refund request, eligibility preview, manual approval, provider handoff, audit, future provider execution. | Merchant support/provider rail + Grantex policy | No automatic refund execution until separately approved. | | Settlement and payouts | Merchants need to know when they are paid. | Settlement/payout read model, provider reconciliation, fee/tax summary, payout export. | Provider/fintech rail + merchant finance + Grantex evidence refs | No raw provider payload exposure. | | Provider-owned mandate readiness | Buyers need provider-backed mandates or payment capability where allowed. | Provider capability verification, non-sensitive evidence refs, human approval, rollback, and separate live rail approval. | Provider/fintech + AgenticOrg verifier + Grantex policy | Live provider remains blocked until approvals exist. | | Commerce Passport production delivery | Users need real consent challenge delivery. | Verified email/SMS/passkey challenge, revocation freshness, signed evidence, user-facing revocation history. | Grantex | No payment-affecting action without valid passport. | | Policy simulator | Merchants need to understand agent permissions. | Simulate policy for product/category/amount/agent/channel before activation. | Grantex | Policy activation requires preview and audit. | | OACP/UCP/ACP/schema.org adapters | Large agent platforms need standard surfaces. | Generate adapter previews from canonical OACP artifacts, conformance fixtures, versioned capability profile. | Grantex authority + AgenticOrg consumer | Adapter tests and no unsupported certification claims. | | AP2-style evidence | Agent payments need non-repudiation. | Intent/cart/payment evidence mapping, hashes, signatures, key rotation, replay defense. | Grantex | Do not claim AP2 until deterministic verification exists. | | AgenticOrg buyer workflow | Buyers need a safe assistant experience. | Grounded discovery, comparison, cart drafting, consent request, checkout state, refusal copy. | AgenticOrg | Agent never invents seller/product/price/inventory/payment state. | | AgenticOrg merchant demo workflow | Merchants need to understand launch. | Demo scripts, status labels, capability preview, blocked-path examples, non-approval disclaimers. | AgenticOrg | Demo remains synthetic or sandbox until Grantex approves real data. | | Ops and support dashboards | Teams need to fix failed runs. | Failed checkout, webhook replay, stale sync, stuck payment, policy denial, support handoff, incident runbook. | Grantex, AgenticOrg reads where needed | On-call and rollback owners assigned. | | Analytics and attribution | Merchants need business value proof. | Agent channel attribution, funnel, conversion, refusal reasons, lost sale due to stale data, revenue by agent. | Grantex source, AgenticOrg contribution | Metrics are derived, not raw secrets. | | Documentation coverage | Merchants and reviewers need a shared source of truth. | docs navigation, layman guides, API docs, runbooks, stop conditions, evidence templates, PRD links. | Both repos | Docs updated before implementation slices. | | GitHub workflow safety | Docs-only planning should not build/push images. | Path guards for deploy-adjacent jobs, docs-only validation, workflow-change caveat handling. | Both repos | No cloud build/push on docs-only planning merges. | | Landing page coverage | Prospects need clear positioning without overclaiming. | Merchant-facing page copy for "connect existing systems, preview agent surface, request approval"; no live/certified claims. | Product/web repos | Copy reviewed before public launch. | ## 7. Fast-Track Plan | Slice | Goal | Merchant-visible result | Scope boundary | | - | - | - | - | | A. Seller-agent self-serve onboarding | Merchant creates seller commerce agent and Grantex authority request. | Merchant sees checklist, connector plan, artifact status, and "not live" state. | No public discovery or payments. | | B. Catalog connector MVP | Merchant imports products from CSV/manual plus one priority connector through AgenticOrg seller-agent workflow. | Products and variants appear in OACP preview with source/freshness and Grantex validation. | No checkout promises yet. | | C. Read-only discovery preview | Merchant sees what agents can read. | Public-safe profile/catalog preview and schema.org draft. | Fail-closed until approved. | | D. Inventory freshness | Agent can warn/refuse stale stock. | Merchant sees stale SKUs and freshness score. | No quantity reservation yet. | | E. Sandbox cart and consent | Buyer can rehearse cart and consent. | Commerce Passport demo with mock/sandbox provider. | No live provider. | | F. Order and fulfillment backbone | Paid flow has operational home. | Order reference, status, cancellation, fulfillment events. | Required before broad checkout. | | G. Return/refund request | Support has safe post-purchase path. | Manual return/refund request and audit. | No automatic refund execution. | | H. OACP and protocol adapters | Agent platforms see standard surfaces. | OACP artifact previews plus UCP-style profile, ACP-style checkout shapes, schema.org JSON-LD, AP2 evidence draft, A2A/MCP-style bridge metadata. | No certification claim until tests and approval. | | I. Controlled real merchant pilot | One merchant, category, geography, provider, and rollback owner. | Minimal approved live surface. | Separate rollout approval only. | ## 8. Implementation Requirements By Surface | Surface | Minimum implementation requirement | Acceptance criteria | | - | - | - | | Merchant dashboard | Onboarding checklist, category preset, connected systems, readiness score, preview, approvals, rollout state, rollback owner. | A non-engineer merchant can see what is missing and why launch is blocked. | | Merchant API | Tenant-safe create/read/update for profile, catalog, inventory, policy, connector source, webhook source, and readiness status. | Every UI action has an equivalent API or is explicitly documented as operator-only. | | Connector framework | Source type, credential reference, sync status, last run, last success, stale state, row-level errors, retry, disable. | A failed sync cannot silently publish stale facts to agents. | | Catalog model | Product, variant, media, category, price, tax, warranty, return summary, source system, freshness. | Agent answers are grounded in exact product/variant IDs. | | Inventory model | Availability bucket now; future quantity, location, reservation, confidence, TTL. | Unknown or stale inventory produces warning/refusal, not a guarantee. | | Cart/checkout model | Cart draft, cart update, fulfillment option, consent request, passport, payment intent, checkout link, cancel/expire. | Checkout cannot progress without consent, policy, idempotency, and audit. | | Order model | Order reference, status, line items, payment reference, fulfillment status, cancellation, support link. | Paid checkout has an operational record merchants can act on. | | Return/refund model | Request, eligibility, manual approval, provider handoff, status, audit. | Refund execution remains blocked until separate provider approval. | | Settlement model | Provider-neutral payout/settlement records, fees, taxes, reconciliation status, export. | Merchant can answer "when do I get paid?" without raw provider payloads. | | Protocol adapters | UCP-style profile, MCP tools, ACP-style checkout/order mapping, schema.org feed, AP2 evidence draft. | Generated from canonical objects with conformance fixtures and no unsupported claims. | | External buyer channels | Capability-safe channel metadata for ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, and future agent surfaces. | A channel cannot show checkout/payment capability unless Grantex approves the merchant, tool scope, consent, and policy state for that channel. | | Audit and observability | Append-only audit, redacted logs, metrics, runbooks, replay controls, incident severity. | No protected action is acknowledged without durable audit evidence. | | Security and privacy | Credential isolation, secret redaction, tenant filtering, role checks, webhook signatures, rate limits. | Cross-tenant and secret-exposure tests fail closed. | ## 9. Release Acceptance Criteria Before any real merchant pilot, all of these must be true: * The merchant completed self-serve sandbox onboarding and selected a category preset. * Legal/compliance, product wording, security, ops/support, rollback, smoke, and evidence retention owners are recorded as non-secret references. * Existing-system connectors are either connected and healthy or explicitly replaced by approved manual/CSV maintenance. * Catalog, price, tax, warranty, return policy, and inventory freshness checks pass for the selected category. * schema.org output is generated only from approved public-safe fields. * AgenticOrg sees only OACP-authorized capabilities with source/freshness labels and refusal semantics. * Checkout/payment creation remains blocked until Commerce Passport consent, policy, audit, idempotency, provider readiness, and rollback checks pass. * Orders, fulfillment, support, and refund handoff are available for any paid flow in the pilot category. * UCP/ACP/AP2 compatibility language is backed by implementation and tests, or clearly described as planned/future compatibility. * GitHub checks pass or skip by documented policy, and docs-only changes do not trigger cloud auth, image build, image push, deploy, or indexing jobs. ## 10. Public Landing Page And Blog Plan Use this as the safe public positioning once product/web owners approve landing page updates. This older PRD section is historical; it did not publish the pages. > Grantex Commerce helps merchants prepare for agentic commerce by connecting > approved merchant facts, policies, and source evidence into signed OACP > artifacts. AgenticOrg runs the seller and buyer agents. Merchant systems stay > the operational source of record. Provider rails own mandates and payments. > Agents can use cached artifacts for non-binding discovery and must refresh or > refuse when facts are stale, risky, or unsupported. Grantex landing page planning blocks: * Hero: "Trust layer for agentic commerce", with a simple four-party workflow. * Section 1: "What Grantex owns" - artifacts, policy, protocol adapters, refusal semantics, source/freshness, revocation, audit evidence. * Section 2: "What Grantex does not own" - seller agents, buyer chat runtime, merchant operational systems, provider mandate/payment execution. * Section 3: "OACP status" - internal C6Z foundation, blocked production vertical, no public standard claim. * Section 4: "Protocol adapters" - schema.org, UCP-style, ACP-style, AP2-style, A2A-style, MCP-style previews generated from artifacts. * Section 5: "Pending before live" - persistent cache, real connectors, provider-owned mandate verification, execution controller, public governance. Blog series plan: | Blog | Audience | Visual workflow | | - | - | - | | The Clean Four-Party Architecture For Agentic Commerce | Founders, architects, merchants | Merchant systems -> AgenticOrg seller agent -> Grantex artifacts -> AgenticOrg buyer agents -> provider rails. | | Why Grantex Must Not Be A Transaction Toll Booth | Product and engineering | Cached artifact lifecycle with refresh/refuse points and risk tiers. | | OACP Artifacts Explained | Developers | Artifact family map from merchant profile to dry-run verification. | | How AgenticOrg Seller Agents Connect Shopify, WooCommerce, And ERP | Merchants and integrators | Connector credential-custody choices and source/freshness flow. | | Provider-Owned Mandates In Agentic Commerce | Fintech and risk teams | Buyer mandate setup at provider rail, AgenticOrg verification, Grantex evidence refs. | | From schema.org To ACP To AP2: Adapter Previews Without Overclaiming | Standards and developers | OACP artifact -> adapter preview fan-out. | | What Still Blocks Autonomous Commerce | Leadership and operators | Gap map from C6Z blocked production vertical to live pilot. | Avoid claims that imply: * live payment capability already exists; * UCP, ACP, or AP2 certification already exists; * public discovery is already active; * automatic refunds are available; * human or source review is unnecessary; * every merchant system is already supported. Detailed page and blog plan lives in `docs/internal/commerce-v1/commerce-v1-oacp-landing-blog-plan.md`. ## 11. Documentation And Workflow Coverage The implementation should update these surfaces as slices land: | Surface | Required update | | - | - | | `GRANTEX_COMMERCE_PRD.md` | Keep target-state requirements and gap register current. | | `GRANTEX_COMMERCE_V1_BUILD_SPEC.md` | Add only committed V1 contracts, not aspirational roadmap claims. | | `docs/docs.json` | Keep merchant/developer/operator docs discoverable under Agentic Commerce V1. | | Commerce overview guide | Link to this PRD and show current posture, allowed paths, and blocked paths. | | End-to-end flow guide | Keep seller one-time setup, buyer one-time setup, regular transaction, exception paths, and source-of-truth rules current. | | Developer guide | Document API and connector contracts after implementation exists. | | Merchant/operator guide | Explain self-serve, approvals, category presets, and existing-system connectors. | | Operations guide | Document rollback, incident, webhook replay, stale sync, and support handoff. | | OpenAPI | Add endpoint contracts only when runtime endpoints exist or are approved for implementation. | | GitHub workflows | Keep docs-only changes away from cloud auth/build/push/deploy jobs. | | Landing pages | Use public-safe copy: "connect existing systems and preview agent-ready commerce." Avoid live, certified, or approved claims. | ## 12. Stop Conditions Stop implementation or rollout if any of these occur: * A real merchant identity is missing or unapproved. * Private contracts, contacts, pricing terms, customer data, credentials, raw payloads, tokens, JWTs, DB/Redis URLs, private keys, or provider secrets enter Git or public docs. * AgenticOrg gains a direct provider, Plural, Stripe, merchant private API, or payment credential path for payment execution or unapproved merchant execution. Approved provider capability verification and approved connector sync must stay separate from execution. * Checkout can happen without user consent, Commerce Passport verification, policy approval, idempotency, and audit. * Catalog, price, tax, inventory, delivery, warranty, or return data is stale or unverifiable but presented as guaranteed. * Paid checkout is enabled before order, fulfillment, support, and refund handoff paths exist for the selected pilot category. * Production flags, allowlists, public discovery, live provider paths, or live Plural are changed without a separate approved rollout. * UCP, ACP, AP2, schema.org, A2A, MPP, or provider certification is claimed before implementation and conformance evidence exists. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Agentic Commerce PRD Source: https://docs.grantex.dev/guides/commerce-v1-agentic-commerce-prd Canonical product requirements document for Grantex Commerce and AgenticOrg agentic commerce implementation. # Agentic Commerce PRD This is the canonical consolidated PRD for Grantex Commerce and AgenticOrg agentic commerce. It brings the seller journey, buyer journey, existing merchant systems, buyer-agent channels, standards fit, safety gates, implementation gaps, and fast-track roadmap into one place. This document is planning and documentation only. It does not deploy, create cloud resources, change production configuration, touch secrets, approve a real merchant, enable public discovery, enable production Commerce V1, enable checkout/payment creation, enable live payments, enable live Plural, or set a production allowlist. ## Current OACP Runtime Closure Boundary For the runtime launch-closure branch, the implemented path is deliberately non-executing: AgenticOrg owns seller onboarding, merchant connector initiation, artifact cache consumption, buyer sessions, and bridge adapters; Grantex owns authority validation and canonical OACP artifact issuance. Merchant systems remain source of record, and provider/fintech rails own mandate and payment execution. Sections that discuss consent, Commerce Passport, checkout, order, fulfillment, refunds, or payment-control belong to the broader Grantex Commerce pilot or future execution-controller work. They must not be read as OACP artifact runtime approval for public discovery, payment, mandate, order, or merchant-private API mutation. ## 1. Product Thesis Agentic commerce should let a buyer ask an AI agent to discover, compare, and buy from a real merchant without the merchant rebuilding their business and without the agent inventing commerce facts. The corrected product boundary is: > AgenticOrg is the buyer and seller AI-agent runtime. Grantex is the trust, > protocol, policy, and canonical-artifact authority. Merchant systems remain > the operational source of record. Provider and fintech rails own mandate and > payment execution. Grantex must not become a toll booth for every buyer and > seller agent interaction. The finished product should allow: * Sellers to self-serve from an AgenticOrg Seller Commerce Agent. * Sellers to connect systems they already use through AgenticOrg seller-agent connector workflows, merchant-owned connector platforms, or approved external integration providers. * Grantex to validate, govern, and sign canonical public-safe commerce artifacts derived from merchant-approved sources. * Buyers to start from familiar chat or agent surfaces. * AgenticOrg to run buyer-agent and seller-agent sessions from cached signed artifacts whenever TTL, revocation, and risk rules allow. * Grantex to define and enforce policy, source/freshness, public-safe fact, and protocol-adapter rules without routing every non-binding interaction. * Provider-owned mandate and payment systems to execute mandates and payments; Grantex stores only non-sensitive evidence references when artifact lineage or policy requires them. * Standards-facing views to be generated from canonical OACP artifacts, not hand-maintained per platform. ## 2. Non-Negotiable Safety Boundary An AI agent can initiate a commerce checkout through a payment provider only when Grantex can verify: * buyer consent; * scope; * merchant policy; * revocation status; * tenant boundary; * agent identity; * channel capability; * amount cap; * product/cart hash or equivalent cart evidence; * idempotency; * provider readiness; * audit evidence; * rollback readiness. AgenticOrg must not: * hold provider credentials; * call Plural, Stripe, Pine, Razorpay, Cashfree, Adyen, PhonePe, or another payment provider directly for payment execution; * call Shopify, WooCommerce, Magento, ERP, OMS, WMS, logistics, CRM/support, or merchant private APIs outside merchant-approved seller-agent connector sync jobs, approved external connector systems, or explicit human-authorized integration workflows; * become the catalog, inventory, order, refund, settlement, or payout source of truth; * store raw Commerce Passport values, JWTs, tokens, idempotency key values, webhook secrets, DB/Redis URLs, private keys, raw payloads, private merchant artifacts, pricing terms, contracts, or customer data; * invent sellers, products, prices, discounts, stock, delivery promises, return eligibility, order status, payment status, settlement state, payout state, or refund outcomes. ## 3. Ownership Model | Area | Grantex owns | AgenticOrg owns | | - | - | - | | Merchant onboarding | Authority-side merchant identity, review state, category policy, canonical artifact issuance, and approval gates. | Seller Commerce Agent self-serve onboarding, merchant education, seller agent cards, connector setup workflow, and launch rehearsal. | | Existing merchant systems | Source/freshness policy, accepted evidence refs, canonical fact validation, artifact signing, and connector risk rules. | Merchant-approved connector sync initiation, connector UX, credential-custody routing, source evidence capture, and seller-agent status. | | Catalog and inventory | Buyer-safe canonical facts, source/freshness artifacts, policy constraints, TTLs, and refusal semantics. | Seller-agent sync, buyer-agent presentation, local cache across buyer agent, seller agent, tenant, and merchant, and stale/unknown UX. | | Policy and consent | Policy rules, artifact authority, revocation snapshots, Commerce Passport posture, and audit evidence requirements. | Buyer-facing explanation, session state, cached artifact use within TTL, and refusal/refresh handoff. | | Payment and mandates | Non-sensitive mandate capability evidence refs only when needed for policy/artifact lineage. Grantex does not own provider mandate setup or execution. | Provider-owned mandate capability verification where approved, buyer/seller UX, and handoff to provider or fintech rails. | | Checkout/order execution | Future eligibility/audit contract checks only. Grantex must not be the default live transaction toll booth. | Future execution-controller handoff orchestration only after separate approval; no execution in current OACP slices. | | Order and fulfillment | Canonical public-safe status artifacts when merchant systems provide evidence. | Buyer-facing status, support handoff, and refusal when facts are missing or stale. | | Agent channels | Capability approval and channel limits. | ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, and future channel adapters. | | Standards and OACP | OACP artifact families, protocol adapter previews, detached-signature posture, TTLs, source/freshness rules, and non-publication guardrails. | Consume OACP artifacts, expose seller cards and channel bridges, and never treat adapters as transaction authority. | | Audit and ops | Canonical evidence rules and future production audit contracts. | Local redacted session evidence, cache telemetry, refusal evals, and seller/buyer support workflow. | ## 4. Current Implementation Snapshot Status as of 2026-06-18: * Grantex and AgenticOrg have merged the internal OACP foundation through C6Z. The implementation is internal, non-publication, non-certifying, non-executing, and fail-closed. * C6W3 defines the OACP artifact family and public-safe fixture corpus. * C6W4 defines internal adapter previews for schema.org, UCP-style, ACP-style, AP2-style, A2A-style, and MCP-style targets without publication. * C6W5 classifies non-binding preview, commitment-adjacent, commitment-bound, and always-blocked actions over cached signed artifacts. * C6W6 prepares non-executing commitment request envelopes for human/source confirmation handoff. * C6W7 reconciles local/cached response evidence with prepared envelopes. * C6W8 prepares eligibility and audit-readiness packets over reconciliations. * C6W9 performs dry-run verification that a future execution controller could read the packet contract. It still does not execute, approve, publish, create orders, create checkout/payment, call providers, call merchant private APIs, or enable public discovery. * C6Z adds the AgenticOrg Seller Commerce Agent runtime path, Shopify read-only sync initiation, Grantex internal authority request route, AgenticOrg artifact cache, buyer answer path, MCP seller facts bridge, and Plural/Pine capability verifier. The 2026-06-18 production closure run is blocked because the mounted AgenticOrg Shopify token returns `401 Unauthorized` and the AgenticOrg-configured Grantex token returns `422 tenant_not_provisioned`. * Public discovery, production Commerce V1, checkout/payment enablement, live payments, live Plural, production allowlists, certification claims, provider execution, and merchant private API execution remain blocked. ### 4.1 Grantex Foundation | Capability | Current state | Gap | | - | - | - | | Merchant/tenant control plane | Seller sandbox onboarding, category readiness, catalog readiness, public-safe agent preview, review request, operator decision, rollout proposal dry run, and AgenticOrg sandbox handoff are implemented through C6G. | Full self-serve production onboarding, KYB/KYC, live approval, and public discovery rollout remain blocked. | | Category presets | `electronics_appliances` readiness checklist and scoring exist for the sandbox path. | Multi-category presets and policy/eval defaults remain future work. | | Catalog and variants | Product/variant APIs, search/detail, bulk dry-run/upsert, patch/archive, CSV/JSON-oriented portal support, catalog readiness preview, and public-safe sample preview exist. | Large async imports, connector sync, conflict handling, rollback, richer price/offer models, and quantity inventory remain pending. | | Inventory | Variant availability and freshness exist. | Quantity, location, confidence, reservations, stock holds, and delivery feasibility are missing. | | Consent and Commerce Passport | Consent request/exchange/verify/revoke/challenge foundation exists. C6M AP2-style deterministic unsigned evidence preview is merged. | Production challenge delivery, signed mandate evidence, independent AP2 conformance, and live payment approval are pending. | | Cart/payment intent | Cart draft and provider-neutral payment intent foundation exists. C6L ACP-style cart/checkout shape preview is merged. | Cart update/cancel/expire, fulfillment selection, ACP conformance, and broad live checkout readiness are pending. | | Provider evidence/webhooks | Provider capability evidence references, webhook intake metadata, replay metadata, and reconciliation posture foundations exist. | Live provider-rail approval, signature evidence, outage handling, and rollback proof remain gated; provider/fintech rails remain payment authority. | | Merchant webhooks | Signed merchant webhook source and catalog update intake exist. | Inventory-only, price-only, order, fulfillment, support, return, and refund webhooks are pending. | | Existing-system connectors | C6N metadata-only connector registry, source precedence, sync health, stale/conflict blockers, and no-credential/no-execution guardrails are merged. | Real Shopify/WooCommerce/Magento/ERP/OMS/WMS/logistics/support/payment sync adapters remain pending. | | Standards adapters | C6J schema.org JSON-LD preview, C6K UCP-style capability profile preview, C6L ACP-style checkout shape preview, and C6M AP2-style evidence preview are merged. | Public publication, certification, conformance suites, live capability negotiation, and public discovery remain blocked. | | MCP/native surface | Read and checkout-oriented tools exist behind Grantex; C6G adds sandbox AgenticOrg buyer discovery handoff. | Public discovery remains fail-closed; public standards conformance is not complete. | | Docs and CI | Commerce docs, docs-only workflow guard, and internal open-protocol packaging draft exist. | Keep consolidated PRD, overview, guides, manifests, and nav current as stacked PRs merge. | ### 4.2 AgenticOrg Foundation | Capability | Current state | Gap | | - | - | - | | Grantex/OACP connector aliases | Merchant profile, catalog search/detail, inventory, cart, consent, prepared checkout state, payment-status evidence, and read-only buyer discovery preview aliases exist. | Order, fulfillment, support, return, refund, settlement, and payout views wait on future approved artifact/evidence APIs. | | Buyer discovery workflow | C6H read-only consumer foundation is merged. C6I channel-neutral buyer-session orchestration is merged. | Live channel adapters and write-capable buyer flows remain blocked. | | Sales agent guardrails | Missing consent/passport, amount-cap breach, disabled merchant/agent, policy denial, no-direct-provider, and read-only buyer discovery refusal protections exist or are merged in C6I. | Guardrails must expand as Grantex adds order, fulfillment, support, return, refund, settlement, and payout capabilities. | | Demo/evals | Demo, golden evals, hosted smoke, no-direct-provider regressions, and focused buyer discovery tests exist. | Channel-specific smoke and live buyer UX evals are pending. | | Public discovery | Fail-closed behind AgenticOrg public discovery flag. | Real merchant public discovery requires Grantex approval and AgenticOrg gating. | | Docs-only CI guard | Docs-only changes skip build/push/deploy-adjacent jobs. | Keep guard current, especially for workflow changes. | | Buyer channel launch | Channel-neutral response model is merged in C6I. | ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, and future live adapters are not launch-ready yet. | ### 4.3 OACP Pending Work Register The current C6Z state proves the internal runtime shape locally and proves the Grantex authority issuer route in isolation, but it does not complete the AgenticOrg-to-Grantex production vertical. It is not a public protocol, not a transaction engine, and not a live commerce launch. Pending work before any public or production OACP claim: | Pending area | Why it matters | Correct owner | First acceptable output | | - | - | - | - | | Seller Commerce Agent onboarding | Merchants expect to start in AgenticOrg, not in a protocol console. | AgenticOrg runtime + Grantex authority | Seller agent creates onboarding packet, connector plan, artifact authority request, and blocked-path explanation. | | Persistent artifact cache | Buyer and seller agents need continuity without Grantex in every non-binding turn. | AgenticOrg | Cache scoped by buyer agent, seller agent, tenant, and merchant with TTL, revocation snapshot, risk tier, and audit-safe telemetry. | | Artifact issuance runtime | C6Z adds an internal Grantex authority route and verifier, but the AgenticOrg-configured production token is not yet mapped to a commerce tenant. | Grantex | Provisioned internal issuer/verifier path for AgenticOrg with key lifecycle, detached signature profile, revocation lookup, and no public endpoint until approved. | | Connector credential custody | Merchants will choose where credentials live. | AgenticOrg + merchant/external connector provider + Grantex policy | Credential-custody matrix, OAuth/app install flow, source evidence refs, no raw secrets in Grantex by default. | | Shopify/WooCommerce/ERP sync | Manual/CSV is not enough for real merchants. | AgenticOrg seller connectors + Grantex validation | First real connector sync dry-run with public-safe normalized evidence and rollback; the 2026-06-18 Shopify production probe is blocked by `401 Unauthorized`. | | Price/inventory freshness | Agents must not promise stale stock or price. | Merchant systems + AgenticOrg cache + Grantex policy | TTL defaults, source timestamps, stale refusal, and refresh request UX per risk tier. | | Provider-owned mandate verification | Mandates belong to fintech rails, not Grantex. | Provider/fintech + AgenticOrg verifier | First provider capability verifier with non-sensitive evidence refs only. | | Execution controller ownership | C6Z still creates no checkout, order, mandate, refund, shipment, inventory hold, or provider execution. | Product decision across AgenticOrg, merchant systems, and providers | Explicit owner, handoff contract, human approval gates, and stop conditions before any execution slice. | | Third-party agent cards | Other agents should consume seller cards safely. | AgenticOrg + Grantex artifacts | Full or reduced seller cards with risk filters, artifact refs, and no transaction authority. | | Public landing and blogs | Market needs clarity; overclaiming would damage trust. | Product/web/docs | Approved public-safe pages and visual explainers after this PRD is approved. | | Public OACP governance | A protocol needs neutral stewardship. | Founder/legal/product decision | Separate repo/site, license, IPR posture, versioning, contribution model, and no standards claims until actually submitted/accepted. | ## 5. End-To-End Architecture ```mermaid theme={null} flowchart TB seller["Seller / merchant"] selleragent["AgenticOrg Seller Commerce Agent"] systems["Existing merchant systems"] connector["Merchant-approved connector sync"] grantex["Grantex trust, protocol, policy, and artifact authority"] artifacts["Signed OACP artifacts and adapter previews"] agenticorg["AgenticOrg buyer-agent runtime and cache"] channels["ChatGPT / Claude / Gemini / WhatsApp / Telegram / web / mobile"] provider["Provider and fintech mandate/payment rails"] buyer["Buyer"] seller --> selleragent selleragent --> connector systems --> connector connector --> grantex grantex --> artifacts artifacts --> agenticorg agenticorg --> channels channels --> buyer buyer --> provider agenticorg -. "provider capability verification, when approved" .-> provider provider -. "non-sensitive evidence refs only when needed" .-> grantex ``` Existing merchant systems may include: * Shopify; * WooCommerce; * Magento; * custom storefront; * marketplace backend; * ERP; * PIM; * inventory system; * WMS; * OMS; * logistics provider; * payment provider; * CRM; * support desk; * finance/reconciliation system; * CSV/API/manual maintenance process. Those systems do not all have to connect directly to Grantex. The preferred merchant experience is: * AgenticOrg Seller Commerce Agent starts onboarding and connector setup. * Connector credentials live where merchants are most comfortable: merchant system native app authorization, merchant-owned connector platform, external integration provider, or AgenticOrg vault only when explicitly selected and approved. * AgenticOrg seller agents may initiate connector sync jobs when merchant policy allows it. * Grantex receives public-safe normalized facts, hashes, source timestamps, and non-sensitive evidence references for artifact validation. * Grantex does not store raw connector credentials by default and must not be required in every non-binding buyer/seller agent interaction. ## 6. One-Time Seller Setup The seller setup must be self-serve for preparation, but not self-approval for live commerce. | Step | Seller action | Grantex requirement | Status/gate | | - | - | - | - | | 1. Create workspace | Sign up and create commerce tenant. | Tenant boundary, merchant shell, owner role, sandbox/live split. | Required before any data import. | | 2. Complete profile | Enter legal name, display name, category, country, currency, support info, public description. | Separate public-safe metadata from private artifacts. | Public fields must pass review. | | 3. Verify merchant | Provide legal/compliance/KYB/KYC evidence outside repos. | Store only non-secret evidence references and decision state. | No live mode without approval. | | 4. Choose category preset | Select category such as home goods, electronics, fashion, grocery, pharmacy, services, B2B. | Apply required fields, policy defaults, tax, return, warranty, and safety expectations. | Missing critical fields block readiness. | | 5. Connect systems | Use AgenticOrg Seller Commerce Agent, merchant-owned connectors, external integration providers, or approved CSV/API flow to connect storefront, ERP/PIM, inventory/WMS, OMS, logistics, payment, support, or CSV/API. | Define accepted source evidence, credential-custody rules, stale/conflict policy, and artifact validation requirements. | Connector health required before use; Grantex does not need raw connector credentials by default. | | 6. Declare source of truth | Decide which source wins for catalog, price, inventory, order, fulfillment, support, refund, settlement. | Record precedence, timestamps, conflict handling, and rollback. | No silent conflict resolution. | | 7. Prepare catalog | Fix title, description, image, brand, SKU, variants, category, price, tax, warranty, return summary. | Normalize to CommerceProduct and CommerceProductVariant. | Public-safe preview required. | | 8. Prepare inventory/delivery | Configure stock state, freshness, delivery/pickup, shipping, serviceability, logistics source. | Block guarantees when stale, unknown, or unsupported. | Checkout blocked if evidence is insufficient. | | 9. Configure agent permissions | Choose browse, compare, cart draft, checkout consent request, order status, support handoff, return/refund request. | Convert to policy, scopes, channel capabilities, amount caps, audit rules. | Each capability separately approved. | | 10. Configure mandates/payment | Configure provider-owned mandate or payment rails outside Grantex first; share only capability/evidence references required for policy and OACP artifacts. | Validate non-sensitive mandate capability evidence references when needed for artifact lineage. | Live remains blocked until separate provider, merchant, and product approval. | | 11. Review previews | See AI-agent-facing profile, catalog, policy, schema.org, and channel labels. | Render exact public-safe payload and blocked paths. | Product wording and security review required. | | 12. Run scans | Run secret/private-detail, overclaim, synthetic-ID, production-ID, config/allowlist, stale-data scans. | Produce redacted evidence and blocker codes. | Clean scans required for readiness. | | 13. Assign owners | Merchant owner, legal, product, security, ops, backup/RPO, rollback, smoke, evidence retention, AgenticOrg dependency. | Store non-secret owner references and approval state. | Missing owners block rollout. | | 14. Rehearse launch | Run sandbox/demo buyer journey and rollback drill. | Generate smoke evidence and rollback checklist. | Required before rollout proposal. | | 15. Request rollout | Ask for smallest approved surface, usually read-only discovery first. | Keep fail-closed until explicit approval. | Separate rollout approval required. | ## 7. One-Time Buyer Setup The buyer setup should feel like normal chat/app account linking. | Step | Buyer action | AgenticOrg requirement | Grantex requirement | | - | - | - | - | | 1. Choose channel | Use ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, or future approved channel. | Start the correct channel adapter. | Publish approved channel capability metadata. | | 2. Link or sign in | Connect account if needed. | Create or resume buyer-agent session and bind channel identity. | Avoid exposing private merchant/provider data. | | 3. Set preferences | Locale, currency, delivery region, notification path, accessibility, optional spend comfort. | Store only safe preferences for conversation and handoff. | Use preferences for policy and consent copy where needed. | | 4. Understand actions | See browse, compare, draft cart, request checkout, read status, support handoff, blocked actions. | Render channel-specific capability labels. | Return approved capabilities and blocker reasons. | | 5. Consent when needed | Approve or deny payment-affecting actions. | Never bypass consent or present it as already granted. | Issue scoped Commerce Passport only after consent and policy pass. | | 6. Manage history | Ask what happened or revoke permission. | Show redacted agent session/evidence status. | Own revocation, protected action history, and audit. | Buyer setup is not a standing payment approval. Every payment-affecting action still needs valid consent, policy, revocation, source/freshness, provider-owned mandate/payment capability, idempotency, and audit evidence. Grantex owns the policy/artifact evidence rules, not the provider-owned mandate setup itself. ## 8. Regular Agentic Commerce Transaction ```mermaid theme={null} sequenceDiagram participant B as Buyer participant C as Buyer channel participant A as AgenticOrg participant G as Grantex participant S as Merchant system or connector participant P as Provider or fintech rail B->>C: Ask to discover, compare, or prepare C->>A: Start or resume buyer-agent session A->>A: Use cached OACP artifacts if TTL and risk allow alt cache absent, stale, revoked, or high risk A->>G: Refresh or verify signed artifacts G-->>A: Public-safe artifacts, TTLs, blocker codes end A-->>B: Grounded options, source/freshness, caveats B->>A: Ask for commitment-bound action A->>G: Boundary/envelope/reconciliation/eligibility/dry-run checks G-->>A: Non-executing eligibility or blocker result A->>S: Request source confirmation through approved connector handoff S-->>A: Confirmation evidence or refusal A->>P: Verify provider-owned mandate capability when approved P-->>A: Provider-owned evidence or refusal A-->>B: Prepared handoff, consent request, or safe refusal ``` Plain-English rules: 1. Buyer asks an agent to discover, compare, or prepare. 2. AgenticOrg starts or resumes the buyer session. 3. AgenticOrg reads valid cached OACP artifacts when TTL, revocation, and risk rules allow; otherwise it asks Grantex to refresh or verify artifacts. 4. AgenticOrg explains product, price, inventory, policy, and source/freshness caveats without inventing facts. 5. Non-binding discovery does not require routing every message through Grantex. 6. Commitment-bound actions require C6W5-C6W9 boundary, envelope, reconciliation, eligibility, and dry-run checks before any future handoff. 7. Merchant-system confirmation happens through approved seller-agent connector handoff or merchant-owned integration systems, not by raw private API calls. 8. Provider-owned mandate and payment capability verification stays with the provider/fintech rail; AgenticOrg may verify capability directly where approved. 9. Grantex stores only non-sensitive evidence references when needed for policy, artifact lineage, or later audit. 10. Current OACP implementation stops before execution. It does not create orders, holds, checkout/payment intents, mandates, refunds, returns, shipments, settlement, or payout actions. ## 9. Exception And Recovery Flows | Situation | Buyer experience | Seller experience | Required behavior | | - | - | - | - | | Merchant not approved | Agent says merchant is not available for agentic commerce yet. | Seller sees missing approval state. | Grantex remains fail-closed. | | Channel is read-only | Buyer can browse and gets safe handoff or unsupported-action copy. | Seller sees channel capability limit. | AgenticOrg cannot pretend write actions are available. | | Product not found | Agent asks clarifying question or says unavailable. | Seller may see missing catalog coverage. | No guessed products. | | Product data incomplete | Agent says it cannot verify enough detail. | Seller sees missing required fields. | Readiness score blocks launch or capability. | | Price changed | Buyer sees updated total and must confirm again. | Seller sees audit and source timestamp. | Grantex invalidates stale cart totals. | | Inventory stale/unknown | Buyer gets warning or checkout refusal. | Seller sees stale inventory blocker. | No stock or delivery guarantee. | | Delivery unavailable | Buyer gets no delivery promise. | Seller sees logistics/serviceability blocker. | Checkout blocked if fulfillment proof required. | | Consent denied/expired | Checkout stops. | Seller sees no payment attempt. | No passport issued or stale passport revoked. | | Policy denied | Buyer sees safe reason without private policy. | Seller sees blocker code. | Audit written, private policy not leaked. | | Payment failed/pending | Buyer sees status and next supported step. | Seller sees reconciliation state. | Provider/fintech rail remains payment authority; Grantex keeps only non-sensitive status/evidence refs where policy or artifact lineage requires them. | | Order API missing | Buyer cannot get order promise. | Seller sees launch gap. | Broad paid rollout blocked. | | Refund/return requested | Buyer gets manual support handoff now; future request/status later. | Seller handles in approved system. | No automatic refund execution until separately approved. | | Settlement/payout question | Buyer usually does not see it; seller sees payout status when available. | Seller sees payout/reconciliation report. | No raw provider payload exposure. | ## 10. Existing Merchant APIs And Systems Merchants should not rebuild for agents. They should connect the systems they already run. | System | Facts OACP needs | Correct source and authority model | Notes | | - | - | - | - | | Shopify/WooCommerce/Magento/custom storefront | Products, variants, media, collections, availability, pricing. | Merchant storefront remains source; AgenticOrg or merchant-approved connector captures facts; Grantex validates public-safe catalog artifacts. | Start read-only; writeback only after approval. | | ERP/PIM | Product master, attributes, tax codes, SKU hierarchy, category truth. | Merchant ERP/PIM remains source; Grantex validates source precedence and category/policy artifacts. | Merchant declares source-of-truth precedence. | | Inventory/WMS | Stock state, quantity, location, reservation, confidence, reorder state. | Merchant WMS remains source; Grantex validates freshness, refusal rules, and evidence refs. | Browse can use buckets; checkout needs stronger freshness. | | OMS | Order creation, order ID, status, cancellation, fulfillment, return status. | Merchant OMS remains operational owner; future artifacts expose public-safe status evidence only. | Required before broad checkout. | | Logistics | Delivery promise, shipping price, pickup slot, tracking, failed delivery. | Carrier/logistics systems remain source; Grantex validates delivery-promise evidence only after approval. | Needed for buyer trust and UCP-style order capability. | | Payment or mandate provider | Provider-owned mandate capability, payment intent/order, hosted checkout, status webhook, settlement, refunds. | Non-sensitive capability/evidence refs and future provider-neutral status artifacts where approved. | Credentials and mandate setup stay provider/fintech-owned; Grantex does not mediate every payment action. | | CRM/support | Ticket, support status, return/refund escalation, customer communication. | Merchant support system remains owner; artifacts expose support/return/refund evidence only where approved. | Start manual/reference, automate later. | | CSV/manual/API | Bootstrap catalog, policy, inventory, and approval metadata. | Manual source remains declared; Grantex validates public-safe artifacts and provenance. | Must include dry-run, validation, history, and rollback. | Connector rule: > A connector imports or syncs data. It does not make a merchant live for > agents. Live agent access requires readiness checks, policy activation, > consent, human approval, audit, and rollback ownership. ## 11. Buyer Channel Requirements Buyers should be able to launch from familiar surfaces, but no channel is launch-ready until its platform-specific constraints and approval gates pass. | Channel | Intended launch | Required work | | - | - | - | | Web/mobile | AgenticOrg-hosted buyer session or merchant-embedded widget. | First controllable launch path, session resume, Grantex merchant selector, consent redirect, analytics attribution. | | ChatGPT | Remote MCP-backed custom app/package where available. | App manifest, OAuth/account linking, action labels, confirmation copy, app review, fallback to read-only when writes unavailable. | | Claude | Remote MCP connector. | Endpoint, auth, tool descriptions, least-privilege scopes, test harness, refusal behavior. | | Gemini | Gemini API/function-calling or hosted wrapper. | Function declarations, tool execution loop, consent handoff, clear label for native support status. | | WhatsApp | WhatsApp Business Platform bot/webhook adapter. | WABA setup, templates, session window, identity binding, consent link, rate limits, opt-out, human escalation. | | Telegram | Telegram Bot API adapter. | Bot setup, webhook receiver, secret validation, identity mapping, inline buttons, consent link, rate limits. | | Other channels | MCP, A2A, REST, webhook, or hosted handoff depending on platform. | Capability matrix, auth, consent, fallback, telemetry, smoke tests. | Launch-ready acceptance: * real user starts without developer setup; * account/channel identity is bound safely; * merchant discovery comes from Grantex; * actions are labeled read-only, consent-required, checkout-capable, or blocked; * OACP artifacts and authority-checked tools preserve refusal semantics; * consent and checkout copy is clear; * platform write-action limitations are respected; * redacted telemetry and audit evidence exist; * smoke tests and refusal tests pass; * fallback exists for unsupported actions. ## 12. Standards And Protocol Fit The strategy is one internal OACP artifact model with protocol views generated from it. Grantex is the artifact and policy authority; AgenticOrg is the runtime consumer and channel bridge. | Surface | Purpose | Requirement | | - | - | - | | OACP artifacts | Internal trust and interoperability profile across buyer/seller agents, Grantex policy, merchant systems, and provider rails. | Implemented internally through C6Z with a blocked production vertical; not public, not standardized, not certified. | | Native Grantex API | First-party authority APIs for canonical artifacts and policy. | Must not be required for every non-binding cached interaction. | | MCP | Agent tool transport. | Use as one bridge for ChatGPT/Claude-style surfaces; tools consume artifacts and must preserve refusal semantics. | | UCP-style profile | Capability discovery and shopping capabilities. | Publish only approved merchant/channel capability state. | | ACP-style checkout | Agentic checkout/session/order/refund shapes. | Map Grantex cart/payment/order state where provider and merchant support it. | | AP2-style evidence | Signed authorization/mandate evidence for agent payments. | Map provider-owned mandate evidence references and Grantex policy/artifact lineage without claiming AP2 conformance. | | schema.org | Public product/offer/shipping/return metadata. | Generate public-safe JSON-LD/feed from approved fields only. | | A2A or future agent surfaces | Agent-to-agent discovery/handoff. | Use only Grantex-approved capability metadata and AgenticOrg channel guardrails. | Do not claim certified UCP, ACP, AP2, A2A, MPP, schema.org production, or live provider readiness until implementation, conformance tests, approvals, and release evidence exist. ### 12.1 IETF And NIST Publication Tracks The external standards strategy is split into two preparation tracks: | Track | Intended artifact | Current posture | Required before external submission | | - | - | - | - | | IETF | Individual Internet-Draft candidate for an agentic commerce trust architecture. | C6T internal outline only; not submitted, not adopted, not an RFC, and not a standard. | Public-safe draft text, IPR/legal review, security/privacy considerations, non-Grantex-specific examples, and explicit approval to submit. | | NIST | Security and risk reference-architecture whitepaper candidate. | C6T internal outline only; not submitted to NIST, not accepted by NCCoE, and not NIST-approved. | Threat model, NIST AI RMF/CSF control mapping, public-safe architecture, collaborator/project path, and explicit approval to engage. | The IETF track should focus on protocol engineering: roles, message envelopes, capability profiles, consent, policy, evidence, refusal semantics, and interoperability with existing protocol surfaces. The NIST track should focus on risk management: threat model, trust boundaries, AI RMF mapping, cybersecurity controls, payment-provider boundary controls, connector governance, audit evidence, privacy, incident response, and rollback. Both tracks must describe Grantex and AgenticOrg as implementation experience only. They must not make Grantex-specific APIs mandatory, must not claim external certification, and must not describe sandbox evidence as production approval. ## 13. Conceptual Data Model The implementation should preserve these conceptual records. Some already exist in V1 form; others are future gaps. | Record | Owner | Purpose | Current posture | | - | - | - | - | | CommerceTenant | Grantex | Tenant boundary. | Foundation exists. | | CommerceMerchant | Grantex | Seller identity and policy anchor. | Foundation exists. | | CommerceCategoryPreset | Grantex | Category-required fields and defaults. | `electronics_appliances` readiness foundation exists. | | CommerceConnectorSource | Grantex | Existing-system connection and health. | C6N metadata-only registry merged; no credentials or outbound sync. | | CommerceImportJob | Grantex | Async import, dry-run, errors, rollback. | Gap. | | CommerceProduct | Grantex | Product truth. | Foundation exists. | | CommerceProductVariant | Grantex | SKU/variant/price/inventory anchor. | Foundation exists. | | CommerceInventoryLevel | Grantex | Quantity/location/freshness/reservation. | Gap beyond availability buckets. | | CommercePrice/Offer | Grantex | Tax, discount, EMI, coupon, offer freshness. | Gap beyond basic price fields. | | CommercePolicy | Grantex | Merchant permissions and amount caps. | Foundation exists. | | CommerceConsentRecord | Grantex | Buyer consent record. | Foundation exists. | | CommercePassport | Grantex | Scoped checkout authorization. | Foundation exists. | | CommerceCart | Grantex | Cart draft/update/cancel/expire. | Foundation exists; lifecycle gaps remain. | | CommercePaymentIntent | Grantex | Provider-neutral payment intent. | Foundation exists. | | CommerceOrder | Grantex | Post-payment order record. | Gap. | | CommerceFulfillmentEvent | Grantex | Shipment/pickup/delivery status. | Gap. | | CommerceReturnRequest | Grantex | Return workflow. | Gap. | | CommerceRefundRequest | Grantex | Refund request and approval. | Gap. | | CommerceSettlement/Payout | Grantex | Seller payment reporting. | Gap. | | CommerceAuditEvent | Grantex | Append-only evidence. | Foundation exists. | | CommerceAgentSession | AgenticOrg/Grantex boundary | Buyer-agent session attribution and channel state. | C6I channel-neutral session orchestration merged; live channels pending. | | OacpArtifact | Grantex authority, AgenticOrg cache | Signed or signature-ready public-safe artifact scoped to tenant, merchant, seller agent, and buyer agent. | C6Z authority route issues/refuses all 11 families; AgenticOrg caches and evaluates them for buyer-safe use. | | OacpProtocolAdapterPreview | Grantex authority, AgenticOrg consumer | schema.org/UCP-style/ACP-style/AP2-style/A2A/MCP preview derived from artifacts. | Grantex `protocol_adapter` artifacts now carry mapping governance; AgenticOrg generates runtime adapter payloads from cached artifacts. Public certification/publication remains pending. | | OacpPreparedEnvelope | Grantex authority, AgenticOrg consumer | Prepared-only request envelope for buyer/source/merchant/provider confirmation. | AgenticOrg purchase preparation now returns a non-executing handoff or exact blocker after OACP/provider checks. | | OacpReconciliation | AgenticOrg local runtime, Grantex rules | Local/cached response-evidence reconciliation result. | Internal C6W7 foundation merged; production persistence pending. | | OacpEligibilityPacket | AgenticOrg local runtime, Grantex rules | Non-executing eligibility/audit packet. | Internal C6W8 foundation merged; execution controller still absent. | | OacpDryRunVerification | Grantex rules, AgenticOrg consumer | Contract dry-run over eligibility packet. | Internal C6W9 foundation merged; execution remains blocked. | | ChannelAdapterConfig | AgenticOrg | Platform launch and capability limits. | Gap. | ## 14. Product Requirements By Surface | Surface | Requirement | Acceptance | | - | - | - | | Merchant dashboard | Checklist, profile, category, systems, readiness score, preview, approvals, rollout, rollback. | Non-engineer can see what is missing and why launch is blocked. | | Merchant API | Tenant-safe create/read/update for profile, catalog, inventory, policy, connector source, webhook source, readiness. | Every UI action has API equivalent or is explicitly operator-only. | | Connector framework | Source type, credential reference, sync status, last run, stale state, row errors, retry, disable. | Failed sync cannot silently publish stale facts. | | Catalog | Product, variant, media, category, price, tax, warranty, return, source, freshness. | Agent answers grounded in exact IDs. | | Inventory | Availability now; future quantity, location, reservation, confidence, TTL. | Unknown/stale inventory warns or blocks, never guarantees. | | Cart/checkout | Cart draft/update/cancel, fulfillment option, consent, passport, payment intent, checkout link. | Checkout cannot progress without consent, policy, idempotency, audit. | | Order | Order reference, status, line items, payment reference, fulfillment status, cancellation, support link. | Paid checkout has an operational record. | | Returns/refunds | Request, eligibility, manual approval, provider handoff, status, audit. | Execution blocked until separate provider approval. | | Settlement | Payout/read model, fees, taxes, reconciliation, export. | Seller can answer "when do I get paid?" without raw provider payloads. | | Protocol adapters | UCP-style, MCP, ACP-style, schema.org, AP2 evidence draft. | Generated from canonical objects with tests and no unsupported claims. | | Buyer channels | Web/mobile, ChatGPT, Claude, Gemini, WhatsApp, Telegram, future adapters. | Each has launch, auth, consent, fallback, smoke evidence. | | Audit/ops | Append-only audit, redacted logs, metrics, runbooks, replay, incident severity. | No protected action acknowledged without durable evidence. | | Security/privacy | Credential isolation, secret redaction, tenant filtering, role checks, webhook signatures, rate limits. | Cross-tenant and secret-exposure tests fail closed. | ## 15. Comprehensive Gap Register | Gap | Impact | Owner | Fast-track output | Blocker if skipped | | - | - | - | - | - | | Self-serve merchant signup | Merchants need to join without engineering tickets. | AgenticOrg seller runtime + Grantex authority | AgenticOrg Seller Commerce Agent onboarding, Grantex authority review, roles, category, checklist. | Operator-only onboarding does not scale. | | Merchant verification | Real merchant identity must be trusted. | Grantex + reviewers | Private evidence refs and review workflow. | No live approval. | | Existing-system connectors | Merchants will not duplicate data manually. | AgenticOrg seller runtime + merchant/external connector custody + Grantex artifact policy | C6N metadata-only registry exists; real sync adapters and credential-custody flow next. | Stale or manual-only launch. | | Large catalog imports | Real sellers have many SKUs. | AgenticOrg connector workflow + Grantex validation | Async import, dry-run, row errors, rollback, artifact issuance. | Timeouts and silent data loss. | | Source-of-truth precedence | Systems can disagree. | Grantex policy + AgenticOrg seller connector workflow | Conflict rules, sync timestamps, and source evidence. | Wrong price/stock/order facts. | | Inventory depth | Agents must not promise stock incorrectly. | Grantex | Quantity/location/freshness/reservation model. | Unsafe checkout promises. | | Delivery/pickup | Buyers need fulfillment confidence. | Grantex | Serviceability, slots, fees, ETA, carrier data. | False delivery promises. | | Pricing/tax/offers/EMI | Agents need correct totals. | Grantex | Fresh price, tax, coupons, discounts, EMI metadata. | Invented discounts or totals. | | Cart lifecycle | Checkout needs update/cancel/expire. | AgenticOrg runtime handoff + Grantex policy/artifact rules | Cart preparation, recalc evidence, fulfillment selection, expire. | Stale cart risk. | | Order lifecycle | Payment without order ops is incomplete. | Merchant OMS + AgenticOrg runtime + Grantex artifact policy | Order create/read/status/cancel evidence without making Grantex the default order engine. | Paid launch unsafe. | | Fulfillment lifecycle | Buyers ask where items are. | Merchant OMS/WMS/logistics + AgenticOrg runtime + Grantex artifact policy | Shipment, pickup, partial fulfillment, failed delivery evidence. | Agent invents delivery. | | Returns/refunds | Post-purchase trust. | Merchant support/OMS/provider rails + AgenticOrg runtime + Grantex artifact policy | Request, eligibility, manual approval, evidence refs. | Unsafe refund claims. | | Settlement/payouts | Sellers need money visibility. | Provider/fintech rails + merchant finance + Grantex evidence refs only if needed | Payout/settlement status artifacts without raw provider payloads. | Merchant ops blind spot. | | Live provider readiness | Payments need approval. | Provider/fintech owner + AgenticOrg verification + Grantex evidence policy | Provider-owned capability verification, non-sensitive evidence refs, rollback rules. | Live provider risk. | | Commerce Passport production delivery | Real consent needs delivery. | Grantex | Email/SMS/passkey challenge, revocation, signed evidence. | Weak authorization. | | Policy simulator | Merchants need confidence. | Grantex | Preview policy by product/category/channel/amount. | Accidental capability exposure. | | OACP and standards adapters | Major platforms need standard surfaces. | Grantex defines artifacts; AgenticOrg consumes and bridges | C6W3-C6Z internal artifact, adapter, boundary, envelope, reconciliation, eligibility, dry-run verifier, authority route, cache, and bridge foundations are merged; external publication remains blocked. | Fragmented protocol state or premature external claims. | | Buyer channel launch | Buyers need easy entry. | AgenticOrg + Grantex approval | C6I channel-neutral response merged; live channel adapters next. | Agentic commerce hard to use. | | Buyer UX | Buyers need understandable flow. | AgenticOrg | C6I read-only buyer session merged; cart/consent/checkout UX remains future. | Confusing or unsafe agent behavior. | | Merchant demo UX | Sellers need education. | AgenticOrg | Demo walkthroughs and blocked-path examples. | Misunderstanding production readiness. | | Evals/regressions | Guardrails must remain true. | AgenticOrg | No invention, no direct-provider, stale/refusal tests. | Regression risk. | | Ops/support dashboards | Teams need recovery. | Both | Policy denial, stale sync, stuck payment, webhook replay, support handoff. | Incidents are hard to resolve. | | Analytics/attribution | Sellers need value proof. | Grantex source + AgenticOrg contribution | Channel attribution, conversion, refusal reasons. | No ROI visibility. | | Docs and workflows | Teams need shared truth. | Both | This consolidated PRD, guides, runbooks, docs nav. | Drift and duplicated plans. | | CI/deploy safety | Docs-only changes should not deploy. | Both | Path guards and docs-only checks. | Planning merges create cloud/build side effects. | ## 16. Fast-Track Roadmap | Phase | Goal | Deliverables | Guardrail | | - | - | - | - | | 1. Consolidated PRD and docs alignment | One source of truth. | This PRD, docs nav, overview links, AgenticOrg pointers. Status: merged. | Docs-only. | | 2. Seller sandbox onboarding | Merchant can prepare without engineers. | AgenticOrg Seller Commerce Agent onboarding packet and dashboard path with Grantex authority review. Status: runtime implemented for Shopify merchant onboarding. | No live enablement. | | 3. Catalog connector MVP | Merchant data enters the OACP artifact path. | AgenticOrg Shopify read-only Admin GraphQL sync stores public-safe product, variant, image, price, inventory, status, and source evidence refs. | No automatic live publish. | | 4. Public-safe preview | Merchant sees what agents can see. | Catalog/profile preview, schema.org draft, readiness score, and protocol previews merged through C6J-C6M. | Fail-closed until approved. | | 5. Buyer web/mobile channel | First controllable buyer launch. | C6H and C6I merged for channel-neutral read-only session; hosted widget/app pending. | Read-only first. | | 6. ChatGPT/Claude MCP | Major AI chat surfaces. | Channel-neutral response model merged; remote MCP/app connector pending. | Respect platform write limits. | | 7. WhatsApp/Telegram | Messaging channels. | Channel-neutral response model merged; bot/webhook adapters pending. | Secrets outside Git. | | 8. Gemini channel | Gemini-powered buyer sessions. | Channel-neutral response model merged; function-calling wrapper pending. | Label native support accurately. | | 9. Inventory freshness and cart | Safe product selection. | Freshness TTL, stale refusal, exact-ID cart draft. | No checkout promise if stale. | | 10. Sandbox consent/checkout | Full rehearsal. | Consent, Passport, mock/provider-neutral checkout. | No live provider. | | 11. Order/fulfillment backbone | Operational paid flow. | Order, shipment, pickup/delivery, cancellation. | Required before broad checkout. | | 12. Return/refund request | Safe post-purchase support. | Request, eligibility, manual approval, audit. | No automatic refund execution. | | 13. Settlement/payout reporting | Seller finance visibility. | Reconciliation and payout read model. | No raw provider payloads. | | 14. OACP hardening | Platform interoperability. | C6W3-C6Z artifacts, adapter governance, commitment boundary, authority route, AgenticOrg cache/bridges, and purchase-preparation blockers are implemented. Real merchant launch still requires valid Shopify access, tenant allowlisting, channel approvals, and provider approvals. | No submission, publication, certification, or execution claims without explicit approval and evidence. | | 15. Controlled pilot | Minimal real launch. | One merchant, category, channel, provider, geography, rollback owner. | Separate explicit approval. | Current sequencing decision: 1. C6I-C6N are merged in dependency order across AgenticOrg and Grantex. 2. The open-protocol packaging draft is refreshed on the actual merged commits and remains internal, preview-only, non-publication, and non-certifying. 3. C6O conformance and C6P-C6Si connector dry-run/remediation foundations are the current runtime-hardening chain. 4. C6T starts the standards-publication preparation track for IETF and NIST without external submission, public publication, or certification claims. ## 17. Release Acceptance Criteria Before a real merchant pilot: * Merchant workspace, identity, category, and owners are approved. * Existing-system connector or approved manual maintenance path is healthy. * Catalog, price, tax, warranty, return, inventory, delivery, and support data pass category requirements. * AgenticOrg can only expose Grantex-authorized artifact capabilities and explicitly approved provider/connector verification states. * Buyer channel has auth, account/session linking, capability labels, consent handoff, fallback behavior, telemetry, and smoke evidence. * Checkout/payment creation remains blocked until Commerce Passport consent, policy, audit, idempotency, provider readiness, and rollback checks pass. * Paid flow has order, fulfillment, support, and return/refund handoff. * Live provider approval, webhook signature evidence, outage handling, and rollback are complete for any live checkout scope. * UCP/ACP/AP2/schema.org/A2A language is backed by implementation and tests, or clearly marked future/planned. * Docs-only changes do not trigger cloud auth, image build, image push, deploy, e2e, or indexing jobs. * No private merchant artifacts, secrets, raw payloads, tokens, JWTs, DB/Redis URLs, provider credentials, production config values, or concrete allowlist values are committed. ## 18. Stop Conditions Stop implementation or rollout if any of these occur: * real merchant identity is missing or unapproved; * private artifacts, customer data, secrets, raw payloads, tokens, JWTs, DB/Redis URLs, private keys, provider credentials, or production config values appear in Git or public docs; * AgenticOrg adds a direct provider payment-execution path or unapproved private merchant-system execution path. Approved provider capability verification and approved connector sync must stay separated from execution; * checkout can happen without consent, Commerce Passport, policy, idempotency, audit, and amount-cap checks; * catalog, price, tax, inventory, delivery, return, warranty, order, payment, or refund data is stale or unverifiable but presented as guaranteed; * paid checkout is enabled before order, fulfillment, support, return/refund handoff, and rollback are ready for the pilot scope; * synthetic/demo data is treated as production approval; * public discovery, production Commerce V1, checkout/payment creation, live payments, live Plural, or allowlist values are enabled without separate approval; * certification or compliance with UCP, ACP, AP2, A2A, MPP, schema.org, or a provider program is claimed before evidence exists; * docs-only changes trigger cloud build/push/deploy-adjacent work without an explicit policy decision. ## 19. Documentation And Workflow Updates | Surface | Requirement | | - | - | | This PRD | Canonical cross-repo product truth. | | Grantex overview | Link to this PRD as the source of truth. | | Grantex implementation PRD and end-to-end guide | Keep as supporting summaries, not divergent product truth. | | Grantex docs.json | Keep this PRD discoverable in Agentic Commerce V1 nav. | | AgenticOrg overview and implementation PRD | Point back to this PRD and preserve AgenticOrg-specific responsibilities. | | AgenticOrg developer guide | Keep payment-execution bans, channel adapter rules, OACP artifact consumption, and approved provider/connector verifier rules current. | | Merchant/operator guide | Explain seller one-time setup, approval gates, existing-system connectors, and rollback. | | Operations guide | Explain stale sync, webhook replay, payment reconciliation, support handoff, rollback, and incidents. | | Landing pages | Use public-safe copy: connect existing systems, preview agent-ready commerce, request approval. No live/certification claims. | | GitHub workflows | Preserve docs-only guard and treat workflow changes as non-docs-only. | ## 20. End-To-End Review Checklist This PRD has been reviewed against the requested coverage: * seller one-time setup covered; * buyer one-time setup covered; * regular transaction flow covered; * failure and recovery paths covered; * Grantex and AgenticOrg responsibilities covered; * existing merchant APIs and systems covered; * ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, and future channels covered as launch surfaces; * MCP, UCP-style, ACP-style, AP2-style, schema.org, and future agent surfaces covered without unsupported certification claims; * catalog, inventory, pricing, tax, delivery, order, fulfillment, support, return, refund, settlement, payout, audit, analytics, and ops gaps covered; * self-serve onboarding, scans, review gates, smoke evidence, and rollout gates covered; * safety boundaries, stop conditions, and production gates covered; * documentation/navigation/workflow coverage covered; * next implementation sequencing covered. Remaining decision for the team: continue the C6P-C6Si connector dry-run/remediation chain for real merchant readiness while C6T prepares public-safe IETF and NIST draft materials. The next implementation chain should avoid external submission, public protocol publication, certification claims, public discovery, and live checkout until explicit approvals and evidence exist. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Commerce V1 Developer Guide Source: https://docs.grantex.dev/guides/commerce-v1-developer-guide Grantex Commerce REST/MCP model, consent/passport sequence, idempotency, webhooks, Shopify live-pilot usage, and provider boundaries. # Commerce V1 Developer Guide This guide explains how developers should reason about the Grantex Commerce V1 contract. Production Commerce V1 is deployed for the approved Shopify pilot merchant `mch_shopify_mgx0n6_22`. This guide does not publish secret material and does not claim production Plural settlement while the available Plural credentials authenticate against UAT-compatible rails. ## API And MCP Surface Developers should use the OpenAPI contract in `docs/api/grantex-commerce-v1.openapi.yaml` plus the MCP tool surface exposed by the Commerce service. The current AgenticOrg integration uses these Grantex-only aliases: | Area | Tool or API responsibility | | - | - | | Merchant profile | Fetch merchant policy and status metadata. | | Catalog search | Search grounded merchant products. | | Catalog get item | Retrieve a specific product or variant. | | Inventory check | Confirm availability before cart or checkout. | | Cart create | Create a cart draft from grounded catalog IDs. | | Consent request | Ask for the user permission required for checkout. | | Consent exchange | Exchange granted consent for a Commerce Passport when a granted consent fixture exists. | | Payment intent | Create a provider-neutral payment intent with passport and policy checks. | | Checkout create | Create a checkout handoff through Grantex. | | Payment status | Poll Grantex payment status, not a provider directly. | ### Schema.org JSON-LD Preview Operators and owning merchants can inspect the C6J schema.org JSON-LD preview through: `GET /v1/commerce/merchants/{merchant_id}/schemaorg-jsonld-preview` The endpoint is tenant-scoped and sandbox-only. It returns a preview-only `https://schema.org` JSON-LD graph generated from canonical Grantex merchant, catalog, and readiness state. The adapter serializes only sanitized evidence for schema.org `Product`, `Offer`, `MerchantReturnPolicy`, and `OfferShippingDetails` fields. It does not expose exact internal tenant, merchant, product, variant, or SKU IDs. The response includes explicit non-enabling flags: * `schemaorg_publication_enabled: false` * `public_discovery_enabled: false` * `checkout_payment_enabled: false` * `live_provider_enabled: false` * `live_plural_enabled: false` * `production_allowlist_written: false` * `certification_claims: []` CommerceAgent and service callers are denied. Live merchants return a blocking error. Missing or unsafe catalog evidence is omitted and reported through `blockers`, `omitted_types`, and `remediation_items`; clients must not fill in missing seller, product, price, inventory, shipping, refund, launch, payment, or certification facts. ### UCP-Style Capability Profile Preview Operators and owning merchants can inspect the C6K UCP-style capability profile preview through: `GET /v1/commerce/merchants/{merchant_id}/ucp-capability-profile-preview` The endpoint is tenant-scoped and sandbox-only. It returns services, capabilities, transports, controls, blockers, and evidence summary fields from canonical Grantex merchant/catalog/readiness state. Every capability ID uses the Grantex-owned namespace: `dev.grantex.commerce.discovery.preview` The endpoint must not publish `dev.ucp.*`, must not claim UCP certification, and must not enable a public discovery route. Response controls explicitly keep public discovery, checkout/payment creation, live provider access, live Plural, production allowlist writes, UCP publication, and UCP certification disabled. Read-only discovery capabilities can be marked `preview_available` when Grantex has public-safe profile and catalog evidence. Checkout, payment, fulfillment, refund/return execution, provider credentials, and live paths remain `blocked` metadata only. ### ACP-Style Checkout Shape Preview Operators and owning merchants can inspect the C6L ACP-style checkout shape preview through: `GET /v1/commerce/merchants/{merchant_id}/acp-checkout-shape-preview` The endpoint is tenant-scoped and sandbox-only. It reads canonical Grantex cart, payment intent, consent, Commerce Passport, and active policy evidence, then returns ACP-style cart and checkout mapping objects as preview metadata. It does not call the payment provider and does not reuse the runtime checkout/payment creation handlers. The response includes explicit non-enabling controls: * `acp_publication_enabled: false` * `acp_certification_claim: none` * `public_checkout_enabled: false` * `payment_intent_creation_enabled: false` * `checkout_link_creation_enabled: false` * `provider_call_enabled_by_preview: false` * `live_payment_enabled_by_preview: false` * `live_plural_enabled_by_preview: false` Checkout mapping stays blocked when granted checkout consent, an unrevoked checkout Commerce Passport, an active policy, cart evidence, or sandbox payment intent evidence is missing. Unsupported fields such as public checkout URLs, provider payment references, provider metadata, live execution, fulfillment, and refunds/returns are represented as explicit blockers. ### AP2-Style Evidence Preview Operators and owning merchants can inspect the C6M AP2-style evidence preview through: `GET /v1/commerce/merchants/{merchant_id}/ap2-evidence-preview` The endpoint is tenant-scoped, sandbox-only, deterministic, and unsigned. It reads canonical Grantex evidence from Commerce Passport, consent record, policy decision, cart snapshot hash, amount cap, merchant state, agent identity, audit reference, and existing idempotency evidence. The response is protocol packaging evidence only. It is not AP2 certification, is not AP2 publication, is not a signed production mandate, does not submit to a payment network, and does not enable checkout/payment creation, live payment, live Plural, public discovery, provider calls, provider credentials, production configuration, or production allowlists. Required evidence gaps return `status: blocked` with explicit blockers. The preview exposes hashes and presence flags where possible, but does not expose tenant IDs, exact merchant IDs, passport JTIs, consent record IDs, audit event IDs, provider references, raw line items, raw payloads, JWTs, idempotency keys, secrets, or private configuration. ### Existing-System Connector Registry Operators and owning merchants can manage the C6N connector registry through: * `POST /v1/commerce/connectors` * `GET /v1/commerce/connectors` * `PATCH /v1/commerce/connectors/{connector_key}` The registry is a metadata-only foundation for existing merchant systems: manual maintenance, CSV, custom API, Shopify, WooCommerce, Magento, ERP, billing, OMS, WMS, logistics, CRM/support, and payment provider. It records connector type, display name, source-of-truth domains, source priority, sync status, health state, last sync timestamps, stale thresholds, webhook source references, and conflict blockers. The source precedence model covers `catalog`, `price`, `inventory`, `order`, `fulfillment`, `refund`, `settlement`, and `support`. List responses derive the primary connector per domain and include stale/conflict blockers instead of publishing stale facts as if they were current. This registry does not store credentials, raw payloads, provider metadata, private URLs, production config, or secrets. It does not call Shopify, WooCommerce, Magento, custom APIs, ERP, billing, OMS, WMS, logistics, CRM/support, payment providers, Plural, Stripe, or Pine. It also does not enable checkout/payment creation, fulfillment, refund execution, live payments, live Plural, public discovery, production Commerce V1, or protocol certification. AgenticOrg must never call merchant private systems directly. AgenticOrg reads only Grantex-grounded commerce APIs; Grantex owns connector metadata, source precedence, freshness, and blocker evidence. ## Auth And Caller Model Commerce callers are modeled as agents operating for a tenant, merchant, and user. A caller must be authenticated through an approved Grantex auth source in runtime configuration. Auth material is runtime-sensitive and must not be committed or printed. The important IDs are not equivalent: | Identifier | Purpose | | - | - | | Tenant ID | Owns isolation and policy boundaries. | | Merchant ID | Owns catalog, provider configuration status, and merchant policy. | | Agent ID | Identifies the delegated agent allowed to use commerce tools. | | User/principal | The person whose consent controls checkout. | | Commerce Passport | Scoped, usable runtime material created after approved consent or smoke fixture export. | Synthetic tenant, merchant, user, product, and variant IDs may appear in evidence when they are non-production fixtures. Usable passports, bearer tokens, idempotency key values, secrets, and provider credentials remain sensitive. ## Consent-First Checkout Sequence ```mermaid theme={null} sequenceDiagram participant A as Agent participant G as Grantex A->>G: merchant.get_profile A->>G: catalog.search A->>G: catalog.get_item A->>G: inventory.check A->>G: cart.create A->>G: consent.request G-->>A: consent request ID or blocker A->>G: consent.exchange when consent is granted G-->>A: Commerce Passport or fail-safe error A->>G: payment.create_intent with allowed fields only A->>G: checkout.create A->>G: payment.get_status ``` During fixture-backed smoke runs, AgenticOrg may already have a checkout passport exported by the Grantex smoke runner. In that case, AgenticOrg evidence treats `consent_exchange` as skipped only when the blocker is exactly `preexported_checkout_passport_without_granted_consent_fixture`. ## Payment Intent Shape Payment intent calls must send only fields supported by the Grantex Commerce contract. Local fixture guardrail data, such as amount cap metadata used by AgenticOrg preflight checks, must not be forwarded to Grantex as arbitrary top-level request fields. Keep idempotency values out of docs and logs. Evidence may record that an idempotency mechanism was used, but never the value. ## Webhooks And Replay Grantex owns provider webhook ingestion, verification, replay controls, and reconciliation status. Current public smoke evidence uses the mock-provider path only. The Plural hosted-checkout adapter verifies the documented HMAC webhook scheme behind the provider abstraction, but failed webhook replay remains operator-only and mock-provider-only until non-mock replay retention and operator controls are separately approved. Replay safety rules: * do not log raw provider payloads; * do not store or expose webhook secrets in documentation; * preserve original signature metadata only in protected runtime storage; * replay only through operator-approved controls; * treat live provider replay as blocked until provider/legal/ops gates pass. ## Error Handling Commerce clients should treat validation and policy failures as explicit outcomes, not retry storms. Important classes include: | Class | Expected handling | | - | - | | `validation_failed` | Fix request shape or unsupported field usage. | | `consent_not_granted` | Do not proceed without granted consent or an approved checkout passport fixture. | | `consent_denied` | Stop the checkout path. | | Amount-cap or policy breach | Fail safely before payment/provider work. | | Disabled merchant or untrusted agent | Stop and surface an operator-safe blocker. | ## Shopify Live Pilot And Sandbox Use mock-provider/internal sandbox flows for local development, tests, and feature work. The production live pilot uses the approved Shopify merchant `mch_shopify_mgx0n6_22` from `mgx0n6-22.myshopify.com`, with five imported products and five variants available through Grantex catalog search. The Plural adapter can be exercised only with stored credentials and explicit runtime flags. Current production credentials authenticate against Plural UAT-compatible rails, so developer docs must not claim production Plural settlement or provider certification. Approved temporary Option A smoke used isolated Cloud Run, Cloud SQL, Redis, smoke secrets, and mock provider resources that were deleted immediately after evidence capture. Developers should use: * `docs/guides/commerce-v1-repeatable-option-a-smoke-workflow.md` for the approval-gated smoke path; * `docs/examples/commerce-option-a-smoke.runbook.json` for runbook fields; * the internal Option A smoke evidence record (operator-internal, kept in `docs/internal/commerce-v1/` and available to authorized reviewers via `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`); * the internal production-discovery readiness record (same access path) for the current posture. ## Live-Readiness Guard The live commerce guard exposes a machine-readable readiness snapshot for operators and tests. The snapshot reports only booleans, requirement keys, env names, and blocker strings. It must not contain tokens, passports, credentials, provider payloads, idempotency values, DB URLs, Redis URLs, or raw webhook material. Live checkout and live Plural still require the master live-mode flag, the Plural live flag, and every readiness acknowledgement. The approved Shopify pilot has those acknowledgements set in production. When any acknowledgement is absent in another environment or future rollout, live provider paths fail closed with `live_readiness_blocked`. Important acknowledgement names include `COMMERCE_LIVE_LEGAL_APPROVED`, `COMMERCE_LIVE_PROVIDER_CONTRACT_CONFIRMED`, `COMMERCE_LIVE_PLURAL_SANDBOX_E2E_PASSED`, `COMMERCE_LIVE_PLURAL_WEBHOOK_SIGNATURE_CONFIRMED`, `COMMERCE_LIVE_INDIA_RESIDENCY_CONFIRMED`, `COMMERCE_LIVE_FINAL_USER_CONFIRMATION_APPROVED`, `COMMERCE_LIVE_PILOT_MERCHANT_APPROVED`, `COMMERCE_LIVE_HOSTED_OACP_E2E_PASSED`, `COMMERCE_LIVE_SECRETS_REVIEWED`, `COMMERCE_LIVE_AUDIT_APPEND_ONLY_VERIFIED`, `COMMERCE_LIVE_OPERATOR_RUNBOOK_APPROVED`, and `COMMERCE_LIVE_ROLLBACK_OWNER_ASSIGNED`. This guard remains the release boundary for future merchants and providers. Public docs, demos, and AgenticOrg handoffs must distinguish the approved Shopify live pilot from broader merchant self-service or provider settlement claims. ## Controlled Until Future Approval * Broad self-serve merchant production onboarding. * External certification or standards conformance claims. * Production Plural settlement claims while only UAT-compatible credentials are validated. * Direct provider calls from AgenticOrg commerce. * Public docs that imply AP2, UCP, ACP, Plural production settlement, payment-provider certification, or external pilot certification beyond the approved Shopify live pilot. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # End-To-End Agentic Commerce Flow Source: https://docs.grantex.dev/guides/commerce-v1-end-to-end-agentic-commerce-flow Plain-language buyer and seller flow for OACP agentic commerce with AgenticOrg agents and Grantex trust authority. # End-To-End Agentic Commerce Flow This guide explains how agentic commerce should work from the perspective of both sellers and buyers. It is documentation only. It does not enable production Commerce V1, public discovery, checkout/payment creation, live payments, live provider rails, merchant approval, or a production allowlist. ## Current OACP Runtime Closure Boundary The launch-closure runtime path is narrower than the full future commerce journey below. The current OACP runtime proof is: AgenticOrg creates a Seller Commerce Agent onboarding packet, performs read-only Shopify Admin GraphQL sync when merchant credentials are valid, sends a Grantex authority request, receives internal signed OACP artifacts, caches them, and answers non-binding buyer questions with source and freshness labels. Checkout, payment, order, mandate, refund, return, shipment, inventory hold, public discovery publication, and live provider execution remain outside this runtime proof. Any cart, checkout, consent, Commerce Passport, order, return, refund, fulfillment, or payment-control wording in this guide describes separate future or Grantex Commerce payment-control pilot scope. It is not evidence that OACP runtime artifacts enable transaction execution. For the consolidated PRD that governs this flow, read `docs/guides/commerce-v1-agentic-commerce-prd.md`. ## One-Sentence Architecture Sellers start in AgenticOrg Seller Commerce Agent and connect existing merchant systems through approved connector custody. Grantex validates public-safe facts into signed OACP artifacts and policy decisions. Buyers start from their preferred agent or chat surface. Provider and fintech rails own mandate and payment execution. For the C6Z live-runtime path, AgenticOrg calls Grantex through `POST /v1/commerce/oacp/c6z/authority-requests`. Production should use the dedicated AgenticOrg C6Z authority service token, limited to that route and to tenant ids configured in `COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS`. Grantex issues the full C6Z artifact set: merchant profile, seller agent card, connector evidence, catalog snapshot, offer/price snapshot, inventory snapshot, policy scope, public-discovery state, mandate capability, protocol adapter, and authority-request status. ```mermaid theme={null} flowchart TB seller["Seller / merchant"] selleragent["AgenticOrg Seller Commerce Agent"] systems["Existing systems: storefront, catalog, ERP, PIM, inventory, OMS, logistics, payment, support"] grantex["Grantex OACP authority"] publish["Signed OACP artifacts and adapter previews"] agenticorg["AgenticOrg buyer-agent runtime and cache"] channels["ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, future channels"] provider["Provider/fintech mandate and payment rails"] buyer["Buyer"] seller --> selleragent selleragent --> systems selleragent --> grantex grantex --> publish publish --> agenticorg agenticorg --> channels channels --> buyer agenticorg -. "approved capability verification" .-> provider ``` ## What Sellers Do One Time The OACP runtime seller journey starts in AgenticOrg Seller Commerce Agent. The seller can prepare an onboarding packet and connector evidence; Grantex controls artifact authority and policy decisions. Merchant approval, public launch, and transaction execution require separate approvals outside this runtime proof. | Step | Seller action | Grantex responsibility | Launch gate | | - | - | - | - | | 1. Create workspace | Sign up, create tenant, invite owner/admin users. | Create tenant boundary, merchant shell, roles, sandbox/live split. | Tenant isolation and owner audit event. | | 2. Complete merchant profile | Enter legal name, display name, category, country, currency, support contact, business policies. | Store canonical merchant profile and public-safe display fields separately from private artifacts. | No public discovery until profile is reviewed. | | 3. Verify business | Provide legal/compliance artifacts in a private evidence system outside Git. | Store only non-secret evidence references and review state. | KYB/KYC/legal/compliance approval required for live. | | 4. Choose category preset | Select home goods, electronics, fashion, grocery, pharmacy, services, B2B, or another preset. | Apply required field checklist, policy defaults, tax/return/warranty expectations. | Category-specific critical fields must pass. | | 5. Connect existing systems | Use AgenticOrg Seller Commerce Agent, merchant-owned connectors, external integration providers, or CSV/API to connect Shopify, WooCommerce, Magento, custom storefront, ERP/PIM, WMS, OMS, logistics, payment provider, CRM/support, or upload CSV/API data. | Validate public-safe facts, source/freshness, and non-sensitive evidence refs into OACP artifacts. | Connector health and credential isolation required; Grantex does not need raw connector credentials by default. | | 6. Define source of truth | Decide whether storefront, ERP/PIM, WMS, OMS, or another system wins for each data type. | Record source precedence, freshness timestamps, sync history, and conflict rules. | No silent overwrite or stale publish. | | 7. Prepare catalog | Fix titles, descriptions, images, brand, variants, SKU, price, tax, warranty, return summary, category, availability. | Validate required fields, generate public-safe preview, schema.org draft, MCP/native output. | Missing critical catalog data blocks launch. | | 8. Prepare inventory and delivery | Configure stock state, freshness TTL, delivery/pickup promises, shipping fees, serviceability, logistics source. | Block guarantees when stock or delivery data is stale, unknown, or unsupported. | Checkout blocked when inventory/delivery evidence is insufficient. | | 9. Configure agent permissions | Choose browse, compare, cart draft, checkout consent request, order status, support handoff, return/refund request. | Convert choices into merchant policy, channel capabilities, scopes, amount caps, and audit rules. | Each capability requires explicit approval. | | 10. Configure mandate/payment path | Configure provider-owned mandate or payment rails outside Grantex; request live rail only after legal/security/ops approval. | Validate non-sensitive capability/evidence refs only where policy or artifact lineage requires them. | Live provider remains blocked until approved. | | 11. Review buyer preview | See exactly what agents and buyers may see. | Render public payload preview and blocked-path warnings. | Product wording and security approval required. | | 12. Run scans | Run secret/private-detail, overclaim, merchant-ID/name, synthetic-ID, config/allowlist, stale-data, and policy scans. | Produce redacted evidence and blocker codes. | Clean scans required for intake readiness. | | 13. Assign owners | Assign merchant owner, legal, product wording, security, ops/support, backup/RPO, rollback, smoke, evidence retention, AgenticOrg dependency owner. | Record non-secret owner references and approval state. | Missing owners block rollout. | | 14. Rehearse launch | Run sandbox/demo buyer journey with blocked checkout/live paths clearly labeled. | Produce smoke evidence and rollback checklist. | Smoke evidence required before proposal. | | 15. Request rollout | Request smallest approved surface, normally read-only discovery first. | Keep fail-closed until approval and rollback readiness exist. | Separate rollout approval required. | ## What Buyers Do One Time The buyer should not need to know how merchant systems work. Their setup should look like normal account linking and permission control. | Step | Buyer action | AgenticOrg responsibility | Grantex responsibility | | - | - | - | - | | 1. Choose a channel | Open ChatGPT, Claude, Gemini, WhatsApp, Telegram, web/mobile, or another approved surface. | Start the matching channel adapter. | Publish approved capabilities for that channel. | | 2. Link or sign in | Connect AgenticOrg identity if needed. | Create or resume buyer-agent session and bind it to channel identity. | Do not expose private merchant/provider data. | | 3. Set preferences | Provide locale, currency, delivery region, notification path, accessibility preferences, and optional spend comfort. | Use preferences for conversation and handoff only. | Use preferences for policy checks and consent copy where needed. | | 4. Understand capabilities | See what the agent can do: browse, compare, draft cart, request checkout, read order status, or support handoff. | Show channel-specific action labels and limitations. | Return approved capability state and blocker reasons. | | 5. Consent when needed | Approve or deny payment-affecting actions in the Grantex consent flow. | Never bypass consent. | Issue scoped Commerce Passport only after consent and policy pass. | | 6. Manage history/revocation | Ask what happened or revoke permissions. | Show redacted session state and safe summaries. | Own revocation, audit evidence, and protected action history. | Buyer setup is not a standing payment approval. Commitment-bound actions must still pass fresh consent, merchant policy, amount cap, idempotency, source/freshness, provider-owned mandate/payment capability, and audit evidence. ## Regular Transaction Flow ```mermaid theme={null} sequenceDiagram participant B as Buyer participant C as Buyer chat channel participant A as AgenticOrg participant G as Grantex participant S as Merchant system or connector participant P as Provider/fintech rail B->>C: Ask to discover, compare, or prepare C->>A: Start or resume buyer-agent session A->>A: Use cached OACP artifacts if valid for TTL/risk A->>G: Refresh/verify artifacts when missing, stale, revoked, or high risk G-->>A: Signed artifacts or blocker codes A-->>B: Grounded options, source/freshness, caveats B->>A: Request commitment-bound action A->>G: Boundary/envelope/reconciliation/eligibility/dry-run checks G-->>A: Non-executing eligibility or blocker A->>S: Request source confirmation via approved connector handoff S-->>A: Confirmation evidence or refusal A->>P: Verify provider-owned mandate capability when approved P-->>A: Capability evidence or refusal A-->>B: Prepared handoff, consent request, or safe refusal ``` ## Happy Path In Plain English 1. Buyer asks an agent to help find, compare, or prepare. 2. AgenticOrg starts the buyer-agent session in the buyer's chosen channel. 3. AgenticOrg reads valid cached OACP artifacts when TTL, revocation, and risk rules allow. 4. AgenticOrg asks Grantex to refresh or verify artifacts when facts are stale, missing, revoked, ambiguous, or high risk. 5. AgenticOrg explains grounded facts and warns about unknown or stale information. 6. If the buyer requests a commitment-bound action, AgenticOrg uses C6W5-C6W9 style boundary, envelope, reconciliation, eligibility, and dry-run checks. 7. Merchant-system confirmation happens through approved connector handoff or merchant-owned systems. 8. Provider-owned mandate/payment capability verification stays with the provider/fintech rail and may be verified directly by AgenticOrg where approved. 9. Current OACP work stops at prepared handoff/dry-run. It does not create orders, holds, checkout/payment, mandates, refunds, returns, shipments, settlement, or payout actions. ## Exception Flows | Exception | Buyer experience | Seller experience | System behavior | | - | - | - | - | | Merchant not approved | Agent says the merchant is not available for agentic commerce yet. | Seller sees missing approval state. | Grantex remains fail-closed. | | Channel is read-only | Buyer can browse but gets a safe checkout handoff or "not supported here" message. | Seller sees channel capability limit. | AgenticOrg cannot pretend write actions are available. | | Product data incomplete | Agent asks clarifying questions or says the product is unavailable. | Seller sees missing required fields. | Grantex blocks or lowers readiness score. | | Price changed | Buyer sees updated total and must confirm again. | Seller sees audit and source system timestamp. | Grantex recalculates and invalidates stale cart totals. | | Inventory stale | Buyer gets warning or checkout refusal. | Seller sees stale inventory blocker. | Grantex blocks promises and may block checkout. | | Consent denied | Checkout stops. | Seller sees no payment attempt. | Grantex records denial and no passport is issued. | | Policy denied | Buyer sees safe reason such as "this purchase is not allowed by merchant policy." | Seller sees policy blocker code. | Grantex writes audit without leaking private policy. | | Payment pending/failed | Buyer sees pending/failed status and next safe step. | Seller sees reconciliation status. | Provider/fintech rail remains payment authority; Grantex uses non-sensitive evidence refs where required. | | Order/fulfillment missing | Buyer cannot get a delivery promise. | Seller sees order/fulfillment gap. | Paid launch should be blocked until operational path exists. | | Refund/return requested | Buyer gets manual support handoff now; future request/status later. | Seller handles refund in approved system. | No automatic refund execution until separately approved. | ## Source Of Truth Rules | Data or action | Source of truth | | - | - | | Merchant identity and approval | Grantex | | Product and variant data | Merchant systems, validated into Grantex/OACP public-safe artifacts | | Price, tax, discount, EMI, offer | Merchant systems/provider rails, validated into OACP source/freshness artifacts | | Inventory and delivery promise | Merchant WMS/OMS/logistics, validated into OACP source/freshness artifacts | | Buyer conversation | AgenticOrg channel/session layer | | Consent and Commerce Passport | Grantex | | Payment mandate and execution | Provider/fintech rail | | Provider capability evidence refs | Provider/fintech rail, optionally referenced by Grantex/OACP policy | | Order, fulfillment, return, refund, settlement | Merchant OMS/support/provider systems, exposed through approved artifacts only | | Buyer-facing explanation | AgenticOrg, grounded only in OACP artifacts and approved source evidence | | Audit and protected action evidence | Grantex artifact rules plus AgenticOrg redacted session evidence where relevant | ## Production Gates Do not go live unless all required gates pass: * Merchant identity and category are approved. * Existing-system connectors or manual maintenance process are healthy. * Catalog, price, tax, warranty, return, inventory, and delivery fields meet category requirements. * Agent channel capability is approved for that merchant. * Buyer consent and Commerce Passport flow is verified. * Checkout/payment path is approved for sandbox or live as appropriate. * Order, fulfillment, support, return, and refund handoff exist for paid flows. * Legal, product, security, ops, rollback, smoke, and evidence owners are set. * Rollback is rehearsed and can disable the channel or merchant capability. * No private merchant artifacts, secrets, raw payloads, provider credentials, or production config values are committed to Git. ## Fast-Track Build Order 1. Seller self-serve sandbox workspace and checklist. 2. CSV/manual catalog plus one priority storefront connector. 3. Public-safe preview and read-only agent discovery. 4. Buyer web/mobile launch surface. 5. ChatGPT/Claude remote MCP channel. 6. WhatsApp/Telegram bot adapters. 7. Gemini function-calling or hosted wrapper path. 8. Inventory freshness, stale refusal, and safe cart draft. 9. Sandbox consent, Commerce Passport, and checkout rehearsal. 10. Order and fulfillment backbone. 11. Return/refund request workflow. 12. Settlement/payout reporting. 13. UCP/ACP/schema.org/AP2-compatible adapter hardening. 14. One real merchant, one category, one provider, one geography, one rollback owner pilot proposal. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Merchant Guide To Agentic Commerce Source: https://docs.grantex.dev/guides/commerce-v1-merchant-agentic-commerce-user-guide A detailed merchant-facing guide for using Grantex Commerce V1 safely with AI agents, read-only discovery, consent, approvals, launch readiness, operations, and support workflows. # Merchant Guide To Agentic Commerce This guide explains how a merchant can prepare for and operate agentic commerce with Grantex Commerce V1. It is written for merchant business owners, ecommerce operators, product teams, compliance reviewers, security reviewers, support teams, and launch owners. This document is guidance only. It describes the approval model now used for the approved Shopify live pilot and for future merchants. It does not approve a new merchant, a new allowlist value, a new provider settlement claim, or a broader self-serve production rollout. A merchant can move toward launch only through separate human approval, technical validation, legal/compliance review, security review, operations readiness, rollback readiness, and production rollout approval. ## What Agentic Commerce Means Agentic commerce lets AI agents help customers discover products, understand merchant policies, assemble carts, and request permission for commerce actions. The agent is not the payment processor and does not receive unrestricted access to the merchant account. Grantex acts as the commerce control plane that keeps identity, consent, merchant policy, audit evidence, amount caps, and provider boundaries under reviewable controls. The merchant experience has three layers: | Layer | Merchant meaning | Grantex responsibility | | - | - | - | | Discovery | Approved agents can read public-safe merchant and catalog metadata. | Publish only approved read-only metadata through gated discovery. | | Consent | A customer must approve sensitive or payment-adjacent actions. | Record consent, issue scoped Commerce Passports, enforce limits, and audit outcomes. | | Operations | Merchant teams review activity, incidents, support cases, and rollback. | Provide evidence, logs, policy checks, fail-closed controls, and operator workflows. | Agentic commerce should feel like a controlled extension of the merchant's existing ecommerce and support operations, not like handing an autonomous agent direct access to payments or private systems. ## Current Safety Posture Grantex Commerce V1 documentation separates local/demo/smoke work from the approved Shopify live pilot and from future merchant production rollout. | Area | Merchant-facing status | | - | - | | Synthetic demo packets | Safe for demos and education only. They are not merchant approval. | | Approved Shopify live pilot | Live for `mch_shopify_mgx0n6_22` from `mgx0n6-22.myshopify.com`. | | Future real merchant onboarding | Requires human artifacts and approval references outside public docs. | | Read-only discovery | Live for the approved pilot; future merchants remain fail-closed until separately approved. | | Checkout/payment creation | Enabled only through the approved Grantex control-plane path and still requires consent, policy, passport, and idempotency controls. | | Plural adapter | Configured for the pilot with UAT-compatible credentials; production Plural settlement is not claimed. | | AgenticOrg public commerce discovery | Gated by Grantex readiness and separate AgenticOrg approval for each merchant/channel. | If any instruction conflicts with these safety controls, stop and escalate to the Grantex launch owner before proceeding. ## Who Should Use This Guide Use this guide if you are one of the following: | Role | Responsibilities | | - | - | | Merchant owner | Approves the merchant identity, public display name, category, and business intent. | | Ecommerce operator | Provides catalog, policy, fulfillment, support, and return information. | | Legal/compliance reviewer | Confirms public metadata is safe and claims are accurate. | | Product wording reviewer | Reviews what agents and customers will see. | | Security reviewer | Confirms no secrets, credentials, private data, or unsafe scopes are exposed. | | Ops/on-call/support owner | Owns production monitoring, support escalation, and incident response. | | Backup/RPO reviewer | Confirms recovery expectations and data retention boundaries. | | Rollback owner | Owns disabling discovery and clearing approved rollout configuration if needed. | | Read-only smoke owner | Owns controlled validation after approval, before broader exposure. | | Evidence retention owner | Owns redacted evidence, audit summaries, and retention policy. | | AgenticOrg dependency owner | Reviews downstream AgenticOrg gating after Grantex readiness. | No single role should approve launch alone. Agentic commerce depends on shared business, technical, compliance, and operational readiness. ## Merchant Journey Overview The standard merchant journey has ten stages: 1. Create a private onboarding workspace. 2. Prepare a public-safe merchant profile draft. 3. Prepare a public payload preview. 4. Store private approval artifacts outside the repository. 5. Attach non-secret artifact references. 6. Assign required owners. 7. Run validation scans. 8. Complete human review gates. 9. Prepare a separate rollout proposal. 10. Launch only after explicit rollout approval and smoke validation. The process is intentionally not automatic. A complete intake packet can make a merchant intake-ready, but it does not by itself approve production rollout. ## Key Concepts ### Merchant Identity Merchant identity is the public-safe metadata that tells agents and customers which merchant they are interacting with. Required public-safe identity fields usually include: | Field | Purpose | Safety rule | | - | - | - | | Approved public merchant ID | Stable identifier used in approved discovery surfaces. | Must be human-approved before production use. | | Approved display name | Public merchant name shown to agents and users. | Must be reviewed for accuracy and rights to use. | | Approved category | High-level category such as home goods, apparel, grocery, or services. | Must not imply unsupported regulated activity. | | Discovery description | Short public description of what the merchant offers. | Must avoid certification, payment-readiness, or unsupported capability claims. | | Country/currency posture | Public operating context, when approved. | Must not expose private tax, bank, or entity details. | | Support posture | How support should be escalated. | Store private contacts outside the repo. | Synthetic or demo merchant IDs must never be proposed as production allowlist candidates. ### Public Payload Preview The public payload preview is the exact read-only metadata that could later be served to agent discovery clients after approval. It lets reviewers inspect what would become public before any production configuration is changed. A safe preview includes: * merchant ID; * display name; * category; * discovery description; * issuer or JWKS reference; * supported read-only capabilities; * explicitly blocked capabilities; * cache/header/rate-limit posture; * no provider credentials; * no private contracts; * no private contacts; * no customer data; * no live-payment or certification claims. The preview must be reviewed before launch. It should be treated as public metadata, even before it is published. ### Schema.org JSON-LD Preview The schema.org JSON-LD preview shows how the same public-safe Grantex merchant and catalog evidence could be shaped as schema.org `Product`, `Offer`, `MerchantReturnPolicy`, and `OfferShippingDetails` objects for later review. The preview is not publication. It is not schema.org certification. It does not enable public discovery, checkout, payment creation, live payments, live Plural, provider access, production configuration, or allowlists. The preview includes fields only when Grantex has safe evidence for them. For example, a product name, description, image, brand, offer price, availability, or return policy can appear only after the corresponding public-safe catalog field exists. Missing shipping or return evidence appears as a blocker or omitted type rather than guessed text. Reviewers should reject the preview if it contains private merchant data, internal IDs, provider metadata, raw payloads, secrets, production claims, allowlist values, payment claims, launch claims, or certification claims. ### UCP-Style Capability Profile Preview The UCP-style capability profile preview shows how Grantex can describe a merchant's agent-commerce capabilities in a standard-looking capability shape without publishing or certifying those capabilities. This preview uses only the Grantex-owned namespace `dev.grantex.commerce.discovery.preview`. It does not publish `dev.ucp.*` capabilities, does not claim UCP certification, and does not make the merchant publicly discoverable. The preview can show read-only discovery capabilities such as merchant profile, catalog search, catalog item read, and availability read when evidence exists. It also shows blocked capabilities such as checkout, payment, fulfillment, refund/return execution, live provider access, and production allowlist writes. Merchants and reviewers should treat the preview as review metadata only. It does not enable public discovery, production Commerce V1, checkout/payment creation, live payments, live Plural, provider credentials, production config, or allowlists. ### ACP-Style Checkout Shape Preview The ACP-style checkout shape preview shows how Grantex cart, checkout, consent, passport, policy, and payment-intent evidence could map into future cart and checkout protocol objects. It is a review aid, not checkout publication. This preview is sandbox-only and non-enabling. It does not claim ACP certification, publish ACP capabilities, create payment intents, create checkout links, expose checkout URLs, call providers, expose provider metadata, enable live payments, enable live Plural, write production configuration, or set allowlists. The preview refuses to mark checkout mapping as available when required evidence is missing. Missing granted checkout consent, an unrevoked checkout Commerce Passport, an active policy, cart evidence, or payment-intent foundation evidence appears as a blocker with remediation rather than being guessed. Unsupported fields such as public checkout URLs, provider payment references, live provider execution, fulfillment execution, and refund/return execution remain explicit blockers. A merchant should treat this as protocol packaging evidence only, not approval to accept live checkout or payment traffic. ### AP2-Style Evidence Preview The AP2-style evidence preview shows whether Grantex has enough sandbox evidence to describe a future mandate-style package for review. It is based on Commerce Passport, consent, policy decision, cart hash, amount cap, merchant state, agent identity, audit reference, and idempotency evidence. This preview is deterministic and unsigned. It is not AP2 certification, does not publish AP2 capabilities, does not create or sign a production mandate, does not submit anything to a payment network, and does not approve live checkout or payment traffic. The preview remains non-enabling. It does not enable public discovery, production Commerce V1, checkout/payment creation, live payments, live Plural, provider calls, provider credentials, production configuration, or allowlists. If required evidence is missing, Grantex returns blockers and remediation instead of filling gaps with guesses. The preview may show safe hashes and presence flags, but it does not expose tenant IDs, exact merchant IDs, passport JTIs, consent IDs, audit IDs, provider references, raw cart details, raw payloads, secrets, or private configuration. ### Existing-System Connectors Many merchants already run commerce through store platforms and back-office systems. C6N adds a safe connector registry so a merchant can declare which systems are the source of truth for catalog, price, inventory, order, fulfillment, refund, settlement, and support data. Supported registry types are manual maintenance, CSV, custom API, Shopify, WooCommerce, Magento, ERP, billing, OMS, WMS, logistics, CRM/support, and payment provider. The first safe runtime paths remain the existing Grantex manual, CSV, and API catalog paths. Other systems can be declared as metadata with blockers until a separately approved connector exists. The registry records sync status, health state, last sync timestamp, stale threshold, and conflict blockers. If data is stale, conflicting, missing, or unsupported, Grantex shows the blocker instead of letting an agent promise stock, price, delivery, fulfillment, refund, settlement, or support facts. The connector registry does not store real credentials, does not call merchant systems, does not call payment providers, does not enable checkout/payment creation, does not enable live payments or live Plural, does not enable public discovery, and does not approve production Commerce V1. AgenticOrg never calls Shopify, WooCommerce, Magento, ERP, OMS, WMS, logistics, CRM/support, payment providers, or merchant private APIs directly. Buyer agents must use Grantex-grounded responses and Grantex-owned blockers. ### Read-Only Discovery Read-only discovery allows approved agent clients to learn that a merchant exists and inspect safe metadata. It does not allow an agent to create checkout, initiate payment, access provider credentials, or claim the merchant is certified for live commerce. Read-only discovery may include: * merchant profile read; * catalog summary read; * policy summary read; * public support posture; * safe issuer/JWKS references; * safe cache and rate-limit posture. Read-only discovery must not include: * live provider credentials; * payment processor secrets; * private approval records; * private contracts; * private contacts; * pricing terms not approved for public disclosure; * customer data; * raw payloads; * private keys; * bearer tokens; * Commerce Passports; * idempotency key values. ### Consent And Commerce Passports Consent is the user approval step for sensitive or payment-adjacent actions. A Commerce Passport is scoped runtime material that proves a specific consent or authorization context. It must never be committed to docs, pasted in tickets, or shared in screenshots. Merchants should understand these rules: * agents can suggest actions, but the system must enforce consent and policy; * passports are scoped, temporary, and sensitive; * amount caps and merchant policy must be checked before payment-adjacent work; * missing, expired, revoked, or denied consent must fail closed; * user-facing wording must clearly describe what is being authorized. ### Checkout And Payment Boundaries This guide does not enable checkout or live payment creation. Checkout and payments require separate approval, provider readiness, legal/compliance review, security review, operations readiness, and rollback planning. Blocked until separate approval: * checkout creation; * payment intent creation; * live payment processing; * live Plural access; * provider credential access; * direct Stripe, Plural, Pine, or provider credential paths from an agent; * payment readiness or certification claims. ### AgenticOrg Dependency AgenticOrg public commerce discovery remains gated until Grantex readiness is complete and separately approved. A merchant should not assume that Grantex intake approval automatically exposes commerce metadata through AgenticOrg. The sequence is: 1. Grantex merchant intake is completed. 2. Grantex read-only rollout proposal is approved separately. 3. Grantex read-only smoke validation passes. 4. AgenticOrg dependency review is completed. 5. AgenticOrg public discovery is separately approved. ## Merchant Onboarding Prerequisites Before starting, the merchant should gather public-safe information and private approval artifacts in the right places. Public-safe information may later be summarized in repository docs or approval packets: * approved public merchant ID or non-secret reference authorizing it; * approved public display name; * approved category; * approved discovery description; * public-safe payload preview; * non-secret private approval references; * owner role labels, if approved for repo storage; * validation scan summaries. Private artifacts must stay outside public repositories: * signed contracts; * private approval records; * private contacts; * pricing terms; * customer data; * tax identifiers; * bank details; * provider credentials; * raw provider payloads; * secrets, tokens, JWTs, passports, private keys, DB URLs, or Redis URLs. ## Step 1: Create A Private Onboarding Workspace The merchant or Grantex operator creates a private workspace for artifact collection. This may be an approved internal system, document repository, ticket system, or compliance workspace. It should have access control, audit history, and retention rules. The private workspace stores: * approval artifacts; * reviewer decisions; * private contacts; * contract references; * support escalation information; * rollback owner details; * operational runbooks that include private data. The public repository stores only public-safe summaries or non-secret references. ## Step 2: Draft The Merchant Profile The merchant drafts a profile that agents may eventually read. Recommended profile fields: | Field | Example guidance | | - | - | | Display name | Use the approved public merchant name. | | Category | Choose a plain category that matches the actual catalog. | | Discovery description | Describe what customers can discover, not what is production-certified. | | Geography | Include only public-safe operating regions. | | Currency posture | Include only public-safe supported currency metadata. | | Support posture | Use role-based escalation labels rather than private contact details. | Review the profile for: * accuracy; * trademark or naming concerns; * overclaims; * unsupported regulated activities; * private information; * confusing payment or checkout promises. ## Step 3: Prepare The Public Payload Preview The payload preview should show what the agent discovery layer would expose. Keep it small, explicit, and reviewable. Suggested structure: ```json theme={null} { "merchant_id": "", "display_name": "", "category": "", "discovery_description": "", "issuer_reference": "", "jwks_uri_reference": "", "supported_read_only_capabilities": [ "merchant_profile_read", "catalog_summary_read", "policy_summary_read" ], "blocked_capabilities": [ "checkout_creation", "payment_intent_creation", "live_payment_processing", "live_plural_access", "provider_credential_access" ] } ``` Use placeholders until approved values exist. Do not paste production secrets, private URLs, provider credentials, raw payloads, passports, or customer data. ## Step 4: Attach Non-Secret Artifact References Each approval artifact should have a non-secret reference. A reference might be an internal ticket ID, approval system ID, or controlled document reference that does not expose the private artifact body. Required approval references: | Approval | Required question | | - | - | | Merchant owner | Is this the correct merchant identity and business intent? | | Legal/compliance | Is the public metadata safe, accurate, and legally acceptable? | | Product wording | Is the wording clear and free of unsupported claims? | | Security | Are secrets, private data, and credential paths excluded? | | Ops/on-call/support | Is support coverage and escalation ready? | | Backup/RPO | Are recovery and retention expectations clear? | | AgenticOrg dependency | Is downstream exposure still gated and separately reviewed? | | Rollback owner | Who can disable discovery and coordinate rollback? | | Read-only smoke owner | Who owns validation after approval? | | Evidence retention owner | Who owns redacted evidence retention? | Placeholders are not approval. A pending or placeholder reference means the packet is not ready. ## Step 5: Assign Owners Owner assignments should use public-safe role labels in repository docs. Private names, emails, phone numbers, and escalation details must remain in private systems. Minimum owner roles: * merchant owner; * legal/compliance reviewer; * product wording reviewer; * security reviewer; * operations/support owner; * backup/RPO reviewer; * rollback owner; * read-only smoke owner; * evidence retention owner; * AgenticOrg dependency owner. If any required owner is missing, the intake state remains not ready. ## Step 6: Run Validation Scans Validation scans protect the merchant, Grantex, downstream agents, and customers. Required scan categories: | Scan | What it catches | | - | - | | Secret/private-detail scan | Secrets, tokens, provider credentials, private contacts, private contracts, raw payloads, DB/Redis URLs, private keys, customer data. | | Overclaim scan | Production-ready, live-payment-ready, certified, external-pilot-ready, or provider-ready language without approval. | | Merchant ID/name safety scan | Production-looking IDs, realistic names without approval, or demo/synthetic names used outside demo context. | | Synthetic-ID production-candidate scan | Any synthetic or demo ID proposed for production. | | Config/allowlist value scan | Concrete production config values or allowlist assignments. | | Payload preview validation | Unsafe fields, unsupported capabilities, missing blocked capabilities, or ambiguous public metadata. | | Owner/review completeness validation | Missing approvals, missing owners, or unresolved blockers. | Any failed scan blocks progression. ## Step 7: Complete Human Review Gates Automated scans are necessary but not sufficient. Human reviewers must verify business intent, legal accuracy, public wording, security posture, operations readiness, and rollback readiness. Reviewers should confirm: * all public fields are approved; * all private artifacts are stored outside the repo; * all approval references are non-secret; * no private material appears in the public payload; * blocked capabilities are explicit; * customer-facing wording is accurate; * support and rollback owners are assigned; * downstream AgenticOrg exposure remains gated; * launch still requires a separate rollout proposal. ## Step 8: Interpret Intake States The merchant intake state should be explicit. | State | Meaning | What happens next | | - | - | - | | Not ready | Required fields, approvals, scans, or owners are missing. | Complete missing work. Do not propose rollout. | | Intake ready | The artifact packet is complete and repo-safe. | Prepare a separate rollout proposal. | | Rollout proposal ready | Intake is complete and rollout materials are prepared. | Seek explicit rollout approval. | | Rejected | Private data, secrets, unsupported claims, or unsafe requests are present. | Remove unsafe material and restart review. | Intake ready is not the same as production approved. ## Step 9: Prepare A Rollout Proposal A rollout proposal is separate from onboarding. It should summarize readiness and request explicit approval for the narrow next step. The proposal should include: * merchant identity summary; * public payload preview summary; * approval reference summary; * scan summary; * read-only discovery scope; * dry-run plan; * smoke validation plan; * rollback plan; * owner assignments; * AgenticOrg dependency status; * stop conditions; * statement that checkout, live payments, live Plural, and provider credentials remain blocked unless separately approved. Do not include private artifacts, raw logs, secret values, tokens, passports, provider credentials, customer data, or production config assignments. ## Step 10: Launch Only After Separate Approval Production read-only discovery may be considered only after the rollout proposal is approved. Even then, launch should be narrow, observable, and reversible. Launch expectations: * change only approved read-only discovery controls; * expose only approved public metadata; * keep broad Commerce V1 runtime disabled unless separately approved; * keep checkout/payment creation disabled unless separately approved; * keep live payments and live Plural disabled; * keep provider credentials unavailable to agents; * run read-only smoke validation; * record redacted evidence; * keep rollback owner available. If launch validation fails, roll back immediately and record redacted evidence. ## Merchant-Facing Workflow ```mermaid theme={null} flowchart TD draft[Draft merchant profile] --> payload[Build public payload preview] payload --> refs[Attach non-secret approval references] refs --> owners[Assign required owners] owners --> scans[Run validation scans] scans --> scanDecision{Scans pass?} scanDecision -- no --> blocked[Blocked: fix unsafe or missing material] scanDecision -- yes --> review[Human review gates] review --> reviewDecision{All approvals present?} reviewDecision -- no --> pending[Approvals pending] reviewDecision -- yes --> intake[Intake ready] intake --> proposal[Separate rollout proposal] proposal --> rolloutDecision{Rollout approved?} rolloutDecision -- no --> wait[Launch blocked] rolloutDecision -- yes --> smoke[Read-only launch smoke] smoke --> agentic[AgenticOrg remains gated until separate approval] ``` ## What Merchants Can Control Merchants can control: * public display name submitted for review; * category submitted for review; * public discovery description submitted for review; * catalog information submitted for read-only preview; * policies and constraints submitted for review; * support posture submitted for review; * approval artifact collection; * operational readiness; * rollback ownership; * evidence retention ownership. Merchants cannot unilaterally control: * production allowlist values; * public discovery enablement; * Commerce V1 production runtime enablement; * checkout/payment creation; * live payment or live Plural access; * provider credential paths; * AgenticOrg public commerce discovery. Those require separate Grantex and, when applicable, AgenticOrg approval. ## Catalog Readiness Agentic commerce depends on grounded catalog data. Agents must not invent products, variants, prices, discounts, delivery promises, return policies, or availability. Catalog readiness should confirm: * product names are approved for public display; * product categories are accurate; * product identifiers are stable; * variants are clear; * availability status is current enough for the proposed use; * price visibility matches merchant policy; * return and warranty summaries are accurate; * restricted products are excluded or clearly blocked; * unsupported products do not appear in the public payload; * private supplier data and cost pricing are excluded. For early read-only discovery, use catalog summaries and policy summaries rather than full transactional catalog operations unless separately approved. ## Policy Readiness Merchant policy tells agents what is allowed, blocked, or requires additional review. Policy areas may include: * allowed read-only capabilities; * blocked capabilities; * customer support escalation; * restricted product categories; * geography constraints; * currency posture; * maximum visible claim scope; * return policy summary; * cancellation policy summary; * escalation policy; * incident response policy; * rollback policy. Policy text should be written for clear interpretation by both humans and agent systems. Ambiguous policy should block launch. ## Customer Consent Experience When a future approved flow requests consent, the customer should understand: * which merchant is involved; * which agent is acting; * what action is being requested; * what information may be read; * whether any payment-adjacent step is involved; * what amount or limit applies, if applicable; * how long the authorization lasts; * how to deny or revoke consent; * where to get support. Consent wording must not hide payment implications, imply unsupported readiness, or overstate agent authority. ## Support And Incident Handling Merchants should prepare support workflows before launch. Support should know how to answer: * What did the agent read? * Which public payload version was active? * Was a customer asked for consent? * Was any payment path involved? * Which capabilities were blocked? * Was a request denied by policy? * Was discovery disabled or rolled back? * Which owner is responsible for follow-up? Support evidence must be redacted. Do not copy secrets, tokens, passports, raw payloads, private customer data, or provider credentials into support tickets. ## Rollback Expectations Rollback should prefer fail-closed behavior. Rollback actions may include: * disable read-only discovery; * clear the approved allowlist when applicable; * keep checkout/payment/live flags disabled; * verify discovery no longer exposes the merchant; * verify AgenticOrg remains gated; * record redacted rollback evidence; * notify merchant support and operations owners; * keep private incident details in approved private systems. Rollback is not a failure of the merchant. It is an expected safety control. ## Evidence And Audit Requirements Evidence should help reviewers understand what happened without exposing private or sensitive material. Record: * command or workflow name; * file or payload reference name; * pass/fail status; * blocker codes; * approval reference labels; * owner role labels; * public payload preview hash or non-secret reference; * redacted scan summaries; * smoke result summaries; * rollback readiness summary. Never record: * private contracts; * private contacts; * signed approval bodies; * pricing terms not approved for public release; * customer data; * secrets; * tokens; * JWTs; * Commerce Passports; * provider credentials; * raw payloads; * DB URLs; * Redis URLs; * private keys; * production config values. ## AgenticOrg Readiness If AgenticOrg will expose commerce metadata, treat it as a separate downstream review. AgenticOrg should remain gated until: * Grantex merchant intake is complete; * Grantex rollout proposal is separately approved; * Grantex read-only smoke passes; * AgenticOrg dependency owner approves the downstream exposure; * AgenticOrg public discovery is explicitly approved. AgenticOrg must not call provider credentials directly for commerce discovery. It must use approved Grantex-only commerce surfaces. ## Merchant Demo Mode Demo mode is useful for education and rehearsal. Demo mode may show: * synthetic merchant identity; * synthetic public payload preview; * placeholder approval references; * placeholder owner assignments; * scan summary examples; * read-only discovery posture; * blocked checkout, payment, live Plural, and provider credential paths. Demo mode must not imply: * real merchant approval; * production allowlist approval; * public discovery enablement; * checkout/payment enablement; * live payment readiness; * live Plural readiness; * provider certification; * synthetic IDs as production candidates. ## Launch Readiness Checklist Use this checklist before asking for rollout approval. | Area | Ready when | | - | - | | Merchant identity | Public merchant ID, display name, category, and description are approved. | | Payload preview | Exact read-only metadata has been reviewed and approved. | | Legal/compliance | Public metadata and consent wording are approved. | | Product wording | Customer and agent-facing claims are accurate and limited. | | Security | No secrets, private data, or provider credential paths are present. | | Operations/support | Support owner and escalation posture are ready. | | Backup/RPO | Recovery and evidence retention expectations are documented. | | Rollback | Rollback owner and fail-closed process are ready. | | Read-only smoke | Smoke owner and validation plan are ready. | | Evidence retention | Redacted evidence owner and retention plan are ready. | | AgenticOrg | Dependency remains gated until separate approval. | | Production safety | Checkout, provider behavior, Plural production settlement, and provider credentials remain controlled unless separately approved. | ## Common Merchant Questions ### Can an AI agent sell my products automatically? Not by default. Early agentic commerce should begin with controlled read-only discovery. Checkout or payment creation requires separate approval, consent, policy checks, and provider readiness. ### Can the demo merchant be used for production? No. Demo and synthetic merchants are for education, local validation, and walkthroughs only. Synthetic IDs must never be used in a production allowlist. ### Can I paste signed approvals into the repository? No. Store signed approvals in an approved private system. The repo may contain only public-safe summaries or non-secret approval references. ### Can the agent access my payment provider directly? No. Agentic commerce must preserve provider boundaries. Agents should not access Stripe, Plural, Pine, or other provider credentials directly. ### What happens if a customer denies consent? The action must stop. Denied, expired, revoked, or missing consent must fail closed before payment-adjacent work. ### What if an agent gives an incorrect product answer? The merchant should investigate catalog grounding, policy summary accuracy, and agent integration behavior. If the issue affects safety or customer trust, rollback discovery until the issue is corrected. ### What does launch approval mean? Launch approval is explicit permission for a narrow reviewed rollout step. It does not automatically approve provider settlement claims, provider credentials, or downstream AgenticOrg public discovery unless those are separately approved. ## Stop Conditions Stop immediately if any of these occur: * real private merchant artifacts appear in the repository; * a secret, token, JWT, passport, provider credential, raw payload, DB URL, Redis URL, or private key appears in docs or logs; * a production config value or allowlist value appears without separate approval; * a synthetic or demo ID is proposed for production; * a merchant is treated as approved without human approval references; * a checkout/payment/live-provider path is requested without separate approval; * live payments or live Plural are requested without separate approval; * AgenticOrg public commerce discovery is requested before Grantex read-only smoke and separate AgenticOrg approval; * validation scans fail; * rollback ownership is missing; * support ownership is missing. ## Merchant Launch Packet Template Use placeholders until values are approved. ```text theme={null} Merchant identity - Public merchant ID: - Display name: - Category: - Discovery description: Public payload preview - Issuer reference: - JWKS reference: - Supported read-only capabilities: merchant_profile_read, catalog_summary_read, policy_summary_read - Blocked capabilities: checkout_creation, payment_intent_creation, live_payment_processing, live_plural_access, provider_credential_access Approval references - Merchant owner: - Legal/compliance: - Product wording: - Security: - Ops/support: - Backup/RPO: - AgenticOrg dependency: Owner roles - Rollback owner: - Read-only smoke owner: - Evidence retention owner: Validation - Secret/private-detail scan: - Overclaim scan: - Merchant ID/name safety scan: - Synthetic-ID production-candidate scan: - Config/allowlist value scan: Decision state - Current state: not_ready - Rollout approval: not_approved ``` ## Summary Agentic commerce can help merchants make public-safe product and policy information available to AI agents while preserving consent, policy, audit, and provider boundaries. The safe path is deliberate: draft public metadata, keep private artifacts outside repos, attach non-secret approval references, run validation scans, complete human review, prepare a separate rollout proposal, and launch only after explicit approval. Reading or using this guide does not approve any merchant, allowlist value, production discovery setting, checkout/payment flow, live payment path, live Plural path, or AgenticOrg public discovery path. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Commerce V1 Merchant And Operator Guide Source: https://docs.grantex.dev/guides/commerce-v1-merchant-operator-guide Merchant onboarding, catalog, policy, audit, webhook, emergency control, and production no-go guidance for Grantex Commerce V1. # Commerce V1 Merchant And Operator Guide This guide is for merchant, support, security, and operations teams. It explains what Grantex Commerce V1 supports for the approved Shopify live pilot, how internal sandbox and temporary smoke evidence still fit, and which boundaries remain controlled before broader merchant or provider expansion. ## Operating Principle Agents may help users find products and prepare checkout, but Grantex controls the commercial boundary: * merchant status and catalog grounding; * consent request and Commerce Passport issuance; * amount caps and merchant policy; * provider-neutral payment intent creation; * webhook ingestion, replay controls, and reconciliation; * audit records for review and incident response. AgenticOrg commerce does not call Stripe, Plural, Pine, or provider credential paths directly. ## Merchant Onboarding Checklist | Step | Current guidance | | - | - | | Tenant and merchant registration | Use synthetic fixtures for sandbox and smoke evidence. Production onboarding requires human approval. | | Catalog import | The approved Shopify pilot imports `mgx0n6-22.myshopify.com` as `mch_shopify_mgx0n6_22`; other merchants require the same approval and import discipline. | | Agent trust | Register or allow only approved agent IDs. Untrusted agents must fail safely. | | Policy activation | Configure amount caps, allowed scopes, merchant status, and checkout controls. | | Provider status | Plural adapter and webhook intake are configured for the pilot with UAT-compatible credentials; do not claim production Plural settlement. | | Audit review | Confirm consent, passport, cart, payment intent, and webhook metadata are observable without secret values. | ## Seller Self-Serve Sandbox Onboarding C5Z adds an API-backed sandbox onboarding foundation for a tenant-owned merchant workspace. It is for preparing public-safe sandbox profile metadata only. It does not approve a merchant, enable public discovery, enable AgenticOrg public commerce discovery, enable Commerce V1 production use, create checkout or payment paths, collect provider credentials, enable live payments, or enable live Plural. Sandbox onboarding stores only: * display name; * category preset; * country and currency; * test-safe support email or support URL; * public discovery description draft; * sandbox environment marker; * agentic commerce requested flag; * sandbox onboarding state and public-safe blocker/status summaries. The state machine is: `draft_created` -> `profile_incomplete` -> `sandbox_ready` -> `submitted_for_review`, with `blocked`, `not_approved`, and `rollout_not_requested` as fail-closed review states. Readiness checks require a merchant profile, category preset, public-safe description, no private artifacts in public fields, no production allowlist or live-mode config values, no provider account references, and no checkout/payment enablement. C6A extends this with a category readiness checklist and score. For `electronics_appliances`, required checks cover a recognized category preset, public display name, country/currency, public-safe description, and test-safe support contact. Recommended checks score sandbox product/variant presence, warranty summary, return-policy summary, India tax/GST metadata where applicable, and inventory freshness without requiring a catalog connector or exact inventory quantities in this slice. Blocked checks continue to fail closed for private artifacts, production discovery or allowlist config, live provider paths, and checkout/payment enablement. C6B adds a computed catalog readiness preview for the same sandbox onboarding surface. It reads the merchant-owned sandbox catalog rows already stored in Grantex and reports product count, variant count, public-safe title and description coverage, category mapping, SKU coverage, price/currency coverage, image/media coverage, availability freshness, warranty summaries, return-policy summaries, tax/GST metadata where applicable, and unsafe public catalog text blockers. Required catalog gaps block `sandbox_ready` and `submitted_for_review` because the profile is not ready for read-only discovery review. Recommended catalog gaps lower the readiness score and show remediation, but they do not require a catalog connector implementation. Operators can prepare the catalog through the existing manual product entry and CSV dry-run/bulk catalog API. Async import jobs, Shopify/WooCommerce/Magento connectors, provider credentials, checkout, payments, public discovery, live payments, and live Plural remain out of scope. C6C adds an agent-facing preview payload to the sandbox onboarding response. The preview is tenant-scoped, read-only, and generated only from public-safe merchant profile, category readiness, and catalog readiness data already stored in Grantex. It always reports `sandbox_only: true`, `not_live`, `not_approved`, `rollout_not_requested`, `public_discovery_enabled: false`, `checkout_payment_enabled: false`, `live_provider_enabled: false`, and `live_plural_enabled: false`. It may include a capped list of public-safe sample products, allowed read-only preview labels, and blocked capability labels. It excludes private legal details, contracts, private contacts, approval evidence, pricing terms, customer data, raw payloads, credentials, tokens, provider secrets, production config values, and production allowlists. C6D adds a read-only discovery review request workflow for sandbox merchants. The onboarding response now includes `read_only_discovery_review`, which shows whether the tenant-scoped sandbox merchant is eligible, requested, blocked, withdrawn, or rejected for review. A merchant or operator may request review only after the sandbox profile, category checklist, catalog checklist, and agent-facing preview all pass. The request writes audit evidence with public blocker summaries, but it is not approval and it does not publish public discovery, change production configuration, set allowlists, create checkout or payment paths, enable provider credentials, enable live payments, or enable live Plural. Blocked requests return remediation so the merchant can fix the sandbox profile or catalog before trying again. Passing category or catalog readiness means only "sandbox profile can be reviewed"; it is not merchant approval, production readiness, public discovery approval, checkout/payment readiness, live payment readiness, or live Plural readiness. C6E adds the operator review workflow for requests that are already pending. Operators can list pending read-only discovery review requests, inspect the readiness evidence, and record one of three readiness-review outcomes: * `changes_requested`: the merchant needs public-safe remediation before a later review request can proceed. * `rejected`: the request is closed without approval. * `rollout_proposal_ready`: the request is eligible for a later separate rollout proposal review. `rollout_proposal_ready` is not approval and is not launch. It does not enable public discovery, write production config, set an allowlist, enable checkout/payment creation, enable live provider paths, or enable live Plural. Decision notes and remediation must be public-safe. Do not paste secrets, private merchant artifacts, provider credentials, raw payloads, tokens, DB/Redis URLs, private keys, production config values, concrete allowlist values, or customer data into review reasons. C6F adds a rollout proposal and dry-run evidence workflow for operators. This workflow is available only after an operator decision of `rollout_proposal_ready`. The proposal records a public-safe package of sandbox profile evidence, category readiness, catalog readiness, agent-facing preview status, operator review decision, blockers, remediation, and the fixed non-enabling controls. The dry run checks the current sandbox evidence again and records either `dry_run_passed` or `dry_run_blocked`. A rollout proposal is not "go live." A passed dry run is not approval, certification, launch, production readiness, public discovery enablement, checkout/payment enablement, live provider enablement, live Plural enablement, production config writing, or allowlist writing. Merchants still need a later separate human-approved production rollout before any customer-facing public discovery or production commerce behavior can be considered. Proposal notes and withdrawal reasons must be public-safe. Do not paste private merchant details, contracts, provider credentials, raw payloads, tokens, JWTs, DB/Redis URLs, private keys, production config values, concrete allowlist values, customer data, checkout/payment claims, live-provider claims, or launch claims into proposal evidence. C6G adds a sandbox-only AgenticOrg buyer-agent discovery preview and handoff request workflow. It packages the public-safe C6C agent-facing preview together with C6D review request evidence, C6E operator decision evidence, and C6F dry-run evidence so AgenticOrg can test a buyer-agent discovery handoff against Grantex-owned data. The handoff request is not customer-facing public discovery. It does not enable AgenticOrg public discovery, production Commerce V1, checkout/payment creation, live payments, live Plural, provider credentials, production config, or allowlists. Trusted CommerceAgent callers can read the C6G payload only after an operator records the sandbox handoff request and current blockers remain absent. Operators can withdraw the handoff request by writing new audit evidence. C6G notes and withdrawal reasons must be public-safe. Do not paste secrets, private merchant artifacts, provider credentials, raw payloads, tokens, JWTs, DB/Redis URLs, private keys, production config values, concrete allowlist values, customer data, checkout/payment claims, live-provider claims, public discovery claims, or launch claims into C6G evidence. C6R adds a sandbox connector sync dry-run foundation for merchants and operators who want to preview how an existing catalog system would map into Grantex. The first supported dry-run inputs are local manual or CSV-style catalog rows, including the C6Q DummyJSON-style fake fixture used in tests. The dry-run normalizes rows into a capped product and variant preview, records counts, blockers, warnings, and redacted audit references, and stores only tenant-scoped dry-run metadata. C6R does not connect to Shopify, WooCommerce, Magento, custom APIs, ERP, OMS, WMS, logistics, CRM/support, payment providers, Plural, Stripe, Pine, or merchant private APIs. It does not accept credentials, provider metadata, private API URLs, raw payloads, production config values, concrete allowlists, checkout URLs, payment IDs, or customer data. It does not write live catalog changes, enable outbound sync, enable public discovery, enable AgenticOrg public discovery, enable checkout/payment creation, enable live payments, or approve production launch. Merchant and operator review should look at: * dry-run status and generated timestamp; * row, product, variant, would-create, would-update, blocked, and warning counts; * capped normalized preview rows; * blocker and warning codes; * requested/completed/blocked audit event IDs; * fixed controls showing sandbox-only, not-live, not-approved, public discovery off, checkout/payment off, live provider off, and no provider credentials. AgenticOrg never directly calls merchant private APIs. It continues to consume only Grantex-owned preview and buyer discovery surfaces after Grantex records the required sandbox evidence. C6Sa adds an operator review foundation for C6R dry-run evidence. A merchant or operator can request review of a tenant-scoped connector dry-run. Operators can read the redacted evidence and record one of three sandbox-only outcomes: * `accepted_for_sandbox_followup`: follow-up sandbox work may continue. * `needs_changes`: the merchant must fix the local snapshot or mapping before another review. * `blocked`: the review is closed because the evidence is unsafe or incomplete. The C6Sa decision is not merchant approval, production approval, connector execution approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. It does not write catalog changes, call merchant systems, call providers, store credentials, enable outbound sync, set allowlists, or change production configuration. C6Sa review notes must be public-safe. Do not paste secrets, private merchant artifacts, provider credentials, raw payloads, tokens, DB/Redis URLs, private keys, production config values, concrete allowlist values, checkout URLs, payment identifiers, customer data, live-provider claims, launch claims, or certification claims into review evidence. C6Sb adds the credential-free portal foundation for C6R dry-runs and C6Sa review evidence. Merchants and operators can use the Commerce onboarding portal to paste or select local sandbox manual/CSV catalog rows, run the tenant-scoped dry-run, review counts and capped normalized preview rows, request operator review, and record a sandbox evidence decision. The C6Sb portal panel is not production connector setup. It does not include credential entry, outbound sync controls, private API URL execution, provider setup, live merchant-system execution, public discovery controls, checkout/payment controls, live payment controls, live Plural controls, or production allowlist controls. It displays fixed safety controls such as `credential_entry_enabled: false`, `outbound_sync_enabled: false`, `production_connector_setup: false`, and `merchant_private_api_calls: false`. Operators should treat C6Sb output as internal sandbox evidence only. A passing dry-run or `accepted_for_sandbox_followup` review decision is not merchant approval, production approval, connector execution approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. C6Sc hardens that portal flow for self-serve sandbox use. Merchants and operators can validate local manual/CSV rows before submission, see parsed row counts, reset the sample fixture, clear local rows, and read disabled-state reasons before requesting review or recording a decision. JSON parse errors are shown before any dry-run request is sent. C6Sc also adds a redacted evidence handoff for internal review. The handoff can be copied or downloaded locally as JSON or Markdown and includes only safe summary fields, fixed non-enabling controls, blockers, warnings, and audit references from the C6R dry-run and C6Sa review responses. It excludes raw rows, raw merchant-system payloads, credentials, tokens, provider metadata, private API URLs, checkout URLs, payment identifiers, production config values, concrete allowlists, customer data, and normalized product titles. C6Sc exports are local/internal evidence only. Do not commit timestamped or merchant-specific exports unless a later approved work item says so. A copied or downloaded handoff does not approve production launch, connector execution, public discovery, checkout/payment, live providers, public protocol publication, or certification. C6Sd adds a local evidence packet schema and operator packet review checklist to the C6Sc handoff. The packet includes a Grantex-owned schema version, checklist result, sandbox follow-up readiness summary, redaction summary, and audit references. The packet status can be `ready_for_sandbox_followup`, `needs_operator_review`, or `blocked`. `ready_for_sandbox_followup` means only that sandbox follow-up review work can continue after a passed dry-run, accepted C6Sa review, clear blockers, and fixed non-enabling controls. It is not production approval, connector execution approval, outbound sync approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. C6Sd evidence packets remain local/internal review artifacts. They must not contain raw rows, normalized product titles, credentials, tokens, provider metadata, private API URLs, checkout URLs, payment identifiers, production config values, concrete allowlists, customer data, or secret material. C6Se adds a local operator handoff and self-serve remediation workflow to the same redacted evidence packet. A `ready` handoff means an operator can continue an internal sandbox follow-up conversation with the merchant. It is not production approval, connector execution approval, outbound sync approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. If a packet is pending or blocked, the remediation workflow points merchants and operators back to the relevant C6R dry-run blockers/warnings, C6Sd packet checks, and C6Sa review decisions. `needs_changes` and `blocked` decisions keep the handoff blocked and require local snapshot remediation plus another sandbox dry-run before follow-up can continue. C6Se exports are still local/internal evidence only. They include handoff and remediation summaries, non-enabling controls, and audit references, and they exclude raw rows, raw merchant-system payloads, normalized product titles, credentials, tokens, provider metadata, private API URLs, checkout URLs, payment identifiers, production config values, concrete allowlists, customer data, and secret material. C6Sf rehearses the remediation loop locally in the portal. After a `needs_changes` or `blocked` C6Sa connector review decision, the portal can show the previous issue summary, a corrected sandbox dry-run, the refreshed packet status, and the operator follow-up status. This is only a local review-aid for sandbox evidence; it does not persist follow-up approval or enable connector execution. C6Sf `followup_ready` means only that a corrected dry-run has an accepted sandbox follow-up decision in the local evidence packet. It is not production approval, connector execution approval, outbound sync approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. C6Sg persists the connector dry-run remediation loop in Grantex so the evidence does not exist only in browser state. A merchant or operator can create a tenant-scoped remediation record from a `needs_changes` or `blocked` C6Sa review, attach a corrected passed C6R dry-run, and request a follow-up C6Sa review. Duplicate remediation requests, corrected dry-run attachments, and follow-up review requests return existing evidence instead of creating duplicate rows or audit events. The persisted C6Sg record stores only redacted summary evidence: original dry-run and review IDs, original decision, remediation status, capped blocker/warning summaries, corrected dry-run ID, follow-up review ID, public-safe notes, and audit references. It does not store raw rows, raw files, raw merchant-system payloads, credentials, tokens, provider metadata, private API URLs, checkout URLs, payment identifiers, production config values, concrete allowlists, customer data, or secret material. C6Sg `followup_ready` means only that the corrected sandbox dry-run has a follow-up review accepted for sandbox follow-up. It is not production approval, connector execution approval, outbound sync approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. C6Sh adds a tenant-scoped remediation queue and redacted evidence timeline for the persisted C6Sg loop. Operators can filter sandbox remediation records by merchant, status, original decision, corrected dry-run attachment, and follow-up review request. Merchants can see their own sandbox remediation status and the next safe step in the portal. The C6Sh timeline is redacted evidence visibility only. It shows remediation status, audit references, corrected dry-run and follow-up review links, and fixed non-enabling controls. It does not store or display raw connector rows, credentials, provider metadata, private API payloads, production config values, or concrete allowlists. It does not approve production launch, connector execution, outbound sync, public discovery, checkout/payment, live providers, public protocol publication, or certification. C6Sia adds backend-only operator triage controls for the same persisted remediation loop. Operators can record a tenant-scoped triage status, assignment reference, public-safe internal note, and merchant-visible follow-up guidance. Duplicate identical triage updates return the existing evidence and audit reference instead of creating duplicate rows or audit events. C6Sia triage helps the merchant understand the next sandbox correction step, for example rerunning a local manual/CSV dry-run or attaching corrected evidence. It is not production approval, connector execution approval, outbound sync approval, public discovery approval, checkout/payment approval, live provider approval, public protocol publication, or certification. The triage fields must not include raw rows, raw merchant-system payloads, credentials, tokens, provider metadata, private API URLs, checkout URLs, payment identifiers, production config values, concrete allowlists, customer data, or secret material. ## Catalog And Inventory Catalog and inventory grounding is the first safety step. Agents must not invent products, variants, prices, or availability. Operator checks should verify: * catalog search returns merchant-owned products; * item retrieval uses exact product and variant IDs; * inventory checks pass the required browse passport when policy requires it; * cart creation uses grounded variant IDs and quantities; * evidence records synthetic IDs only when they are safe fixture identifiers. ## Consent, Passport, And Amount Caps Consent-first checkout is mandatory. A Commerce Passport is scoped runtime material and must remain out of docs, logs, PRs, chat, and evidence reports. Operators should verify: * consent scopes match the supported Grantex schema; * payment amount is less than or equal to the passport cap before positive payment work proceeds; * amount-cap breach is preserved as a fail-safe negative case; * missing, revoked, expired, or denied passport cases stop before provider work; * `consent_exchange` skip evidence uses the stable blocker code when a pre-exported checkout passport fixture is used without granted consent fixture material. ## Webhook And Replay Safety Provider webhook handling is owned by Grantex. Current evidence covers the mock provider path only. For any future live provider review: * record secret names by name only, never values; * avoid raw payload dumps in evidence; * keep replay operator-only; * verify signature handling with provider-approved material; * confirm replay cannot change production state without explicit approval. ## Emergency Disable And Re-Enable Emergency controls must prefer fail-closed behavior: 1. Disable Commerce V1 discovery or keep it disabled. 2. Disable merchant checkout policy. 3. Remove or gate commerce agent discovery. 4. Stop temporary smoke resources and delete temporary smoke secrets. 5. Verify production `grantex-auth`, `grantex-pg16`, and `grantex-redis` remain unchanged after smoke runs. 6. Re-enable only through a reviewed proposal, with rollback and secret-scan evidence. ## Internal Sandbox Checklist * Use mock provider only. * Use synthetic tenant, merchant, agent, product, and variant IDs. * Keep usable auth material and passports under `.tmp/` during approved local handoff runs. * Record only hosts, case status, HTTP status, latency, error/blocker codes, synthetic IDs, variable names, and redacted hashes. * Confirm cleanup of temporary Cloud Run, Cloud SQL, Redis, smoke secrets, and image tags after hosted smoke. ## Production Expansion Checklist Do not expand beyond the approved Shopify live pilot if any of these are true: * The merchant is not explicitly approved for the target environment. * AgenticOrg commerce discovery is not gated or reviewed against the approved Grantex production discovery payload. * Provider/live-payment/Plural production settlement signoff is missing for the claimed provider behavior. * Legal, compliance, security, operations, and product approvals are incomplete. * The rollback plan is missing. * Evidence includes secrets, raw payloads, passports/JWTs, DB/Redis URLs, provider credentials, private keys, or idempotency key values. ## Evidence Links * Option A smoke evidence: operator-internal artifact, available to authorized reviewers via `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`. * Production discovery readiness: operator-internal artifact, same access path. * Repeatable workflow: `docs/guides/commerce-v1-repeatable-option-a-smoke-workflow.md` * Operations guide: `docs/guides/commerce-v1-operations.mdx` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Commerce V1 Operations Source: https://docs.grantex.dev/guides/commerce-v1-operations Sandbox operations, import columns, and runbooks for Grantex Commerce V1. # Commerce V1 Operations This guide is the current operating baseline for Grantex Commerce V1 on `grantex.dev`. It covers sandbox use, safe observability, the approved Shopify live pilot, pilot readiness evidence, and runbooks. Production Commerce V1 is deployed for `mch_shopify_mgx0n6_22` from `mgx0n6-22.myshopify.com`. The Plural adapter is configured with credentials that authenticate against UAT-compatible rails, so this guide does not claim production Plural settlement. ## Sandbox Playground Usage Use `web/commerce-playground.html` against a local or sandbox API base URL. 1. Start the auth-service in a local sandbox with `COMMERCE_V1_ENABLED=true`. 2. Open the static playground file in a browser. 3. Set the API base URL, for example `http://localhost:3001`. 4. Paste bearer tokens and Commerce Passports only into the in-memory inputs. 5. Load `/.well-known/grantex-commerce`. 6. List `/mcp` tools. 7. Run read tools first: `merchant.get_profile`, `catalog.search`, `catalog.get_item`, and `inventory.check`. 8. Run write demo tools only in sandbox with explicit idempotency keys and, for payment-affecting tools, a checkout Commerce Passport. The playground may store only non-secret connection settings such as API base URL and merchant ID. It must not store bearer tokens, Commerce Passports, provider credentials, payment credentials, or idempotency keys in `localStorage` or `sessionStorage`. Production live-pilot mode is available for the approved Shopify merchant. Plural hosted-checkout and webhook contracts are configured through stored credentials, but production Plural settlement remains an external provider claim that requires credentials accepted by production Plural rails. ## Security And Rate-Limit Evidence Consent approval and denial use the dedicated consent host, an HttpOnly same-site principal session cookie, CSRF bound to the session and consent request ID, and a verified single-use challenge before a decision can be recorded. Consent HTML responses send `Content-Security-Policy` with `frame-ancestors 'none'`, `X-Frame-Options: DENY`, `Cache-Control: no-store`, and `Referrer-Policy: no-referrer`. Consent pages must not render bearer tokens, raw Commerce Passports, provider credentials, or secrets. Rate-limit evidence for M6C: * JWKS is exempt from Fastify rate limiting. * `/.well-known/grantex-commerce` is limited to 60 requests per minute. * `/mcp` is limited to 600 requests per minute. * Provider webhooks are limited to 1000 requests per minute. * Consent approval, denial, and challenge endpoints are limited to 60 requests per minute. * Passport consent request, exchange, list, and revoke endpoints are limited to 120 requests per minute. * Passport verify and policy evaluate endpoints are limited to 1000 requests per minute. * Policy create, policy activate, payment intent create, checkout link create, manual reconcile, and ops health are limited to 60 requests per minute. * Cart create is limited to 120 requests per minute. * Catalog search is limited to 600 requests per minute. These route-local Fastify limits replace the 5,000 requests per minute Fastify default on those routes; they are not layered with that default. Commerce routes use custom authentication and do not consume the standard developer API-key plan bucket. Caller-keyed budgets per merchant, agent, or provider remain an open operations refinement for production quota isolation. ## Pilot SLA And Readiness Dashboard The portal route `/dashboard/commerce/ops` is the M7A API-backed operations dashboard. It reads: * `/v1/commerce/ops/health` for API, database, provider adapter, reconciliation worker, and webhook backlog status. * `/v1/commerce/payments/intents?status=payment_pending` for stuck pending payment visibility. * `/v1/commerce/ops/provider-webhook-events?processing_status=failed` for safe failed provider webhook metadata. Pilot SLA targets from the V1 spec: * Payment intent create: `POST /v1/commerce/payments/intents`, 10 RPS, p95 under 500 ms excluding provider latency. * Catalog search: `POST /v1/commerce/catalog/search`, 50 RPS, p95 under 300 ms for pilot catalog size. * Provider webhooks: `POST /v1/webhooks/providers/{provider_key}`, 5 events/sec, p95 under 500 ms excluding provider verification latency. M7B dashboard status: * Health checks are measured from the API. * SLA target cards are visible as target indicators; measured local load evidence is recorded in the internal local-pilot-load report (operator-internal artifact in `docs/internal/commerce-v1/`, available to authorized reviewers via `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`). * Live-pilot readiness is complete for the approved Shopify merchant. * Plural adapter health must be read with the UAT-compatible credential caveat until production Plural credentials are accepted by production rails. * Failed provider webhook replay is operator-only and mock-provider-only until non-mock replay storage, payload retention, and operator approval are separately cleared. Replay is available only for future failed events with valid original signatures and encrypted replay material. ## Local Pilot Load Harness Use `scripts/commerce-pilot-load-harness.mjs` for local-only load evidence. The harness preserves dry-run mode and refuses non-local API base URLs before it sends any request. Local dev stack discovery: * The repo's documented local stack is `docker compose up --build` from the repo root. * That stack provides PostgreSQL on `localhost:5432`, Redis on `localhost:6379`, and auth-service on `http://localhost:3001`. * Auth-service runs SQL migrations on startup through `apps/auth-service/src/db/migrate.ts`. * M7C does not add Docker or external infrastructure. If Docker, local Postgres, or local Redis are unavailable, measured load remains blocked. Local sandbox seed command: ```bash theme={null} node scripts/commerce-pilot-seed-local.mjs --run \ --migrate \ --database-url=postgres://grantex:grantex@localhost:5432/grantex \ --env-output=.tmp/commerce-pilot-load.env ``` The seed script refuses `NODE_ENV=production`, live commerce flags, live Plural flags, and non-local or production-like database URLs. It creates synthetic local data only: * sandbox tenant `cten_internal_sandbox`; * mock-provider merchant `mch_internal_sandbox_pilot`; * trusted agent `cag_internal_sandbox_pilot`; * active policy `m7c-local-pilot-v1`; * one pilot catalog product and variant; * checkout Commerce Passport; * 100 draft cart IDs; * 51 pending mock provider payment references. The generated `.tmp/commerce-pilot-load.env` file contains synthetic local bearer/passport values for the harness. Do not commit it and do not replace those values with production secrets. Dry-run validation: ```bash theme={null} node scripts/commerce-pilot-load-harness.mjs --dry-run ``` Measured run command: ```bash theme={null} node scripts/commerce-pilot-load-harness.mjs --run \ --env-file=.tmp/commerce-pilot-load.env \ --report=docs/internal/commerce-v1/commerce-v1-local-pilot-load.md ``` Optional target subset for staged local checks: ```bash theme={null} node scripts/commerce-pilot-load-harness.mjs --run \ --api-base=http://localhost:3001 \ --targets=catalog_search ``` Run mode requires local seeded sandbox data and these environment variables, either in the process environment or in `--env-file`: * `COMMERCE_LOAD_API_BASE` * `COMMERCE_LOAD_AUTH_TOKEN` * `COMMERCE_LOAD_MERCHANT_ID` * `COMMERCE_LOAD_AGENT_ID` * `COMMERCE_LOAD_CART_IDS` * `COMMERCE_LOAD_CHECKOUT_PASSPORT` * `COMMERCE_LOAD_PROVIDER_PAYMENT_IDS` * `COMMERCE_LOAD_MOCK_WEBHOOK_SECRET` For the full M7B target run, seed at least: * 100 unique draft carts for `payment_intent_create`. * 51 pending mock provider payment references for `mock_provider_webhook`; the first reference is used for the duplicate event probe and the remaining 50 references are used for the 5 events/sec measured target. The measured report includes request count, success count, error count, p50, p95, max latency, per-target pass/fail, and duplicate webhook transition evidence. The report location is `docs/internal/commerce-v1/commerce-v1-local-pilot-load.md`. Do not use production credentials, real provider credentials, live payment mode, or real Plural calls with this harness. ## Pilot Merchant Configuration Path The internal sandbox pilot template is `docs/examples/commerce-pilot-merchant.sandbox.json`. Setup path: 1. Create or select an internal sandbox tenant. 2. Create a sandbox merchant using the `electronics_appliances` category preset. 3. Keep provider key set to `mock`. 4. Create and activate a policy with explicit amount and scope caps. 5. Register only trusted CommerceAgents needed for the pilot. 6. Keep live Plural disabled. Enable Plural sandbox only for controlled credentialed validation with `PLURAL_SANDBOX_ENABLED=true`, stored sandbox credentials, and a scrubbed evidence report. 7. Confirm emergency disable ownership before any external pilot traffic. ## Support Ownership Approved Shopify pilot owner references are stored in private operational systems outside the repository. Future expansion must assign these roles before traffic is widened: * Product owner. * Engineering owner. * Security reviewer. * Operations/on-call owner. * Legal/compliance reviewer. Do not place private names, phone numbers, escalation channels, or approval artifacts in public docs. ## On-Call Escalation Template Escalation fields: * Incident ID. * Request ID. * Tenant ID. * Merchant ID. * Agent ID when available. * Payment intent ID. * Provider payment ID. * Provider webhook event ID. * Normalized error code. * Whether emergency disable is active. * Whether live mode, Plural, mock provider, or another provider was involved. Initial response: 1. Check `/dashboard/commerce/ops`. 2. Verify `/v1/commerce/ops/health`. 3. If payments are stuck, use manual reconcile from the dashboard. 4. If provider webhooks failed, inspect metadata-only failed events. 5. Do not replay provider webhooks through ad hoc scripts. 6. Use emergency disable if protected actions must stop immediately. ## Backup Restore And RPO Notes Live readiness requires reviewed backup and restore evidence for: * Commerce tenants, merchants, agents, products, variants, policies, passports, provider credentials, carts, payment intents, provider webhook events, and audit events. * Point-in-time restore for Postgres. * Secret manager or KMS recovery for credential encryption keys. * RPO target and RTO target approved by operations. * Restore drill evidence in a non-production environment. Historical M7A evidence did not certify live RPO or RTO. The approved Shopify pilot uses private operations signoff; future expansion must refresh restore and RPO/RTO evidence before rollout. ## Final V1 Sign-Off Checklist * OpenAPI matches implemented V1 routes. * Auth-service typecheck and full tests pass. * Portal typecheck, tests, and build pass when dashboard files are touched. * Docs and playground validators pass. * Local pilot load harness dry-run passes. * Local pilot load run is executed against seeded sandbox data and records p50, p95, max latency, success counts, error counts, and duplicate webhook transition evidence. * First pilot merchant is named or internal sandbox pilot is approved. * Support, security, legal, compliance, and operations owners are assigned. * Plural adapter evidence is complete for the approved pilot or explicitly out of scope for a future target. * Live payment mode remains controlled by legal, compliance, security, operations, provider, and runtime-readiness gates. M7C internal-sandbox signoff status: | Gate | Status | Evidence | | - | - | - | | Dry-run harness validation | Passing | `node scripts/commerce-pilot-load-harness.mjs --dry-run` | | Non-local URL refusal | Passing | Harness refuses production-like base URLs before network calls | | Local sandbox seed path | Available | `node scripts/commerce-pilot-seed-local.mjs --run --migrate --env-output=.tmp/commerce-pilot-load.env` | | Measured local load report | Passing | `docs/internal/commerce-v1/commerce-v1-local-pilot-load.md` records localhost/mock run results: payment intent create 100/100 p95 16.53 ms, catalog search 500/500 p95 5.41 ms, mock provider webhook 52/52 p95 20.45 ms, duplicate transitions 0 | | Historical internal sandbox recommendation | Superseded by approved Shopify pilot | Keep this localhost/mock evidence as regression coverage only | ## Expansion Not-Ready-If Checklist V1 is not ready to expand beyond the approved Shopify live pilot if any item is true: * Production Plural settlement or real provider certification is required but production provider credentials do not authenticate. * Plural sandbox E2E evidence, webhook verification evidence, or provider approval is missing for the target pilot. * No named support or on-call owner exists. * No approved sandbox pilot merchant exists. * Local load evidence is missing or fails the p95 targets. * Failed webhook replay is required for a non-mock provider. * Failed webhook replay is required for an event that lacks valid original signature status or encrypted replay material. * Emergency re-enable is required without operator review, active policy evidence, and audit. * Backup/restore/RPO review is required for the pilot but not approved. ## CSV Product And Variant Import Columns Commerce V1 exposes API-backed product and variant creation plus a local dry-run CSV validator. The validator does not connect to a database, call Grantex APIs, create cloud resources, or import data. Use it to normalize and validate catalog files before a later operator-controlled importer exists: ```bash theme={null} node scripts/commerce-catalog-csv-validate.mjs --input=docs/examples/commerce-catalog-import.sample.csv --dry-run ``` Required product columns: | Column | Type | Notes | | - | - | - | | `merchant_id` | string | Existing merchant in the caller tenant. | | `product_id` | string | Merchant catalog reference. | | `title` | string | Customer-facing product title. | | `category_preset` | string | Must be `electronics_appliances` for V1. | Optional product columns: | Column | Type | Notes | | - | - | - | | `brand` | string | Brand display name. | | `description` | string | Safe product description. | | `image_url` | HTTPS URL | Product image URL. | | `source_system` | string | Defaults to `manual`. | | `manually_maintained` | boolean | Defaults to `false`. | Required variant columns: | Column | Type | Notes | | - | - | - | | `sku` | string | Unique variant SKU within the product import row. | | `price_amount` | integer | Minor currency units. | Optional variant columns: | Column | Type | Notes | | - | - | - | | `parent_sku` | string | Parent SKU where applicable. | | `model` | string | Variant model reference. | | `variant_title` | string | Variant display title. | | `attributes` | JSON object | Color, size, capacity, or other structured attributes. | | `currency` | string | ISO 4217 uppercase code. Defaults to `INR`. | | `tax_inclusive` | boolean | Defaults to `true`. | | `gst_slab` | string | GST slab label. | | `tax_rate` | number | Tax rate when known. | | `hsn_code` | string | HSN code when known. | | `availability_status` | string | `in_stock`, `out_of_stock`, `pre_order`, `back_order`, or `unknown`. | | `warranty_summary` | string | Short warranty text. | | `return_policy_summary` | string | Short return policy text. | | `source_system` | string | Source system for the variant row. | Importer safety requirements before write-mode implementation: * Validate tenant and merchant ownership before inserting rows. * Reject archived or duplicate variant references in the same import. * Do not infer inventory guarantees when availability is `unknown`. * Emit append-only audit events for created or updated products. * Keep raw import files out of application logs. M12B status: the local CSV validator covers column shape, required values, duplicate SKUs within the file, JSON attributes, and secret-like content refusal. It intentionally does not insert rows or upload files. ## Provider Outage Runbook Signals: * `/v1/commerce/ops/health` reports provider adapter status down or degraded. * Payment intent creation or checkout link creation returns a provider error. * Reconciliation stores a safe provider error on payment intents. Immediate actions: 1. Confirm the outage is isolated to the provider adapter and not database or tenant auth. 2. Keep live payment mode disabled. 3. Verify emergency disable status for affected merchants. 4. For sandbox, switch only test traffic to the mock provider when appropriate. 5. Use `GET /v1/commerce/payments/intents` to inspect safe payment status fields. 6. Avoid manual database edits. Use reconciliation once the provider is healthy. Escalation: * Legal and payment operations must approve any real provider retry strategy. * Plural production settlement remains unclaimed until legal, provider, security, operations, readiness-gate evidence, and production credential authentication are complete. ## Webhook Backlog And Failure Runbook Signals: * `/v1/commerce/ops/health` reports webhook backlog or recent failures. * `commerce_provider_webhook_events.processing_status` is `received` or `failed`. * Audit contains `provider.webhook.signature_failed` or denied webhook processing events. Immediate actions: 1. Check whether failures are signature failures, stale replay timestamps, unsupported events, or invalid transitions. 2. Confirm no raw payload, raw signature, or webhook secret is present in logs or audit metadata. 3. Confirm duplicate provider event IDs are accepted idempotently without a second payment transition. 4. Do not replay failed events through ad hoc scripts. 5. Use `POST /v1/commerce/ops/provider-webhook-events/{event_id}/replay` only after operator review. Safe replay rules: * Ops health reports webhook backlog and failure counts; the replay endpoint rejects non-mock replay with `provider_replay_not_supported`. * Replay is available only when the original provider signature was valid, the provider is `mock`, `processing_status=failed`, `replay_status=fresh`, and encrypted replay material exists. * Do not replay invalid-signature, stale, unsupported-provider, malformed, or unauthenticated events. * Dry-run with `{ "reason": "...", "dry_run": true }` before real replay when investigating. * Real replay requires a non-empty reason, uses the payment state machine, and must not double-transition payments that already reached the target terminal state. * Production non-mock replay remains blocked with `webhook_failed_event_list_and_replay_api_contract_deferred` until provider API and signature contracts are explicitly approved. * Responses, logs, audit metadata, and portal UI must never include raw payloads, raw signatures, encrypted payload material, webhook secrets, provider credentials, bearer tokens, passports, or plaintext idempotency keys. * Plural replay remains blocked until non-mock replay retention, provider approval, and operator controls are separately approved. ## Reconciliation Failure Runbook Signals: * Manual reconcile returns a provider error. * Batch reconciliation reports failed results. * Payment intents have `last_reconciliation_error` set. Immediate actions: 1. Confirm the payment intent is still tenant-owned and not terminal. 2. Check `provider_payment_id`, `provider_raw_status`, `last_reconciliation_attempt_at`, `last_reconciliation_error`, and `last_reconciliation_retryable`. 3. Retry only retryable provider errors after provider health is restored. 4. Do not retry validation, auth, policy, tenant, or missing provider payment errors. 5. If pending longer than the timeout and provider remains pending, allow the normal expired transition. Scheduler status: * The reconciliation helper and disabled-by-default worker exist. * Runtime scheduler enablement remains an operations gate controlled by configuration review. ## Audit Write Failure Runbook Signals: * `grantex_commerce_audit_write_failures_total` increases. * State-changing requests fail where transactional audit write is required. Immediate actions: 1. Treat the system as not release-ready for commerce state changes. 2. Check database availability, migration state, append-only trigger errors, and application role grants. 3. Do not bypass audit writes. 4. Retry only after database health and audit insert permissions are verified. 5. Preserve request IDs from structured logs for incident correlation. Safe audit data: * Audit events may include IDs, policy version, decision ID, idempotency key hash, provider reference ID, and normalized error codes. * Audit events must not include bearer tokens, raw passports, raw credentials, webhook secrets, raw signatures, raw payment data, or plaintext idempotency keys. ## Emergency Disable Runbook Signals: * Merchant `agentic_commerce_enabled=false`. * Merchant disabled timestamp is set. * Active policy sets emergency disable. * Protected payment-affecting action returns `emergency_disabled`. Immediate actions: 1. Confirm the tenant and merchant in the request. 2. Keep protected commerce actions blocked. 3. Verify audit events for denied policy decisions. 4. Do not create payment intents or checkout links for the merchant until a new policy version and activation explicitly restore access. 5. Emergency re-enable is operator-only through `POST /v1/commerce/merchants/{merchant_id}/enable-agentic-commerce`. 6. Re-enable requires a non-empty reason, a reviewed active policy ID, and `confirm_reenable=true`. 7. Re-enable does not modify provider credentials, live payment flags, Plural flags, or production config. 8. Communicate sandbox or pilot status to the operator team. Re-enable decision: * M14 implements a reviewed operator-only recovery route and portal control. * Merchant self-service re-enable remains blocked. * Re-enable requires incident review, active policy evidence, and audit semantics. * Re-enable must not enable live payments, live Plural, provider credentials, or production config. ## Legal And Live Mode Controls Before expanding live provider behavior beyond the approved Shopify pilot, require human signoff for: * Confirmed production Plural account, credential fields, idempotency behavior, callback URLs, and webhook secret rotation. * Legal review of user consent, payment authorization, refund handling, dispute handling, and customer support obligations. * Compliance review for India payment requirements, strong customer authentication, data residency, and audit retention. * Security review for provider credential vaulting, webhook secret rotation, and incident response. * Operations review for provider outage handling, webhook replay tooling, reconciliation scheduler, dashboard coverage, and on-call ownership. * Merchant pilot approval with explicit sandbox-to-live or pilot-to-broader promotion criteria. ### Machine-Readable Acknowledgement Gate Live payment mode must remain controlled by the runtime live-readiness snapshot. The snapshot may report only booleans, requirement keys, env names, and blocker strings. It must not include tokens, passports, provider payloads, credentials, DB URLs, Redis URLs, webhook secrets, raw signatures, or plaintext idempotency values. Feature flags alone are not sufficient. For the approved Shopify live pilot, operators recorded all of these acknowledgement names in the approved runtime configuration source, and future live-mode proposals must do the same: * `COMMERCE_LIVE_LEGAL_APPROVED` * `COMMERCE_LIVE_PROVIDER_CONTRACT_CONFIRMED` * `COMMERCE_LIVE_PLURAL_SANDBOX_E2E_PASSED` * `COMMERCE_LIVE_PLURAL_WEBHOOK_SIGNATURE_CONFIRMED` * `COMMERCE_LIVE_INDIA_RESIDENCY_CONFIRMED` * `COMMERCE_LIVE_FINAL_USER_CONFIRMATION_APPROVED` * `COMMERCE_LIVE_PILOT_MERCHANT_APPROVED` * `COMMERCE_LIVE_HOSTED_OACP_E2E_PASSED` * `COMMERCE_LIVE_SECRETS_REVIEWED` * `COMMERCE_LIVE_AUDIT_APPEND_ONLY_VERIFIED` * `COMMERCE_LIVE_OPERATOR_RUNBOOK_APPROVED` * `COMMERCE_LIVE_ROLLBACK_OWNER_ASSIGNED` If any acknowledgement is absent, Commerce must return the `live_readiness_blocked` reason and the release remains blocked. Do not advertise external commerce protocol certification unless a separate conformance and legal review has approved the exact claim. ## Human Review Gates Required before Milestone 6 release signoff: * Engineering review of OpenAPI drift, tenant filters, idempotency conflict handling, policy/passport enforcement, and webhook transition idempotency. * Security review of logs, audit metadata, playground storage, credential handling, and provider webhook verification. * Operations review of health, metrics, runbooks, reconciliation worker configuration, and alert thresholds. * Legal and compliance review before any live payment mode or real Plural processing. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Commerce V1 Overview Source: https://docs.grantex.dev/guides/commerce-v1-overview Start here for Grantex Agentic Commerce architecture, readiness, safety gates, and audience-specific paths. # Commerce V1 Overview Current OACP authority docs start at [OACP Authority Overview](./oacp/overview). This Commerce V1 page is retained for historical/contextual payment-control pilot material and must not be used to imply that Grantex owns the AgenticOrg seller/buyer runtime, Shopify connector runtime, or provider rail execution. Grantex Commerce V1 is the trust, protocol, policy, and canonical-artifact authority for agentic commerce. AgenticOrg runs buyer and seller agents. Merchant systems remain operational sources of record. Provider and fintech rails own mandate and payment execution. Grantex keeps OACP artifacts, source/freshness rules, refusal semantics, consent posture, and audit evidence safe without becoming a toll booth for every non-binding agent interaction. This page also references the separate Grantex Commerce payment-control pilot. That pilot is not OACP runtime artifact launch proof. OACP runtime proof is limited to AgenticOrg seller onboarding, read-only Shopify evidence, Grantex internal artifact issuance, AgenticOrg cache consumption, buyer-safe answers, bridge metadata, and provider-owned capability metadata. Public discovery, checkout, payment, order, mandate, refund, return, shipment, live-provider execution, certification, conformance, standardization, and public OACP publication are not enabled by the OACP runtime artifact path. ## Current Posture | Capability | Status | Evidence or gate | | - | - | - | | Local/internal sandbox | Implemented | Synthetic catalog, cart, consent, passport, payment, webhook, and audit flows. | | Temporary Grantex Option A smoke | Verified | internal evidence record (operator-internal, available on request) records 14 passed, 0 failed, 6 failed-safe, 0 skipped. | | AgenticOrg real-staging handoff | Verified externally | AgenticOrg evidence records authority-checked tools and redacted fixture handling. | | Internal OACP foundation | Implemented through C6W9 | Artifact family, adapter preview, commitment boundary, prepared envelope, reconciliation, eligibility packet, and dry-run verifier foundations are internal and non-executing. | | Hosted AgenticOrg API-only discovery | Verified externally | AgenticOrg C3 evidence records hosted MCP/A2A discovery against temporary Grantex smoke. | | Plural sandbox hosted-checkout adapter | Implemented/gated | Token, hosted checkout order creation, order-status polling, and HMAC webhook verification are behind the provider abstraction and require explicit sandbox flags and credentials. | | Shopify payment-control pilot merchant | Deployed for the separate Grantex Commerce pilot | `mgx0n6-22.myshopify.com` is imported as `mch_shopify_mgx0n6_22`; this is not OACP runtime artifact launch proof. | | Grantex production Commerce V1 discovery | Separate payment-control pilot surface | `/.well-known/grantex-commerce` belongs to Grantex Commerce V1 discovery, not AgenticOrg OACP runtime public discovery. | | Machine-readable live-readiness gate | Separate payment-control pilot gate | Runtime snapshot requires explicit legal, provider, security, operations, OACP E2E, audit, rollback, and human approval evidence. Missing evidence still returns `live_readiness_blocked`. | | Production checkout control plane | Separate payment-control pilot only | Consent, Commerce Passport, policy, amount-cap, cart, payment-intent, webhook, reconciliation, and audit controls are not enabled by OACP runtime artifact proof. | | Plural adapter | Separate payment-control pilot adapter | Webhook intake and adapter health are deployed; production Plural settlement is not claimed while credentials authenticate against UAT-compatible rails. | ## Start Here | Audience | Path | | - | - | | Product, merchant success, and implementation owners | Read `docs/guides/commerce-v1-agentic-commerce-prd.md` as the canonical consolidated PRD, then use `docs/guides/commerce-v1-agentic-commerce-implementation-prd.md` for the implementation summary. | | Sellers and buyer-experience reviewers | Read `docs/guides/commerce-v1-end-to-end-agentic-commerce-flow.mdx` for the one-time seller setup, one-time buyer setup, and regular transaction walkthrough. | | Developers integrating with Grantex Commerce APIs/MCP | Read this overview, then `docs/guides/commerce-v1-developer-guide.mdx` and `docs/api/grantex-commerce-v1.openapi.yaml`. | | Merchants and operators | Read `docs/guides/commerce-v1-merchant-operator-guide.mdx`, then `docs/guides/commerce-v1-operations.mdx`. | | AgenticOrg integration owners | Read this page, the AgenticOrg commerce docs, and the Option A smoke workflow. | | Reviewers assessing readiness | Read the smoke evidence and production discovery readiness report before any enablement proposal. | | End users | For OACP runtime, use buyer-safe product questions with source/freshness labels. Purchases, payments, mandates, and orders are outside this runtime proof. | ## Architecture ```mermaid theme={null} flowchart LR buyer[End user] --> agent[AgenticOrg buyer agent] seller[Merchant] --> selleragent[AgenticOrg seller agent] selleragent --> systems[Merchant systems] selleragent --> grantex[Grantex OACP authority] grantex --> artifacts[Signed artifacts and adapter previews] artifacts --> agent grantex --> plural[Plural hosted-checkout adapter and webhook intake] plural -. settlement claim not made .-> provider agent -. approved capability verification .-> provider[Provider/fintech rail] provider -. non-sensitive evidence refs .-> grantex ``` The important boundary is that the agent does not execute payments or become the merchant system of record. AgenticOrg may initiate approved connector sync jobs and may verify provider-owned mandate capability where approved. Grantex owns artifact authority, policy enforcement, refusal semantics, and evidence requirements. For the full consolidated product requirements, use `docs/guides/commerce-v1-agentic-commerce-prd.md` as the source of truth. ## End-To-End Flow Summary 1. Seller starts in AgenticOrg Seller Commerce Agent, creates an onboarding packet, connects systems through approved connector custody, and requests Grantex authority review. 2. Buyer completes one-time setup in their preferred channel: account/session linking, safe preferences, and understanding that current OACP runtime answers are non-binding. 3. Buyer asks an AgenticOrg-powered agent to discover or compare. 4. AgenticOrg uses cached OACP artifacts when TTL, revocation, and risk rules allow; otherwise it asks Grantex to refresh or verify. 5. Commitment-bound actions require boundary, envelope, reconciliation, eligibility, and dry-run checks before any future handoff. 6. AgenticOrg explains sourced facts, warns about stale or unknown data, and refuses unsupported claims. ## Consent And Commerce Passport Lifecycle ```mermaid theme={null} sequenceDiagram participant U as User participant A as Commerce Agent participant G as Grantex participant P as Provider/Fintech Rail A->>G: Search catalog and check inventory G-->>A: OACP artifacts or blocker codes A->>G: Request boundary/envelope/dry-run checks G-->>U: Consent UI / approval challenge U-->>G: Approve or deny A->>P: Verify provider-owned mandate capability when approved P-->>A: Capability evidence or refusal G->>P: Provider-neutral payment path or configured Plural adapter A->>P: Verify provider-owned mandate capability when approved P-->>A: Capability evidence or refusal ``` The Commerce Passport is scoped runtime material. It may be used during approved smoke runs, but it must never appear in committed docs, PR bodies, logs, raw payload dumps, or chat. ## Readiness Gate Ladder ```mermaid theme={null} flowchart TB local[Local tests and internal sandbox] --> optionA[Temporary Grantex Option A smoke] optionA --> real[Local AgenticOrg real-staging eval] real --> hosted[Temporary hosted AgenticOrg API smoke] hosted --> shopify[Shopify merchant import] shopify --> live[Approved live-pilot runtime] live --> provider[Provider settlement expansion] ``` Each gate is separate. The approved Shopify live pilot does not imply broad merchant self-service, provider certification, or production Plural settlement. The runtime live-readiness gate still requires machine-readable approval evidence; feature flags alone are not sufficient to start live commerce. ## Allowed And Blocked | Enabled now | Controlled or not claimed | | - | - | | Local mock demo and internal sandbox work. | Broad public self-serve merchant onboarding. | | Approved temporary Option A smoke resources and historical evidence. | Provider certification or standards certification claims. | | Shopify live pilot profile and catalog discovery for `mch_shopify_mgx0n6_22`. | Production Plural settlement claims while only UAT-compatible credentials are validated. | | OACP artifact-backed AgenticOrg commerce tools and approved capability verification. | Direct payment execution, live provider credentials, or unapproved private merchant API execution from AgenticOrg commerce. | | Public JWKS references as verification metadata. | Raw passports, bearer tokens, idempotency key values, secrets, DB/Redis URLs, or raw payloads in docs/evidence. | ## Related Documents * `docs/guides/commerce-v1-agentic-commerce-implementation-prd.md` * `docs/guides/commerce-v1-developer-guide.mdx` * `docs/guides/commerce-v1-merchant-operator-guide.mdx` * `docs/guides/commerce-v1-operations.mdx` * `docs/guides/commerce-v1-repeatable-option-a-smoke-workflow.md` * `docs/api/grantex-commerce-v1.openapi.yaml` Internal Option A smoke evidence and production-discovery readiness records are operator-internal artifacts kept in `docs/internal/commerce-v1/` and are available to authorized reviewers on request via `sanjeev@orchestrum.in or mishra.sanjeev@gmail.com`. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Compliance Matrix Source: https://docs.grantex.dev/guides/compliance-matrix How Grantex maps to OWASP Agentic Top 10, EU AI Act, and NIST AI RMF requirements for AI agent security and authorization. Grantex provides technical controls that can be mapped to widely used AI agent security and risk-management frameworks. This page documents current mapping posture and the corresponding Grantex feature areas without claiming formal third-party attestation unless separately stated. ## OWASP Agentic Security Top 10 Published December 2025 — the first industry-standard threat taxonomy for autonomous AI agents. | Risk | Description | Grantex Control | Implementation | | - | - | - | - | | **ASI-01** | Agent goal hijacking | Scoped permissions | JWT `scp` claim limits actions regardless of agent intent. A hijacked agent cannot exceed its granted scopes. | | **ASI-03** | Identity & privilege abuse | Per-agent cryptographic identity | Each agent gets a unique DID. The `agt` claim binds tokens to specific agents. Tokens are non-transferable. | | **ASI-05** | Privilege escalation | Delegation invariants | Child scopes must be a strict subset of parent scopes. `delegationDepth` claim enforced at protocol level. | | **ASI-10** | Rogue agents (no revocation) | Instant cascading revocation | `POST /v1/tokens/revoke` blocklists the JTI. All child delegations invalidated in the same operation. | ## EU AI Act The Act applies in phases. Under Regulation (EU) 2024/1689 as amended by Regulation (EU) 2026/1744, Art. 50 transparency obligations apply from 2 August 2026, Annex III high-risk obligations from 2 December 2027, and product-embedded Annex I high-risk obligations from 2 August 2028. The controls below are technical mappings, not a determination that a deployment satisfies the regulation; see [EU AI Act](/compliance/eu-ai-act) for provider and deployer duties and the limits of each mapping. | Article | Requirement | Grantex Control | Implementation | | - | - | - | - | | **Art. 9** | Risk management per AI system | Budget controls + anomaly detection | `POST /v1/budget/allocate` sets spending limits. Anomaly detection flags unusual patterns. Policy-as-code (OPA, Cedar) for fine-grained rules. | | **Art. 13** | Transparency for autonomous actions | Consent flow + Verifiable Credentials | Human sees plain-English consent screen. VC-JWTs provide portable proof of authorization. SD-JWT enables selective disclosure. | | **Art. 12** | Record-keeping (logs kept at least six months, Arts. 19(1) and 26(6)) | Hash-chained audit log | Authorisation, delegation and revocation events; the `eu-ai-act-evidence` export reports chain integrity. Not the AI system's own logs. | | **Art. 14** | Human oversight — logged & auditable | Principal dashboard + event streaming + revocation | `GET /v1/events/stream` (SSE) surfaces actions in real time. Principal Sessions dashboard. One-click cascade revocation. | ## NIST AI Risk Management Framework Active now as voluntary risk-management guidance; useful for US government and federal-contractor alignment where applicable. | Control | Requirement | Grantex Control | Implementation | | - | - | - | - | | **Govern 1.1** | AI roles & accountability | Grant-to-human traceability | Every JWT carries `sub` (human principal), `dev` (developer), and `agt` (agent). The accountability chain is unbroken. | | **Map 5.1** | Agent action attribution | Audit trail linked to grants | `POST /v1/audit/log` records actions. Each entry references the `grantId` and `agentId`. Entries are hash-chained. | | **Measure 2.5** | Audit trails for autonomous operations | Hash-chained, append-only audit | Audit entries are append-only, hash-chained (tamper-evident), filterable by agent/grant/principal/time, and exportable via compliance evidence packs. | ## Full Compliance Matrix | Requirement | Framework | Grantex Feature | Verification Method | | - | - | - | - | | Scoped permissions | OWASP ASI-01 | JWT `scp` claim | Service verifies scope before executing action | | Per-agent identity | OWASP ASI-03 | DID per agent, `agt` claim | Signature verification against agent's public key | | Delegation depth limits | OWASP ASI-05 | `delegationDepth` claim | Protocol rejects invariant-violating delegations | | Instant revocation | OWASP ASI-10 | Redis blocklist + StatusList2021 | `POST /v1/tokens/verify` returns `valid: false` | | Risk management | EU Art. 9 | Budgets, anomaly detection, policies | API endpoints + dashboard monitoring | | Transparency | EU Art. 13 | Consent flow, VCs, SD-JWT | Consent URL, credential verification | | Human oversight | EU Art. 14 | Events, principal sessions, revocation | SSE stream, dashboard, API | | Accountability | NIST Govern 1.1 | `sub` + `dev` + `agt` claims | JWT decode traces action to human | | Action attribution | NIST Map 5.1 | Hash-chained audit entries | `GET /v1/audit/entries` with filters | | Audit trails | NIST Measure 2.5 | Append-only, exportable audit | Evidence pack export via API | ## Compliance Evidence Pack Grantex can generate a compliance evidence pack that bundles all relevant data for auditors: ```bash theme={null} # Generate a compliance evidence pack via CLI grantex compliance evidence-pack \ --since 2026-01-01 \ --until 2026-03-28 \ --framework all \ --output evidence.json # Or via API curl "https://api.grantex.dev/v1/compliance/evidence-pack?since=2026-01-01&until=2026-03-28&framework=all" \ -H "Authorization: Bearer $API_KEY" ``` The evidence pack includes: * Grant records issued in the selected period * Audit entries in the selected period, including hash-chain integrity results * Current policy records * Summary counts for agents, grants, audit outcomes, policies, and plan ## Evidence And Standards Mappings * **SOC 2** — readiness control mapping published; formal third-party attestation is not published. [View mapping](/security/soc2-report) * **IETF Internet-Draft** — preparation track only; no IETF submission, working-group adoption, RFC approval, or standards status is claimed. Internal Commerce V1 C6T materials define the draft outline and review gates. * **NIST AI RMF** — mapping and whitepaper preparation track only; no NIST submission, public comment submission, NCCoE acceptance, or NIST approval is claimed. Internal Commerce V1 C6T materials define the candidate reference architecture and control-map outline. * **OpenID AuthZEN** — conformance mapping complete. [View mapping](/community/authzen-mapping) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Custom Domains Source: https://docs.grantex.dev/guides/custom-domains Use your own domain for Grantex API endpoints (Enterprise) ## Overview Enterprise plan customers can register a custom domain and prove ownership via DNS TXT records. The current release covers the registration and verification lifecycle — runtime traffic routing through verified domains (consent UI and API endpoints actually served from your domain) is on the roadmap; today both still resolve under `*.grantex.dev`. ## Setup ### 1. Register a Domain ```bash theme={null} curl -X POST https://api.grantex.dev/v1/domains \ -H "Authorization: Bearer $GRANTEX_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "auth.yourcompany.com"}' ``` Response includes verification instructions: ```json theme={null} { "id": "dom_...", "domain": "auth.yourcompany.com", "verified": false, "verificationToken": "grantex-verify-abc123", "instructions": "Add a DNS TXT record: _grantex.auth.yourcompany.com = grantex-verify-abc123" } ``` ### 2. Add DNS Record Add a TXT record to your DNS: * **Name**: `_grantex.auth.yourcompany.com` * **Value**: `grantex-verify-abc123` ### 3. Verify ```bash theme={null} curl -X POST https://api.grantex.dev/v1/domains/dom_.../verify \ -H "Authorization: Bearer $GRANTEX_KEY" ``` ## SDK Usage ```typescript theme={null} // TypeScript const result = await grantex.domains.create({ domain: 'auth.yourcompany.com' }); console.log(result.instructions); // After DNS setup: await grantex.domains.verify(result.id); // List domains const { domains } = await grantex.domains.list(); ``` ```python theme={null} # Python result = grantex.domains.create(CreateDomainParams(domain="auth.yourcompany.com")) print(result.instructions) ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Custom Manifests Source: https://docs.grantex.dev/guides/custom-manifests Define tool manifests for any connector — internal APIs, new SaaS tools, proprietary services. No dependency on Grantex. ## You Own the Manifest Grantex is a **permission enforcement engine**, not a connector catalog. The 53 pre-built manifests are a convenience — the real power is that you define permissions for **any tool your agent calls**. Internal APIs, new SaaS tools, proprietary services — if your agent calls it, you can enforce it. No waiting for a Grantex release. Define a manifest in your codebase, enforce immediately. Custom and pre-built manifests use the same enforce() engine, permission hierarchy, and JWT scope resolution. Define inline, load from JSON files, load a directory, auto-generate from source code, or extend pre-built manifests. *** ## Define Inline The most common approach — define a manifest right in your code: ```python Python theme={null} from grantex import Grantex, ToolManifest, Permission grantex = Grantex(api_key="gx_...") grantex.load_manifest(ToolManifest( connector="inventory-service", description="Internal warehouse inventory API", tools={ "get_stock_level": Permission.READ, "list_warehouses": Permission.READ, "reserve_inventory": Permission.WRITE, "update_quantity": Permission.WRITE, "remove_item": Permission.DELETE, "force_stock_reset": Permission.ADMIN, }, )) # Works exactly like a pre-built manifest result = grantex.enforce( grant_token=token, connector="inventory-service", tool="force_stock_reset", ) # Token with tool:inventory-service:write:* → DENIED (write < admin) ``` ```typescript TypeScript theme={null} import { Grantex, ToolManifest, Permission } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: 'gx_...' }); grantex.loadManifest(new ToolManifest({ connector: 'inventory-service', description: 'Internal warehouse inventory API', tools: { get_stock_level: Permission.READ, list_warehouses: Permission.READ, reserve_inventory: Permission.WRITE, update_quantity: Permission.WRITE, remove_item: Permission.DELETE, force_stock_reset: Permission.ADMIN, }, })); // Works exactly like a pre-built manifest const result = await grantex.enforce({ grantToken: token, connector: 'inventory-service', tool: 'force_stock_reset', }); // Token with tool:inventory-service:write:* → DENIED (write < admin) ``` *** ## Load from JSON Files Store manifests as JSON for version control, code review, and team sharing: ```json inventory-service.json theme={null} { "connector": "inventory-service", "version": "1.0.0", "description": "Internal warehouse inventory API", "tools": { "get_stock_level": "read", "list_warehouses": "read", "reserve_inventory": "write", "update_quantity": "write", "remove_item": "delete", "force_stock_reset": "admin" } } ``` ```python Python theme={null} grantex.load_manifest(ToolManifest.from_file("./manifests/inventory-service.json")) ``` ```typescript TypeScript theme={null} const manifest = await ToolManifest.fromFile('./manifests/inventory-service.json'); grantex.loadManifest(manifest); ``` `ToolManifest.from_file()` supports both JSON and YAML (`.yml` / `.yaml`). However, `load_manifests_from_dir()` only loads `.json` files — rename or convert YAML files before using directory loading. *** ## Load a Directory If you keep manifests as `.json` files in a folder, load them all at once: ``` manifests/ inventory-service.json billing-api.json notification-service.json salesforce.json ← you can mix custom and exported pre-built ``` ```python Python theme={null} grantex.load_manifests_from_dir("./manifests/") ``` ```typescript TypeScript theme={null} await grantex.loadManifestsFromDir('./manifests/'); ``` *** ## Auto-Generate from Source Code The CLI can scan your tool definitions and generate a manifest automatically: ```bash theme={null} # Generate from Python tool registry grantex manifest generate agent_tools.py --out inventory-service.json # Generate from TypeScript tool definitions grantex manifest generate tools.ts --out inventory-service.json ``` The CLI infers permissions from tool name patterns (`get_*` → read, `create_*` → write, `delete_*` → delete). **Always review the output** — the inference is a starting point, not a guarantee. *** ## Extend Pre-Built Manifests Add tools to any existing manifest — pre-built or custom: ```python Python theme={null} from grantex.manifests.salesforce import manifest as sf # Add tools specific to your Salesforce org sf.add_tool("custom_bulk_export", Permission.READ) sf.add_tool("custom_data_purge", Permission.ADMIN) grantex.load_manifest(sf) ``` ```typescript TypeScript theme={null} import { salesforceManifest } from '@grantex/sdk/manifests/salesforce'; import { Permission } from '@grantex/sdk'; // Add tools specific to your Salesforce org salesforceManifest.addTool('custom_bulk_export', Permission.READ); salesforceManifest.addTool('custom_data_purge', Permission.ADMIN); grantex.loadManifest(salesforceManifest); ``` *** ## Mix Custom and Pre-Built Most production deployments use both. Load pre-built manifests for well-known connectors, custom manifests for everything else: ```python Python theme={null} from grantex import Grantex, ToolManifest, Permission from grantex.manifests.salesforce import manifest as sf from grantex.manifests.stripe import manifest as stripe from grantex.manifests.jira import manifest as jira grantex = Grantex(api_key="gx_...") # Pre-built for well-known connectors grantex.load_manifests([sf, stripe, jira]) # Custom for your own services grantex.load_manifest(ToolManifest( connector="billing-api", tools={ "get_invoice": Permission.READ, "create_charge": Permission.WRITE, "issue_refund": Permission.DELETE, "override_billing": Permission.ADMIN, }, )) grantex.load_manifests_from_dir("./manifests/") # more custom manifests ``` ```typescript TypeScript theme={null} import { Grantex, ToolManifest, Permission } from '@grantex/sdk'; import { salesforceManifest } from '@grantex/sdk/manifests/salesforce'; import { stripeManifest } from '@grantex/sdk/manifests/stripe'; import { jiraManifest } from '@grantex/sdk/manifests/jira'; const grantex = new Grantex({ apiKey: 'gx_...' }); // Pre-built for well-known connectors grantex.loadManifests([salesforceManifest, stripeManifest, jiraManifest]); // Custom for your own services grantex.loadManifest(new ToolManifest({ connector: 'billing-api', tools: { get_invoice: Permission.READ, create_charge: Permission.WRITE, issue_refund: Permission.DELETE, override_billing: Permission.ADMIN, }, })); await grantex.loadManifestsFromDir('./manifests/'); // more custom manifests ``` *** ## Permission Hierarchy All manifests — custom or pre-built — use the same four permission levels: ``` admin (level 3) └── delete (level 2) └── write (level 1) └── read (level 0) ``` | Granted Scope | READ Tools | WRITE Tools | DELETE Tools | ADMIN Tools | | :-: | :-: | :-: | :-: | :-: | | `tool:X:read` | Allowed | Denied | Denied | Denied | | `tool:X:write` | Allowed | Allowed | Denied | Denied | | `tool:X:delete` | Allowed | Allowed | Allowed | Denied | | `tool:X:admin` | Allowed | Allowed | Allowed | Allowed | The scope format is `tool:{connector}:{permission}:{resource}`. Your custom connector name goes in place of `{connector}`: ``` tool:inventory-service:write:* ← custom connector tool:salesforce:read:* ← pre-built connector tool:billing-api:admin:* ← custom connector ``` *** ## Validation Validate your manifests before deploying: ```bash theme={null} # Load and inspect a custom manifest file grantex manifest load ./manifests/inventory-service.json ``` The CLI commands `grantex manifest validate` and `grantex enforce test` currently only work with the 53 bundled manifests. For custom manifests, use `grantex manifest load` to inspect the file, and test enforcement programmatically using the SDK's `enforce()` method directly. *** ## FAQ No. Define a `ToolManifest` in your codebase and load it at startup. The enforce engine makes no distinction between your manifests and the bundled ones. Use the pre-built manifests for well-known connectors (Salesforce, Stripe, etc.). For third-party connectors not in the bundled set, define your own manifest based on the connector's API documentation. Yes. Load your own manifest with the same connector name — it fully replaces the previous one (all tools, not just overlapping ones). If you want to keep the original tools and add new ones, use `add_tool()` instead. `enforce()` returns `allowed: false` with the reason "Unknown tool". This fail-closed default means agents cannot call tools that were never declared. Use the same format: `tool:{your-connector}:{permission}:*`. For example, `tool:inventory-service:write:*` grants write access to your inventory service. *** ## Related * [Tool Manifests concept](/concepts/tool-manifests) — permission hierarchy, scope format, fail-closed behavior * [Scope Enforcement guide](/guides/scope-enforcement) — end-to-end walkthrough with framework integrations * [CLI manifest commands](/cli/manifest) — browse, validate, and generate manifests * [AgenticOrg case study](/case-studies/agenticorg) — 53 pre-built + custom manifests in production ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Dependency Updates and Validation Source: https://docs.grantex.dev/guides/dependency-updates Validate dependency upgrades locally before deploying Grantex. ## Toolchain and Lockfiles Use Node.js 24 LTS for repository development and CI. Vitest 5 requires at least Node.js 22.12. The production auth-service container uses the digest-pinned Node 26 image in its Dockerfile. A repository tooling upgrade is not an SDK release. Install with `npm ci` in each affected package. Upgrade `vitest` and `@vitest/coverage-v8` together to satisfy their exact peer-version requirement. Do not bypass a conflict with `--force` or `--legacy-peer-deps`. Dependabot groups these packages in the auth service, portal, CLI and TypeScript SDK. When multiple PRs touch one lockfile, preserve all requested manifest changes, regenerate the combined lockfile with npm, review its diff and perform a clean install. Recheck the root lockfile when a local `file:` SDK dependency changes. ## Local Gates Run from the repository root. These commands do not deploy or publish packages: ```bash theme={null} npm ci node scripts/check-docs-integrity.mjs --live node scripts/check-supply-chain.mjs --audit-npm node scripts/verify-node-suite.mjs packages node scripts/verify-node-suite.mjs examples ``` For `node scripts/verify-node-suite.mjs apps` or `all`, set `AUDIT_INTEGRATION_DATABASE_URL` to a disposable local PostgreSQL database first. The suite intentionally refuses to silently skip the real-database audit tests. Never use the production database. Set `GRANTEX_TEST_REPORT_DIR` to an output parent directory to retain per-command logs, Vitest JSON and a summary in a new private, randomly named subdirectory; the parent defaults to the operating system temporary directory. The runner performs clean installs, typechecks, unit tests and available builds, with at most two Vitest workers. ## Docker API and Payment Tests Build the changed auth-service image on the workstation. Point all of `E2E_BASE_URL`, `E2E_PUBLIC_BASE_URL` and `E2E_ISSUER` at the local service. Use the local metrics key only. Run every test file listed in `.github/workflows/e2e.yml`, including wallet limits, replay protection, consent, OAuth, refresh, SSO, compliance, audit, events and principal sessions. The isolated Base test stack is in `tests/base-usdc/compose.yml`. Build the TypeScript SDK and x402 package, install the auth-service and test-fixture dependencies, start that stack with Compose project `grantex-base-compat`, then set `GRANTEX_BASE_DOCKER_TEST=1` and run: ```bash theme={null} node --test tests/base-usdc/base-usdc.test.mjs ``` This uses an owned local Anvil chain, fixture USDC and the official x402 facilitator implementation. It exercises real local EVM transfers, not funded Base-mainnet transactions. Never reuse fixture keys on a funded network. ## Merge, Deploy and Verify Require green checks on the exact combined commit, not only older individual PR heads. Preserve security settings and review major-version API migrations. Merge the validated candidate, verify main CI, then confirm the Cloud Run revision and Firebase Hosting deployment before running the production E2E workflow. Keep production metrics credentials in secret storage, never reports. Record tested commits, actual pass/fail/skip counts, deployment identifiers, coverage limitations and any separate registry publication in the release report. Passing automated tests is not a guarantee that every device, issuer or external payment provider is compatible. SDK publishing is a separate versioned action. # End-User Permission Dashboard Source: https://docs.grantex.dev/guides/end-user-permissions Give your users a self-service page to view and revoke agent access. End-users (the people your agents act on behalf of) need visibility and control over which agents have access to their data. The Permission Dashboard lets you generate a short-lived, secure link that your users can open to view and revoke agent access — no API key needed. ## How It Works 1. **Developer generates a session URL** — Your backend calls `POST /v1/principal-sessions` with the user's `principalId` and gets back a short-lived session token embedded in a URL. 2. **You share the URL with the user** — Via email, in-app notification, settings page link, etc. 3. **User opens the dashboard** — They see all agents with active access, scopes granted, and a revoke button for each. 4. **User revokes access** — One click revokes the grant and cascade-revokes any delegated sub-grants. Session tokens are JWT-based, scoped to a specific principal+developer pair, and expire after 1 hour by default (max 24 hours). ## Generate a Session URL ### Using curl ```bash theme={null} curl -X POST https://api.grantex.dev/v1/principal-sessions \ -H "Authorization: Bearer $GRANTEX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"principalId": "user_abc123", "expiresIn": "2h"}' ``` Response: ```json theme={null} { "sessionToken": "eyJhbGciOiJSUzI1NiIs...", "dashboardUrl": "https://api.grantex.dev/permissions#session=eyJhbGciOiJSUzI1NiIs...", "expiresAt": "2026-03-01T14:00:00.000Z" } ``` ### Using the TypeScript SDK ```typescript TypeScript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); const session = await grantex.principalSessions.create({ principalId: 'user_abc123', expiresIn: '2h', // optional, defaults to "1h", max "24h" }); console.log(session.dashboardUrl); // → "https://api.grantex.dev/permissions#session=eyJ..." // The session token is in the URL fragment (#), not the query string, // so it is never transmitted to the server or leaked via Referer headers. // Send this URL to your user (email, in-app, etc.) ``` ```python Python theme={null} from grantex import Grantex, CreatePrincipalSessionParams grantex = Grantex(api_key="your-api-key") session = grantex.principal_sessions.create( CreatePrincipalSessionParams( principal_id="user_abc123", expires_in="2h", # optional, defaults to "1h", max "24h" ) ) print(session.dashboard_url) # → "https://api.grantex.dev/permissions#session=eyJ..." # The session token is in the URL fragment (#), not the query string, # so it is never transmitted to the server or leaked via Referer headers. ``` ## Parameters The user's principal ID — the same `userId` / `principalId` used when creating authorization requests. How long the session token is valid. Format: `"30m"`, `"1h"`, `"24h"`. Capped at 24 hours. ## Response | Field | Type | Description | | - | - | - | | `sessionToken` | `string` | The JWT session token | | `dashboardUrl` | `string` | Full URL the user can open in their browser | | `expiresAt` | `string` | ISO 8601 expiry timestamp | ## What the User Sees When the user opens the dashboard URL, they see: * **Apps with access** — A card for each agent with an active grant, showing the agent name, description, granted scopes, issue date, expiry date, and a "Revoke access" button. * **Recent activity** — A table of recent audit log entries for actions taken by agents on their behalf. * **Session expired** — If the session token has expired, a clear message asking them to request a new link. ## Revoking Access When a user clicks "Revoke access" on a grant: 1. The grant is marked as revoked 2. All delegated sub-grants (child grants from agent-to-agent delegation) are also revoked 3. Redis revocation keys are set so tokens are rejected immediately 4. A `grant.revoked` webhook event is fired This is the same cascade-revoke behavior as the developer-facing `DELETE /v1/grants/:id` endpoint. ## Security Considerations * **Short-lived tokens** — Session tokens default to 1 hour. Use the shortest duration practical for your use case. * **Scoped access** — Session tokens are bound to a specific principal+developer pair. A user cannot see or revoke another user's grants, and cannot see grants from other developers. * **Purpose claim** — Session JWTs include a `purpose: "principal_dashboard"` claim that prevents grant tokens from being reused as session tokens (and vice versa). * **No API key exposure** — End-users never see or need the developer's API key. * **HTTPS only** — Always serve dashboard URLs over HTTPS in production. ## API Endpoints The permission dashboard uses these endpoints under the hood: | Method | Endpoint | Auth | Description | | - | - | - | - | | `POST` | `/v1/principal-sessions` | Developer API key | Create a session token | | `GET` | `/v1/principal/grants` | Session token | List active grants | | `GET` | `/v1/principal/audit` | Session token | List recent audit entries | | `DELETE` | `/v1/principal/grants/:id` | Session token | Revoke a grant | | `GET` | `/permissions` | None (public HTML) | Permission dashboard page | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Enterprise SSO Source: https://docs.grantex.dev/guides/enterprise-sso Configure enterprise identity providers and enforce human SSO for dashboard and consent decisions. ## Overview Grantex supports **OIDC** and **SAML 2.0** enterprise SSO, plus an **LDAP direct-bind preview**. You can configure multiple identity provider connections per organization, route users by email domain, and map OIDC/SAML groups to Grantex scopes. The built-in LDAP preview has the limitations described below. **Key capabilities:** | Feature | Description | | - | - | | Multi-IdP connections | Configure multiple OIDC and SAML providers, plus LDAP direct-bind preview connections, per org | | OIDC Discovery | Automatic endpoint discovery via `.well-known/openid-configuration` | | ID token verification | Cryptographic JWT signature verification via JWKS | | SAML 2.0 | Response parsing with X.509 certificate-based signature verification | | LDAP preview | Service-account bind followed by a user bind to a constructed DN; no directory search or provider certification | | Domain routing | Route users to the correct IdP based on email domain | | JIT provisioning | Auto-create principals on first SSO login | | Group mapping | Map OIDC/SAML groups or roles to Grantex scopes; the default LDAP client does not retrieve groups | | Human SSO enforcement | Organization opt-in gates dashboard humans and grant consent decisions; machine API keys remain valid | | Session management | Track and revoke opaque, expiring SSO bearer sessions | The hosted dashboard sign-in currently completes **OIDC** only. SAML and LDAP callbacks are available to custom integrations, but they are not a browser sign-in path in the hosted dashboard. Do not turn on organization enforcement unless a JIT-enabled OIDC connection has already completed a successful login that maps an administrator to the `admin` scope. *** ## Setting up OIDC ### Step 1: Register Grantex in your IdP In your identity provider (Okta, Azure AD, Google Workspace, Auth0, etc.), create a new application: * **Application type:** Web application * **Sign-in redirect URI:** `https://grantex.dev/dashboard/sso/callback` for the hosted dashboard, or your own callback URL for a custom integration * **Scopes:** `openid email profile` * **Grant type:** Authorization Code Note the **Client ID**, **Client Secret**, and **Issuer URL**. ### Step 2: Create the SSO connection ```typescript theme={null} const connection = await grantex.sso.createConnection({ name: 'Okta Production', protocol: 'oidc', issuerUrl: 'https://mycompany.okta.com', clientId: 'your-client-id', clientSecret: 'your-client-secret', domains: ['mycompany.com'], jitProvisioning: true, groupAttribute: 'groups', groupMappings: { Engineering: ['read', 'write', 'deploy'], Admins: ['admin', 'read', 'write'], }, defaultScopes: ['read'], }); ``` ### Step 3: Test the connection ```typescript theme={null} const result = await grantex.sso.testConnection(connection.id); // result.success === true // result.issuer, result.authorizationEndpoint, result.tokenEndpoint, result.jwksUri ``` This checks provider metadata and connectivity, **not** a human login, group mapping, dashboard authorization, or consent. Complete an actual OIDC login with a test administrator before enabling enforcement. ### Step 4: Handle the login flow ```typescript theme={null} // Get the authorize URL (domain-based routing) const { authorizeUrl } = await grantex.sso.getLoginUrl('dev_01HXYZ...', 'mycompany.com'); // Redirect user to authorizeUrl // After IdP redirects back, handle the callback const result = await grantex.sso.handleOidcCallback({ code: req.query.code, state: req.query.state, redirect_uri: 'https://yourapp.com/sso/callback/oidc', }); console.log(result.email); // 'alice@mycompany.com' console.log(result.mappedScopes); // ['read', 'write', 'deploy'] console.log(result.sessionId); // 'ssosess_01HXYZ...' console.log(result.principalId); // 'scimuser_01HXYZ...' (JIT-provisioned) // result.sessionToken is an opaque bearer credential when the server's // human-session feature is enabled. Never log or expose it to another user. ``` *** ## Setting up SAML 2.0 ### Step 1: Configure your IdP In your SAML identity provider: * **SP Entity ID:** `urn:grantex:yourorg` * **ACS URL:** `https://yourapp.com/sso/callback/saml` * **NameID format:** Email address or persistent identifier * **Attributes:** Map `email`, `displayName`, and `groups` Download the **IdP certificate** and note the **SSO URL** and **Entity ID**. ### Step 2: Create the SAML connection ```typescript theme={null} const connection = await grantex.sso.createConnection({ name: 'Azure AD SAML', protocol: 'saml', idpEntityId: 'https://sts.windows.net/tenant-id/', idpSsoUrl: 'https://login.microsoftonline.com/tenant-id/saml2', idpCertificate: '-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----', spEntityId: 'urn:grantex:yourorg', spAcsUrl: 'https://yourapp.com/sso/callback/saml', domains: ['yourorg.com'], jitProvisioning: true, groupMappings: { SecurityTeam: ['admin', 'audit'], Developers: ['read', 'write'], }, }); ``` ### Step 3: Handle the SAML callback ```typescript theme={null} // After the IdP POSTs the SAML response: const result = await grantex.sso.handleSamlCallback({ SAMLResponse: req.body.SAMLResponse, RelayState: req.body.RelayState, }); ``` *** ## Setting up LDAP LDAP authentication differs from OIDC and SAML in that there is no browser redirect. The user submits credentials directly to your application, and Grantex performs a service-account bind followed by a user bind. LDAP support is currently a direct-bind preview. The built-in client does not search for user entries, read directory attributes, or retrieve group memberships. It constructs the user DN from `ldapSearchFilter` and `ldapSearchBase`, returns no email or display name, and returns an empty groups list. Validate the exact DN convention against a staging directory before enabling it. ### Step 1: Gather LDAP server details You will need the following from your directory administrator: * **LDAP URL** — The server address (e.g. `ldaps://ldap.mycompany.com:636` for TLS or `ldap://ldap.mycompany.com:389`) * **Bind DN** — The service-account distinguished name used for the initial connectivity/authentication bind (e.g. `cn=readonly,dc=mycompany,dc=com`) * **Bind password** — The password for the bind DN * **Search base** — The base DN appended to the constructed user RDN (e.g. `ou=people,dc=mycompany,dc=com`) * **Search filter** — A template such as `(uid={{username}})`. In the preview client this is not executed as a search; parentheses are removed and the result is used as the user RDN. Always use `ldaps://` (LDAP over TLS) in production. Unencrypted LDAP transmits passwords in plaintext. ### Step 2: Create the LDAP connection ```typescript theme={null} const connection = await grantex.sso.createConnection({ name: 'Corporate LDAP', protocol: 'ldap', ldapUrl: 'ldaps://ldap.mycompany.com:636', ldapBindDn: 'cn=readonly,dc=mycompany,dc=com', ldapBindPassword: 'bind-password', ldapSearchBase: 'ou=people,dc=mycompany,dc=com', ldapSearchFilter: '(uid={{username}})', // -> uid=alice,ou=people,... domains: ['mycompany.com'], jitProvisioning: true, defaultScopes: ['read'], }); ``` ### Step 3: Handle LDAP authentication Since LDAP does not use browser redirects, the user submits credentials directly to your application. Your backend then calls the LDAP callback endpoint: ```typescript theme={null} // User submits username/password via your login form const result = await grantex.sso.handleLdapCallback({ username: req.body.username, password: req.body.password, connectionId: connection.id, org: 'org_01HXYZ...', }); console.log(result.email); // null (attributes are not retrieved) console.log(result.name); // null (attributes are not retrieved) console.log(result.groups); // [] console.log(result.mappedScopes); // ['read'] (defaultScopes) console.log(result.sessionId); // 'ssosess_01HXYZ...' console.log(result.principalId); // JIT principal ID, when enabled ``` The preview performs two binds, not a directory search. It first binds the configured service account, constructs the user DN, then binds that DN with the submitted password. LDAP group-search settings are stored but are not used by the built-in client; only `defaultScopes` apply to its empty group list. *** ## Domain-based routing When you assign domains to connections, Grantex automatically routes users to the correct IdP: ```typescript theme={null} // Connection A: domains ['engineering.co'] -> Okta OIDC // Connection B: domains ['marketing.co'] -> Azure AD SAML // User with @engineering.co email -> routed to Okta const login = await grantex.sso.getLoginUrl('dev_01HXYZ...', 'engineering.co'); // login.protocol === 'oidc', login.connectionId === Connection A's ID // User with @marketing.co email -> routed to Azure AD const login2 = await grantex.sso.getLoginUrl('dev_01HXYZ...', 'marketing.co'); // login2.protocol === 'saml', login2.connectionId === Connection B's ID ``` *** ## Group-to-scope mapping Map identity provider groups to Grantex scopes: ```typescript theme={null} await grantex.sso.createConnection({ // ... groupAttribute: 'groups', // OIDC claim name or SAML attribute name groupMappings: { 'Engineering': ['read', 'write', 'deploy'], 'Administrators': ['admin', 'read', 'write', 'delete'], 'ReadOnly': ['read'], }, defaultScopes: ['read'], // Fallback when no groups match }); ``` When a user logs in with the groups `['Engineering', 'ReadOnly']`, they receive the union of matched scopes: `['read', 'write', 'deploy']`. *** ## JIT provisioning When `jitProvisioning` is enabled, Grantex automatically creates or updates a principal (SCIM user) on each SSO login. This eliminates the need for manual user provisioning: 1. User authenticates via SSO 2. Grantex checks the IdP subject; after organization enforcement is enabled, it is namespaced by the connection ID to prevent cross-provider collisions 3. If not found, creates a new SCIM user with the IdP's email and display name 4. If found, updates the existing user's details 5. Returns the `principalId` in the callback result *** ## SSO enforcement Human SSO enforcement is opt-in at **two** levels. The server operator sets `SSO_HUMAN_ENFORCEMENT_ENABLED=true` (source default: off). The organization then enables its own policy in Dashboard > Settings > SSO or via the API: ```typescript theme={null} // Enable SSO enforcement await grantex.sso.setEnforcement({ enforce: true }); // Disable SSO enforcement await grantex.sso.setEnforcement({ enforce: false }); ``` Enabling requires a recent, successful OIDC administrator session with JIT provisioning and the mapped `admin` scope. Existing browser SSO sessions are invalidated. Legacy JIT identities are migrated only when a prior SSO session proves their exact connection and subject; ambiguous mappings return `SSO_IDENTITY_CONFLICT` and require manual resolution. Re-authenticate after enabling. Keep a recovery machine API key secured outside the browser. The hosted dashboard rejects API-key **human sign-in** for an enforced organization. Its OIDC callback exchanges the code and uses an opaque SSO session with `admin` scope. The hosted consent page requires a separate active SSO session bound to the exact organization and principal for both approval and denial. In live mode, the existing passkey requirement still applies; SSO does not replace a WebAuthn assertion. Sessions expire after eight hours and may be revoked earlier. Consent fails closed on missing, expired, revoked, or other-principal sessions. The developer API key deliberately continues to authorize programmatic management API calls. It cannot impersonate a principal SSO session for consent, but a key holder can still manage the organization, including turning enforcement off. Protect it as a privileged machine credential; this policy is **not** a blanket ban on non-SSO API access. For a local end-to-end check, start the dedicated Docker stack with `docker compose -p grantex-sso-e2e -f docker-compose.yml -f docker-compose.sso-test.yml up -d --build`, then run `node apps/auth-service/scripts/sso-local-idp-e2e.mjs` from the repository root. This uses a disposable, locally signed OIDC test provider. It does not certify any external identity provider or prove a live tenant's redirect and claim configuration. Validate the intended provider in staging before enabling a production organization. *** ## Session management Track and manage active SSO sessions: ```typescript theme={null} // List all active sessions const { sessions } = await grantex.sso.listSessions(); for (const session of sessions) { console.log(session.email, session.groups, session.expiresAt); } // Revoke a specific session await grantex.sso.revokeSession(sessions[0].id); ``` Sessions expire after 8 hours by default. A session ID is an audit identifier, not an authentication credential; use the returned `sessionToken` as a bearer credential and keep it secret. *** ## CLI reference ```bash theme={null} # Create an OIDC connection grantex sso connections create --name "Okta" --protocol oidc \ --issuer-url https://mycompany.okta.com \ --client-id $CLIENT_ID --client-secret $CLIENT_SECRET \ --domains mycompany.com --jit-provisioning # Create a SAML connection grantex sso connections create --name "Azure AD" --protocol saml \ --idp-entity-id "https://sts.windows.net/tenant-id/" \ --idp-sso-url "https://login.microsoftonline.com/tenant-id/saml2" \ --idp-certificate "$(cat idp-cert.pem)" \ --sp-entity-id "urn:grantex:mycompany" \ --sp-acs-url "https://myapp.com/sso/callback/saml" # Create an LDAP connection grantex sso connections create --name "Corporate LDAP" --protocol ldap \ --ldap-url ldaps://ldap.mycompany.com:636 \ --ldap-bind-dn "cn=readonly,dc=mycompany,dc=com" \ --ldap-bind-password "$LDAP_BIND_PW" \ --ldap-search-base "ou=people,dc=mycompany,dc=com" \ --ldap-search-filter "(uid={{username}})" \ --domains mycompany.com --jit-provisioning # List all connections grantex sso connections list # Test a connection grantex sso connections test sso_01HXYZ... # Enable SSO enforcement grantex sso enforce --enable # List active sessions grantex sso sessions list # Revoke a session grantex sso sessions revoke ssosess_01HXYZ... ``` *** ## Supported identity providers | Provider | OIDC | SAML 2.0 | | - | - | - | | Standards-compliant IdP | OIDC discovery, JWKS and Authorization Code flow required; validate issuer, claims, redirect and groups in staging | Signed SAML responses and a configured trust certificate required; custom integration callback only | LDAP provider compatibility is not currently certified. The preview works only when the configured filter and base construct the directory's exact user DN; test the intended server and schema before production use. *** ## API endpoints | Endpoint | Method | Description | | - | - | - | | `/v1/sso/connections` | POST | Create SSO connection | | `/v1/sso/connections` | GET | List SSO connections | | `/v1/sso/connections/:id` | GET | Get SSO connection | | `/v1/sso/connections/:id` | PATCH | Update SSO connection | | `/v1/sso/connections/:id` | DELETE | Delete SSO connection | | `/v1/sso/connections/:id/test` | POST | Test SSO connection | | `/v1/sso/enforce` | POST | Set SSO enforcement | | `/v1/sso/enforce` | GET | Read effective organization enforcement | | `/sso/capabilities` | GET | Read server human-session availability | | `/v1/sso/sessions` | GET | List SSO sessions | | `/v1/sso/sessions/:id` | DELETE | Revoke SSO session | | `/sso/login` | GET | Get IdP authorization URL | | `/sso/callback/oidc` | POST | OIDC callback (with verification) | | `/sso/callback/saml` | POST | SAML callback | | `/sso/callback/ldap` | POST | LDAP direct-bind preview (no directory or group search) | ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Event Streaming Source: https://docs.grantex.dev/guides/event-streaming Stream Grantex authorization events in real time via SSE or WebSocket, and forward them to external systems with @grantex/destinations. Grantex provides real-time event streaming endpoints that push authorization lifecycle events as they happen. Combined with the `@grantex/destinations` package, you can forward events to SIEMs, data warehouses, and message brokers without writing custom plumbing. ## Architecture ``` ┌─────────────────┐ SSE / WebSocket ┌──────────────────┐ │ Grantex Auth │ ─────────────────────► │ EventSource │ │ Service │ │ (@grantex/ │ │ │ │ destinations) │ └─────────────────┘ └────────┬─────────┘ │ ┌──────────┼──────────┐ │ │ │ ▼ ▼ ▼ Datadog Splunk S3 Splunk BigQuery Kafka ``` The `EventSource` class connects to the SSE stream and dispatches each event to one or more **destinations** you configure. Destinations buffer events and flush them in batches for efficiency. ## Event Types | Event | When it fires | | - | - | | `grant.created` | A new grant is issued after the user completes the consent flow | | `grant.revoked` | A grant is revoked (root or cascade revocation) | | `token.issued` | A grant token is issued (initial exchange or refresh) | | `budget.threshold` | A budget usage threshold is crossed (e.g., 80%) | | `budget.exhausted` | A budget is fully consumed | Each event has the same envelope: ```json theme={null} { "id": "evt_01JXYZ...", "type": "grant.created", "createdAt": "2026-03-01T12:00:00Z", "data": { "grantId": "grnt_01...", "agentId": "ag_01...", "principalId": "user-123", "scopes": ["calendar:read"] } } ``` ## SSE Endpoint **`GET /v1/events/stream`** returns a Server-Sent Events stream. Authenticate with your API key as a Bearer token. ### curl ```bash theme={null} curl -N -H "Authorization: Bearer $GRANTEX_API_KEY" \ "https://api.grantex.dev/v1/events/stream" ``` Filter by event type with the `types` query parameter (comma-separated): ```bash theme={null} curl -N -H "Authorization: Bearer $GRANTEX_API_KEY" \ "https://api.grantex.dev/v1/events/stream?types=grant.created,grant.revoked" ``` ### TypeScript ```typescript theme={null} const res = await fetch('https://api.grantex.dev/v1/events/stream', { headers: { Authorization: `Bearer ${process.env.GRANTEX_API_KEY}` }, }); const reader = res.body!.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const text = decoder.decode(value, { stream: true }); for (const line of text.split('\n')) { if (line.startsWith('data: ')) { const event = JSON.parse(line.slice(6)); console.log(event.type, event.data); } } } ``` ### Python ```python theme={null} import httpx import json import os with httpx.stream( "GET", "https://api.grantex.dev/v1/events/stream", headers={"Authorization": f"Bearer {os.environ['GRANTEX_API_KEY']}"}, ) as response: for line in response.iter_lines(): if line.startswith("data: "): event = json.loads(line[6:]) print(event["type"], event["data"]) ``` ## WebSocket Endpoint **`GET /v1/events/ws`** upgrades to a WebSocket connection. Each message is a JSON-encoded event. ### TypeScript ```typescript theme={null} const ws = new WebSocket('wss://api.grantex.dev/v1/events/ws', { headers: { Authorization: `Bearer ${process.env.GRANTEX_API_KEY}` }, }); ws.on('message', (raw) => { const event = JSON.parse(raw.toString()); console.log(event.type, event.data); }); ``` ### Python ```python theme={null} import asyncio import json import os import websockets async def listen(): uri = "wss://api.grantex.dev/v1/events/ws" headers = {"Authorization": f"Bearer {os.environ['GRANTEX_API_KEY']}"} async with websockets.connect(uri, additional_headers=headers) as ws: async for message in ws: event = json.loads(message) print(event["type"], event["data"]) asyncio.run(listen()) ``` ## @grantex/destinations Package The `@grantex/destinations` package provides a high-level `EventSource` class that connects to the SSE stream and dispatches events to one or more destinations. Install it with: ```bash theme={null} npm install @grantex/destinations ``` ### Basic Usage ```typescript theme={null} import { EventSource, DatadogDestination, SplunkDestination } from '@grantex/destinations'; const source = new EventSource({ url: 'https://api.grantex.dev', apiKey: process.env.GRANTEX_API_KEY!, types: ['grant.created', 'grant.revoked', 'token.issued'], }); // Add one or more destinations source.addDestination(new DatadogDestination({ apiKey: process.env.DD_API_KEY!, })); source.addDestination(new SplunkDestination({ hecUrl: 'https://splunk.example.com:8088', hecToken: process.env.SPLUNK_HEC_TOKEN!, })); // Start streaming — runs until you call stop() await source.start(); ``` ### Available Destinations | Destination | Class | Target | | - | - | - | | Datadog | `DatadogDestination` | Datadog Logs API | | Splunk | `SplunkDestination` | Splunk HTTP Event Collector | | Amazon S3 | `S3Destination` | S3 bucket (NDJSON files) | | BigQuery | `BigQueryDestination` | Google BigQuery table | | Kafka | `KafkaDestination` | Apache Kafka topic | ### Shared Configuration All destinations accept these base options: | Option | Type | Default | Description | | - | - | - | - | | `batchSize` | `number` | Varies | Number of events to buffer before flushing | | `flushIntervalMs` | `number` | — | Flush on a timer (milliseconds) even if batch is not full | ### Graceful Shutdown Always call `stop()` to flush pending events and close connections: ```typescript theme={null} process.on('SIGTERM', async () => { await source.stop(); process.exit(0); }); ``` ### Custom Destinations Implement the `EventDestination` interface to build your own: ```typescript theme={null} import type { EventDestination, GrantexEvent } from '@grantex/destinations'; class MyDestination implements EventDestination { readonly name = 'my-destination'; async send(events: GrantexEvent[]): Promise { for (const event of events) { // Forward to your system } } async flush(): Promise { // Flush any buffered events } async close(): Promise { // Clean up resources } } ``` ## Next Steps * [Datadog Integration](/guides/siem-datadog) — monitors and alerting * [Splunk Integration](/guides/siem-splunk) — saved searches and dashboards * [S3 & BigQuery Archival](/guides/siem-s3-bigquery) — compliance archival * [Webhooks](/guides/webhooks) — push-based event delivery to your own endpoints * [Metrics & Observability](/guides/metrics-observability) — Prometheus metrics and Grafana dashboards ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # iOS + Gemma 4 Guide Source: https://docs.grantex.dev/guides/ios-gemma4 Integrate Grantex offline authorization into iOS apps running Gemma 4 agents using Swift and CryptoKit. ## Overview iOS apps running Gemma 4 via Google AI Edge SDK can use Grantex consent bundles for offline authorization. This guide covers Keychain storage, RS256 JWT verification with CryptoKit and Security framework, Ed25519 audit signing, and hash-chained audit logging in Swift. ## Prerequisites * **Xcode 15+** with iOS 16+ deployment target * **Swift 5.9+** * **A Grantex account** with API key and registered agent ## Step 1: SwiftPM Setup Add the following dependencies to your `Package.swift` or via Xcode's package manager: ```swift theme={null} // Package.swift dependencies: [ .package(url: "https://github.com/nicklama/jose-swift.git", from: "3.0.0"), ] ``` Alternatively, for a lighter approach, use Apple's CryptoKit and Security framework directly (no third-party JWT library needed). This guide shows both approaches. ## Step 2: Define the ConsentBundle Model ```swift theme={null} import Foundation struct JWKSSnapshot: Codable { let keys: [[String: String]] let fetchedAt: String let validUntil: String } struct OfflineAuditKey: Codable { let publicKey: String let privateKey: String let algorithm: String } struct ConsentBundle: Codable { let bundleId: String let grantToken: String let jwksSnapshot: JWKSSnapshot let offlineAuditKey: OfflineAuditKey let checkpointAt: Int64 let syncEndpoint: String let offlineExpiresAt: String } ``` ## Step 3: Keychain Storage Store the consent bundle in the iOS Keychain, which provides hardware-backed encryption on devices with Secure Enclave. ```swift theme={null} import Security import Foundation enum BundleStore { private static let service = "dev.grantex.bundle" private static let account = "active_bundle" static func store(_ bundle: ConsentBundle) throws { let data = try JSONEncoder().encode(bundle) // Delete existing item let deleteQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, ] SecItemDelete(deleteQuery as CFDictionary) // Add new item let addQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecValueData as String: data, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, ] let status = SecItemAdd(addQuery as CFDictionary, nil) guard status == errSecSuccess else { throw BundleStoreError.keychainWriteFailed(status) } } static func load() throws -> ConsentBundle? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { if status == errSecItemNotFound { return nil } throw BundleStoreError.keychainReadFailed(status) } return try JSONDecoder().decode(ConsentBundle.self, from: data) } static func clear() { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, ] SecItemDelete(query as CFDictionary) } } enum BundleStoreError: Error { case keychainWriteFailed(OSStatus) case keychainReadFailed(OSStatus) } ``` ## Step 4: JWT Verification with Security Framework Verify RS256 JWTs using Apple's Security framework. This avoids third-party dependencies and runs entirely on-device. ```swift theme={null} import Foundation import Security import CryptoKit struct VerifiedGrant { let agentDID: String let principalDID: String let scopes: [String] let expiresAt: Date let grantId: String let depth: Int } enum VerificationError: Error { case malformedToken case blockedAlgorithm(String) case missingKid case kidNotFound(String) case signatureInvalid case tokenExpired(Date) case scopeViolation(required: [String], granted: [String]) case delegationDepthExceeded(Int, max: Int) } class OfflineVerifier { private let jwksSnapshot: JWKSSnapshot private let requiredScopes: [String] private let clockSkewSeconds: TimeInterval private let maxDelegationDepth: Int? private let blockedAlgorithms: Set = ["none", "HS256"] init( jwksSnapshot: JWKSSnapshot, requiredScopes: [String] = [], clockSkewSeconds: TimeInterval = 30, maxDelegationDepth: Int? = nil ) { self.jwksSnapshot = jwksSnapshot self.requiredScopes = requiredScopes self.clockSkewSeconds = clockSkewSeconds self.maxDelegationDepth = maxDelegationDepth } func verify(_ token: String) throws -> VerifiedGrant { let parts = token.split(separator: ".") guard parts.count == 3 else { throw VerificationError.malformedToken } // Decode header guard let headerData = base64urlDecode(String(parts[0])), let header = try? JSONSerialization.jsonObject(with: headerData) as? [String: Any] else { throw VerificationError.malformedToken } // Check algorithm let alg = header["alg"] as? String ?? "" guard !blockedAlgorithms.contains(alg) else { throw VerificationError.blockedAlgorithm(alg) } // Get kid guard let kid = header["kid"] as? String else { throw VerificationError.missingKid } // Find key in snapshot guard let jwk = jwksSnapshot.keys.first(where: { $0["kid"] == kid }) else { throw VerificationError.kidNotFound(kid) } // Build RSA public key from JWK let publicKey = try rsaPublicKey(from: jwk) // Verify RS256 signature let signedInput = "\(parts[0]).\(parts[1])" guard let signatureData = base64urlDecode(String(parts[2])), let signedData = signedInput.data(using: .utf8) else { throw VerificationError.malformedToken } let verified = SecKeyVerifySignature( publicKey, .rsaSignatureMessagePKCS1v15SHA256, signedData as CFData, signatureData as CFData, nil ) guard verified else { throw VerificationError.signatureInvalid } // Decode payload guard let payloadData = base64urlDecode(String(parts[1])), let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any] else { throw VerificationError.malformedToken } // Check expiry guard let exp = payload["exp"] as? TimeInterval else { throw VerificationError.malformedToken } let expiresAt = Date(timeIntervalSince1970: exp) let now = Date() guard expiresAt.addingTimeInterval(clockSkewSeconds) > now else { throw VerificationError.tokenExpired(expiresAt) } // Extract claims let scopes = payload["scp"] as? [String] ?? [] let agentDID = payload["agt"] as? String ?? "" let principalDID = payload["sub"] as? String ?? "" let jti = payload["jti"] as? String ?? "" let grantId = payload["grnt"] as? String ?? jti let depth = payload["delegationDepth"] as? Int ?? 0 // Enforce scopes if !requiredScopes.isEmpty { let missing = requiredScopes.filter { !scopes.contains($0) } guard missing.isEmpty else { throw VerificationError.scopeViolation( required: requiredScopes, granted: scopes ) } } // Enforce delegation depth if let max = maxDelegationDepth, depth > max { throw VerificationError.delegationDepthExceeded(depth, max: max) } return VerifiedGrant( agentDID: agentDID, principalDID: principalDID, scopes: scopes, expiresAt: expiresAt, grantId: grantId, depth: depth ) } // MARK: - Helpers private func base64urlDecode(_ string: String) -> Data? { var base64 = string .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") let remainder = base64.count % 4 if remainder > 0 { base64 += String(repeating: "=", count: 4 - remainder) } return Data(base64Encoded: base64) } private func rsaPublicKey(from jwk: [String: String]) throws -> SecKey { guard let n = jwk["n"], let e = jwk["e"], let nData = base64urlDecode(n), let eData = base64urlDecode(e) else { throw VerificationError.malformedToken } // Build DER-encoded RSA public key let keyData = buildRSAPublicKeyDER(modulus: nData, exponent: eData) let attributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeRSA, kSecAttrKeyClass as String: kSecAttrKeyClassPublic, kSecAttrKeySizeInBits as String: nData.count * 8, ] var error: Unmanaged? guard let key = SecKeyCreateWithData( keyData as CFData, attributes as CFDictionary, &error ) else { throw VerificationError.malformedToken } return key } private func buildRSAPublicKeyDER(modulus: Data, exponent: Data) -> Data { // ASN.1 DER encoding for RSA public key var modBytes = [UInt8](modulus) if modBytes[0] >= 0x80 { modBytes.insert(0x00, at: 0) } var expBytes = [UInt8](exponent) if expBytes[0] >= 0x80 { expBytes.insert(0x00, at: 0) } let modEncoded = asn1Integer(modBytes) let expEncoded = asn1Integer(expBytes) let sequence = asn1Sequence(modEncoded + expEncoded) return Data(sequence) } private func asn1Integer(_ bytes: [UInt8]) -> [UInt8] { return [0x02] + asn1Length(bytes.count) + bytes } private func asn1Sequence(_ bytes: [UInt8]) -> [UInt8] { return [0x30] + asn1Length(bytes.count) + bytes } private func asn1Length(_ length: Int) -> [UInt8] { if length < 0x80 { return [UInt8(length)] } if length < 0x100 { return [0x81, UInt8(length)] } return [0x82, UInt8(length >> 8), UInt8(length & 0xFF)] } } ``` ## Step 5: Audit Logging with CryptoKit Use CryptoKit for SHA-256 hashing and Ed25519 signing. ```swift theme={null} import CryptoKit import Foundation struct SignedAuditEntry: Codable { let seq: Int let timestamp: String let action: String let agentDID: String let grantId: String let scopes: [String] let result: String let prevHash: String let hash: String let signature: String } class OfflineAuditLog { private let privateKey: Curve25519.Signing.PrivateKey private let logURL: URL private var seq = 0 private var prevHash = "0000000000000000" init(signingKeyPem: String, logURL: URL) throws { // Parse Ed25519 private key from PEM let base64 = signingKeyPem .replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "") .replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "") .replacingOccurrences(of: "\n", with: "") guard let keyData = Data(base64Encoded: base64) else { throw AuditLogError.invalidSigningKey } // Ed25519 private key is the last 32 bytes of the PKCS#8 DER let seed = keyData.suffix(32) self.privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: seed) self.logURL = logURL // Resume from existing log if let data = try? String(contentsOf: logURL, encoding: .utf8) { let lines = data.split(separator: "\n").filter { !$0.isEmpty } if let lastLine = lines.last, let lastEntry = try? JSONDecoder().decode( SignedAuditEntry.self, from: Data(lastLine.utf8) ) { seq = lastEntry.seq prevHash = lastEntry.hash } } } func append( action: String, agentDID: String, grantId: String, scopes: [String], result: String ) throws -> SignedAuditEntry { seq += 1 let timestamp = ISO8601DateFormatter().string(from: Date()) // Compute hash let hashInput = "\(seq)|\(timestamp)|\(action)|\(agentDID)|\(grantId)|\(scopes.joined(separator: ","))|\(result)||\(prevHash)" let hashData = SHA256.hash(data: Data(hashInput.utf8)) let hash = hashData.map { String(format: "%02x", $0) }.joined() // Sign the hash let sigData = try privateKey.signature(for: Data(hash.utf8)) let signature = sigData.map { String(format: "%02x", $0) }.joined() let entry = SignedAuditEntry( seq: seq, timestamp: timestamp, action: action, agentDID: agentDID, grantId: grantId, scopes: scopes, result: result, prevHash: prevHash, hash: hash, signature: signature ) // Append to log file let json = try JSONEncoder().encode(entry) let line = String(data: json, encoding: .utf8)! + "\n" let handle = try FileHandle(forWritingTo: logURL) handle.seekToEndOfFile() handle.write(Data(line.utf8)) handle.closeFile() prevHash = hash return entry } } enum AuditLogError: Error { case invalidSigningKey } ``` ## Step 6: Usage in a SwiftUI App ```swift theme={null} import SwiftUI struct AgentView: View { @State private var status = "Initializing..." @State private var entries: [String] = [] var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Gemma 4 Agent").font(.title) Text(status).foregroundColor(.secondary) List(entries, id: \.self) { entry in Text(entry).font(.caption) } } .padding() .task { await runAgent() } } func runAgent() async { do { guard let bundle = try BundleStore.load() else { status = "No bundle. Provision first." return } let verifier = OfflineVerifier( jwksSnapshot: bundle.jwksSnapshot, requiredScopes: ["sensor:read"] ) let grant = try verifier.verify(bundle.grantToken) status = "Verified: \(grant.agentDID)" let logURL = FileManager.default .urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("audit.jsonl") let auditLog = try OfflineAuditLog( signingKeyPem: bundle.offlineAuditKey.privateKey, logURL: logURL ) let entry = try auditLog.append( action: "sensor.read", agentDID: grant.agentDID, grantId: grant.grantId, scopes: grant.scopes, result: "success" ) entries.append("#\(entry.seq): \(entry.action) [\(entry.hash.prefix(12))...]") } catch { status = "Error: \(error.localizedDescription)" } } } ``` ## Security Considerations * **Keychain protection class**: `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` prevents backup extraction and access when locked. * **App Transport Security**: ATS is enabled by default. The provisioning and sync calls to `api.grantex.dev` use HTTPS. * **Jailbreak detection**: Consider using frameworks like IOSSecuritySuite for high-security use cases. Jailbroken devices can extract Keychain items. * **Background execution**: If the agent runs in the background, ensure the Keychain item is accessible. Use `kSecAttrAccessibleAfterFirstUnlock` instead. * **Biometric gate**: Add `kSecAccessControlBiometryCurrentSet` to the Keychain item for Face ID / Touch ID protection before accessing the bundle. ## Next Steps * [Android Guide](/guides/android-gemma4) -- integrate on Android * [Raspberry Pi Guide](/guides/raspberry-pi) -- run on Pi 5 * [Offline Authorization](/features/offline-authorization) -- architecture and security model * [Gemma 4 SDK Reference](/sdks/gemma) -- complete API reference ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # MCP Certification Applications Source: https://docs.grantex.dev/guides/mcp-certification Register an MCP server and submit a Bronze, Silver, or Gold certification application. ## Current Status Grantex provides an MCP server directory and a certification **application intake** workflow. A server owner can register a server, request a Bronze, Silver, or Gold level, and inspect the resulting application status. Submitting an application does not certify a server. The current public contract creates an application with status `pending_conformance_test`; it does not promise an automated decision, a review deadline, a public badge URL, or an external compliance attestation. Do not display a Grantex certification badge unless the server record has been approved and Grantex provides a verifiable public record. ## Register a Server Use the developer portal: 1. Open [MCP Servers](https://grantex.dev/dashboard/mcp). 2. Select **Register MCP Server**. 3. Enter the server metadata and save it. 4. Open the server detail page. The same operation is available through `POST /v1/mcp/servers`. Registration requires a developer API key. ## Submit an Application On an MCP server detail page, choose Bronze, Silver, or Gold. The portal calls: ```http theme={null} POST /v1/mcp/certification/apply Authorization: Bearer gx_live_... Content-Type: application/json { "serverId": "mcp_01HXYZ...", "requestedLevel": "bronze" } ``` Accepted levels are `bronze`, `silver`, and `gold`. A successful submission returns HTTP `202`: ```json theme={null} { "certificationId": "cert_01HXYZ...", "serverId": "mcp_01HXYZ...", "requestedLevel": "bronze", "status": "pending_conformance_test", "conformancePassed": 0, "conformanceTotal": 13, "createdAt": "2026-07-10T12:00:00.000Z" } ``` The server must belong to the authenticated developer. Submitting a duplicate active application may return a conflict response. ## Check Application Status ```http theme={null} GET /v1/mcp/certification/cert_01HXYZ... Authorization: Bearer gx_live_... ``` The response includes the requested level, current status, conformance counts and results when available, and reviewer fields when a review has occurred. Treat the returned status as the source of truth. ## Conformance Suite `@grantex/conformance` validates a Grantex-compatible authorization service. It does not currently accept `--issuer`, `--tier`, or `--output` flags and does not by itself submit an MCP certification application. ```bash theme={null} npx @grantex/conformance \ --base-url https://api.grantex.dev \ --api-key "$GRANTEX_API_KEY" \ --format json ``` Available options include `--suite`, `--include`, `--format text|json`, and `--bail`. Redirect standard output if you need to retain the JSON report: ```bash theme={null} npx @grantex/conformance \ --base-url https://api.grantex.dev \ --api-key "$GRANTEX_API_KEY" \ --format json > conformance-results.json ``` The general Grantex conformance report and MCP certification application are separate contracts today. The application endpoint does not accept an uploaded report. ## Badges and Public Claims There is currently no documented public badge asset, Shields.io endpoint, or `/mcp-registry/:slug` verification page. Until a stable verification URL is published: * do not link to guessed badge or registry paths; * do not claim that a pending application is certified; * do not claim SOC 2, GDPR, OAuth, or production-readiness certification from a Grantex application; * link to an official server record only when the API or portal exposes one as approved. ## Related * [MCP Auth Server](/features/mcp-auth-server) * [Conformance Suite](/integrations/conformance) * [MCP Server (17 tools)](/integrations/mcp) * [Security Best Practices](/guides/security-best-practices) ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Metrics & Observability Source: https://docs.grantex.dev/guides/metrics-observability Monitor your Grantex deployment with Prometheus metrics, Grafana dashboards, alerting rules, and structured logging. Effective monitoring ensures your Grantex deployment is healthy, performant, and secure. This guide covers the native Prometheus metrics endpoint, Grafana dashboard templates, alert thresholds, and logging best practices. ## Prometheus Metrics Endpoint The auth service exposes a `GET /metrics` endpoint in Prometheus exposition format: ```bash theme={null} curl https://your-auth-service/metrics ``` This endpoint is **unauthenticated** (no API key required) and rate-limited to 10 requests/minute per IP. ### Counters | Metric | Labels | Description | | - | - | - | | `grantex_token_exchange_total` | `status` | Token exchange attempts | | `grantex_authorize_total` | `status` | Authorization requests | | `grantex_grants_revoked_total` | — | Grants revoked (including cascade) | | `grantex_webhook_deliveries_total` | `status` | Webhook delivery outcomes | | `grantex_anomalies_detected_total` | `type`, `severity` | Anomalies detected | | `grantex_rate_limit_decisions_total` | `bucket`, `outcome` | Per-developer rate-limit decisions. `bucket`: `plan`, `containment` or `status`; `outcome`: `allowed`, `limited`, `unavailable` (refused with `503`), or `local_allowed` / `local_limited` for a containment call counted in-process while Redis was unreachable. See the [rate limits guide](/guides/rate-limits) | ### Histograms | Metric | Labels | Description | | - | - | - | | `grantex_authorize_duration_seconds` | — | Authorization request duration | | `grantex_token_exchange_duration_seconds` | — | Token exchange duration | | `grantex_http_request_duration_seconds` | `method`, `route`, `status_code` | HTTP request duration (all routes) | ### Gauges | Metric | Description | | - | - | | `grantex_active_grants` | Current active grants count | | `grantex_anomalies_unacknowledged` | Unacknowledged anomalies | ### Environment Variables | Variable | Default | Description | | - | - | - | | `METRICS_ENABLED` | `true` | Set to `false` to disable metrics collection | ## Grafana Dashboards Pre-built Grafana dashboards are available at [`deploy/grafana/`](https://github.com/mishrasanjeev/grantex/tree/main/deploy/grafana): | Dashboard | Description | | - | - | | `overview-dashboard.json` | Token exchange rate, success rate gauge, latency p50/p99, grants revoked, active grants, webhook deliveries, anomalies, HTTP error rate | | `per-agent-dashboard.json` | Per-agent drill-down with a `$agent_id` template variable | ### Import Instructions 1. In Grafana, go to **Dashboards > Import** 2. Upload the JSON file or paste its contents 3. Select your Prometheus data source when prompted (`${DS_PROMETHEUS}`) 4. Click **Import** ## Health Check Endpoint The auth service exposes a `GET /health` endpoint that returns the service status: ```bash theme={null} curl https://your-auth-service/health ``` ```json theme={null} { "status": "ok" } ``` Use this endpoint for: * **Load balancer health checks** — poll `/health` every 10–30 seconds * **Uptime monitoring** — UptimeRobot, Pingdom, Cloud Monitoring * **Kubernetes liveness probes** — `livenessProbe.httpGet.path: /health` ## Alerting Thresholds Recommended thresholds for production alerting: | Metric | Warning | Critical | Action | | - | - | - | - | | Token exchange failure rate | > 5% | > 15% | Check auth service logs | | Token refresh failure rate | > 5% | > 15% | Check for refresh token reuse or clock skew | | Anomalies detected | > 5/hour | > 10/hour | Review anomaly details | | Webhook delivery success | \< 98% | \< 95% | Verify endpoint availability | | 429 rate | > 50/min | > 200/min | Client misconfiguration or abuse | | Auth request latency (p99) | > 500ms | > 2s | Database or Redis performance issue | | Health check failures | 1 consecutive | 3 consecutive | Service restart | ### Alertmanager Rules ```yaml theme={null} groups: - name: grantex rules: - alert: HighTokenExchangeFailureRate expr: | sum(rate(grantex_token_exchange_total{status!="success"}[5m])) / sum(rate(grantex_token_exchange_total[5m])) > 0.05 for: 5m labels: severity: warning annotations: summary: "Token exchange failure rate > 5%" - alert: HighAuthLatency expr: | histogram_quantile(0.99, rate(grantex_authorize_duration_seconds_bucket[5m])) > 2 for: 5m labels: severity: warning annotations: summary: "Authorization p99 latency > 2s" - alert: WebhookDeliveryFailure expr: | sum(rate(grantex_webhook_deliveries_total{status="failed"}[5m])) / sum(rate(grantex_webhook_deliveries_total[5m])) > 0.05 for: 5m labels: severity: warning annotations: summary: "Webhook delivery failure rate > 5%" ``` ## Logging ### Structured Logging The auth service uses [Pino](https://getpino.io/) for JSON-structured logging: ```json theme={null} { "level": "info", "msg": "grant.created", "timestamp": "2026-03-01T12:00:00.000Z", "grantId": "grnt_abc123", "agentId": "ag_def456", "principalId": "user_789", "scopes": ["calendar:read", "email:send"], "latencyMs": 45 } ``` ### What to Log | Event | Log Level | Key Fields | | - | - | - | | Grant created | `info` | `grantId`, `agentId`, `principalId`, `scopes` | | Grant revoked | `info` | `grantId`, `revokedBy`, `cascadeCount` | | Token exchanged | `info` | `grantId`, `agentId` | | Token refreshed | `info` | `grantId`, `agentId` | | Token verification failed | `warn` | `reason`, `tokenId` | | Auth request denied | `warn` | `agentId`, `principalId`, `reason` | | Rate limit hit | `warn` | `ip`, `endpoint`, `retryAfter` | | Anomaly detected | `warn` | `type`, `severity`, `agentId` | | Webhook delivery failed | `error` | `webhookId`, `url`, `statusCode`, `attempt` | | Database connection error | `error` | `error`, `pool` | ## Webhook-Based Monitoring Subscribe to webhook events for real-time alerting without polling: ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY! }); await grantex.webhooks.create({ url: 'https://your-app.com/webhooks/grantex-alerts', events: ['grant.revoked', 'token.issued'], secret: process.env.WEBHOOK_SECRET!, }); ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # Migration Guide Source: https://docs.grantex.dev/guides/migration Step-by-step guides for migrating to Grantex from API keys or raw OAuth 2.0. ## Migrating from API Keys + Manual Audit If your agents currently authenticate with static API keys and you log actions manually, Grantex gives you verifiable permissions, automatic audit trails, and user-controlled revocation. ### What stays the same * Your upstream APIs (Google Calendar, Slack, Stripe, etc.) — agents still call them * Your users — they keep their accounts and data * Your agent logic — business logic doesn't change ### What changes * **Auth layer**: Static API keys are replaced with scoped, time-bound grant tokens * **Audit**: Manual logging is replaced with tamper-evident, hash-chained audit entries * **Revocation**: Instead of rotating API keys, users revoke grants through a dashboard ### Step-by-step Sign up at the [Grantex portal](https://grantex.dev/dashboard) or use the SDK: ```typescript theme={null} const grantex = new Grantex({ baseUrl: 'https://your-auth-service.com' }); const dev = await grantex.developers.register({ name: 'Acme Corp', email: 'dev@acme.com', }); ``` ```typescript theme={null} const agent = await grantex.agents.create({ name: 'Calendar Assistant', description: 'Manages calendar events on behalf of users', scopes: ['calendar:read', 'calendar:write'], }); ``` Before (API key): ```typescript theme={null} // Old: static key, no user consent, no audit const response = await fetch('/api/events', { headers: { Authorization: `Bearer ${API_KEY}` }, }); ``` After (Grantex): ```typescript theme={null} // New: user-consented, scoped, audited const { grantToken } = await grantex.tokens.exchange({ code: authorizationCode, agentId: agent.agentDid, }); const response = await fetch('/api/events', { headers: { Authorization: `Bearer ${grantToken}` }, }); ``` Use the Express middleware or verify manually: ```typescript theme={null} import { createGrantexMiddleware } from '@grantex/express'; app.use(createGrantexMiddleware({ baseUrl: 'https://your-auth-service.com', apiKey: process.env.GRANTEX_API_KEY, requiredScopes: ['calendar:read'], })); ``` Log actions automatically through the SDK: ```typescript theme={null} await grantex.audit.log({ agentId, agentDid, principalId, grantId, action: 'calendar.event.created', status: 'success', metadata: { resource: `event:${eventId}` }, }); ``` *** ## Migrating from Raw OAuth 2.0 If you already use OAuth 2.0 for agent authorization, Grantex extends the model with agent-aware grants, delegation, and built-in audit. ### What stays the same * Your **identity provider** (Okta, Auth0, Google) — keep using it for user login * Your **users** and their accounts * The general **authorize → consent → token** flow pattern ### What changes * **Token format**: Opaque access tokens become Grantex JWTs with agent, principal, and scope claims * **Delegation**: OAuth has no standard sub-delegation — Grantex tracks the full chain * **Audit**: No more bolting on audit logging — it's built into the protocol * **Revocation**: Grant-level revocation cascades to all tokens (not just individual token revocation) ### Step-by-step Continue using OIDC for user authentication. Map the authenticated user to a Grantex `principalId`. Before (OAuth): ``` GET /oauth/authorize?client_id=...&scope=read+write&redirect_uri=... POST /oauth/token (authorization_code grant) ``` After (Grantex): ```typescript theme={null} const { consentUrl } = await grantex.authorize({ agentId: agent.agentDid, userId: user.id, scopes: ['calendar:read', 'calendar:write'], callbackUrl: 'https://app.com/callback', }); // Redirect user to consentUrl // On callback, exchange the code: const { grantToken } = await grantex.tokens.exchange({ code, agentId: agent.agentDid, }); ``` Before (OAuth introspection): ``` POST /oauth/introspect { token: "..." } ``` After (Grantex signature-and-claim verification): ```typescript theme={null} import { verifyGrantToken } from '@grantex/sdk'; const result = await verifyGrantToken(token, { jwksUri: 'https://your-auth-service.com/.well-known/jwks.json', }); // Claims are verified locally after the standalone helper fetches JWKS ``` If your agents delegate to sub-agents: ```typescript theme={null} const delegated = await grantex.grants.delegate({ grantId: parentGrant.grantId, agentId: subAgent.agentDid, scopes: ['calendar:read'], // subset of parent scopes }); ``` Give users self-service grant management: ```typescript theme={null} const { dashboardUrl } = await grantex.principalSessions.create({ principalId: user.id, }); // Redirect user to dashboardUrl ``` ## Further Reading * [Quickstart](/quickstart) — Get running in 5 minutes * [Token Verification](/guides/token-verification) — Online and local JWKS-based verification * [End-User Permissions](/guides/end-user-permissions) — Principal sessions and dashboard * [Express Middleware](/integrations/express) — Drop-in middleware for Node.js APIs * [FastAPI Middleware](/integrations/fastapi) — Drop-in middleware for Python APIs ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # MPP Integration Guide Source: https://docs.grantex.dev/guides/mpp-integration Step-by-step guide to adding Grantex agent identity to MPP payment flows — for both agents and merchants. ## Overview This guide walks through integrating Grantex agent passports with the Machine Payments Protocol (MPP). You'll learn how to: 1. **Agent side** — Issue passports and attach them to outgoing MPP requests 2. **Merchant side** — Verify passports on incoming requests with one line of code 3. **Trust registry** — Look up organization trust levels before fulfilling requests ## Prerequisites * A Grantex account with an API key ([sign up free](https://grantex.dev/dashboard/signup)) * A registered agent with an active grant that includes `payments:mpp:*` scopes * Node.js 18+ for the TypeScript SDK ## Installation ```bash theme={null} npm install @grantex/sdk @grantex/mpp ``` ## Agent Side: Issue and Attach Passports ### Step 1: Issue a Passport ```typescript theme={null} import { Grantex } from '@grantex/sdk'; const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY }); // Issue a passport for your agent const passport = await grantex.passports.issue({ agentId: 'ag_01HXYZ...', grantId: 'grnt_01HXYZ...', allowedMPPCategories: ['inference', 'compute'], maxTransactionAmount: { amount: 50, currency: 'USDC' }, paymentRails: ['tempo'], expiresIn: '24h', // max: 720h (30 days) }); console.log(passport.passportId); // "urn:grantex:passport:01HXYZ..." ``` The grant must include the corresponding `payments:mpp:*` scopes for each category. For example, requesting `['inference', 'compute']` requires the grant to have `payments:mpp:inference` and `payments:mpp:compute` scopes. ### Step 2: Attach to MPP Requests Use the middleware to automatically inject the `X-Grantex-Passport` header: ```typescript theme={null} import { createMppPassportMiddleware } from '@grantex/mpp'; const middleware = createMppPassportMiddleware({ passport, autoRefreshThreshold: 300, // warn 5 min before expiry }); // Wrap every outgoing fetch const request = new Request('https://merchant.example.com/api/inference', { method: 'GET', headers: { 'Authorization': 'Payment ' }, }); const enrichedRequest = await middleware(request); const response = await fetch(enrichedRequest); // enrichedRequest has X-Grantex-Passport header attached ``` ## Merchant Side: Verify Passports ### Option A: Express Middleware (Recommended) One line to protect any route: ```typescript theme={null} import express from 'express'; import { requireAgentPassport } from '@grantex/mpp'; const app = express(); // Protect inference endpoints — require inference category app.use('/api/inference', requireAgentPassport({ requiredCategories: ['inference'], maxAmount: 10, })); app.get('/api/inference', (req, res) => { // req.agentPassport is populated console.log(req.agentPassport.humanDID); // "did:grantex:user_alice" console.log(req.agentPassport.organizationDID); // "did:web:acme.com" console.log(req.agentPassport.allowedCategories); // ["inference", "compute"] res.json({ result: 'inference complete' }); }); ``` Invalid passports return `403` with a typed error code: ```json theme={null} { "error": "CATEGORY_MISMATCH", "message": "Passport does not cover required categories: storage" } ``` ### Option B: Standalone Verification ```typescript theme={null} import { verifyPassport } from '@grantex/mpp'; const encodedPassport = req.headers['x-grantex-passport']; try { const verified = await verifyPassport(encodedPassport, { trustedIssuers: ['did:web:grantex.dev'], requiredCategories: ['inference'], maxAmount: 10, checkRevocation: false, // true for online revocation check }); console.log(verified.valid); // true console.log(verified.humanDID); // "did:grantex:user_alice" console.log(verified.agentDID); // "did:grantex:ag_01HXYZ..." console.log(verified.grantId); // "grnt_01HXYZ..." console.log(verified.delegationDepth); // 0 } catch (err) { // err.code is a typed PassportErrorCode console.error(err.code, err.message); } ``` ## Trust Registry Lookup Before fulfilling a high-value request, check the organization's trust level: ```typescript theme={null} import { lookupOrgTrust } from '@grantex/mpp'; const record = await lookupOrgTrust(verified.organizationDID); if (!record || record.trustLevel === 'basic') { // Require additional verification for unverified orgs return res.status(403).json({ error: 'ORG_NOT_VERIFIED' }); } // record.trustLevel: "basic" | "verified" | "soc2" // record.verificationMethod: "dns-txt" | "manual" | "soc2" ``` Results are cached in-memory for 1 hour by default. ## Revoking Passports Instantly revoke a passport — flips the StatusList2021 bit: ```typescript theme={null} const result = await grantex.passports.revoke('urn:grantex:passport:01HXYZ...'); // { revoked: true, revokedAt: "2026-03-20T..." } ``` After revocation, any `verifyPassport()` call with `checkRevocation: true` will throw `PASSPORT_REVOKED`. ## Verification Options Reference | Option | Type | Default | Description | | - | - | - | - | | `jwksUri` | `string` | `https://api.grantex.dev/.well-known/jwks.json` | JWKS endpoint for signature verification | | `trustedIssuers` | `string[]` | `['did:web:grantex.dev']` | Allowed issuer DIDs | | `requiredCategories` | `MPPCategory[]` | `undefined` | Required MPP categories | | `maxAmount` | `number` | `undefined` | Minimum passport amount ceiling | | `checkRevocation` | `boolean` | `false` | Online revocation check via StatusList2021 | | `revocationEndpoint` | `string` | — | Required when `checkRevocation: true` | ## Next Steps * **[MPP Agent Passport](/features/mpp-agent-passport)** — credential specification and trust registry details * **[Budget Controls](/guides/budget-controls)** — combine passports with budget enforcement * **[Event Streaming](/guides/event-streaming)** — subscribe to `passport.issued` and `passport.revoked` events ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OACP Integration Guide For AgenticOrg Source: https://docs.grantex.dev/guides/oacp/agenticorg-integration How AgenticOrg requests and consumes Grantex OACP authority artifacts. # OACP Integration Guide For AgenticOrg Canonical end-to-end flow: [OACP authority overview](./overview). AgenticOrg calls Grantex only when it needs authority artifacts or verification. It does not route every buyer message through Grantex. ## Runtime Flow ```mermaid theme={null} sequenceDiagram participant M as Merchant participant A as AgenticOrg participant G as Grantex M->>A: Save merchant config and create Seller Commerce Agent A->>A: Store source/channel/provider/POS refs A->>A: Read-only Shopify sync and redacted evidence A->>G: POST /v1/commerce/oacp/c6z/authority-requests G-->>A: Signed/internal OACP artifacts or blockers A->>A: Cache artifacts by tenant, merchant, seller, buyer scope A->>A: Prepare purchase or Offline POS handoff without execution A-->>M: Runtime ready, refresh needed, or blocker ``` ## Request Contract AgenticOrg sends: * tenant, merchant, seller agent, and source identifiers; * merchant self-service config refs for source connector, buyer channels, provider-owned payment rails, public publishing, and POS metadata; * requested OACP artifact families; * source observed timestamp; * public-safe connector evidence; * no raw Shopify credential, raw provider payload, checkout URL, or payment URL. * no raw POS payload, raw payment payload, order-created claim, or POS paid-state claim. Grantex returns: * `201 artifact_issuance_ready` with artifact families when ready; * `202 received` when connector evidence is still required; * `422` when the request is private, stale, executable, or otherwise unsafe. ## Public Catalog Publishing Boundary AgenticOrg publishes public buyer-safe seller and product surfaces after it has valid Shopify/OACP evidence and the operator enables public catalog publishing. Grantex does not host these catalog pages and does not sit in the hot path for non-binding buyer questions. Expected AgenticOrg public surfaces: | Surface | AgenticOrg route | | - | - | | Seller profile page | `GET /api/v1/public/commerce/sellers/{merchant_id}?tenant_id=...&seller_agent_id=...` | | Buyer-safe catalog JSON | `GET /api/v1/public/commerce/sellers/{merchant_id}/catalog.json?tenant_id=...&seller_agent_id=...` | | Product detail page | `GET /api/v1/public/commerce/sellers/{merchant_id}/products/{product_slug}?tenant_id=...&seller_agent_id=...` | | Product detail JSON | `GET /api/v1/public/commerce/sellers/{merchant_id}/products/{product_slug}.json?tenant_id=...&seller_agent_id=...` | | Schema.org JSON-LD | `GET /api/v1/public/commerce/sellers/{merchant_id}/schema-org.jsonld?tenant_id=...&seller_agent_id=...` | | Sitemap | `GET /api/v1/public/commerce/sellers/{merchant_id}/sitemap.xml?tenant_id=...&seller_agent_id=...` | | llms.txt | `GET /api/v1/public/commerce/sellers/{merchant_id}/llms.txt?tenant_id=...&seller_agent_id=...` | Those outputs must include source and freshness labels, must not expose Shopify credentials or raw provider payloads, and must not claim checkout, payment, mandate, order, certification, or external platform approval. If the AgenticOrg cache is stale, revoked, or source-mismatched, the public surface must block or label the missing requirement instead of inventing facts. ## Required Configuration | Config | Owner | Purpose | | - | - | - | | `COMMERCE_C6Z_AUTHORITY_SERVICE_TOKEN` | Grantex + AgenticOrg | Service-token auth for authority requests. | | `COMMERCE_C6Z_AUTHORITY_SERVICE_TENANTS` | Grantex | Tenant allowlist. | | Shopify credential custody | AgenticOrg + merchant | Read-only source evidence generation. | | Future connector config | AgenticOrg + merchant | WooCommerce, ERP, PIM, OMS, WMS, and custom API setup intent; not live until adapters exist. | | Provider capability config | AgenticOrg + provider or bank | Capability evidence checks outside Grantex. | | POS provider approval | AgenticOrg + merchant + POS/payment provider | Verified callback/evidence refs for Offline POS Bridge reconciliation. | ## Failure Handling If Grantex is unavailable, AgenticOrg may answer non-binding buyer questions from valid cached artifacts. Commitment-bound or POS-bound requests must refresh, prepare no execution, use only valid cached policy when risk permits, or refuse. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OACP Architecture Source: https://docs.grantex.dev/guides/oacp/architecture The Grantex view of the OACP four-party architecture. # OACP Architecture Canonical end-to-end flow: [OACP authority overview](./overview). OACP separates facts, authority, runtime behavior, and payment rails so no agent has to invent commerce state. ```mermaid theme={null} flowchart TD config[AgenticOrg merchant config] --> agenticSeller[AgenticOrg Seller Commerce Agent] shopify[Shopify and merchant systems] -->|catalog, price, inventory, policy facts| agenticSeller future[WooCommerce/ERP/bank/provider refs] -->|pending-adapter config only| config agenticSeller -->|redacted connector evidence| grantex[Grantex OACP authority] grantex -->|signed/internal artifacts or refusals| cache[AgenticOrg OACP cache] cache --> buyerAgent[AgenticOrg buyer agent] buyerAgent --> web[Web] buyerAgent --> mcp[MCP clients] buyerAgent --> openapi[OpenAPI clients] buyerAgent --> a2a[A2A card] buyerAgent --> search[Public catalog/search when enabled] buyerAgent --> wa[WhatsApp] buyerAgent --> tg[Telegram] buyerAgent -->|capability check only| provider[Pine Labs Plural/P3P, bank/POS/provider rails] provider -->|redacted evidence ref| cache ``` ## Responsibilities | Party | Owns | Does not own | | - | - | - | | Merchant systems | Catalog, price, inventory, order, policy, support, and source timestamps. | OACP artifact policy. | | AgenticOrg | Merchant self-service config, Seller and buyer agents, Shopify connector runtime, pending-adapter setup refs, cache, buyer channels, public publishing, POS handoff orchestration, and capability verification. | Canonical OACP authority or payment/bank/POS rail execution. | | Grantex | Policy, trust, artifacts, verification, and adapter authority. | Merchant connector runtime, buyer sessions, orders, or payments. | | Provider/bank/POS rails | Mandate and payment execution, provider status, settlement, receipt evidence, and provider webhooks. | OACP trust authority or buyer-agent answers. | ## Why Grantex Is Not In Every Loop Grantex signs and verifies authority artifacts. AgenticOrg can answer non-binding questions from valid cached artifacts until TTL, revocation, or policy state requires refresh. This reduces latency and avoids making Grantex a relay for every buyer question while preserving a hard boundary for commitment requests. ## Failure Shape ```mermaid theme={null} flowchart LR request[Buyer request] --> classify{Commitment?} classify -->|No| cache{Valid cached artifacts?} cache -->|Yes| answer[Answer with source and freshness] cache -->|No| refresh[Refresh or refuse] classify -->|Yes| handoff{Provider and merchant approval present?} handoff -->|Yes| prepare[Prepare handoff only] handoff -->|No| refuse[Refuse with blocker] ``` ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OACP Artifact Authority Source: https://docs.grantex.dev/guides/oacp/artifact-authority How Grantex issues, refuses, and verifies OACP artifacts for AgenticOrg. # OACP Artifact Authority Canonical end-to-end flow: [OACP authority overview](./overview). Grantex authority is exposed through `POST /v1/commerce/oacp/c6z/authority-requests`. The route accepts an AgenticOrg seller authority request and public-safe connector evidence, validates scope, and returns issued artifacts or a refusal. ```mermaid theme={null} sequenceDiagram participant A as AgenticOrg participant G as Grantex participant C as OACP cache A->>G: Authority request plus redacted Shopify evidence G->>G: Validate tenant, scope, source age, and unsafe fields alt ready G-->>A: 201 artifact_issuance_ready A->>C: Cache signed/internal OACP artifacts else blocked G-->>A: 202/422 with safe blocker end ``` ## Artifact Families | Family | Purpose | | - | - | | `merchant_profile` | Merchant identity and public profile evidence. | | `seller_agent_card` | Seller Commerce Agent identity and runtime boundary. | | `connector_evidence` | Public-safe source evidence from AgenticOrg connector custody. | | `catalog_snapshot` | Product catalog facts from Shopify or merchant source. | | `offer_price_snapshot` | Price and offer facts with freshness metadata. | | `inventory_snapshot` | Inventory/availability facts with source time. | | `policy_scope` | Merchant and Grantex policy boundaries. | | `public_discovery_state` | Whether buyer-safe discovery may be shown. | | `mandate_capability` | Provider-owned capability evidence requirement and state. | | `protocol_adapter` | Compatibility mapping metadata. | | `authority_request_status` | Request result and blocker state. | ## Verification Rules Artifacts must carry issuer, issuer key, artifact type, issued/expires timestamps, payload hash, signature algorithm, source refs, freshness metadata, revocation posture, risk tier, and `no_checkout_payment_enablement` behavior. Payloads must not contain raw connector payloads, tokens, provider secrets, card or bank data, checkout URLs, payment URLs, executable targets, or private merchant data. ## Pending Runtime Gap Detached signature verification and key governance are implemented internally, but external public key distribution, rotation policy publication, and partner acceptance remain approval work before external program launch. ## Ownership Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com). # OACP Buyer Safety And Freshness Source: https://docs.grantex.dev/guides/oacp/buyer-safety-freshness How source, freshness, cache, and refusal behavior keep buyer agents grounded. # OACP Buyer Safety And Freshness Canonical end-to-end flow: [OACP authority overview](./overview). Buyer agents can ask, compare, reason, prepare, and hand off. They cannot invent product facts, stock promises, discounts, payment outcomes, orders, or mandates. ```mermaid theme={null} flowchart TD ask[Buyer asks] --> cache{Cached OACP artifacts valid?} cache -->|Yes| risk{Low-risk answer?} risk -->|Yes| answer[Answer with source and freshness] risk -->|No| prepare{Commitment checks pass?} prepare -->|Yes| handoff[Prepared handoff only] prepare -->|No| refuse[Safe refusal] cache -->|No| unavailable{Grantex available?} unavailable -->|Yes| refresh[Refresh authority request] unavailable -->|No| refuse2[Refuse commitment or stale answer] ``` ## Valid Cache, Grantex Unavailable If Grantex is temporarily unavailable and the cached artifacts are valid for the action class, AgenticOrg may continue non-binding Q\&A with visible source and freshness labels. ## Grantex Unavailable And Commitment Requested If the buyer asks for checkout, order, payment, mandate, refund, return, shipment, or inventory hold while Grantex is unavailable, AgenticOrg must refuse or prepare no further than a non-executing review handoff that clearly says execution did not occur. ## Safe User Wording * "I can answer from the merchant source snapshot last refreshed at `