> ## 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` |
| `page`     | `number` | No       | Page number (default `1`)                        |
| `pageSize` | `number` | No       | Results per page (default `20`, 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": [
    {
      "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
}
```

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