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

# Service Provider Adapters

> Pre-built integrations that translate Grantex grants into real API calls.

## Overview

`@grantex/adapters` provides pre-built adapters for popular APIs — Google Calendar, Gmail, Stripe, and Slack. Each adapter verifies grant tokens offline (JWKS), checks scopes, enforces constraints, and calls the upstream API.

```bash theme={null}
npm install @grantex/adapters@0.2.1 @grantex/sdk@0.8.2
```

Node.js 22.12+ is required. Configure the expected resource audience and
derive trusted amounts at your service boundary. Offline adapter checks
do not prove current revocation; combine them with operational current-state
enforcement. See [migration](/migration-enforcement).

## Google Calendar

Scopes: `calendar:read`, `calendar:write`

```typescript theme={null}
import { GoogleCalendarAdapter } from '@grantex/adapters';

const calendar = new GoogleCalendarAdapter({
  jwksUri: 'https://your-auth-server/.well-known/jwks.json',
  credentials: process.env.GOOGLE_ACCESS_TOKEN,
});

// List events (requires calendar:read)
const events = await calendar.listEvents(grantToken, {
  timeMin: new Date().toISOString(),
  maxResults: 10,
});

// Create event (requires calendar:write)
const created = await calendar.createEvent(grantToken, {
  summary: 'Team Sync',
  start: { dateTime: '2026-03-01T10:00:00Z' },
  end: { dateTime: '2026-03-01T11:00:00Z' },
});
```

## Gmail

Scopes: `email:read`, `email:send`

```typescript theme={null}
import { GmailAdapter } from '@grantex/adapters';

const gmail = new GmailAdapter({ jwksUri, credentials });

// List messages (requires email:read)
const messages = await gmail.listMessages(grantToken, { q: 'from:alice' });

// Send message (requires email:send)
await gmail.sendMessage(grantToken, {
  to: 'bob@example.com',
  subject: 'Hello',
  body: 'Hi Bob!',
});
```

## Stripe

Scopes: `payments:read`, `payments:initiate`

Supports constraint enforcement — `payments:initiate:max_500` limits payments to \$500.

```typescript theme={null}
import { StripeAdapter } from '@grantex/adapters';

const stripe = new StripeAdapter({
  jwksUri,
  credentials: process.env.STRIPE_SECRET_KEY,
});

// List payment intents (requires payments:read)
const intents = await stripe.listPaymentIntents(grantToken, { limit: 10 });

// Create payment intent (requires payments:initiate)
// If grant has payments:initiate:max_500, amount must be <= $500
await stripe.createPaymentIntent(grantToken, {
  amount: 10000, // $100 in cents
  currency: 'usd',
});
```

## Slack

Scopes: `notifications:send`, `notifications:read`

```typescript theme={null}
import { SlackAdapter } from '@grantex/adapters';

const slack = new SlackAdapter({
  jwksUri,
  credentials: process.env.SLACK_BOT_TOKEN,
});

// Send message (requires notifications:send)
await slack.sendMessage(grantToken, {
  channel: 'C123ABC',
  text: 'Hello from agent!',
});

// List messages (requires notifications:read)
const history = await slack.listMessages(grantToken, {
  channel: 'C123ABC',
  limit: 20,
});
```

## Constraint Enforcement

Scopes can include constraints that adapters enforce automatically:

| Constraint | Meaning | Example |
| - | - | - |
| `max_N` | Value must be \<= N | `payments:initiate:max_500` — max \$500 |
| `min_N` | Value must be >= N | `payments:initiate:min_10` — min \$10 |
| `limit_N` | Count must be \<= N | `api:calls:limit_1000` — max 1000 calls |

```typescript theme={null}
import { parseScope, enforceConstraint } from '@grantex/adapters';

const parsed = parseScope('payments:initiate:max_500');
// { baseScope: 'payments:initiate', constraint: { type: 'max', value: 500 } }

const result = enforceConstraint(parsed, 600);
// { allowed: false, reason: 'Value 600 exceeds maximum 500' }
```

