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

# Verifiable Credentials

> W3C Verifiable Credentials issued alongside grant tokens. Portable, tamper-proof proof of agent authorization for any verifier.

## Overview

The Grantex-hosted service enabled opt-in portable WebAuthn assertion evidence
on September 27, 2026. Self-hosted deployments must enable it explicitly.
The current TypeScript, Python, and Go SDKs listed in [Release Status](/release-status)
expose typed grant-evidence references and VC attestations.

Grantex can issue W3C Verifiable Credentials (VCs) alongside standard RS256 JWTs. While grant tokens are optimized for real-time authorization (short-lived, revocation-checked, scope-enforced), Verifiable Credentials provide a portable, standards-compliant proof of authorization that any party can verify independently using the Grantex DID document.

<Info>
  Verifiable Credentials are opt-in. Existing token exchange flows continue to work unchanged. Pass `credentialFormat: "vc-jwt"` during token exchange to receive a VC alongside the standard grant token.

  Portable WebAuthn evidence additionally requires `PORTABLE_WEBAUTHN_EVIDENCE_ENABLED=true`, `IRREGULARITY_CASCADE_REVOCATION_ENABLED=true`, and `PORTABLE_WEBAUTHN_EVIDENCE_STATUS_CHECK_ENABLED=true` on the auth service (all source defaults are off). Keep the latter two safety flags on if issuance is rolled back. The current primary SDKs expose the typed reference; independent verification still requires checking the VC, assertion, trusted origin, and current status.
</Info>

## Why Verifiable Credentials?

Grant tokens work well within systems that integrate with Grantex. But in agentic commerce, agents interact with third-party services that may have no relationship with Grantex:

* A payment processor needs proof that the agent is authorized to spend on the user's behalf
* A contract-signing service needs proof of human consent
* An insurance API needs proof of delegated authority from a specific principal

Verifiable Credentials provide a signed document that a verifier can check using published keys without a Grantex account. The verifier must trust the issuer's enrollment and authorization process, and revocation checks require current status-list data.

## W3C Compliance

Grantex VCs conform to:

