> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Consent Bundles API

> 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 <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

```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

<CodeGroup>
  ```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",
  )
  ```
</CodeGroup>

***

## List Consent Bundles

List all consent bundles for the authenticated developer.

```
GET /v1/consent-bundles
```

### Request Headers

| 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` |
| `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"
}
```

<Note>
  The list response does not include `grantToken` or `offlineAuditKey.privateKey` for security. Only the creation response includes these sensitive fields.
</Note>

***

## 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 <api_key>` |

### 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 <api_key>` |

### 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 <api_key>` |

### 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).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.