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.
| Header | Value |
|---|
Authorization | Bearer <api_key> |
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
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
{
"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
import { createConsentBundle } from '@grantex/gemma';
const bundle = await createConsentBundle({
apiKey: 'gx_...',
agentId: 'ag_01HXYZ...',
userId: 'user_abc123',
scopes: ['calendar:read', 'email:send'],
offlineTTL: '72h',
});
List Consent Bundles
List all consent bundles for the authenticated developer.
| Header | Value |
|---|
Authorization | Bearer <api_key> |
Query Parameters
| Parameter | Type | Required | Description |
|---|
agentId | string | No | Filter by agent ID |
status | string | No | Filter by status: active, expired, revoked |
page | number | No | Page number (default 1) |
pageSize | number | No | Results per page (default 20, max 100) |
Example Request
curl https://api.grantex.dev/v1/consent-bundles?status=active \
-H "Authorization: Bearer gx_..."
Response — 200 OK
{
"bundles": [
{
"bundleId": "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
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
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...) |
| Header | Value |
|---|
Authorization | Bearer <api_key> |
Example Request
curl -X POST https://api.grantex.dev/v1/consent-bundles/cb_01HXYZ.../revoke \
-H "Authorization: Bearer gx_..."
Response — 200 OK
{
"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 |
| Header | Value |
|---|
Authorization | Bearer <api_key> |
Example Request
curl https://api.grantex.dev/v1/consent-bundles/cb_01HXYZ.../revocation-status \
-H "Authorization: Bearer gx_..."
Response — 200 OK
{
"bundleId": "cb_01HXYZ...",
"status": "active",
"revokedAt": null
}
Or if revoked:
{
"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 |
| Header | Value |
|---|
Authorization | Bearer <api_key> |
Example Request
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 |