| Standard | Version | Description |
| - | - | - |
| [VC Data Model](https://www.w3.org/TR/vc-data-model-2.0/) | v2.0 | Credential structure and semantics |
| [VC-JWT](https://www.w3.org/TR/vc-data-model-2.0/#json-web-token) | -- | JWT encoding of VCs (compact, URL-safe) |
| [StatusList2021](https://www.w3.org/TR/vc-status-list/) | -- | Bitstring-based revocation mechanism |
| [DID Core](https://www.w3.org/TR/did-core/) | v1.0 | Issuer identification via `did:web` |

## Issuing a Verifiable Credential

Request a VC during token exchange by setting the `credentialFormat` parameter:

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

  const grantex = new Grantex({ apiKey: process.env.GRANTEX_API_KEY });

  const result = await grantex.tokens.exchange({
    code,
    agentId: agent.id,
    credentialFormat: 'vc-jwt',
  });

  console.log(result.grantToken);           // standard RS256 JWT (always present)
  console.log(result.verifiableCredential);  // W3C VC-JWT (present when requested)
  ```

  ```python Python theme={null}
  from grantex import Grantex, ExchangeTokenParams

  client = Grantex(api_key="gx_...")

  result = client.tokens.exchange(ExchangeTokenParams(
      code=code,
      agent_id=agent.id,
      credential_format="vc-jwt",
  ))

  print(result.verifiable_credential)  # W3C VC-JWT
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.grantex.dev/v1/token \
    -H "Authorization: Bearer $GRANTEX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"code": "01J...", "agentId": "ag_01...", "credentialFormat": "vc-jwt"}'
  ```
</CodeGroup>

## Credential Types

### AgentGrantCredential

Issued for direct grants (user authorizes an agent directly). The credential subject attests that a specific principal authorized a specific agent with specific scopes.

```json theme={null}
{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://grantex.dev/ns/credentials/v1"
  ],
  "type": ["VerifiableCredential", "AgentGrantCredential"],
  "issuer": "did:web:grantex.dev",
  "issuanceDate": "2026-03-08T12:00:00Z",
  "expirationDate": "2026-03-09T12:00:00Z",
  "credentialSubject": {
    "id": "did:grantex:ag_01HXYZ123abc",
    "type": "Agent",
    "grantId": "grnt_01HXYZ...",
    "principalId": "user_abc123",
    "developerId": "org_yourcompany",
    "scopes": ["calendar:read", "payments:initiate:max_500"],
    "authorizedAt": "2026-03-08T12:00:00Z"
  },
  "credentialStatus": {
    "id": "https://api.grantex.dev/v1/credentials/status/1#42",
    "type": "StatusList2021Entry",
    "statusPurpose": "revocation",
    "statusListIndex": "42",
    "statusListCredential": "https://api.grantex.dev/v1/credentials/status/1"
  }
}
```

### DelegatedGrantCredential

Issued for delegated grants (agent delegates to a sub-agent). Includes the full delegation chain for traceability.

```json theme={null}
{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://grantex.dev/ns/credentials/v1"
  ],
  "type": ["VerifiableCredential", "DelegatedGrantCredential"],
  "issuer": "did:web:grantex.dev",
  "credentialSubject": {
    "id": "did:grantex:ag_SUB_AGENT",
    "type": "DelegatedAgent",
    "grantId": "grnt_01CHILD...",
    "parentGrantId": "grnt_01ROOT...",
    "parentAgentDid": "did:grantex:ag_ROOT_AGENT",
    "delegationDepth": 1,
    "principalId": "user_abc123",
    "scopes": ["calendar:read"]
  }
}
```

### Portable WebAuthn Evidence

With portable evidence enabled, for a new passkey-approved grant, Grantex stores the verified assertion and includes a signed `webauthnEvidence` reference in the grant token. When `credentialFormat` is `vc-jwt` or `both`, the VC's `evidence` array contains a `GrantexWebAuthnAssertion` with `version: 1`, `authRequestId`, `credentialId`, base64url `credentialPublicKey`, `previousCounter`, `rpId`, `origin`, `challenge`, base64url `clientDataJSON`, `authenticatorData`, `signature`, `userVerified`, `assertedAt`, and a SHA-256 hex `digest`. With the rollout flag enabled (or for already-evidenced grants), the VC and grant commit together and failed requested VC issuance fails the exchange. Existing grants cannot acquire past assertion evidence retroactively.

When an agent delegates a grant, the child token and opt-in VC inherit the original assertion reference/evidence only after Grantex checks it against the signed parent reference. This proves the original principal ceremony in the chain, not a new human approval of the delegation. Requested delegated VCs also commit atomically with their child grant.

The digest is SHA-256 of the UTF-8 bytes of compact JavaScript `JSON.stringify` output (no added whitespace) for this ordered array: `[authRequestId, credentialId, credentialPublicKey, previousCounter, rpId, origin, challenge, clientDataJSON, authenticatorData, signature, userVerified, assertedAt]`. It binds the compact grant reference to the exact exported assertion. Verify the issuer signature and status, pin the expected RP ID and origin, recalculate the digest, then verify the WebAuthn signature and challenge using the included public key and prior counter. The `webauthnVerified` field from `/v1/credentials/verify` reports successful assertion verification, not independent customer-identity verification. The authenticator signs the challenge, not the grant/scopes; the Grantex issuer signature binds them and attests enrollment. Trust in the issuer's enrollment and identity checks remains essential. Raw public keys are correlatable, so request and distribute these VCs only when needed.

## Verifying a VC

### Using the Grantex SDK

<CodeGroup>
  ```typescript TypeScript theme={null}
  const verification = await grantex.credentials.verify(vcJwt);

  if (verification.valid) {
    console.log('Issuer:', verification.payload?.['iss']);
    console.log('Assertion verified:', verification.webauthnVerified === true);
  } else {
    console.log('Invalid:', verification.error);
  }
  ```

  ```python Python theme={null}
  verification = client.credentials.verify(vc_jwt)

  if verification.valid:
      print("Issuer:", verification.payload["iss"] if verification.payload else None)
      print("Assertion verified:", verification.webauthn_verified is True)
  else:
      print("Invalid:", verification.error)
  ```
</CodeGroup>

### Independent Verification

Any party can verify a Grantex VC without the SDK by:

1. Decoding the VC-JWT (standard JWT decode)
2. Resolving the issuer DID (`did:web:grantex.dev` resolves to `https://grantex.dev/.well-known/did.json`)
3. Extracting the public key from the DID document
4. Verifying the JWT signature against the public key
5. Checking the `credentialStatus` endpoint for revocation
6. If WebAuthn evidence is required, checking the trusted RP ID/origin, assertion signature/challenge/counter, and grant-reference digest

```bash theme={null}
# Resolve the DID document
curl https://api.grantex.dev/.well-known/did.json

# Check revocation status
curl https://api.grantex.dev/v1/credentials/status/1
```

No Grantex account or API key is required for verification. The DID document and status list endpoints are public, but the verifier still needs a trusted issuer policy and fresh status data.

## Revocation via StatusList2021

Grantex uses the W3C StatusList2021 standard for credential revocation. Each VC references a position in a bitstring-based status list. When a grant is revoked, the corresponding bit is flipped.

### How It Works

1. Each credential is assigned a `statusListIndex` (a position in the bitstring)
2. The status list credential is published at a public URL (`/v1/credentials/status/:id`)
3. When a grant is revoked, Grantex sets the bit at that index
4. Verifiers fetch the status list and check the bit to determine revocation

### Checking Status

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Via SDK
  const cred = await grantex.credentials.get('vc_01HXYZ...');
  console.log(cred.status);  // "active" or "revoked"

  // The SDK also checks status during verify()
  const result = await grantex.credentials.verify(vcJwt);
  console.log(result.valid, result.revoked === true);
  ```

  ```python Python theme={null}
  cred = client.credentials.get("vc_01HXYZ...")
  print(cred.status)  # "active" or "revoked"

  result = client.credentials.verify(vc_jwt)
  print(result.valid, result.revoked is True)
  ```
</CodeGroup>

### Revocation Timing

When you revoke a grant via `DELETE /v1/grants/:id` or an enabled irregularity cascade, the following database changes happen atomically:

1. The grant record is marked as revoked
2. The StatusList2021 bit is flipped (VC shows as revoked)
3. All delegated sub-grants are cascade-revoked (and their VCs)

The Redis revocation cache is updated after commit; the database remains authoritative. An issuer-side check for evidence-bearing VCs also consults their stored grant status. Independent verifiers must check a fresh status list; a signature and WebAuthn assertion alone do not establish current authorization. Operators should run the [historical VC reconciliation](/features/fido-webauthn#portable-evidence-rollout) when upgrading a deployment that previously used grant-only irregularity revocation.

## Listing Credentials

<CodeGroup>
  ```typescript TypeScript theme={null}
  // List all credentials
  const { credentials } = await grantex.credentials.list();

  // Filter by grant
  const { credentials: grantCreds } = await grantex.credentials.list({
    grantId: 'grnt_01HXYZ...',
  });

  // Filter by principal
  const { credentials: userCreds } = await grantex.credentials.list({
    principalId: 'user_abc123',
    status: 'active',
  });
  ```

  ```python Python theme={null}
  # List all credentials
  result = client.credentials.list()

  # Filter by grant
  result = client.credentials.list(grant_id="grnt_01HXYZ...")

  # Filter by principal and status
  result = client.credentials.list(
      principal_id="user_abc123",
      status="active",
  )
  ```
</CodeGroup>

## Mastercard Verifiable Intent

Grantex VCs have some building blocks relevant to agentic commerce, but no Mastercard certification or full Verifiable Intent interoperability is claimed:

| Requirement | Grantex Implementation |
| - | - |
| Cryptographic human presence proof | Verified assertion evidence in opt-in VCs for new passkey-approved grants; issuer/enrollment trust still required |
| Verifiable agent identity | Agent DID (`did:grantex:ag_...`) as credential subject |
| Scoped authorization | `scopes` array in credential subject |
| Revocable credentials | StatusList2021 with real-time revocation |
| Standard-compliant format | W3C VC Data Model v2.0 + VC-JWT encoding |
| Independent verification | Public DID document + public status list endpoints |

## API Reference

| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/credentials/:id` | Retrieve a specific Verifiable Credential |
| `GET` | `/v1/credentials` | List credentials with optional filters |
| `POST` | `/v1/credentials/verify` | Verify a VC-JWT (signature + status + expiry) |
| `GET` | `/v1/credentials/status/:id` | StatusList2021 credential (public, no auth) |

## Next Steps

* [FIDO2 / WebAuthn](/features/fido-webauthn) -- passkey-based human presence verification
* [DID Infrastructure](/features/did-infrastructure) -- how the issuer DID works
* [Grant Token](/concepts/grant-token) -- the standard JWT-based grant token
* [Multi-Agent Delegation](/concepts/delegation) -- delegation chains in VCs

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