## Dynamic Credentials

Pass an async function instead of a static string:

```typescript theme={null}
const calendar = new GoogleCalendarAdapter({
  jwksUri,
  credentials: async () => {
    return await refreshGoogleToken(serviceAccountId);
  },
});
```

## Audit Logging

Connect adapters to Grantex audit:

```typescript theme={null}
import { Grantex } from '@grantex/sdk';

const grantex = new Grantex({ apiKey, baseUrl });

const stripe = new StripeAdapter({
  jwksUri,
  credentials: stripeKey,
  auditLogger: (params) => grantex.audit.log(params),
});
```

## Grant token audience

<Warning>
  From version 0.2.0 (not in `@grantex/adapters` 0.1.5). This is a breaking
  change: a grant token that carries an `aud` claim is refused unless the
  adapter is configured with that audience. Tokens issued without an audience
  are unaffected.
</Warning>

Every adapter checks the token's `aud` claim
([RFC 7519 section 4.1.3](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3))
with the same semantics as `enforce()` in the SDKs, right after the signature
and before the upstream call. `aud` may be a string or an array of strings and
matches when the configured `audience` is one of its values, compared as exact
strings.

```typescript theme={null}
const stripe = new StripeAdapter({
  jwksUri,
  credentials: stripeKey,
  audience: 'https://api.merchant.example',
});
```

| Option | Type | Default | Description |
| - | - | - | - |
| `audience` | `string` | none | The audience the adapter expects. A token without it in `aud`, or without `aud`, throws `AUDIENCE_MISMATCH`. |
| `audienceCheck` | `'on' \| 'off'` | `'on'` | With `'on'` and no `audience`, a token that carries `aud` throws `AUDIENCE_UNCONFIGURED`. `'off'` ignores `aud`, as earlier releases did, and cannot be combined with `audience`. |

To keep the earlier behaviour while you find the audience, pass
`audienceCheck: 'off'`, and remove it once `audience` is set.

## Building Custom Adapters

Extend `BaseAdapter` to integrate any API:

```typescript theme={null}
import { BaseAdapter } from '@grantex/adapters';
import type { AdapterConfig, AdapterResult } from '@grantex/adapters';

class MyApiAdapter extends BaseAdapter {
  constructor(config: AdapterConfig) {
    super(config);
  }

  async getData(token: string): Promise<AdapterResult> {
    const { grant } = await this.verifyAndCheckScope(token, 'myapi:read');
    const credential = await this.resolveCredential();

    const data = await this.callUpstream('https://api.myservice.com/data', {
      method: 'GET',
      headers: { Authorization: `Bearer ${credential}` },
    });

    await this.logAudit(grant, 'myapi:getData', 'success');
    return this.wrapResult(grant, data);
  }
}
```

## Error Codes

| Code | Meaning |
| - | - |
| `TOKEN_INVALID` | Grant token verification failed, or its audience cannot be read |
| `AUDIENCE_UNCONFIGURED` | The token carries `aud` and no `audience` is configured (0.2.0) |
| `AUDIENCE_MISMATCH` | The token's `aud` does not contain the configured `audience`, or it has no `aud` (0.2.0) |
| `SCOPE_MISSING` | Grant doesn't include required scope |
| `CONSTRAINT_VIOLATED` | Value exceeds scope constraint |
| `UPSTREAM_ERROR` | Upstream API returned an error |
| `CREDENTIAL_ERROR` | Failed to resolve 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).

## Current Authority and Human Identity

Use the exact integration version and minimum primary SDK in [Release Status](/release-status).
Per-invocation issuer authority and trusted principal/agent binding are opt-in.
Configure the callback with the trusted issuer and derive identity from the host's
authenticated session; signature and scope checks alone do not prove current
revocation or human consent. See [SDK execution authority](/guides/sdk-execution-authority)
for configuration, denial behavior and manifest/decision/caps boundaries.


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