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

# Exchange Grant Token for Service Credential

> 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:<service>:exchange` (for example `vault:github:exchange`)

<Warning>
  **Rollout note (2026-09-13, security sweep).** Releasing the raw upstream token is strictly more powerful than a `<service>:read` grant, so exchange no longer accepts wildcard or read-style scopes (`*`, `<service>:*`, `<service>:read`, `<service>:credentials:read`, `vault:<service>:*`, `vault:<service>: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:<service>:exchange`. In addition, key-bound grant tokens (those carrying `cnf.jkt`) must now be presented with a `DPoP` proof (`Authorization: DPoP <grant_token>` 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`.
</Warning>

## Request Headers

| Header | Value |
| - | - |
| `Authorization` | `Bearer <grant_token>` |
| `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 |

<Warning>
  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.
</Warning>

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

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

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